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(...@@ -8523,28 +8523,27 @@ fn zirCmpEq(
8523 return Air.Inst.Ref.bool_false;8523 return Air.Inst.Ref.bool_false;
8524 }8524 }
8525 }8525 }
8526 if (((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or8526
8527 rhs_ty_tag == .Null and lhs_ty_tag == .Optional))8527 // comparing null with optionals
8528 {8528 if (lhs_ty_tag == .Null and (rhs_ty_tag == .Optional or rhs_ty.isCPtr())) {
8529 // comparing null with optionals8529 return sema.analyzeIsNull(block, src, rhs, op == .neq);
8530 const opt_operand = if (lhs_ty_tag == .Null) rhs else lhs;
8531 return sema.analyzeIsNull(block, src, opt_operand, op == .neq);
8532 }8530 }
8533 if (((lhs_ty_tag == .Null and rhs_ty.isCPtr()) or (rhs_ty_tag == .Null and lhs_ty.isCPtr()))) {8531 if (rhs_ty_tag == .Null and (lhs_ty_tag == .Optional or lhs_ty.isCPtr())) {
8534 // comparing null with C pointers8532 return sema.analyzeIsNull(block, src, lhs, op == .neq);
8535 const opt_operand = if (lhs_ty_tag == .Null) rhs else lhs;
8536 return sema.analyzeIsNull(block, src, opt_operand, op == .neq);
8537 }8533 }
8534
8538 if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) {8535 if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) {
8539 const non_null_type = if (lhs_ty_tag == .Null) rhs_ty else lhs_ty;8536 const non_null_type = if (lhs_ty_tag == .Null) rhs_ty else lhs_ty;
8540 return sema.fail(block, src, "comparison of '{}' with null", .{non_null_type});8537 return sema.fail(block, src, "comparison of '{}' with null", .{non_null_type});
8541 }8538 }
8542 if (lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) {8539
8543 return sema.analyzeCmpUnionTag(block, rhs, rhs_src, lhs, lhs_src, op);8540 if (lhs_ty_tag == .Union and (rhs_ty_tag == .EnumLiteral or rhs_ty_tag == .Enum)) {
8544 }
8545 if (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union) {
8546 return sema.analyzeCmpUnionTag(block, lhs, lhs_src, rhs, rhs_src, op);8541 return sema.analyzeCmpUnionTag(block, lhs, lhs_src, rhs, rhs_src, op);
8547 }8542 }
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
8548 if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {8547 if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {
8549 const runtime_src: LazySrcLoc = src: {8548 const runtime_src: LazySrcLoc = src: {
8550 if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lval| {8549 if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lval| {
...@@ -12174,7 +12173,14 @@ fn fieldCallBind(...@@ -12174,7 +12173,14 @@ fn fieldCallBind(
12174 const ptr_inst = try block.addStructFieldPtr(object_ptr, field_index, ptr_field_ty);12173 const ptr_inst = try block.addStructFieldPtr(object_ptr, field_index, ptr_field_ty);
12175 return sema.analyzeLoad(block, src, ptr_inst, src);12174 return sema.analyzeLoad(block, src, ptr_inst, src);
12176 },12175 },
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 },
12178 .Type => {12184 .Type => {
12179 const namespace = try sema.analyzeLoad(block, src, object_ptr, src);12185 const namespace = try sema.analyzeLoad(block, src, object_ptr, src);
12180 return sema.fieldVal(block, src, namespace, field_name, field_name_src);12186 return sema.fieldVal(block, src, namespace, field_name, field_name_src);
...@@ -12922,7 +12928,7 @@ fn coerce(...@@ -12922,7 +12928,7 @@ fn coerce(
12922 // union to its own tag type12928 // union to its own tag type
12923 const union_tag_ty = inst_ty.unionTagType() orelse break :blk;12929 const union_tag_ty = inst_ty.unionTagType() orelse break :blk;
12924 if (union_tag_ty.eql(dest_ty)) {12930 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);
12926 }12932 }
12927 },12933 },
12928 else => {},12934 else => {},
...@@ -14589,10 +14595,19 @@ fn resolvePeerTypes(...@@ -14589,10 +14595,19 @@ fn resolvePeerTypes(
14589 chosen_i = candidate_i + 1;14595 chosen_i = candidate_i + 1;
14590 continue;14596 continue;
14591 },14597 },
14598 .Union => continue,
14592 else => {},14599 else => {},
14593 },14600 },
14594 .EnumLiteral => switch (chosen_ty_tag) {14601 .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 },
14596 else => {},14611 else => {},
14597 },14612 },
14598 .Pointer => {14613 .Pointer => {
...@@ -15160,7 +15175,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {...@@ -15160,7 +15175,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
15160 enum_value_map = &union_obj.tag_ty.castTag(.enum_numbered).?.data.values;15175 enum_value_map = &union_obj.tag_ty.castTag(.enum_numbered).?.data.values;
15161 } else {15176 } else {
15162 // The provided type is the enum tag type.15177 // 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);
15164 }15179 }
15165 } else {15180 } else {
15166 // If auto_enum_tag is false, this is an untagged union. However, for semantic analysis15181 // 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 {...@@ -1781,7 +1781,7 @@ pub const Value = extern union {
17811781
1782 pub fn unionTag(val: Value) Value {1782 pub fn unionTag(val: Value) Value {
1783 switch (val.tag()) {1783 switch (val.tag()) {
1784 .undef => return val,1784 .undef, .enum_field_index => return val,
1785 .@"union" => return val.castTag(.@"union").?.data.tag,1785 .@"union" => return val.castTag(.@"union").?.data.tag,
1786 else => unreachable,1786 else => unreachable,
1787 }1787 }
test/behavior/union.zig+91
...@@ -152,3 +152,94 @@ const AlignTestTaggedUnion = union(enum) {...@@ -152,3 +152,94 @@ const AlignTestTaggedUnion = union(enum) {
152 A: [9]u8,152 A: [9]u8,
153 B: u64,153 B: u64,
154};154};
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) {...@@ -10,11 +10,6 @@ const Payload = union(Letter) {
10 C: bool,10 C: bool,
11};11};
1212
13test "union with specified enum tag" {
14 try doTest();
15 comptime try doTest();
16}
17
18fn doTest() error{TestUnexpectedResult}!void {13fn doTest() error{TestUnexpectedResult}!void {
19 try expect((try bar(Payload{ .A = 1234 })) == -10);14 try expect((try bar(Payload{ .A = 1234 })) == -10);
20}15}
...@@ -28,6 +23,18 @@ fn bar(value: Payload) error{TestUnexpectedResult}!i32 {...@@ -28,6 +23,18 @@ fn bar(value: Payload) error{TestUnexpectedResult}!i32 {
28 };23 };
29}24}
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
31const MultipleChoice = union(enum(u32)) {38const MultipleChoice = union(enum(u32)) {
32 A = 20,39 A = 20,
33 B = 40,40 B = 40,
...@@ -100,51 +107,6 @@ test "union field access gives the enum values" {...@@ -100,51 +107,6 @@ test "union field access gives the enum values" {
100 try expect(TheUnion.C == TheTag.C);107 try expect(TheUnion.C == TheTag.C);
101}108}
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
148test "switch on union with only 1 field" {110test "switch on union with only 1 field" {
149 var r: PartialInst = undefined;111 var r: PartialInst = undefined;
150 r = PartialInst.Compiled;112 r = PartialInst.Compiled;
...@@ -355,33 +317,6 @@ test "union no tag with struct member" {...@@ -355,33 +317,6 @@ test "union no tag with struct member" {
355 u.foo();317 u.foo();
356}318}
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
385test "union with one member defaults to u0 tag type" {320test "union with one member defaults to u0 tag type" {
386 const U0 = union(enum) {321 const U0 = union(enum) {
387 X: u32,322 X: u32,
test/behavior/union_with_members.zig+4-3
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1const expect = @import("std").testing.expect;1const std = @import("std");
2const mem = @import("std").mem;2const expect = std.testing.expect;
3const fmt = @import("std").fmt;3const mem = std.mem;
4const fmt = std.fmt;
45
5const ET = union(enum) {6const ET = union(enum) {
6 SINT: i32,7 SINT: i32,