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

Add extensibility support for arithmetic operators. (#1751)

Add new prelude interfaces `Negate`, `AddWith`, `SubWith`, and `MulWith`, and
use them to implement non-builtin arithmetic operations.
Richard Smith 3 лет назад
Родитель
Сommit
15ec5113aa

+ 41 - 0
explorer/data/prelude.carbon

@@ -49,6 +49,47 @@ impl forall [U1:! Type, U2:! Type, U3:! Type,
   }
 }
 
+interface Negate {
+  // TODO: = Self
+  let Result:! Type;
+  fn Op[me: Self]() -> Result;
+}
+
+interface AddWith(U:! Type) {
+  // TODO: = Self
+  let Result:! Type;
+  fn Op[me: Self](other: U) -> Result;
+}
+// TODO: constraint Add { ... }
+
+interface SubWith(U:! Type) {
+  // TODO: = Self
+  let Result:! Type;
+  fn Op[me: Self](other: U) -> Result;
+}
+// TODO: constraint Sub { ... }
+
+interface MulWith(U:! Type) {
+  // TODO: = Self
+  let Result:! Type;
+  fn Op[me: Self](other: U) -> Result;
+}
+// TODO: constraint Mul { ... }
+
+// Note, these impls use the builtin addition for i32.
+external impl i32 as Negate where .Result == i32 {
+  fn Op[me: i32]() -> i32 { return -me; }
+}
+external impl i32 as AddWith(i32) where .Result == i32 {
+  fn Op[me: i32](other: i32) -> i32 { return me + other; }
+}
+external impl i32 as SubWith(i32) where .Result == i32 {
+  fn Op[me: i32](other: i32) -> i32 { return me - other; }
+}
+external impl i32 as MulWith(i32) where .Result == i32 {
+  fn Op[me: i32](other: i32) -> i32 { return me * other; }
+}
+
 // Note that Print is experimental, and not part of an accepted proposal, but
 // is included here for printing state in tests.
 // TODO: Remove Print special casing once we have variadics or overloads.

+ 11 - 5
explorer/interpreter/builtins.cpp

@@ -12,11 +12,17 @@ namespace Carbon {
 
 void Builtins::Register(Nonnull<const Declaration*> decl) {
   if (auto* interface = dyn_cast<InterfaceDeclaration>(decl)) {
-    if (interface->name() == GetName(Builtin::ImplicitAs)) {
-      builtins_[static_cast<int>(Builtin::ImplicitAs)] = interface;
-    }
-    if (interface->name() == GetName(Builtin::As)) {
-      builtins_[static_cast<int>(Builtin::As)] = interface;
+    static std::map<std::string, int>* builtin_indexes = [] {
+      std::map<std::string, int> builtin_indexes;
+      for (int index = 0; index <= static_cast<int>(Builtin::Last); ++index) {
+        builtin_indexes.emplace(BuiltinNames[index], index);
+      }
+      return new auto(std::move(builtin_indexes));
+    }();
+
+    auto it = builtin_indexes->find(interface->name());
+    if (it != builtin_indexes->end()) {
+      builtins_[it->second] = interface;
     }
   }
 }

+ 20 - 3
explorer/interpreter/builtins.h

@@ -20,10 +20,26 @@ class Builtins {
  public:
   explicit Builtins() {}
 
-  enum class Builtin { ImplicitAs, As, Last = As };
+  enum class Builtin {
+    // Conversions.
+    As,
+    ImplicitAs,
+
+    // Arithmetic.
+    Negate,
+    AddWith,
+    SubWith,
+    MulWith,
+
+    Last = MulWith
+  };
   // TODO: In C++20, replace with `using enum Builtin;`.
-  static constexpr Builtin ImplicitAs = Builtin::ImplicitAs;
   static constexpr Builtin As = Builtin::As;
+  static constexpr Builtin ImplicitAs = Builtin::ImplicitAs;
+  static constexpr Builtin Negate = Builtin::Negate;
+  static constexpr Builtin AddWith = Builtin::AddWith;
+  static constexpr Builtin SubWith = Builtin::SubWith;
+  static constexpr Builtin MulWith = Builtin::MulWith;
 
   // Register a declaration that might be a builtin.
   void Register(Nonnull<const Declaration*> decl);
@@ -39,7 +55,8 @@ class Builtins {
 
  private:
   static constexpr int NumBuiltins = static_cast<int>(Builtin::Last) + 1;
-  static constexpr const char* BuiltinNames[NumBuiltins] = {"ImplicitAs", "As"};
+  static constexpr const char* BuiltinNames[NumBuiltins] = {
+      "As", "ImplicitAs", "Negate", "AddWith", "SubWith", "MulWith"};
 
   std::optional<Nonnull<const Declaration*>> builtins_[NumBuiltins] = {};
 };

+ 58 - 40
explorer/interpreter/type_checker.cpp

@@ -127,14 +127,20 @@ static void SetValue(Nonnull<Pattern*> pattern, Nonnull<const Value*> value) {
   }
 }
 
+auto TypeChecker::IsSameType(Nonnull<const Value*> type1,
+                             Nonnull<const Value*> type2,
+                             const ImplScope& impl_scope) const -> bool {
+  SingleStepEqualityContext equality_ctx(this, &impl_scope);
+  return TypeEqual(type1, type2, &equality_ctx);
+}
+
 auto TypeChecker::ExpectExactType(SourceLocation source_loc,
                                   const std::string& context,
                                   Nonnull<const Value*> expected,
                                   Nonnull<const Value*> actual,
                                   const ImplScope& impl_scope) const
     -> ErrorOr<Success> {
-  SingleStepEqualityContext equality_ctx(this, &impl_scope);
-  if (!TypeEqual(expected, actual, &equality_ctx)) {
+  if (!IsSameType(expected, actual, impl_scope)) {
     return CompilationError(source_loc) << "type error in " << context << "\n"
                                         << "expected: " << *expected << "\n"
                                         << "actual: " << *actual;
@@ -380,8 +386,7 @@ auto TypeChecker::IsImplicitlyConvertible(
   // conversions.
   CARBON_CHECK(IsConcreteType(source));
   CARBON_CHECK(IsConcreteType(destination));
-  SingleStepEqualityContext equality_ctx(this, &impl_scope);
-  if (TypeEqual(source, destination, &equality_ctx)) {
+  if (IsSameType(source, destination, impl_scope)) {
     return true;
   }
 
@@ -1938,44 +1943,58 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
         CARBON_RETURN_IF_ERROR(TypeCheckExp(argument, impl_scope));
         ts.push_back(&argument->static_type());
       }
-      switch (op.op()) {
-        case Operator::Neg:
-          CARBON_RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "negation",
-                                                 arena_->New<IntType>(), ts[0],
-                                                 impl_scope));
-          op.set_static_type(arena_->New<IntType>());
+
+      auto handle_binary_arithmetic =
+          [&](Builtins::Builtin builtin) -> ErrorOr<Success> {
+        // Handle a built-in operator first.
+        if (isa<IntType>(ts[0]) && isa<IntType>(ts[1]) &&
+            IsSameType(ts[0], ts[1], impl_scope)) {
+          op.set_static_type(ts[0]);
           op.set_value_category(ValueCategory::Let);
           return Success();
-        case Operator::Add:
-          CARBON_RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "addition(1)",
-                                                 arena_->New<IntType>(), ts[0],
-                                                 impl_scope));
-          CARBON_RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "addition(2)",
-                                                 arena_->New<IntType>(), ts[1],
-                                                 impl_scope));
-          op.set_static_type(arena_->New<IntType>());
-          op.set_value_category(ValueCategory::Let);
+        }
+
+        // Now try an overloaded operator.
+        ErrorOr<Nonnull<Expression*>> result = BuildBuiltinMethodCall(
+            impl_scope, op.arguments()[0], BuiltinInterfaceName{builtin, ts[1]},
+            BuiltinMethodCall{"Op", {op.arguments()[1]}});
+        if (!result.ok()) {
+          // We couldn't find a matching `impl`.
+          return CompilationError(e->source_loc())
+                 << "type error in `" << ToString(op.op()) << "`:\n"
+                 << result.error().message();
+        }
+        op.set_rewritten_form(*result);
+        return Success();
+      };
+
+      switch (op.op()) {
+        case Operator::Neg: {
+          // Handle a built-in negation first.
+          if (isa<IntType>(ts[0])) {
+            op.set_static_type(arena_->New<IntType>());
+            op.set_value_category(ValueCategory::Let);
+            return Success();
+          }
+          // Now try an overloaded negation.
+          ErrorOr<Nonnull<Expression*>> result = BuildBuiltinMethodCall(
+              impl_scope, op.arguments()[0],
+              BuiltinInterfaceName{Builtins::Negate}, BuiltinMethodCall{"Op"});
+          if (!result.ok()) {
+            // We couldn't find a matching `impl`.
+            return CompilationError(e->source_loc())
+                   << "type error in `" << ToString(op.op()) << "`:\n"
+                   << result.error().message();
+          }
+          op.set_rewritten_form(*result);
           return Success();
+        }
+        case Operator::Add:
+          return handle_binary_arithmetic(Builtins::AddWith);
         case Operator::Sub:
-          CARBON_RETURN_IF_ERROR(
-              ExpectExactType(e->source_loc(), "subtraction(1)",
-                              arena_->New<IntType>(), ts[0], impl_scope));
-          CARBON_RETURN_IF_ERROR(
-              ExpectExactType(e->source_loc(), "subtraction(2)",
-                              arena_->New<IntType>(), ts[1], impl_scope));
-          op.set_static_type(arena_->New<IntType>());
-          op.set_value_category(ValueCategory::Let);
-          return Success();
+          return handle_binary_arithmetic(Builtins::SubWith);
         case Operator::Mul:
-          CARBON_RETURN_IF_ERROR(
-              ExpectExactType(e->source_loc(), "multiplication(1)",
-                              arena_->New<IntType>(), ts[0], impl_scope));
-          CARBON_RETURN_IF_ERROR(
-              ExpectExactType(e->source_loc(), "multiplication(2)",
-                              arena_->New<IntType>(), ts[1], impl_scope));
-          op.set_static_type(arena_->New<IntType>());
-          op.set_value_category(ValueCategory::Let);
-          return Success();
+          return handle_binary_arithmetic(Builtins::MulWith);
         case Operator::And:
           CARBON_RETURN_IF_ERROR(ExpectExactType(e->source_loc(), "&&(1)",
                                                  arena_->New<BoolType>(), ts[0],
@@ -2812,9 +2831,8 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
         // TODO: Consider using `ExpectExactType` here.
         CARBON_CHECK(IsConcreteType(&return_term.static_type()));
         CARBON_CHECK(IsConcreteType(&ret.value_node().static_type()));
-        SingleStepEqualityContext equality_ctx(this, &impl_scope);
-        if (!TypeEqual(&return_term.static_type(),
-                       &ret.value_node().static_type(), &equality_ctx)) {
+        if (!IsSameType(&return_term.static_type(),
+                        &ret.value_node().static_type(), impl_scope)) {
           return CompilationError(ret.value_node().base().source_loc())
                  << "type of returned var `" << ret.value_node().static_type()
                  << "` does not match return type `"

+ 7 - 0
explorer/interpreter/type_checker.h

@@ -328,6 +328,13 @@ class TypeChecker {
                          Nonnull<const Value*> destination)
       -> ErrorOr<Nonnull<Expression*>>;
 
+  // Determine whether `type1` and `type2` are considered to be the same type
+  // in the given scope. This is true if they're structurally identical or if
+  // there is an equality relation in scope that specifies that they are the
+  // same.
+  auto IsSameType(Nonnull<const Value*> type1, Nonnull<const Value*> type2,
+                  const ImplScope& impl_scope) const -> bool;
+
   // Check whether `actual` is implicitly convertible to `expected`
   // and halt with a fatal compilation error if it is not.
   //

+ 4 - 3
explorer/testdata/function/fail_invalid_fnty.carbon

@@ -10,9 +10,10 @@
 
 package ExplorerTest api;
 
-// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/function/fail_invalid_fnty.carbon:[[@LINE+3]]: type error in negation
-// CHECK: expected: i32
-// CHECK: actual: Bool
+// TODO: We should type-check the prelude before the main input so that we can
+// use it within the types of top-level declarations.
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/function/fail_invalid_fnty.carbon:[[@LINE+2]]: type error in `-`:
+// CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/function/fail_invalid_fnty.carbon:[[@LINE+1]]: missing declaration for builtin `Negate`
 fn f(g: __Fn(-true) -> true) {
 }
 

+ 2 - 3
explorer/testdata/generic_function/fail_not_addable.carbon

@@ -11,9 +11,8 @@
 package ExplorerTest api;
 
 fn id[T:! Type](x: T) -> T {
-  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_function/fail_not_addable.carbon:[[@LINE+3]]: type error in addition(1)
-  // CHECK: expected: i32
-  // CHECK: actual: T:! Type
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_function/fail_not_addable.carbon:[[@LINE+2]]: type error in `+`:
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_function/fail_not_addable.carbon:[[@LINE+1]]: could not find implementation of interface AddWith(U = i32) for T:! Type
   return x + 0;
 }
 

+ 3 - 3
explorer/testdata/interface/impl_self_interface_parameter.carbon

@@ -11,7 +11,7 @@
 
 package ExplorerTest api;
 
-interface AddWith(T:! Type) {
+interface MyAddWith(T:! Type) {
   fn Op[me: Self](b: T) -> Self;
 }
 
@@ -21,13 +21,13 @@ class Point {
 }
 
 // Allowed: `Self` means `Point` after `as`
-impl Point as AddWith(Self) {
+impl Point as MyAddWith(Self) {
   fn Op[me: Point](b: Point) -> Point {
     return {.x = me.x + b.x, .y = me.y + b.y};
   }
 }
 
-fn DoAddGeneric[T:! Type, U:! AddWith(T)](a: U, b: T) -> U {
+fn DoAddGeneric[T:! Type, U:! MyAddWith(T)](a: U, b: T) -> U {
   return a.Op(b);
 }
 

+ 24 - 0
explorer/testdata/operators/add.carbon

@@ -0,0 +1,24 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 6
+
+package ExplorerTest api;
+
+class A { var n: i32; }
+
+external impl A as AddWith(i32) where .Result == A {
+  fn Op[me: Self](rhs: i32) -> A { return {.n = me.n + rhs}; }
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 5};
+  a = a + 1;
+  return a.n;
+}

+ 19 - 0
explorer/testdata/operators/add_builtin.carbon

@@ -0,0 +1,19 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 3
+
+package ExplorerTest api;
+
+// TODO: T:! Add
+fn DoAdd[T:! AddWith(.Self) where .Result == .Self](x: T, y: T) -> T { return x + y; }
+
+fn Main() -> i32 {
+  return DoAdd(1, 2);
+}

+ 21 - 0
explorer/testdata/operators/fail_no_add.carbon

@@ -0,0 +1,21 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+class A {}
+
+fn Main() -> i32 {
+  var a: A = {};
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/operators/fail_no_add.carbon:[[@LINE+2]]: type error in `+`:
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/operators/fail_no_add.carbon:[[@LINE+1]]: could not find implementation of interface AddWith(U = i32) for class A
+  a + 1;
+  return 0;
+}

+ 21 - 0
explorer/testdata/operators/fail_no_mul.carbon

@@ -0,0 +1,21 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+class A {}
+
+fn Main() -> i32 {
+  var a: A = {};
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/operators/fail_no_mul.carbon:[[@LINE+2]]: type error in `*`:
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/operators/fail_no_mul.carbon:[[@LINE+1]]: could not find implementation of interface MulWith(U = i32) for class A
+  a * 1;
+  return 0;
+}

+ 21 - 0
explorer/testdata/operators/fail_no_negate.carbon

@@ -0,0 +1,21 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+class A {}
+
+fn Main() -> i32 {
+  var a: A = {};
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/operators/fail_no_negate.carbon:[[@LINE+2]]: type error in `-`:
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/operators/fail_no_negate.carbon:[[@LINE+1]]: could not find implementation of interface Negate for class A
+  -a;
+  return 0;
+}

+ 21 - 0
explorer/testdata/operators/fail_no_sub.carbon

@@ -0,0 +1,21 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+class A {}
+
+fn Main() -> i32 {
+  var a: A = {};
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/operators/fail_no_sub.carbon:[[@LINE+2]]: type error in `-`:
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/operators/fail_no_sub.carbon:[[@LINE+1]]: could not find implementation of interface SubWith(U = i32) for class A
+  a - 1;
+  return 0;
+}

+ 24 - 0
explorer/testdata/operators/mul.carbon

@@ -0,0 +1,24 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 10
+
+package ExplorerTest api;
+
+class A { var n: i32; }
+
+external impl A as MulWith(i32) where .Result == A {
+  fn Op[me: Self](rhs: i32) -> A { return {.n = me.n * rhs}; }
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 5};
+  a = a * 2;
+  return a.n;
+}

+ 19 - 0
explorer/testdata/operators/mul_builtin.carbon

@@ -0,0 +1,19 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 6
+
+package ExplorerTest api;
+
+// TODO: T:! Mul
+fn DoMul[T:! MulWith(.Self) where .Result == .Self](x: T, y: T) -> T { return x * y; }
+
+fn Main() -> i32 {
+  return DoMul(2, 3);
+}

+ 24 - 0
explorer/testdata/operators/negate.carbon

@@ -0,0 +1,24 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: -5
+
+package ExplorerTest api;
+
+class A { var n: i32; }
+
+external impl A as Negate where .Result == A {
+  fn Op[me: Self]() -> A { return {.n = -me.n}; }
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 5};
+  a = -a;
+  return a.n;
+}

+ 18 - 0
explorer/testdata/operators/negate_builtin.carbon

@@ -0,0 +1,18 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: -3
+
+package ExplorerTest api;
+
+fn DoNegate[T:! Negate where .Result == .Self](x: T) -> T { return -x; }
+
+fn Main() -> i32 {
+  return DoNegate(3);
+}

+ 24 - 0
explorer/testdata/operators/sub.carbon

@@ -0,0 +1,24 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 4
+
+package ExplorerTest api;
+
+class A { var n: i32; }
+
+external impl A as SubWith(i32) where .Result == A {
+  fn Op[me: Self](rhs: i32) -> A { return {.n = me.n - rhs}; }
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 5};
+  a = a - 1;
+  return a.n;
+}

+ 19 - 0
explorer/testdata/operators/sub_builtin.carbon

@@ -0,0 +1,19 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 1
+
+package ExplorerTest api;
+
+// TODO: T:! Sub
+fn DoSub[T:! SubWith(.Self) where .Result == .Self](x: T, y: T) -> T { return x - y; }
+
+fn Main() -> i32 {
+  return DoSub(3, 2);
+}