From 5ef6e9b6a0f3e4980b5c44b3976b3809b7047d03 Mon Sep 17 00:00:00 2001 From: pmqtt <51272730+pmqtt@users.noreply.github.com> Date: Fri, 9 Sep 2022 21:25:20 +0200 Subject: [PATCH] Feature not equal (#2146) Implement != operator. Co-authored-by: m new --- common/fuzzing/carbon.proto | 1 + common/fuzzing/proto_to_carbon.cpp | 3 + explorer/ast/expression.cpp | 2 + explorer/ast/expression.h | 1 + explorer/data/prelude.carbon | 15 ++- explorer/fuzzing/ast_to_proto.cpp | 2 + explorer/interpreter/interpreter.cpp | 1 + explorer/interpreter/type_checker.cpp | 99 ++++++------------- explorer/syntax/lexer.lpp | 2 + explorer/syntax/parser.ypp | 4 +- .../comparison/builtin_equality.carbon | 4 + .../comparison/custom_equality.carbon | 5 + .../comparison/fail_empty_struct.carbon | 3 + explorer/testdata/struct/equality.carbon | 13 ++- .../testdata/struct/equality_false.carbon | 3 + .../testdata/struct/fail_equality_type.carbon | 3 + 16 files changed, 84 insertions(+), 77 deletions(-) diff --git a/common/fuzzing/carbon.proto b/common/fuzzing/carbon.proto index 06431bfc1a41..07ce2d73fd03 100644 --- a/common/fuzzing/carbon.proto +++ b/common/fuzzing/carbon.proto @@ -64,6 +64,7 @@ message OperatorExpression { LessEq = 21; Greater = 22; GreaterEq = 23; + NotEq = 24; } optional Operator op = 1; repeated Expression arguments = 2; diff --git a/common/fuzzing/proto_to_carbon.cpp b/common/fuzzing/proto_to_carbon.cpp index 91635acfd088..e71b8d448c27 100644 --- a/common/fuzzing/proto_to_carbon.cpp +++ b/common/fuzzing/proto_to_carbon.cpp @@ -189,6 +189,9 @@ static auto OperatorToCarbon(const Fuzzing::OperatorExpression& operator_expr, case Fuzzing::OperatorExpression::BitShiftRight: BinaryOperatorToCarbon(arg0, " >> ", arg1, out); break; + case Fuzzing::OperatorExpression::NotEq: + BinaryOperatorToCarbon(arg0, " != ", arg1, out); + break; } out << ")"; } diff --git a/explorer/ast/expression.cpp b/explorer/ast/expression.cpp index 607f213dc5ba..6784730e4850 100644 --- a/explorer/ast/expression.cpp +++ b/explorer/ast/expression.cpp @@ -129,6 +129,8 @@ auto ToString(Operator op) -> std::string_view { return "*"; case Operator::Not: return "not"; + case Operator::NotEq: + return "!="; case Operator::And: return "and"; case Operator::Or: diff --git a/explorer/ast/expression.h b/explorer/ast/expression.h index 68efcd22179f..574ce08c4ee1 100644 --- a/explorer/ast/expression.h +++ b/explorer/ast/expression.h @@ -132,6 +132,7 @@ enum class Operator { Mod, Neg, Not, + NotEq, Or, Sub, Ptr, diff --git a/explorer/data/prelude.carbon b/explorer/data/prelude.carbon index 430604885ebb..511e248636b0 100644 --- a/explorer/data/prelude.carbon +++ b/explorer/data/prelude.carbon @@ -63,7 +63,7 @@ impl forall [U1:! Type, U2:! Type, U3:! Type, interface EqWith(U:! Type) { fn Equal[me: Self](other: U) -> bool; - // TODO: NotEqual with default impl + fn NotEqual[me: Self](other: U) -> bool; } // TODO: constraint Eq { ... } @@ -76,18 +76,31 @@ impl forall [T2:! Type, U2:! Type, T1:! EqWith(T2), U1:! EqWith(U2)] let (r1: T2, r2: U2) = other; return l1 == r1 and l2 == r2; } + fn NotEqual[me: Self](other: (T2, U2)) -> bool { + let (l1: T1, l2: U1) = me; + let (r1: T2, r2: U2) = other; + return l1 != r1 or l2 != r2; + } } impl i32 as EqWith(Self) { fn Equal[me: Self](other: Self) -> bool { return __intrinsic_int_eq(me, other); } + + fn NotEqual[me: Self](other: Self) -> bool { + return not __intrinsic_int_eq(me, other); + } } impl String as EqWith(Self) { fn Equal[me: Self](other: Self) -> bool { return __intrinsic_str_eq(me, other); } + + fn NotEqual[me: Self](other: Self) -> bool { + return not __intrinsic_str_eq(me, other); + } } // ---------------------- diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index 3dc4c28ca8c4..336b3b7b3026 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/explorer/fuzzing/ast_to_proto.cpp @@ -53,6 +53,8 @@ static auto OperatorToProtoEnum(const Operator op) return Fuzzing::OperatorExpression::And; case Operator::Eq: return Fuzzing::OperatorExpression::Eq; + case Operator::NotEq: + return Fuzzing::OperatorExpression::NotEq; case Operator::Less: return Fuzzing::OperatorExpression::Less; case Operator::LessEq: diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 0401be44c3c9..24907eefde6c 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -214,6 +214,7 @@ auto Interpreter::EvalPrim(Operator op, Nonnull static_type, return &cast(static_type)->constraint_type(); case Operator::As: case Operator::Eq: + case Operator::NotEq: case Operator::Less: case Operator::LessEq: case Operator::Greater: diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 001e14f5a4bb..e8f0402d028f 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -2026,6 +2026,22 @@ auto TypeChecker::TypeCheckExp(Nonnull e, return handle_binary_operator(builtin); }; + auto handle_compare = + [&](Builtins::Builtin builtin, const std::string& method_name, + const std::string_view& operator_desc) -> ErrorOr { + ErrorOr> converted = BuildBuiltinMethodCall( + impl_scope, op.arguments()[0], BuiltinInterfaceName{builtin, ts[1]}, + BuiltinMethodCall{method_name, op.arguments()[1]}); + if (!converted.ok()) { + // We couldn't find a matching `impl`. + return CompilationError(e->source_loc()) + << *ts[0] << " is not " << operator_desc << " comparable with " + << *ts[1] << " (" << converted.error().message() << ")"; + } + op.set_rewritten_form(*converted); + return Success(); + }; + switch (op.op()) { case Operator::Neg: { // Handle a built-in negation first. @@ -2109,76 +2125,19 @@ auto TypeChecker::TypeCheckExp(Nonnull e, op.set_static_type(arena_->New()); op.set_value_category(ValueCategory::Let); return Success(); - case Operator::Eq: { - ErrorOr> converted = BuildBuiltinMethodCall( - impl_scope, op.arguments()[0], - BuiltinInterfaceName{Builtins::EqWith, ts[1]}, - BuiltinMethodCall{"Equal", op.arguments()[1]}); - if (!converted.ok()) { - // We couldn't find a matching `impl`. - return CompilationError(e->source_loc()) - << *ts[0] << " is not equality comparable with " << *ts[1] - << " (" << converted.error().message() << ")"; - } - op.set_rewritten_form(*converted); - return Success(); - } - case Operator::Less: { - ErrorOr> converted = BuildBuiltinMethodCall( - impl_scope, op.arguments()[0], - BuiltinInterfaceName{Builtins::LessWith, ts[1]}, - BuiltinMethodCall{"Less", op.arguments()[1]}); - if (!converted.ok()) { - // We couldn't find a matching `impl`. - return CompilationError(e->source_loc()) - << *ts[0] << " is not less comparable with " << *ts[1] - << " (" << converted.error().message() << ")"; - } - op.set_rewritten_form(*converted); - return Success(); - } - case Operator::LessEq: { - ErrorOr> converted = BuildBuiltinMethodCall( - impl_scope, op.arguments()[0], - BuiltinInterfaceName{Builtins::LessEqWith, ts[1]}, - BuiltinMethodCall{"LessEq", op.arguments()[1]}); - if (!converted.ok()) { - // We couldn't find a matching `impl`. - return CompilationError(e->source_loc()) - << *ts[0] << " is not less equal comparable with " << *ts[1] - << " (" << converted.error().message() << ")"; - } - op.set_rewritten_form(*converted); - return Success(); - } - case Operator::GreaterEq: { - ErrorOr> converted = BuildBuiltinMethodCall( - impl_scope, op.arguments()[0], - BuiltinInterfaceName{Builtins::GreaterEqWith, ts[1]}, - BuiltinMethodCall{"GreaterEq", op.arguments()[1]}); - if (!converted.ok()) { - // We couldn't find a matching `impl`. - return CompilationError(e->source_loc()) - << *ts[0] << " is not greater equal comparable with " - << *ts[1] << " (" << converted.error().message() << ")"; - } - op.set_rewritten_form(*converted); - return Success(); - } - case Operator::Greater: { - ErrorOr> converted = BuildBuiltinMethodCall( - impl_scope, op.arguments()[0], - BuiltinInterfaceName{Builtins::GreaterWith, ts[1]}, - BuiltinMethodCall{"Greater", op.arguments()[1]}); - if (!converted.ok()) { - // We couldn't find a matching `impl`. - return CompilationError(e->source_loc()) - << *ts[0] << " is not greater comparable with " << *ts[1] - << " (" << converted.error().message() << ")"; - } - op.set_rewritten_form(*converted); - return Success(); - } + case Operator::Eq: + return handle_compare(Builtins::EqWith, "Equal", "equality"); + case Operator::NotEq: + return handle_compare(Builtins::EqWith, "NotEqual", "equality"); + case Operator::Less: + return handle_compare(Builtins::LessWith, "Less", "less"); + case Operator::LessEq: + return handle_compare(Builtins::LessEqWith, "LessEq", "less equal"); + case Operator::GreaterEq: + return handle_compare(Builtins::GreaterEqWith, "GreaterEq", + "greater equal"); + case Operator::Greater: + return handle_compare(Builtins::GreaterWith, "Greater", "greater"); case Operator::Deref: CARBON_RETURN_IF_ERROR( ExpectPointerType(e->source_loc(), "*", ts[0])); diff --git a/explorer/syntax/lexer.lpp b/explorer/syntax/lexer.lpp index 241ed780d465..82ef494482d5 100644 --- a/explorer/syntax/lexer.lpp +++ b/explorer/syntax/lexer.lpp @@ -88,6 +88,7 @@ MINUS "-" MIX "__mix" MIXIN "__mixin" NOT "not" +NOT_EQUAL "!=" OR "or" PACKAGE "package" PERCENT "%" @@ -191,6 +192,7 @@ operand_start [(A-Za-z0-9_\"] {MINUS} { return CARBON_SIMPLE_TOKEN(MINUS); } {MIXIN} { return CARBON_SIMPLE_TOKEN(MIXIN); } {MIX} { return CARBON_SIMPLE_TOKEN(MIX); } +{NOT_EQUAL} { return CARBON_SIMPLE_TOKEN(NOT_EQUAL); } {NOT} { return CARBON_SIMPLE_TOKEN(NOT); } {OR} { return CARBON_SIMPLE_TOKEN(OR); } {PACKAGE} { return CARBON_SIMPLE_TOKEN(PACKAGE); } diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index c7abc6d7f8cc..ea77ff5dbf63 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -256,6 +256,7 @@ MIX MIXIN NOT + NOT_EQUAL OR PACKAGE PERCENT @@ -604,7 +605,6 @@ comparison_operand: ref_deref_expression | value_expression ; -/* TODO: Add NOT_EQUAL */ comparison_operator: EQUAL_EQUAL { $$ = Operator::Eq; } @@ -616,6 +616,8 @@ comparison_operator: { $$ = Operator::Greater; } | GREATER_EQUAL { $$ = Operator::GreaterEq; } +| NOT_EQUAL + { $$ = Operator::NotEq; } ; comparison_expression: value_expression diff --git a/explorer/testdata/comparison/builtin_equality.carbon b/explorer/testdata/comparison/builtin_equality.carbon index 4a9a578c8486..4bcc51f60c00 100644 --- a/explorer/testdata/comparison/builtin_equality.carbon +++ b/explorer/testdata/comparison/builtin_equality.carbon @@ -9,6 +9,8 @@ // AUTOUPDATE: %{explorer} %s // CHECK: strings equal: 0 // CHECK: ints equal: 1 +// CHECK: strings not eq: 1 +// CHECK: ints not eq: 0 // CHECK: result: 0 package ExplorerTest api; @@ -16,5 +18,7 @@ package ExplorerTest api; fn Main() -> i32 { Print("strings equal: {0}", if "hello" == "world" then 1 else 0); Print("ints equal: {0}", if 1 == 1 then 1 else 0); + Print("strings not eq: {0}", if "hello" != "world" then 1 else 0); + Print("ints not eq: {0}", if 1 != 1 then 1 else 0); return 0; } diff --git a/explorer/testdata/comparison/custom_equality.carbon b/explorer/testdata/comparison/custom_equality.carbon index 0cf3e8909480..d243a8dab55c 100644 --- a/explorer/testdata/comparison/custom_equality.carbon +++ b/explorer/testdata/comparison/custom_equality.carbon @@ -8,6 +8,7 @@ // RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s // AUTOUPDATE: %{explorer} %s // CHECK: structs equal: 0 +// CHECK: structs not equal: 1 // CHECK: result: 0 package ExplorerTest api; @@ -19,6 +20,9 @@ class MyType { fn Equal[me: Self](other: Self) -> bool { return me.value == other.value; } + fn NotEqual[me: Self](other: Self) -> bool{ + return me.value != other.value; + } } } @@ -26,5 +30,6 @@ fn Main() -> i32 { let x: MyType = {.value = 1}; let y: MyType = {.value = 2}; Print("structs equal: {0}", if x == y then 1 else 0); + Print("structs not equal: {0}", if x != y then 1 else 0); return 0; } diff --git a/explorer/testdata/comparison/fail_empty_struct.carbon b/explorer/testdata/comparison/fail_empty_struct.carbon index 5e701cca79e3..d09a2b189dfb 100644 --- a/explorer/testdata/comparison/fail_empty_struct.carbon +++ b/explorer/testdata/comparison/fail_empty_struct.carbon @@ -13,6 +13,9 @@ external impl {} as EqWith({}) { fn Equal[me: Self](other: Self) -> bool { return true; } + fn NotEqual[me: Self](other: Self) -> bool { + return false; + } } fn Main() -> i32 { diff --git a/explorer/testdata/struct/equality.carbon b/explorer/testdata/struct/equality.carbon index af3fe3172754..cfee857cdd81 100644 --- a/explorer/testdata/struct/equality.carbon +++ b/explorer/testdata/struct/equality.carbon @@ -7,6 +7,8 @@ // RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ // RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s // AUTOUPDATE: %{explorer} %s +// CHECK: t1 == t2: 1 +// CHECK: t1 != t2: 0 // CHECK: result: 0 package ExplorerTest api; @@ -16,14 +18,15 @@ external impl {.x: i32, .y: i32} as EqWith(Self) { fn Equal[me: Self](other: Self) -> bool { return me.x == other.x and me.y == other.y; } + fn NotEqual[me: Self](other: Self) -> bool { + return me.x != other.x or me.y != other.y; + } } fn Main() -> i32 { var t1: {.x: i32, .y: i32} = {.x = 5, .y = 2}; var t2: {.x: i32, .y: i32} = {.x = 5, .y = 2}; - if (t1 == t2) { - return 0; - } else { - return 1; - } + Print("t1 == t2: {0}", if t1 == t2 then 1 else 0); + Print("t1 != t2: {0}", if t1 != t2 then 1 else 0); + return 0; } diff --git a/explorer/testdata/struct/equality_false.carbon b/explorer/testdata/struct/equality_false.carbon index 6d0e769dd9c9..7146e053295c 100644 --- a/explorer/testdata/struct/equality_false.carbon +++ b/explorer/testdata/struct/equality_false.carbon @@ -16,6 +16,9 @@ external impl {.x: i32, .y: i32} as EqWith(Self) { fn Equal[me: Self](other: Self) -> bool { return me.x == other.x and me.y == other.y; } + fn NotEqual[me: Self](other: Self) -> bool { + return me.x != other.x or me.y != other.y; + } } fn Main() -> i32 { diff --git a/explorer/testdata/struct/fail_equality_type.carbon b/explorer/testdata/struct/fail_equality_type.carbon index 6807318fdcc0..8dbbd0438ffd 100644 --- a/explorer/testdata/struct/fail_equality_type.carbon +++ b/explorer/testdata/struct/fail_equality_type.carbon @@ -12,6 +12,9 @@ external impl {.x: i32, .y: i32} as EqWith(Self) { fn Equal[me: Self](other: Self) -> bool { return me.x == other.x and me.y == other.y; } + fn NotEqual[me: Self](other: Self) -> bool { + return me.x != other.x or me.y != other.y; + } } fn Main() -> i32 {