authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-04-07 12:15:05-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-04-07 12:15:05-07:00
log18119aae30660c27b088214319cfca396fdf04bf
treecd82afe23de654adefaab1b96ecf76c354232c90
parentd9c25ec6720ecb0bc79fcab67659ee12ca6ad687

Sema: implement comparison analysis for non-numeric types


3 files changed, 94 insertions(+), 9 deletions(-)

src/Sema.zig+32-9
...@@ -3776,9 +3776,13 @@ fn zirCmp(...@@ -3776,9 +3776,13 @@ fn zirCmp(
3776 const tracy = trace(@src());3776 const tracy = trace(@src());
3777 defer tracy.end();3777 defer tracy.end();
37783778
3779 const mod = sema.mod;
3780
3779 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;3781 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
3780 const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;3782 const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
3781 const src: LazySrcLoc = inst_data.src();3783 const src: LazySrcLoc = inst_data.src();
3784 const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
3785 const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
3782 const lhs = try sema.resolveInst(extra.lhs);3786 const lhs = try sema.resolveInst(extra.lhs);
3783 const rhs = try sema.resolveInst(extra.rhs);3787 const rhs = try sema.resolveInst(extra.rhs);
37843788
...@@ -3790,7 +3794,7 @@ fn zirCmp(...@@ -3790,7 +3794,7 @@ fn zirCmp(
3790 const rhs_ty_tag = rhs.ty.zigTypeTag();3794 const rhs_ty_tag = rhs.ty.zigTypeTag();
3791 if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) {3795 if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) {
3792 // null == null, null != null3796 // null == null, null != null
3793 return sema.mod.constBool(sema.arena, src, op == .eq);3797 return mod.constBool(sema.arena, src, op == .eq);
3794 } else if (is_equality_cmp and3798 } else if (is_equality_cmp and
3795 ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or3799 ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or
3796 rhs_ty_tag == .Null and lhs_ty_tag == .Optional))3800 rhs_ty_tag == .Null and lhs_ty_tag == .Optional))
...@@ -3801,23 +3805,23 @@ fn zirCmp(...@@ -3801,23 +3805,23 @@ fn zirCmp(
3801 } else if (is_equality_cmp and3805 } else if (is_equality_cmp and
3802 ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr())))3806 ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr())))
3803 {3807 {
3804 return sema.mod.fail(&block.base, src, "TODO implement C pointer cmp", .{});3808 return mod.fail(&block.base, src, "TODO implement C pointer cmp", .{});
3805 } else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) {3809 } else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) {
3806 const non_null_type = if (lhs_ty_tag == .Null) rhs.ty else lhs.ty;3810 const non_null_type = if (lhs_ty_tag == .Null) rhs.ty else lhs.ty;
3807 return sema.mod.fail(&block.base, src, "comparison of '{}' with null", .{non_null_type});3811 return mod.fail(&block.base, src, "comparison of '{}' with null", .{non_null_type});
3808 } else if (is_equality_cmp and3812 } else if (is_equality_cmp and
3809 ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or3813 ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or
3810 (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union)))3814 (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union)))
3811 {3815 {
3812 return sema.mod.fail(&block.base, src, "TODO implement equality comparison between a union's tag value and an enum literal", .{});3816 return mod.fail(&block.base, src, "TODO implement equality comparison between a union's tag value and an enum literal", .{});
3813 } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {3817 } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {
3814 if (!is_equality_cmp) {3818 if (!is_equality_cmp) {
3815 return sema.mod.fail(&block.base, src, "{s} operator not allowed for errors", .{@tagName(op)});3819 return mod.fail(&block.base, src, "{s} operator not allowed for errors", .{@tagName(op)});
3816 }3820 }
3817 if (rhs.value()) |rval| {3821 if (rhs.value()) |rval| {
3818 if (lhs.value()) |lval| {3822 if (lhs.value()) |lval| {
3819 // TODO optimisation oppurtunity: evaluate if std.mem.eql is faster with the names, or calling to Module.getErrorValue to get the values and then compare them is faster3823 // TODO optimisation oppurtunity: evaluate if std.mem.eql is faster with the names, or calling to Module.getErrorValue to get the values and then compare them is faster
3820 return sema.mod.constBool(sema.arena, src, std.mem.eql(u8, lval.castTag(.@"error").?.data.name, rval.castTag(.@"error").?.data.name) == (op == .eq));3824 return mod.constBool(sema.arena, src, std.mem.eql(u8, lval.castTag(.@"error").?.data.name, rval.castTag(.@"error").?.data.name) == (op == .eq));
3821 }3825 }
3822 }3826 }
3823 try sema.requireRuntimeBlock(block, src);3827 try sema.requireRuntimeBlock(block, src);
...@@ -3829,11 +3833,30 @@ fn zirCmp(...@@ -3829,11 +3833,30 @@ fn zirCmp(
3829 return sema.cmpNumeric(block, src, lhs, rhs, op);3833 return sema.cmpNumeric(block, src, lhs, rhs, op);
3830 } else if (lhs_ty_tag == .Type and rhs_ty_tag == .Type) {3834 } else if (lhs_ty_tag == .Type and rhs_ty_tag == .Type) {
3831 if (!is_equality_cmp) {3835 if (!is_equality_cmp) {
3832 return sema.mod.fail(&block.base, src, "{s} operator not allowed for types", .{@tagName(op)});3836 return mod.fail(&block.base, src, "{s} operator not allowed for types", .{@tagName(op)});
3833 }3837 }
3834 return sema.mod.constBool(sema.arena, src, lhs.value().?.eql(rhs.value().?) == (op == .eq));3838 return mod.constBool(sema.arena, src, lhs.value().?.eql(rhs.value().?) == (op == .eq));
3839 }
3840
3841 const instructions = &[_]*Inst{ lhs, rhs };
3842 const resolved_type = try sema.resolvePeerTypes(block, src, instructions);
3843 if (!resolved_type.isSelfComparable(is_equality_cmp)) {
3844 return mod.fail(&block.base, src, "operator not allowed for type '{}'", .{resolved_type});
3835 }3845 }
3836 return sema.mod.fail(&block.base, src, "TODO implement more cmp analysis", .{});3846
3847 const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
3848 const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
3849 try sema.requireRuntimeBlock(block, src); // TODO try to do it at comptime
3850 const bool_type = Type.initTag(.bool); // TODO handle vectors
3851 const tag: Inst.Tag = switch (op) {
3852 .lt => .cmp_lt,
3853 .lte => .cmp_lte,
3854 .eq => .cmp_eq,
3855 .gte => .cmp_gte,
3856 .gt => .cmp_gt,
3857 .neq => .cmp_neq,
3858 };
3859 return block.addBinOp(src, bool_type, tag, casted_lhs, casted_rhs);
3837}3860}
38383861
3839fn zirTypeof(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {3862fn zirTypeof(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
src/type.zig+41
...@@ -107,6 +107,42 @@ pub const Type = extern union {...@@ -107,6 +107,42 @@ pub const Type = extern union {
107 }107 }
108 }108 }
109109
110 pub fn isSelfComparable(ty: Type, is_equality_cmp: bool) bool {
111 return switch (ty.zigTypeTag()) {
112 .Int,
113 .Float,
114 .ComptimeFloat,
115 .ComptimeInt,
116 .Vector, // TODO some vectors require is_equality_cmp==true
117 => true,
118
119 .Bool,
120 .Type,
121 .Void,
122 .ErrorSet,
123 .Fn,
124 .BoundFn,
125 .Opaque,
126 .AnyFrame,
127 .Enum,
128 .EnumLiteral,
129 => is_equality_cmp,
130
131 .NoReturn,
132 .Array,
133 .Struct,
134 .Undefined,
135 .Null,
136 .ErrorUnion,
137 .Union,
138 .Frame,
139 => false,
140
141 .Pointer => is_equality_cmp or ty.isCPtr(),
142 .Optional => is_equality_cmp and ty.isAbiPtr(),
143 };
144 }
145
110 pub fn initTag(comptime small_tag: Tag) Type {146 pub fn initTag(comptime small_tag: Tag) Type {
111 comptime assert(@enumToInt(small_tag) < Tag.no_payload_count);147 comptime assert(@enumToInt(small_tag) < Tag.no_payload_count);
112 return .{ .tag_if_small_enough = @enumToInt(small_tag) };148 return .{ .tag_if_small_enough = @enumToInt(small_tag) };
...@@ -1583,6 +1619,11 @@ pub const Type = extern union {...@@ -1583,6 +1619,11 @@ pub const Type = extern union {
1583 }1619 }
1584 }1620 }
15851621
1622 /// Returns whether the type is represented as a pointer in the ABI.
1623 pub fn isAbiPtr(self: Type) bool {
1624 @panic("TODO implement this");
1625 }
1626
1586 /// Asserts that the type is an error union.1627 /// Asserts that the type is an error union.
1587 pub fn errorUnionChild(self: Type) Type {1628 pub fn errorUnionChild(self: Type) Type {
1588 return switch (self.tag()) {1629 return switch (self.tag()) {
test/stage2/cbe.zig+21
...@@ -536,6 +536,27 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -536,6 +536,27 @@ pub fn addCases(ctx: *TestContext) !void {
536 , "");536 , "");
537 }537 }
538538
539 {
540 var case = ctx.exeFromCompiledC("enums", .{});
541 case.addCompareOutput(
542 \\const Number = enum { One, Two, Three };
543 \\
544 \\export fn main() c_int {
545 \\ var number1 = Number.One;
546 \\ var number2: Number = .Two;
547 \\ const number3 = @intToEnum(Number, 2);
548 \\ if (number1 == number2) return 1;
549 \\ if (number2 == number3) return 1;
550 \\ if (@enumToInt(number1) != 0) return 1;
551 \\ if (@enumToInt(number2) != 1) return 1;
552 \\ if (@enumToInt(number3) != 2) return 1;
553 \\ var x: Number = .Two;
554 \\ if (number2 != x) return 1;
555 \\ return 0;
556 \\}
557 , "");
558 }
559
539 ctx.c("empty start function", linux_x64,560 ctx.c("empty start function", linux_x64,
540 \\export fn _start() noreturn {561 \\export fn _start() noreturn {
541 \\ unreachable;562 \\ unreachable;