| 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,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) or | 8526 | |
| 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 optionals | 8529 | 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 pointers | 8532 | 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 type | 12928 | // 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 analysis | 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,7 +1781,7 @@ pub const Value = extern union { |
| 1781 | 1781 | ||
| 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 | |||
| 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,11 +10,6 @@ const Payload = union(Letter) { |
| 10 | C: bool, | 10 | C: bool, |
| 11 | }; | 11 | }; |
| 12 | 12 | ||
| 13 | test "union with specified enum tag" { | ||
| 14 | try doTest(); | ||
| 15 | comptime try doTest(); | ||
| 16 | } | ||
| 17 | |||
| 18 | fn doTest() error{TestUnexpectedResult}!void { | 13 | fn 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 | } |
| 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 | const MultipleChoice = union(enum(u32)) { | 38 | const 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 | } |
| 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 | test "switch on union with only 1 field" { | 110 | test "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 | } |
| 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 | test "union with one member defaults to u0 tag type" { | 320 | test "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 @@ |
| 1 | const expect = @import("std").testing.expect; | 1 | const std = @import("std"); |
| 2 | const mem = @import("std").mem; | 2 | const expect = std.testing.expect; |
| 3 | const fmt = @import("std").fmt; | 3 | const mem = std.mem; |
| 4 | const fmt = std.fmt; | ||
| 4 | 5 | ||
| 5 | const ET = union(enum) { | 6 | const ET = union(enum) { |
| 6 | SINT: i32, | 7 | SINT: i32, |