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(
37763776 const tracy = trace(@src());
37773777 defer tracy.end();
37783778
3779 const mod = sema.mod;
3780
37793781 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
37803782 const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
37813783 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 };
37823786 const lhs = try sema.resolveInst(extra.lhs);
37833787 const rhs = try sema.resolveInst(extra.rhs);
37843788
......@@ -3790,7 +3794,7 @@ fn zirCmp(
37903794 const rhs_ty_tag = rhs.ty.zigTypeTag();
37913795 if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) {
37923796 // null == null, null != null
3793 return sema.mod.constBool(sema.arena, src, op == .eq);
3797 return mod.constBool(sema.arena, src, op == .eq);
37943798 } else if (is_equality_cmp and
37953799 ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or
37963800 rhs_ty_tag == .Null and lhs_ty_tag == .Optional))
......@@ -3801,23 +3805,23 @@ fn zirCmp(
38013805 } else if (is_equality_cmp and
38023806 ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr())))
38033807 {
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", .{});
38053809 } else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) {
38063810 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});
38083812 } else if (is_equality_cmp and
38093813 ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or
38103814 (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union)))
38113815 {
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", .{});
38133817 } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {
38143818 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)});
38163820 }
38173821 if (rhs.value()) |rval| {
38183822 if (lhs.value()) |lval| {
38193823 // 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));
38213825 }
38223826 }
38233827 try sema.requireRuntimeBlock(block, src);
......@@ -3829,11 +3833,30 @@ fn zirCmp(
38293833 return sema.cmpNumeric(block, src, lhs, rhs, op);
38303834 } else if (lhs_ty_tag == .Type and rhs_ty_tag == .Type) {
38313835 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)});
38333837 }
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});
38353845 }
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);
38373860}
38383861
38393862fn 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 {
107107 }
108108 }
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
110146 pub fn initTag(comptime small_tag: Tag) Type {
111147 comptime assert(@enumToInt(small_tag) < Tag.no_payload_count);
112148 return .{ .tag_if_small_enough = @enumToInt(small_tag) };
......@@ -1583,6 +1619,11 @@ pub const Type = extern union {
15831619 }
15841620 }
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
15861627 /// Asserts that the type is an error union.
15871628 pub fn errorUnionChild(self: Type) Type {
15881629 return switch (self.tag()) {
test/stage2/cbe.zig+21
......@@ -536,6 +536,27 @@ pub fn addCases(ctx: *TestContext) !void {
536536 , "");
537537 }
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
539560 ctx.c("empty start function", linux_x64,
540561 \\export fn _start() noreturn {
541562 \\ unreachable;