Просмотр исходного кода

Feature not equal (#2146)

Implement != operator.

Co-authored-by: m new <michael.burzan@outlook.de>
pmqtt 3 лет назад
Родитель
Сommit
5ef6e9b6a0

+ 1 - 0
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;

+ 3 - 0
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 << ")";
 }

+ 2 - 0
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:

+ 1 - 0
explorer/ast/expression.h

@@ -132,6 +132,7 @@ enum class Operator {
   Mod,
   Neg,
   Not,
+  NotEq,
   Or,
   Sub,
   Ptr,

+ 14 - 1
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);
+  }
 }
 
 // ----------------------

+ 2 - 0
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:

+ 1 - 0
explorer/interpreter/interpreter.cpp

@@ -214,6 +214,7 @@ auto Interpreter::EvalPrim(Operator op, Nonnull<const Value*> static_type,
       return &cast<TypeOfConstraintType>(static_type)->constraint_type();
     case Operator::As:
     case Operator::Eq:
+    case Operator::NotEq:
     case Operator::Less:
     case Operator::LessEq:
     case Operator::Greater:

+ 29 - 70
explorer/interpreter/type_checker.cpp

@@ -2026,6 +2026,22 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
         return handle_binary_operator(builtin);
       };
 
+      auto handle_compare =
+          [&](Builtins::Builtin builtin, const std::string& method_name,
+              const std::string_view& operator_desc) -> ErrorOr<Success> {
+        ErrorOr<Nonnull<Expression*>> 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<Expression*> e,
           op.set_static_type(arena_->New<BoolType>());
           op.set_value_category(ValueCategory::Let);
           return Success();
-        case Operator::Eq: {
-          ErrorOr<Nonnull<Expression*>> 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<Nonnull<Expression*>> 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<Nonnull<Expression*>> 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<Nonnull<Expression*>> 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<Nonnull<Expression*>> 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]));

+ 2 - 0
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);             }

+ 3 - 1
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

+ 4 - 0
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;
 }

+ 5 - 0
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;
 }

+ 3 - 0
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 {

+ 8 - 5
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;
 }

+ 3 - 0
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 {

+ 3 - 0
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 {