authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-28 20:17:34-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-12-28 20:20:30-07:00
log81a3910e443c71674a5eb96487431721bb8f1cad
tree51ba8b555c7ba60c4efb52b67d6ec8b06bd57f43
parent6229d37dcfec393880109c7aaed1c18d08756631

Sema: improve union support

* reduce number of branches in zirCmpEq * implement equality comparison for enums and unions * fix coercion from union to its tag type resulting in the wrong type * fix method calls of unions * implement peer type resolution for unions, enums, and enum literals * fix union tag type memory in the wrong arena

5 files changed, 141 insertions(+), 99 deletions(-)

src/Sema.zig+33-18
......@@ -8523,28 +8523,27 @@ fn zirCmpEq(
85238523 return Air.Inst.Ref.bool_false;
85248524 }
85258525 }
8526 if (((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or
8527 rhs_ty_tag == .Null and lhs_ty_tag == .Optional))
8528 {
8529 // comparing null with optionals
8530 const opt_operand = if (lhs_ty_tag == .Null) rhs else lhs;
8531 return sema.analyzeIsNull(block, src, opt_operand, op == .neq);
8526
8527 // comparing null with optionals
8528 if (lhs_ty_tag == .Null and (rhs_ty_tag == .Optional or rhs_ty.isCPtr())) {
8529 return sema.analyzeIsNull(block, src, rhs, op == .neq);
85328530 }
8533 if (((lhs_ty_tag == .Null and rhs_ty.isCPtr()) or (rhs_ty_tag == .Null and lhs_ty.isCPtr()))) {
8534 // comparing null with C pointers
8535 const opt_operand = if (lhs_ty_tag == .Null) rhs else lhs;
8536 return sema.analyzeIsNull(block, src, opt_operand, op == .neq);
8531 if (rhs_ty_tag == .Null and (lhs_ty_tag == .Optional or lhs_ty.isCPtr())) {
8532 return sema.analyzeIsNull(block, src, lhs, op == .neq);
85378533 }
8534
85388535 if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) {
85398536 const non_null_type = if (lhs_ty_tag == .Null) rhs_ty else lhs_ty;
85408537 return sema.fail(block, src, "comparison of '{}' with null", .{non_null_type});
85418538 }
8542 if (lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) {
8543 return sema.analyzeCmpUnionTag(block, rhs, rhs_src, lhs, lhs_src, op);
8544 }
8545 if (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union) {
8539
8540 if (lhs_ty_tag == .Union and (rhs_ty_tag == .EnumLiteral or rhs_ty_tag == .Enum)) {
85468541 return sema.analyzeCmpUnionTag(block, lhs, lhs_src, rhs, rhs_src, op);
85478542 }
8543 if (rhs_ty_tag == .Union and (lhs_ty_tag == .EnumLiteral or lhs_ty_tag == .Enum)) {
8544 return sema.analyzeCmpUnionTag(block, rhs, rhs_src, lhs, lhs_src, op);
8545 }
8546
85488547 if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {
85498548 const runtime_src: LazySrcLoc = src: {
85508549 if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lval| {
......@@ -12174,7 +12173,14 @@ fn fieldCallBind(
1217412173 const ptr_inst = try block.addStructFieldPtr(object_ptr, field_index, ptr_field_ty);
1217512174 return sema.analyzeLoad(block, src, ptr_inst, src);
1217612175 },
12177 .Union => return sema.fail(block, src, "TODO implement field calls on unions", .{}),
12176 .Union => {
12177 const union_ty = try sema.resolveTypeFields(block, src, concrete_ty);
12178 const fields = union_ty.unionFields();
12179 const field_index_usize = fields.getIndex(field_name) orelse break :find_field;
12180
12181 _ = field_index_usize;
12182 return sema.fail(block, src, "TODO implement field calls on unions", .{});
12183 },
1217812184 .Type => {
1217912185 const namespace = try sema.analyzeLoad(block, src, object_ptr, src);
1218012186 return sema.fieldVal(block, src, namespace, field_name, field_name_src);
......@@ -12922,7 +12928,7 @@ fn coerce(
1292212928 // union to its own tag type
1292312929 const union_tag_ty = inst_ty.unionTagType() orelse break :blk;
1292412930 if (union_tag_ty.eql(dest_ty)) {
12925 return sema.unionToTag(block, inst_ty, inst, inst_src);
12931 return sema.unionToTag(block, dest_ty, inst, inst_src);
1292612932 }
1292712933 },
1292812934 else => {},
......@@ -14589,10 +14595,19 @@ fn resolvePeerTypes(
1458914595 chosen_i = candidate_i + 1;
1459014596 continue;
1459114597 },
14598 .Union => continue,
1459214599 else => {},
1459314600 },
1459414601 .EnumLiteral => switch (chosen_ty_tag) {
14595 .Enum => continue,
14602 .Enum, .Union => continue,
14603 else => {},
14604 },
14605 .Union => switch (chosen_ty_tag) {
14606 .Enum, .EnumLiteral => {
14607 chosen = candidate;
14608 chosen_i = candidate_i + 1;
14609 continue;
14610 },
1459614611 else => {},
1459714612 },
1459814613 .Pointer => {
......@@ -15160,7 +15175,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
1516015175 enum_value_map = &union_obj.tag_ty.castTag(.enum_numbered).?.data.values;
1516115176 } else {
1516215177 // The provided type is the enum tag type.
15163 union_obj.tag_ty = provided_ty;
15178 union_obj.tag_ty = try provided_ty.copy(decl_arena_allocator);
1516415179 }
1516515180 } else {
1516615181 // If auto_enum_tag is false, this is an untagged union. However, for semantic analysis
src/value.zig+1-1
......@@ -1781,7 +1781,7 @@ pub const Value = extern union {
17811781
17821782 pub fn unionTag(val: Value) Value {
17831783 switch (val.tag()) {
1784 .undef => return val,
1784 .undef, .enum_field_index => return val,
17851785 .@"union" => return val.castTag(.@"union").?.data.tag,
17861786 else => unreachable,
17871787 }
test/behavior/union.zig+91
......@@ -152,3 +152,94 @@ const AlignTestTaggedUnion = union(enum) {
152152 A: [9]u8,
153153 B: u64,
154154};
155
156const Letter = enum { A, B, C };
157const Payload = union(Letter) {
158 A: i32,
159 B: f64,
160 C: bool,
161};
162
163test "union with specified enum tag" {
164 try doTest();
165 comptime try doTest();
166}
167
168fn doTest() error{TestUnexpectedResult}!void {
169 try expect((try bar(Payload{ .A = 1234 })) == -10);
170}
171
172fn bar(value: Payload) error{TestUnexpectedResult}!i32 {
173 try expect(@as(Letter, value) == Letter.A);
174 return switch (value) {
175 Payload.A => |x| return x - 1244,
176 Payload.B => |x| if (x == 12.34) @as(i32, 20) else 21,
177 Payload.C => |x| if (x) @as(i32, 30) else 31,
178 };
179}
180
181fn testComparison() !void {
182 var x = Payload{ .A = 42 };
183 try expect(x == .A);
184 try expect(x != .B);
185 try expect(x != .C);
186 try expect((x == .B) == false);
187 try expect((x == .C) == false);
188 try expect((x != .A) == false);
189}
190
191test "comparison between union and enum literal" {
192 try testComparison();
193 comptime try testComparison();
194}
195
196const TheTag = enum { A, B, C };
197const TheUnion = union(TheTag) {
198 A: i32,
199 B: i32,
200 C: i32,
201};
202test "cast union to tag type of union" {
203 try testCastUnionToTag();
204 comptime try testCastUnionToTag();
205}
206
207fn testCastUnionToTag() !void {
208 var u = TheUnion{ .B = 1234 };
209 try expect(@as(TheTag, u) == TheTag.B);
210}
211
212test "cast tag type of union to union" {
213 var x: Value2 = Letter2.B;
214 try expect(@as(Letter2, x) == Letter2.B);
215}
216const Letter2 = enum { A, B, C };
217const Value2 = union(Letter2) {
218 A: i32,
219 B,
220 C,
221};
222
223test "implicit cast union to its tag type" {
224 var x: Value2 = Letter2.B;
225 try expect(x == Letter2.B);
226 try giveMeLetterB(x);
227}
228fn giveMeLetterB(x: Letter2) !void {
229 try expect(x == Value2.B);
230}
231
232// TODO it looks like this test intended to test packed unions, but this is not a packed
233// union. go through git history and find out what happened.
234pub const PackThis = union(enum) {
235 Invalid: bool,
236 StringLiteral: u2,
237};
238
239test "constant packed union" {
240 try testConstPackedUnion(&[_]PackThis{PackThis{ .StringLiteral = 1 }});
241}
242
243fn testConstPackedUnion(expected_tokens: []const PackThis) !void {
244 try expect(expected_tokens[0].StringLiteral == 1);
245}
test/behavior/union_stage1.zig+12-77
......@@ -10,11 +10,6 @@ const Payload = union(Letter) {
1010 C: bool,
1111};
1212
13test "union with specified enum tag" {
14 try doTest();
15 comptime try doTest();
16}
17
1813fn doTest() error{TestUnexpectedResult}!void {
1914 try expect((try bar(Payload{ .A = 1234 })) == -10);
2015}
......@@ -28,6 +23,18 @@ fn bar(value: Payload) error{TestUnexpectedResult}!i32 {
2823 };
2924}
3025
26test "packed union generates correctly aligned LLVM type" {
27 const U = packed union {
28 f1: fn () error{TestUnexpectedResult}!void,
29 f2: u32,
30 };
31 var foo = [_]U{
32 U{ .f1 = doTest },
33 U{ .f2 = 0 },
34 };
35 try foo[0].f1();
36}
37
3138const MultipleChoice = union(enum(u32)) {
3239 A = 20,
3340 B = 40,
......@@ -100,51 +107,6 @@ test "union field access gives the enum values" {
100107 try expect(TheUnion.C == TheTag.C);
101108}
102109
103test "cast union to tag type of union" {
104 try testCastUnionToTag();
105 comptime try testCastUnionToTag();
106}
107
108fn testCastUnionToTag() !void {
109 var u = TheUnion{ .B = 1234 };
110 try expect(@as(TheTag, u) == TheTag.B);
111}
112
113test "cast tag type of union to union" {
114 var x: Value2 = Letter2.B;
115 try expect(@as(Letter2, x) == Letter2.B);
116}
117const Letter2 = enum { A, B, C };
118const Value2 = union(Letter2) {
119 A: i32,
120 B,
121 C,
122};
123
124test "implicit cast union to its tag type" {
125 var x: Value2 = Letter2.B;
126 try expect(x == Letter2.B);
127 try giveMeLetterB(x);
128}
129fn giveMeLetterB(x: Letter2) !void {
130 try expect(x == Value2.B);
131}
132
133// TODO it looks like this test intended to test packed unions, but this is not a packed
134// union. go through git history and find out what happened.
135pub const PackThis = union(enum) {
136 Invalid: bool,
137 StringLiteral: u2,
138};
139
140test "constant packed union" {
141 try testConstPackedUnion(&[_]PackThis{PackThis{ .StringLiteral = 1 }});
142}
143
144fn testConstPackedUnion(expected_tokens: []const PackThis) !void {
145 try expect(expected_tokens[0].StringLiteral == 1);
146}
147
148110test "switch on union with only 1 field" {
149111 var r: PartialInst = undefined;
150112 r = PartialInst.Compiled;
......@@ -355,33 +317,6 @@ test "union no tag with struct member" {
355317 u.foo();
356318}
357319
358fn testComparison() !void {
359 var x = Payload{ .A = 42 };
360 try expect(x == .A);
361 try expect(x != .B);
362 try expect(x != .C);
363 try expect((x == .B) == false);
364 try expect((x == .C) == false);
365 try expect((x != .A) == false);
366}
367
368test "comparison between union and enum literal" {
369 try testComparison();
370 comptime try testComparison();
371}
372
373test "packed union generates correctly aligned LLVM type" {
374 const U = packed union {
375 f1: fn () error{TestUnexpectedResult}!void,
376 f2: u32,
377 };
378 var foo = [_]U{
379 U{ .f1 = doTest },
380 U{ .f2 = 0 },
381 };
382 try foo[0].f1();
383}
384
385320test "union with one member defaults to u0 tag type" {
386321 const U0 = union(enum) {
387322 X: u32,
test/behavior/union_with_members.zig+4-3
......@@ -1,6 +1,7 @@
1const expect = @import("std").testing.expect;
2const mem = @import("std").mem;
3const fmt = @import("std").fmt;
1const std = @import("std");
2const expect = std.testing.expect;
3const mem = std.mem;
4const fmt = std.fmt;
45
56const ET = union(enum) {
67 SINT: i32,