Skip to content

Commit 11330cb

Browse files
authored
Merge pull request #10201 from Snektron/stage2-more-coercion
stage2: more in-memory coercion
2 parents 691090f + 2b589d7 commit 11330cb

12 files changed

+388
-339
lines changed

src/Sema.zig

Lines changed: 97 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12364,31 +12364,6 @@ fn coerce(
1236412364
// T to E!T or E to E!T
1236512365
return sema.wrapErrorUnion(block, dest_ty, inst, inst_src);
1236612366
},
12367-
.ErrorSet => switch (inst_ty.zigTypeTag()) {
12368-
.ErrorSet => {
12369-
// Coercion to `anyerror`. Note that this check can return false positives
12370-
// in case the error sets did not get resolved.
12371-
if (dest_ty.isAnyError()) {
12372-
return sema.coerceCompatibleErrorSets(block, inst, inst_src);
12373-
}
12374-
// If both are inferred error sets of functions, and
12375-
// the dest includes the source function, the coercion is OK.
12376-
// This check is important because it works without forcing a full resolution
12377-
// of inferred error sets.
12378-
if (inst_ty.castTag(.error_set_inferred)) |src_payload| {
12379-
if (dest_ty.castTag(.error_set_inferred)) |dst_payload| {
12380-
const src_func = src_payload.data.func;
12381-
const dst_func = dst_payload.data.func;
12382-
12383-
if (src_func == dst_func or dst_payload.data.functions.contains(src_func)) {
12384-
return sema.coerceCompatibleErrorSets(block, inst, inst_src);
12385-
}
12386-
}
12387-
}
12388-
// TODO full error set resolution and compare sets by names.
12389-
},
12390-
else => {},
12391-
},
1239212367
.Union => switch (inst_ty.zigTypeTag()) {
1239312368
.Enum, .EnumLiteral => return sema.coerceEnumToUnion(block, dest_ty, dest_ty_src, inst, inst_src),
1239412369
else => {},
@@ -12441,16 +12416,110 @@ fn coerceInMemoryAllowed(dest_ty: Type, src_ty: Type, dest_is_mut: bool, target:
1244112416
return coerceInMemoryAllowedPtrs(dest_ty, src_ty, dest_ty, src_ty, dest_is_mut, target);
1244212417
}
1244312418

12419+
// Functions
12420+
if (dest_ty.zigTypeTag() == .Fn and src_ty.zigTypeTag() == .Fn) {
12421+
return coerceInMemoryAllowedFns(dest_ty, src_ty, target);
12422+
}
12423+
12424+
// Error Unions
12425+
if (dest_ty.zigTypeTag() == .ErrorUnion and src_ty.zigTypeTag() == .ErrorUnion) {
12426+
const child = coerceInMemoryAllowed(dest_ty.errorUnionPayload(), src_ty.errorUnionPayload(), dest_is_mut, target);
12427+
if (child == .no_match) {
12428+
return child;
12429+
}
12430+
return coerceInMemoryAllowed(dest_ty.errorUnionSet(), src_ty.errorUnionSet(), dest_is_mut, target);
12431+
}
12432+
12433+
// Error Sets
12434+
if (dest_ty.zigTypeTag() == .ErrorSet and src_ty.zigTypeTag() == .ErrorSet) {
12435+
return coerceInMemoryAllowedErrorSets(dest_ty, src_ty);
12436+
}
12437+
1244412438
// TODO: arrays
1244512439
// TODO: non-pointer-like optionals
12446-
// TODO: error unions
12447-
// TODO: error sets
12448-
// TODO: functions
1244912440
// TODO: vectors
1245012441

1245112442
return .no_match;
1245212443
}
1245312444

12445+
fn coerceInMemoryAllowedErrorSets(
12446+
dest_ty: Type,
12447+
src_ty: Type,
12448+
) InMemoryCoercionResult {
12449+
// Coercion to `anyerror`. Note that this check can return false positives
12450+
// in case the error sets did not get resolved.
12451+
if (dest_ty.isAnyError()) {
12452+
return .ok;
12453+
}
12454+
// If both are inferred error sets of functions, and
12455+
// the dest includes the source function, the coercion is OK.
12456+
// This check is important because it works without forcing a full resolution
12457+
// of inferred error sets.
12458+
if (src_ty.castTag(.error_set_inferred)) |src_payload| {
12459+
if (dest_ty.castTag(.error_set_inferred)) |dst_payload| {
12460+
const src_func = src_payload.data.func;
12461+
const dst_func = dst_payload.data.func;
12462+
12463+
if (src_func == dst_func or dst_payload.data.functions.contains(src_func)) {
12464+
return .ok;
12465+
}
12466+
}
12467+
}
12468+
12469+
// TODO full error set resolution and compare sets by names.
12470+
return .no_match;
12471+
}
12472+
12473+
fn coerceInMemoryAllowedFns(
12474+
dest_ty: Type,
12475+
src_ty: Type,
12476+
target: std.Target,
12477+
) InMemoryCoercionResult {
12478+
const dest_info = dest_ty.fnInfo();
12479+
const src_info = src_ty.fnInfo();
12480+
12481+
if (dest_info.is_var_args != src_info.is_var_args) {
12482+
return .no_match;
12483+
}
12484+
12485+
if (dest_info.is_generic != src_info.is_generic) {
12486+
return .no_match;
12487+
}
12488+
12489+
if (!src_info.return_type.isNoReturn()) {
12490+
const rt = coerceInMemoryAllowed(dest_info.return_type, src_info.return_type, false, target);
12491+
if (rt == .no_match) {
12492+
return rt;
12493+
}
12494+
}
12495+
12496+
if (dest_info.param_types.len != src_info.param_types.len) {
12497+
return .no_match;
12498+
}
12499+
12500+
for (dest_info.param_types) |dest_param_ty, i| {
12501+
const src_param_ty = src_info.param_types[i];
12502+
12503+
if (dest_info.comptime_params[i] != src_info.comptime_params[i]) {
12504+
return .no_match;
12505+
}
12506+
12507+
// TODO: nolias
12508+
12509+
// Note: Cast direction is reversed here.
12510+
const param = coerceInMemoryAllowed(src_param_ty, dest_param_ty, false, target);
12511+
if (param == .no_match) {
12512+
return param;
12513+
}
12514+
}
12515+
12516+
if (dest_info.cc != src_info.cc) {
12517+
return .no_match;
12518+
}
12519+
12520+
return .ok;
12521+
}
12522+
1245412523
fn coerceInMemoryAllowedPtrs(
1245512524
dest_ty: Type,
1245612525
src_ty: Type,
@@ -13198,26 +13267,6 @@ fn coerceVectorInMemory(
1319813267
return block.addBitCast(dest_ty, inst);
1319913268
}
1320013269

13201-
fn coerceCompatibleErrorSets(
13202-
sema: *Sema,
13203-
block: *Block,
13204-
err_set: Air.Inst.Ref,
13205-
err_set_src: LazySrcLoc,
13206-
) !Air.Inst.Ref {
13207-
if (try sema.resolveDefinedValue(block, err_set_src, err_set)) |err_set_val| {
13208-
// Same representation works.
13209-
return sema.addConstant(Type.anyerror, err_set_val);
13210-
}
13211-
try sema.requireRuntimeBlock(block, err_set_src);
13212-
return block.addInst(.{
13213-
.tag = .bitcast,
13214-
.data = .{ .ty_op = .{
13215-
.ty = Air.Inst.Ref.anyerror_type,
13216-
.operand = err_set,
13217-
} },
13218-
});
13219-
}
13220-
1322113270
fn analyzeDeclVal(
1322213271
sema: *Sema,
1322313272
block: *Block,

test/behavior.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ test {
5858
_ = @import("behavior/floatop.zig");
5959
_ = @import("behavior/fn.zig");
6060
_ = @import("behavior/for.zig");
61+
_ = @import("behavior/generics_llvm.zig");
6162
_ = @import("behavior/math.zig");
6263
_ = @import("behavior/maximum_minimum.zig");
6364
_ = @import("behavior/null_llvm.zig");
@@ -145,7 +146,6 @@ test {
145146
_ = @import("behavior/fn_delegation.zig");
146147
_ = @import("behavior/fn_in_struct_in_comptime.zig");
147148
_ = @import("behavior/for_stage1.zig");
148-
_ = @import("behavior/generics_stage1.zig");
149149
_ = @import("behavior/if_stage1.zig");
150150
_ = @import("behavior/import.zig");
151151
_ = @import("behavior/incomplete_struct_param_tld.zig");

test/behavior/cast.zig

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,32 @@ test "array coersion to undefined at runtime" {
266266
array = undefined;
267267
try expect(std.mem.eql(u8, &array, &undefined_val));
268268
}
269+
270+
test "implicitly cast from int to anyerror!?T" {
271+
implicitIntLitToOptional();
272+
comptime implicitIntLitToOptional();
273+
}
274+
fn implicitIntLitToOptional() void {
275+
const f: ?i32 = 1;
276+
_ = f;
277+
const g: anyerror!?i32 = 1;
278+
_ = g catch {};
279+
}
280+
281+
test "return u8 coercing into ?u32 return type" {
282+
const S = struct {
283+
fn doTheTest() !void {
284+
try expect(foo(123).? == 123);
285+
}
286+
fn foo(arg: u8) ?u32 {
287+
return arg;
288+
}
289+
};
290+
try S.doTheTest();
291+
comptime try S.doTheTest();
292+
}
293+
294+
test "cast from ?[*]T to ??[*]T" {
295+
const a: ??[*]u8 = @as(?[*]u8, null);
296+
try expect(a != null and a.? == null);
297+
}

test/behavior/cast_llvm.zig

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,135 @@ test "implicit ptr to *c_void" {
6565
var c: *u32 = @ptrCast(*u32, ptr2.?);
6666
try expect(c.* == 1);
6767
}
68+
69+
const A = struct {
70+
a: i32,
71+
};
72+
test "return null from fn() anyerror!?&T" {
73+
const a = returnNullFromOptionalTypeErrorRef();
74+
const b = returnNullLitFromOptionalTypeErrorRef();
75+
try expect((try a) == null and (try b) == null);
76+
}
77+
fn returnNullFromOptionalTypeErrorRef() anyerror!?*A {
78+
const a: ?*A = null;
79+
return a;
80+
}
81+
fn returnNullLitFromOptionalTypeErrorRef() anyerror!?*A {
82+
return null;
83+
}
84+
85+
test "peer type resolution: [0]u8 and []const u8" {
86+
try expect(peerTypeEmptyArrayAndSlice(true, "hi").len == 0);
87+
try expect(peerTypeEmptyArrayAndSlice(false, "hi").len == 1);
88+
comptime {
89+
try expect(peerTypeEmptyArrayAndSlice(true, "hi").len == 0);
90+
try expect(peerTypeEmptyArrayAndSlice(false, "hi").len == 1);
91+
}
92+
}
93+
fn peerTypeEmptyArrayAndSlice(a: bool, slice: []const u8) []const u8 {
94+
if (a) {
95+
return &[_]u8{};
96+
}
97+
98+
return slice[0..1];
99+
}
100+
101+
test "implicitly cast from [N]T to ?[]const T" {
102+
try expect(mem.eql(u8, castToOptionalSlice().?, "hi"));
103+
comptime try expect(mem.eql(u8, castToOptionalSlice().?, "hi"));
104+
}
105+
106+
fn castToOptionalSlice() ?[]const u8 {
107+
return "hi";
108+
}
109+
110+
test "cast u128 to f128 and back" {
111+
comptime try testCast128();
112+
try testCast128();
113+
}
114+
115+
fn testCast128() !void {
116+
try expect(cast128Int(cast128Float(0x7fff0000000000000000000000000000)) == 0x7fff0000000000000000000000000000);
117+
}
118+
119+
fn cast128Int(x: f128) u128 {
120+
return @bitCast(u128, x);
121+
}
122+
123+
fn cast128Float(x: u128) f128 {
124+
return @bitCast(f128, x);
125+
}
126+
127+
test "implicit cast from *[N]T to ?[*]T" {
128+
var x: ?[*]u16 = null;
129+
var y: [4]u16 = [4]u16{ 0, 1, 2, 3 };
130+
131+
x = &y;
132+
try expect(std.mem.eql(u16, x.?[0..4], y[0..4]));
133+
x.?[0] = 8;
134+
y[3] = 6;
135+
try expect(std.mem.eql(u16, x.?[0..4], y[0..4]));
136+
}
137+
138+
test "implicit cast from *T to ?*c_void" {
139+
var a: u8 = 1;
140+
incrementVoidPtrValue(&a);
141+
try std.testing.expect(a == 2);
142+
}
143+
144+
fn incrementVoidPtrValue(value: ?*c_void) void {
145+
@ptrCast(*u8, value.?).* += 1;
146+
}
147+
148+
test "implicit cast *[0]T to E![]const u8" {
149+
var x = @as(anyerror![]const u8, &[0]u8{});
150+
try expect((x catch unreachable).len == 0);
151+
}
152+
153+
var global_array: [4]u8 = undefined;
154+
test "cast from array reference to fn" {
155+
const f = @ptrCast(fn () callconv(.C) void, &global_array);
156+
try expect(@ptrToInt(f) == @ptrToInt(&global_array));
157+
}
158+
159+
test "*const [N]null u8 to ?[]const u8" {
160+
const S = struct {
161+
fn doTheTest() !void {
162+
var a = "Hello";
163+
var b: ?[]const u8 = a;
164+
try expect(mem.eql(u8, b.?, "Hello"));
165+
}
166+
};
167+
try S.doTheTest();
168+
comptime try S.doTheTest();
169+
}
170+
171+
test "cast between [*c]T and ?[*:0]T on fn parameter" {
172+
const S = struct {
173+
const Handler = ?fn ([*c]const u8) callconv(.C) void;
174+
fn addCallback(handler: Handler) void {
175+
_ = handler;
176+
}
177+
178+
fn myCallback(cstr: ?[*:0]const u8) callconv(.C) void {
179+
_ = cstr;
180+
}
181+
182+
fn doTheTest() void {
183+
addCallback(myCallback);
184+
}
185+
};
186+
S.doTheTest();
187+
}
188+
189+
var global_struct: struct { f0: usize } = undefined;
190+
test "assignment to optional pointer result loc" {
191+
var foo: struct { ptr: ?*c_void } = .{ .ptr = &global_struct };
192+
try expect(foo.ptr.? == @ptrCast(*c_void, &global_struct));
193+
}
194+
195+
test "cast between *[N]void and []void" {
196+
var a: [4]void = undefined;
197+
var b: []void = &a;
198+
try expect(b.len == 4);
199+
}

0 commit comments

Comments
 (0)