| author | |
| committer | |
| log | 81a3910e443c71674a5eb96487431721bb8f1cad |
| tree | 51ba8b555c7ba60c4efb52b67d6ec8b06bd57f43 |
| parent | 6229d37dcfec393880109c7aaed1c18d08756631 |
* 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 arena5 files changed, 141 insertions(+), 99 deletions(-)
src/Sema.zig+33-18| ... | ... | @@ -8523,28 +8523,27 @@ fn zirCmpEq( |
| 8523 | 8523 | return Air.Inst.Ref.bool_false; |
| 8524 | 8524 | } |
| 8525 | 8525 | } |
| 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); | |
| 8532 | 8530 | } |
| 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); | |
| 8537 | 8533 | } |
| 8534 | ||
| 8538 | 8535 | if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) { |
| 8539 | 8536 | const non_null_type = if (lhs_ty_tag == .Null) rhs_ty else lhs_ty; |
| 8540 | 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) { | |
| 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)) { | |
| 8546 | 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 | 8547 | if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) { |
| 8549 | 8548 | const runtime_src: LazySrcLoc = src: { |
| 8550 | 8549 | if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lval| { |
| ... | ... | @@ -12174,7 +12173,14 @@ fn fieldCallBind( |
| 12174 | 12173 | const ptr_inst = try block.addStructFieldPtr(object_ptr, field_index, ptr_field_ty); |
| 12175 | 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 | 12184 | .Type => { |
| 12179 | 12185 | const namespace = try sema.analyzeLoad(block, src, object_ptr, src); |
| 12180 | 12186 | return sema.fieldVal(block, src, namespace, field_name, field_name_src); |
| ... | ... | @@ -12922,7 +12928,7 @@ fn coerce( |
| 12922 | 12928 | // union to its own tag type |
| 12923 | 12929 | const union_tag_ty = inst_ty.unionTagType() orelse break :blk; |
| 12924 | 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 | 12934 | else => {}, |
| ... | ... | @@ -14589,10 +14595,19 @@ fn resolvePeerTypes( |
| 14589 | 14595 | chosen_i = candidate_i + 1; |
| 14590 | 14596 | continue; |
| 14591 | 14597 | }, |
| 14598 | .Union => continue, | |
| 14592 | 14599 | else => {}, |
| 14593 | 14600 | }, |
| 14594 | 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 | 14611 | else => {}, |
| 14597 | 14612 | }, |
| 14598 | 14613 | .Pointer => { |
| ... | ... | @@ -15160,7 +15175,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void { |
| 15160 | 15175 | enum_value_map = &union_obj.tag_ty.castTag(.enum_numbered).?.data.values; |
| 15161 | 15176 | } else { |
| 15162 | 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 | 15180 | } else { |
| 15166 | 15181 | // 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 | 1781 | |
| 1782 | 1782 | pub fn unionTag(val: Value) Value { |
| 1783 | 1783 | switch (val.tag()) { |
| 1784 | .undef => return val, | |
| 1784 | .undef, .enum_field_index => return val, | |
| 1785 | 1785 | .@"union" => return val.castTag(.@"union").?.data.tag, |
| 1786 | 1786 | else => unreachable, |
| 1787 | 1787 | } |
test/behavior/union.zig+91| ... | ... | @@ -152,3 +152,94 @@ const AlignTestTaggedUnion = union(enum) { |
| 152 | 152 | A: [9]u8, |
| 153 | 153 | B: u64, |
| 154 | 154 | }; |
| 155 | ||
| 156 | const Letter = enum { A, B, C }; | |
| 157 | const Payload = union(Letter) { | |
| 158 | A: i32, | |
| 159 | B: f64, | |
| 160 | C: bool, | |
| 161 | }; | |
| 162 | ||
| 163 | test "union with specified enum tag" { | |
| 164 | try doTest(); | |
| 165 | comptime try doTest(); | |
| 166 | } | |
| 167 | ||
| 168 | fn doTest() error{TestUnexpectedResult}!void { | |
| 169 | try expect((try bar(Payload{ .A = 1234 })) == -10); | |
| 170 | } | |
| 171 | ||
| 172 | fn 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 | ||
| 181 | fn 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 | ||
| 191 | test "comparison between union and enum literal" { | |
| 192 | try testComparison(); | |
| 193 | comptime try testComparison(); | |
| 194 | } | |
| 195 | ||
| 196 | const TheTag = enum { A, B, C }; | |
| 197 | const TheUnion = union(TheTag) { | |
| 198 | A: i32, | |
| 199 | B: i32, | |
| 200 | C: i32, | |
| 201 | }; | |
| 202 | test "cast union to tag type of union" { | |
| 203 | try testCastUnionToTag(); | |
| 204 | comptime try testCastUnionToTag(); | |
| 205 | } | |
| 206 | ||
| 207 | fn testCastUnionToTag() !void { | |
| 208 | var u = TheUnion{ .B = 1234 }; | |
| 209 | try expect(@as(TheTag, u) == TheTag.B); | |
| 210 | } | |
| 211 | ||
| 212 | test "cast tag type of union to union" { | |
| 213 | var x: Value2 = Letter2.B; | |
| 214 | try expect(@as(Letter2, x) == Letter2.B); | |
| 215 | } | |
| 216 | const Letter2 = enum { A, B, C }; | |
| 217 | const Value2 = union(Letter2) { | |
| 218 | A: i32, | |
| 219 | B, | |
| 220 | C, | |
| 221 | }; | |
| 222 | ||
| 223 | test "implicit cast union to its tag type" { | |
| 224 | var x: Value2 = Letter2.B; | |
| 225 | try expect(x == Letter2.B); | |
| 226 | try giveMeLetterB(x); | |
| 227 | } | |
| 228 | fn 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. | |
| 234 | pub const PackThis = union(enum) { | |
| 235 | Invalid: bool, | |
| 236 | StringLiteral: u2, | |
| 237 | }; | |
| 238 | ||
| 239 | test "constant packed union" { | |
| 240 | try testConstPackedUnion(&[_]PackThis{PackThis{ .StringLiteral = 1 }}); | |
| 241 | } | |
| 242 | ||
| 243 | fn 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 | 10 | C: bool, |
| 11 | 11 | }; |
| 12 | 12 | |
| 13 | test "union with specified enum tag" { | |
| 14 | try doTest(); | |
| 15 | comptime try doTest(); | |
| 16 | } | |
| 17 | ||
| 18 | 13 | fn doTest() error{TestUnexpectedResult}!void { |
| 19 | 14 | try expect((try bar(Payload{ .A = 1234 })) == -10); |
| 20 | 15 | } |
| ... | ... | @@ -28,6 +23,18 @@ fn bar(value: Payload) error{TestUnexpectedResult}!i32 { |
| 28 | 23 | }; |
| 29 | 24 | } |
| 30 | 25 | |
| 26 | test "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 | ||
| 31 | 38 | const MultipleChoice = union(enum(u32)) { |
| 32 | 39 | A = 20, |
| 33 | 40 | B = 40, |
| ... | ... | @@ -100,51 +107,6 @@ test "union field access gives the enum values" { |
| 100 | 107 | try expect(TheUnion.C == TheTag.C); |
| 101 | 108 | } |
| 102 | 109 | |
| 103 | test "cast union to tag type of union" { | |
| 104 | try testCastUnionToTag(); | |
| 105 | comptime try testCastUnionToTag(); | |
| 106 | } | |
| 107 | ||
| 108 | fn testCastUnionToTag() !void { | |
| 109 | var u = TheUnion{ .B = 1234 }; | |
| 110 | try expect(@as(TheTag, u) == TheTag.B); | |
| 111 | } | |
| 112 | ||
| 113 | test "cast tag type of union to union" { | |
| 114 | var x: Value2 = Letter2.B; | |
| 115 | try expect(@as(Letter2, x) == Letter2.B); | |
| 116 | } | |
| 117 | const Letter2 = enum { A, B, C }; | |
| 118 | const Value2 = union(Letter2) { | |
| 119 | A: i32, | |
| 120 | B, | |
| 121 | C, | |
| 122 | }; | |
| 123 | ||
| 124 | test "implicit cast union to its tag type" { | |
| 125 | var x: Value2 = Letter2.B; | |
| 126 | try expect(x == Letter2.B); | |
| 127 | try giveMeLetterB(x); | |
| 128 | } | |
| 129 | fn 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. | |
| 135 | pub const PackThis = union(enum) { | |
| 136 | Invalid: bool, | |
| 137 | StringLiteral: u2, | |
| 138 | }; | |
| 139 | ||
| 140 | test "constant packed union" { | |
| 141 | try testConstPackedUnion(&[_]PackThis{PackThis{ .StringLiteral = 1 }}); | |
| 142 | } | |
| 143 | ||
| 144 | fn testConstPackedUnion(expected_tokens: []const PackThis) !void { | |
| 145 | try expect(expected_tokens[0].StringLiteral == 1); | |
| 146 | } | |
| 147 | ||
| 148 | 110 | test "switch on union with only 1 field" { |
| 149 | 111 | var r: PartialInst = undefined; |
| 150 | 112 | r = PartialInst.Compiled; |
| ... | ... | @@ -355,33 +317,6 @@ test "union no tag with struct member" { |
| 355 | 317 | u.foo(); |
| 356 | 318 | } |
| 357 | 319 | |
| 358 | fn 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 | ||
| 368 | test "comparison between union and enum literal" { | |
| 369 | try testComparison(); | |
| 370 | comptime try testComparison(); | |
| 371 | } | |
| 372 | ||
| 373 | test "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 | ||
| 385 | 320 | test "union with one member defaults to u0 tag type" { |
| 386 | 321 | const U0 = union(enum) { |
| 387 | 322 | X: u32, |
test/behavior/union_with_members.zig+4-3| ... | ... | @@ -1,6 +1,7 @@ |
| 1 | const expect = @import("std").testing.expect; | |
| 2 | const mem = @import("std").mem; | |
| 3 | const fmt = @import("std").fmt; | |
| 1 | const std = @import("std"); | |
| 2 | const expect = std.testing.expect; | |
| 3 | const mem = std.mem; | |
| 4 | const fmt = std.fmt; | |
| 4 | 5 | |
| 5 | 6 | const ET = union(enum) { |
| 6 | 7 | SINT: i32, |