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

C++ interop: Support unary operators (#6020)

Newly supported: `-`.
Partially supported due to lack of reference support: `++` (prefix),
`--` (prefix).
Not supported due to lack of Carbon support to call them correctly: `+`,
`++` (postfix), `--` (postfix), `~`, `!`, `&`, `*`, `->`.

Also (for consistency):
* Add the operator declarations to unsupported binary operators tests.
* Logical operators and the unary `operator&` (address of) are expected
to be called by explicitly calling `operatorX`.

C++ Interop Demo:

```c++
// my_number.h

class MyNumber {
 public:
  explicit MyNumber(int value) : value_(value) {}
  auto value() const -> int { return value_; }

 private:
  int value_;
};

auto operator++(MyNumber operand) -> MyNumber;
auto operator--(MyNumber operand) -> MyNumber;
auto operator-(MyNumber operand) -> MyNumber;
```

```c++
// my_number.cpp

#include "my_number.h"

auto operator++(MyNumber operand) -> MyNumber {
  return MyNumber(operand.value() + 1);
}

auto operator--(MyNumber operand) -> MyNumber {
  return MyNumber(operand.value() - 1);
}

auto operator-(MyNumber operand) -> MyNumber {
  return MyNumber(-operand.value());
}
```

```carbon
// main.carbon

library "Main";

import Core library "io";
import Cpp library "my_number.h";

fn Run() -> i32 {
  var num: Cpp.MyNumber = Cpp.MyNumber.MyNumber(14);
  Core.Print(num.value());
  ++num;
  Core.Print(num.value());
  --num;
  Core.Print(num.value());
  num = -num;
  Core.Print(num.value());
  return 0;
}
```

```shell
$ clang -c my_number.cpp
$ bazel-bin/toolchain/carbon compile main.carbon
$ bazel-bin/toolchain/carbon link my_number.o main.o --output=demo
$ ./demo
14
14
14
-14
```

Part of https://github.com/carbon-language/carbon-lang/issues/5995.
Boaz Brickner 7 месяцев назад
Родитель
Сommit
471b394c6d

+ 25 - 0
toolchain/check/import_cpp.cpp

@@ -2076,6 +2076,31 @@ static auto GetClangOperatorKind(Context& context, SemIR::LocId loc_id,
                                  llvm::StringLiteral interface_name,
                                  llvm::StringLiteral op_name)
     -> std::optional<clang::OverloadedOperatorKind> {
+  // Unary operators.
+  if (interface_name == "Destroy" || interface_name == "As" ||
+      interface_name == "ImplicitAs") {
+    // TODO: Support destructors and conversions.
+    return std::nullopt;
+  }
+
+  // Increment and Decrement.
+  if (interface_name == "Inc") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_PlusPlus;
+  }
+  if (interface_name == "Dec") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_MinusMinus;
+  }
+
+  // Arithmetic.
+  if (interface_name == "Negate") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_Minus;
+  }
+
+  // Binary operators.
+
   // Arithmetic Operators.
   if (interface_name == "AddWith") {
     CARBON_CHECK(op_name == "Op");

+ 30 - 14
toolchain/check/operator.cpp

@@ -35,10 +35,40 @@ static auto GetOperatorOpFunction(Context& context, SemIR::LocId loc_id,
                              op_name_id);
 }
 
+// Returns whether the type of the instruction is a C++ class.
+static auto IsOfCppClassType(Context& context, SemIR::InstId inst_id) -> bool {
+  auto class_type = context.insts().TryGetAs<SemIR::ClassType>(
+      context.types().GetInstId(context.insts().Get(inst_id).type_id()));
+  if (!class_type) {
+    // Not a class.
+    return false;
+  }
+
+  const auto& class_info = context.classes().Get(class_type->class_id);
+  if (!class_info.is_complete()) {
+    return false;
+  }
+
+  return context.name_scopes().Get(class_info.scope_id).is_cpp_scope();
+}
+
 auto BuildUnaryOperator(Context& context, SemIR::LocId loc_id, Operator op,
                         SemIR::InstId operand_id,
                         MakeDiagnosticBuilderFn missing_impl_diagnoser)
     -> SemIR::InstId {
+  // For unary operators with a C++ class as the operand, try to import and call
+  // the C++ operator.
+  // TODO: Change impl lookup instead. See
+  // https://github.com/carbon-language/carbon-lang/blob/db0a00d713015436844c55e7ac190a0f95556499/toolchain/check/operator.cpp#L76
+  if (IsOfCppClassType(context, operand_id)) {
+    SemIR::ScopeLookupResult cpp_lookup_result =
+        ImportOperatorFromCpp(context, loc_id, op);
+    if (cpp_lookup_result.is_found()) {
+      return PerformCall(context, loc_id, cpp_lookup_result.target_inst_id(),
+                         {operand_id});
+    }
+  }
+
   // Look up the operator function.
   auto op_fn = GetOperatorOpFunction(context, loc_id, op);
 
@@ -53,20 +83,6 @@ auto BuildUnaryOperator(Context& context, SemIR::LocId loc_id, Operator op,
   return PerformCall(context, loc_id, bound_op_id, {});
 }
 
-// Returns whether the type of the instruction is a C++ class.
-static auto IsOfCppClassType(Context& context, SemIR::InstId inst_id) -> bool {
-  auto class_type = context.insts().TryGetAs<SemIR::ClassType>(
-      context.types().GetInstId(context.insts().Get(inst_id).type_id()));
-  if (!class_type) {
-    // Not a class.
-    return false;
-  }
-
-  return context.name_scopes()
-      .Get(context.classes().Get(class_type->class_id).scope_id)
-      .is_cpp_scope();
-}
-
 auto BuildBinaryOperator(Context& context, SemIR::LocId loc_id, Operator op,
                          SemIR::InstId lhs_id, SemIR::InstId rhs_id,
                          MakeDiagnosticBuilderFn missing_impl_diagnoser)

+ 308 - 90
toolchain/check/testdata/interop/cpp/function/operators.carbon

@@ -12,31 +12,103 @@
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/function/operators.carbon
 
 // ============================================================================
-// Negate
+// Unary operators
 // ============================================================================
 
-// --- negate.h
+// --- unary_operators.h
 
 class C {};
+
+// Increment and Decrement.
+// TODO: Change `operand` and return value to reference when it is supported.
+auto operator++(C operand) -> C;
+auto operator--(C operand) -> C;
+
+// Arithmetic.
 auto operator-(C operand) -> C;
 
-// --- fail_todo_import_negate.carbon
+// --- import_unary_operators.carbon
 
 library "[[@TEST_NAME]]";
 
-import Cpp library "negate.h";
+import Cpp library "unary_operators.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  let c1: Cpp.C = Cpp.C.C();
-  // CHECK:STDERR: fail_todo_import_negate.carbon:[[@LINE+4]]:19: error: cannot access member of interface `Core.Negate` in type `Cpp.C` that does not implement that interface [MissingImplInMemberAccess]
-  // CHECK:STDERR:   let c2: Cpp.C = -c1;
-  // CHECK:STDERR:                   ^~~
-  // CHECK:STDERR:
-  let c2: Cpp.C = -c1;
+  let c: Cpp.C = Cpp.C.C();
+
+  // Increment and Decrement.
+  ++c;
+  --c;
+
+  // Arithmetic.
+  let minus: Cpp.C = -c;
   //@dump-sem-ir-end
 }
 
+// ============================================================================
+// Prefix increment and decrement vs postfix increment and decrement
+// ============================================================================
+
+// --- prefix_inc_and_dec.h
+
+class Prefix {};
+auto operator++(Prefix operand) -> Prefix;
+auto operator--(Prefix operand) -> Prefix;
+
+// --- fail_prefix_calling_postfix.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "prefix_inc_and_dec.h";
+
+fn F() {
+  let prefix: Cpp.Prefix = Cpp.Prefix.Prefix();
+  // CHECK:STDERR: fail_prefix_calling_postfix.carbon:[[@LINE+8]]:9: error: expected `;` after expression statement [ExpectedExprSemi]
+  // CHECK:STDERR:   prefix++;
+  // CHECK:STDERR:         ^~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_prefix_calling_postfix.carbon:[[@LINE+4]]:3: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
+  // CHECK:STDERR:   prefix++;
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR:
+  prefix++;
+  // CHECK:STDERR: fail_prefix_calling_postfix.carbon:[[@LINE+4]]:9: error: expected `;` after expression statement [ExpectedExprSemi]
+  // CHECK:STDERR:   prefix--;
+  // CHECK:STDERR:         ^~
+  // CHECK:STDERR:
+  prefix--;
+}
+
+// --- postfix_inc_and_dec.h
+
+class Postfix {};
+auto operator++(Postfix operand, int) -> Postfix;
+auto operator--(Postfix operand, int) -> Postfix;
+
+// --- fail_postfix_calling_prefix.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "postfix_inc_and_dec.h";
+
+fn F() {
+  let postfix: Cpp.Postfix = Cpp.Postfix.Postfix();
+  // TODO: Make the error more descriptive to clarify we call prefix operators of a class with suffix operators.
+  // CHECK:STDERR: fail_postfix_calling_prefix.carbon:[[@LINE+5]]:3: error: 1 argument passed to function expecting 2 arguments [CallArgCountMismatch]
+  // CHECK:STDERR:   ++postfix;
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR: fail_postfix_calling_prefix.carbon: note: calling function declared here [InCallToEntity]
+  // CHECK:STDERR:
+  ++postfix;
+  // CHECK:STDERR: fail_postfix_calling_prefix.carbon:[[@LINE+5]]:3: error: 1 argument passed to function expecting 2 arguments [CallArgCountMismatch]
+  // CHECK:STDERR:   --postfix;
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR: fail_postfix_calling_prefix.carbon: note: calling function declared here [InCallToEntity]
+  // CHECK:STDERR:
+  --postfix;
+}
+
 // ============================================================================
 // Binary operators
 // ============================================================================
@@ -262,6 +334,99 @@ fn F() {
   let c: Cpp.C = s1 + s2;
 }
 
+// ============================================================================
+// Unsupported unary operators
+// ============================================================================
+
+// --- unsupported_unary_operators.h
+
+class C {};
+
+// Arithmetic.
+auto operator+(C operand) -> C;
+
+// Increment and Decrement.
+auto operator++(C operand, int) -> C;
+auto operator--(C operand, int) -> C;
+
+// Bitwise.
+auto operator~(C operand) -> C;
+
+// Logical.
+auto operator!(C operand) -> C;
+
+// Pointer and Memory.
+class P {
+ public:
+  auto foo() -> P;
+  auto operator->() const -> P* _Nonnull;
+};
+auto operator*(P operand) -> P;
+auto operator&(P operand) -> P;
+
+// --- fail_todo_import_unsupported_unary_operators.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "unsupported_unary_operators.h";
+
+fn F() {
+  let c: Cpp.C = Cpp.C.C();
+
+  // Arithmetic.
+  // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+12]]:21: error: expected expression [ExpectedExpr]
+  // CHECK:STDERR:   let plus: Cpp.C = +c;
+  // CHECK:STDERR:                     ^
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+8]]:21: error: whitespace missing after binary operator [BinaryOperatorRequiresWhitespace]
+  // CHECK:STDERR:   let plus: Cpp.C = +c;
+  // CHECK:STDERR:                     ^
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+4]]:21: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
+  // CHECK:STDERR:   let plus: Cpp.C = +c;
+  // CHECK:STDERR:                     ^
+  // CHECK:STDERR:
+  let plus: Cpp.C = +c;
+
+  // Increment and Decrement.
+  // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+4]]:4: error: expected `;` after expression statement [ExpectedExprSemi]
+  // CHECK:STDERR:   c++;
+  // CHECK:STDERR:    ^~
+  // CHECK:STDERR:
+  c++;
+  // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+4]]:4: error: expected `;` after expression statement [ExpectedExprSemi]
+  // CHECK:STDERR:   c--;
+  // CHECK:STDERR:    ^~
+  // CHECK:STDERR:
+  c--;
+
+  // Bitwise.
+  // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+8]]:26: error: expected expression [ExpectedExpr]
+  // CHECK:STDERR:   let not_value: Cpp.C = ~c;
+  // CHECK:STDERR:                          ^
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+4]]:26: error: `let` declarations must end with a `;` [ExpectedDeclSemi]
+  // CHECK:STDERR:   let not_value: Cpp.C = ~c;
+  // CHECK:STDERR:                          ^
+  // CHECK:STDERR:
+  let not_value: Cpp.C = ~c;
+
+  // Logical.
+  // CHECK:STDERR: fail_todo_import_unsupported_unary_operators.carbon:[[@LINE+4]]:35: error: `let` declarations must end with a `;` [ExpectedDeclSemi]
+  // CHECK:STDERR:   let not_result: Cpp.C = operator!(c1);
+  // CHECK:STDERR:                                   ^
+  // CHECK:STDERR:
+  let not_result: Cpp.C = operator!(c1);
+
+  // Pointer and Memory.
+  var p: Cpp.P = Cpp.P.P();
+
+  // Pointer and Memory.
+  let dereference: Cpp.P = *p;
+  let address: Cpp.P = operator&(p);
+  let call_result: Cpp.P = p->foo();
+}
+
 // ============================================================================
 // Unsupported binary operators
 // ============================================================================
@@ -270,6 +435,15 @@ fn F() {
 
 class C {};
 
+// Bitwise.
+// TODO: Change `lhs` and return value to reference when it is supported.
+auto operator<<=(C lhs, int num_bits) -> C;
+auto operator>>=(C lhs, int num_bits) -> C;
+
+// Logical.
+auto operator&&(C lhs, C rhs) -> C;
+auto operator||(C lhs, C rhs) -> C;
+
 // --- fail_todo_import_unsupported_binary_operators.carbon
 
 library "[[@TEST_NAME]]";
@@ -278,6 +452,8 @@ import Cpp library "unsupported_binary_operators.h";
 
 fn F() {
   let c1: Cpp.C = Cpp.C.C();
+
+  // Bitwise.
   // CHECK:STDERR: fail_todo_import_unsupported_binary_operators.carbon:[[@LINE+11]]:3: error: semantics TODO: `Unsupported operator interface `LeftShiftAssignWith`` [SemanticsTodo]
   // CHECK:STDERR:   c1 <<= 1;
   // CHECK:STDERR:   ^~~~~~~~
@@ -302,64 +478,46 @@ fn F() {
   // CHECK:STDERR:   ^~~~~~~~
   // CHECK:STDERR:
   c1 >>= 2;
-}
-
-// ============================================================================
-// Logical operators
-// ============================================================================
-
-// --- logical_operators.h
-
-class C {};
-
-// --- fail_import_logical_operators.carbon
 
-library "[[@TEST_NAME]]";
-
-import Cpp library "logical_operators.h";
-
-fn F() {
-  let c1: Cpp.C = Cpp.C.C();
-  let c2: Cpp.C = Cpp.C.C();
-
-  // CHECK:STDERR: fail_import_logical_operators.carbon:[[@LINE+7]]:26: error: cannot implicitly convert expression of type `Cpp.C` to `bool` [ConversionFailure]
-  // CHECK:STDERR:   let not_result: bool = not c1;
-  // CHECK:STDERR:                          ^~~~~~
-  // CHECK:STDERR: fail_import_logical_operators.carbon:[[@LINE+4]]:26: note: type `Cpp.C` does not implement interface `Core.ImplicitAs(bool)` [MissingImplInMemberAccessNote]
-  // CHECK:STDERR:   let not_result: bool = not c1;
-  // CHECK:STDERR:                          ^~~~~~
+  // Logical.
+  // CHECK:STDERR: fail_todo_import_unsupported_binary_operators.carbon:[[@LINE+16]]:27: error: name `operator` not found [NameNotFound]
+  // CHECK:STDERR:   let and_result: Cpp.C = operator&&(c1, c2);
+  // CHECK:STDERR:                           ^~~~~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_import_unsupported_binary_operators.carbon:[[@LINE+12]]:35: error: whitespace missing around binary operator [BinaryOperatorRequiresWhitespace]
+  // CHECK:STDERR:   let and_result: Cpp.C = operator&&(c1, c2);
+  // CHECK:STDERR:                                   ^
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_import_unsupported_binary_operators.carbon:[[@LINE+8]]:42: error: name `c2` not found [NameNotFound]
+  // CHECK:STDERR:   let and_result: Cpp.C = operator&&(c1, c2);
+  // CHECK:STDERR:                                          ^~
   // CHECK:STDERR:
-  let not_result: bool = not c1;
-  // CHECK:STDERR: fail_import_logical_operators.carbon:[[@LINE+14]]:26: error: cannot implicitly convert expression of type `Cpp.C` to `bool` [ConversionFailure]
-  // CHECK:STDERR:   let and_result: bool = c1 and c2;
-  // CHECK:STDERR:                          ^~~~~~
-  // CHECK:STDERR: fail_import_logical_operators.carbon:[[@LINE+11]]:26: note: type `Cpp.C` does not implement interface `Core.ImplicitAs(bool)` [MissingImplInMemberAccessNote]
-  // CHECK:STDERR:   let and_result: bool = c1 and c2;
-  // CHECK:STDERR:                          ^~~~~~
+  // CHECK:STDERR: fail_todo_import_unsupported_binary_operators.carbon:[[@LINE+4]]:36: error: cannot take the address of non-reference expression [AddrOfNonRef]
+  // CHECK:STDERR:   let and_result: Cpp.C = operator&&(c1, c2);
+  // CHECK:STDERR:                                    ^
   // CHECK:STDERR:
-  // CHECK:STDERR: fail_import_logical_operators.carbon:[[@LINE+7]]:26: error: cannot implicitly convert expression of type `Cpp.C` to `bool` [ConversionFailure]
-  // CHECK:STDERR:   let and_result: bool = c1 and c2;
-  // CHECK:STDERR:                          ^~~~~~~~~
-  // CHECK:STDERR: fail_import_logical_operators.carbon:[[@LINE+4]]:26: note: type `Cpp.C` does not implement interface `Core.ImplicitAs(bool)` [MissingImplInMemberAccessNote]
-  // CHECK:STDERR:   let and_result: bool = c1 and c2;
-  // CHECK:STDERR:                          ^~~~~~~~~
+  let and_result: Cpp.C = operator&&(c1, c2);
+  // CHECK:STDERR: fail_todo_import_unsupported_binary_operators.carbon:[[@LINE+20]]:26: error: name `operator` not found [NameNotFound]
+  // CHECK:STDERR:   let or_result: Cpp.C = operator||(c1, c2);
+  // CHECK:STDERR:                          ^~~~~~~~
   // CHECK:STDERR:
-  let and_result: bool = c1 and c2;
-  // CHECK:STDERR: fail_import_logical_operators.carbon:[[@LINE+14]]:25: error: cannot implicitly convert expression of type `Cpp.C` to `bool` [ConversionFailure]
-  // CHECK:STDERR:   let or_result: bool = c1 or c2;
-  // CHECK:STDERR:                         ^~~~~
-  // CHECK:STDERR: fail_import_logical_operators.carbon:[[@LINE+11]]:25: note: type `Cpp.C` does not implement interface `Core.ImplicitAs(bool)` [MissingImplInMemberAccessNote]
-  // CHECK:STDERR:   let or_result: bool = c1 or c2;
-  // CHECK:STDERR:                         ^~~~~
+  // CHECK:STDERR: fail_todo_import_unsupported_binary_operators.carbon:[[@LINE+16]]:34: error: whitespace missing around binary operator [BinaryOperatorRequiresWhitespace]
+  // CHECK:STDERR:   let or_result: Cpp.C = operator||(c1, c2);
+  // CHECK:STDERR:                                  ^
   // CHECK:STDERR:
-  // CHECK:STDERR: fail_import_logical_operators.carbon:[[@LINE+7]]:25: error: cannot implicitly convert expression of type `Cpp.C` to `bool` [ConversionFailure]
-  // CHECK:STDERR:   let or_result: bool = c1 or c2;
-  // CHECK:STDERR:                         ^~~~~~~~
-  // CHECK:STDERR: fail_import_logical_operators.carbon:[[@LINE+4]]:25: note: type `Cpp.C` does not implement interface `Core.ImplicitAs(bool)` [MissingImplInMemberAccessNote]
-  // CHECK:STDERR:   let or_result: bool = c1 or c2;
-  // CHECK:STDERR:                         ^~~~~~~~
+  // CHECK:STDERR: fail_todo_import_unsupported_binary_operators.carbon:[[@LINE+12]]:35: error: expected expression [ExpectedExpr]
+  // CHECK:STDERR:   let or_result: Cpp.C = operator||(c1, c2);
+  // CHECK:STDERR:                                   ^
   // CHECK:STDERR:
-  let or_result: bool = c1 or c2;
+  // CHECK:STDERR: fail_todo_import_unsupported_binary_operators.carbon:[[@LINE+8]]:35: error: whitespace missing around binary operator [BinaryOperatorRequiresWhitespace]
+  // CHECK:STDERR:   let or_result: Cpp.C = operator||(c1, c2);
+  // CHECK:STDERR:                                   ^
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_import_unsupported_binary_operators.carbon:[[@LINE+4]]:35: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
+  // CHECK:STDERR:   let or_result: Cpp.C = operator||(c1, c2);
+  // CHECK:STDERR:                                   ^
+  // CHECK:STDERR:
+  let or_result: Cpp.C = operator||(c1, c2);
 }
 
 // ============================================================================
@@ -475,7 +633,7 @@ fn F() {
   let c3: Cpp.C = c1 + c2;
 }
 
-// CHECK:STDOUT: --- fail_todo_import_negate.carbon
+// CHECK:STDOUT: --- import_unary_operators.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
@@ -486,6 +644,12 @@ fn F() {
 // CHECK:STDOUT:   %ptr.d9e: type = ptr_type %C [concrete]
 // CHECK:STDOUT:   %C__carbon_thunk.type: type = fn_type @C__carbon_thunk [concrete]
 // CHECK:STDOUT:   %C__carbon_thunk: %C__carbon_thunk.type = struct_value () [concrete]
+// CHECK:STDOUT:   %operator++__carbon_thunk.type: type = fn_type @operator++__carbon_thunk [concrete]
+// CHECK:STDOUT:   %operator++__carbon_thunk: %operator++__carbon_thunk.type = struct_value () [concrete]
+// CHECK:STDOUT:   %operator--__carbon_thunk.type: type = fn_type @operator--__carbon_thunk [concrete]
+// CHECK:STDOUT:   %operator--__carbon_thunk: %operator--__carbon_thunk.type = struct_value () [concrete]
+// CHECK:STDOUT:   %operator-__carbon_thunk.type: type = fn_type @operator-__carbon_thunk [concrete]
+// CHECK:STDOUT:   %operator-__carbon_thunk: %operator-__carbon_thunk.type = struct_value () [concrete]
 // CHECK:STDOUT:   %T.as.Destroy.impl.Op.type.1b3: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%C) [concrete]
 // CHECK:STDOUT:   %T.as.Destroy.impl.Op.21b: %T.as.Destroy.impl.Op.type.1b3 = struct_value () [concrete]
 // CHECK:STDOUT: }
@@ -506,41 +670,95 @@ fn F() {
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %operator++__carbon_thunk.decl: %operator++__carbon_thunk.type = fn_decl @operator++__carbon_thunk [concrete = constants.%operator++__carbon_thunk] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %operator--__carbon_thunk.decl: %operator--__carbon_thunk.type = fn_decl @operator--__carbon_thunk [concrete = constants.%operator--__carbon_thunk] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %operator-__carbon_thunk.decl: %operator-__carbon_thunk.type = fn_decl @operator-__carbon_thunk [concrete = constants.%operator-__carbon_thunk] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %c1.patt: %pattern_type.217 = binding_pattern c1 [concrete]
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Cpp.ref.loc8_19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %C.ref.loc8_22: type = name_ref C, imports.%C.decl [concrete = constants.%C]
-// CHECK:STDOUT:   %C.ref.loc8_24: %C.C.type = name_ref C, imports.%C.C.decl [concrete = constants.%C.C]
-// CHECK:STDOUT:   %.loc8_27.1: ref %C = temporary_storage
-// CHECK:STDOUT:   %addr.loc8_27.1: %ptr.d9e = addr_of %.loc8_27.1
-// CHECK:STDOUT:   %C__carbon_thunk.call: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_27.1)
-// CHECK:STDOUT:   %.loc8_27.2: init %C = in_place_init %C__carbon_thunk.call, %.loc8_27.1
-// CHECK:STDOUT:   %.loc8_14: type = splice_block %C.ref.loc8_14 [concrete = constants.%C] {
-// CHECK:STDOUT:     %Cpp.ref.loc8_11: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     %C.ref.loc8_14: type = name_ref C, imports.%C.decl [concrete = constants.%C]
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %.loc8_27.3: ref %C = temporary %.loc8_27.1, %.loc8_27.2
-// CHECK:STDOUT:   %.loc8_27.4: %C = bind_value %.loc8_27.3
-// CHECK:STDOUT:   %c1: %C = bind_name c1, %.loc8_27.4
+// CHECK:STDOUT:     %c.patt: %pattern_type.217 = binding_pattern c [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc8_18: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %C.ref.loc8_21: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %C.ref.loc8_23: %C.C.type = name_ref C, imports.%C.C.decl [concrete = constants.%C.C]
+// CHECK:STDOUT:   %.loc8_26.1: ref %C = temporary_storage
+// CHECK:STDOUT:   %addr.loc8_26.1: %ptr.d9e = addr_of %.loc8_26.1
+// CHECK:STDOUT:   %C__carbon_thunk.call: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_26.1)
+// CHECK:STDOUT:   %.loc8_26.2: init %C = in_place_init %C__carbon_thunk.call, %.loc8_26.1
+// CHECK:STDOUT:   %.loc8_13: type = splice_block %C.ref.loc8_13 [concrete = constants.%C] {
+// CHECK:STDOUT:     %Cpp.ref.loc8_10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %C.ref.loc8_13: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc8_26.3: ref %C = temporary %.loc8_26.1, %.loc8_26.2
+// CHECK:STDOUT:   %.loc8_26.4: %C = bind_value %.loc8_26.3
+// CHECK:STDOUT:   %c: %C = bind_name c, %.loc8_26.4
+// CHECK:STDOUT:   %c.ref.loc11: %C = name_ref c, %c
+// CHECK:STDOUT:   %.loc11_3.1: ref %C = temporary_storage
+// CHECK:STDOUT:   %.loc11_5: ref %C = value_as_ref %c.ref.loc11
+// CHECK:STDOUT:   %addr.loc11_3.1: %ptr.d9e = addr_of %.loc11_5
+// CHECK:STDOUT:   %addr.loc11_3.2: %ptr.d9e = addr_of %.loc11_3.1
+// CHECK:STDOUT:   %operator++__carbon_thunk.call: init %empty_tuple.type = call imports.%operator++__carbon_thunk.decl(%addr.loc11_3.1, %addr.loc11_3.2)
+// CHECK:STDOUT:   %.loc11_3.2: init %C = in_place_init %operator++__carbon_thunk.call, %.loc11_3.1
+// CHECK:STDOUT:   %.loc11_3.3: ref %C = temporary %.loc11_3.1, %.loc11_3.2
+// CHECK:STDOUT:   %c.ref.loc12: %C = name_ref c, %c
+// CHECK:STDOUT:   %.loc12_3.1: ref %C = temporary_storage
+// CHECK:STDOUT:   %.loc12_5: ref %C = value_as_ref %c.ref.loc12
+// CHECK:STDOUT:   %addr.loc12_3.1: %ptr.d9e = addr_of %.loc12_5
+// CHECK:STDOUT:   %addr.loc12_3.2: %ptr.d9e = addr_of %.loc12_3.1
+// CHECK:STDOUT:   %operator--__carbon_thunk.call: init %empty_tuple.type = call imports.%operator--__carbon_thunk.decl(%addr.loc12_3.1, %addr.loc12_3.2)
+// CHECK:STDOUT:   %.loc12_3.2: init %C = in_place_init %operator--__carbon_thunk.call, %.loc12_3.1
+// CHECK:STDOUT:   %.loc12_3.3: ref %C = temporary %.loc12_3.1, %.loc12_3.2
 // CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %c2.patt: %pattern_type.217 = binding_pattern c2 [concrete]
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %c1.ref: %C = name_ref c1, %c1
-// CHECK:STDOUT:   %.loc13: type = splice_block %C.ref.loc13 [concrete = constants.%C] {
-// CHECK:STDOUT:     %Cpp.ref.loc13: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     %C.ref.loc13: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:     %minus.patt: %pattern_type.217 = binding_pattern minus [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %c.ref.loc15: %C = name_ref c, %c
+// CHECK:STDOUT:   %.loc15_22.1: ref %C = temporary_storage
+// CHECK:STDOUT:   %.loc15_23: ref %C = value_as_ref %c.ref.loc15
+// CHECK:STDOUT:   %addr.loc15_22.1: %ptr.d9e = addr_of %.loc15_23
+// CHECK:STDOUT:   %addr.loc15_22.2: %ptr.d9e = addr_of %.loc15_22.1
+// CHECK:STDOUT:   %operator-__carbon_thunk.call: init %empty_tuple.type = call imports.%operator-__carbon_thunk.decl(%addr.loc15_22.1, %addr.loc15_22.2)
+// CHECK:STDOUT:   %.loc15_22.2: init %C = in_place_init %operator-__carbon_thunk.call, %.loc15_22.1
+// CHECK:STDOUT:   %.loc15_17: type = splice_block %C.ref.loc15 [concrete = constants.%C] {
+// CHECK:STDOUT:     %Cpp.ref.loc15: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %C.ref.loc15: type = name_ref C, imports.%C.decl [concrete = constants.%C]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %c2: %C = bind_name c2, <error> [concrete = <error>]
-// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound: <bound method> = bound_method %.loc8_27.3, constants.%T.as.Destroy.impl.Op.21b
+// CHECK:STDOUT:   %.loc15_22.3: ref %C = temporary %.loc15_22.1, %.loc15_22.2
+// CHECK:STDOUT:   %.loc15_22.4: %C = bind_value %.loc15_22.3
+// CHECK:STDOUT:   %minus: %C = bind_name minus, %.loc15_22.4
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound.loc15: <bound method> = bound_method %.loc15_22.3, constants.%T.as.Destroy.impl.Op.21b
 // CHECK:STDOUT:   <elided>
-// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %.loc8_27.3, %T.as.Destroy.impl.Op.specific_fn
-// CHECK:STDOUT:   %addr.loc8_27.2: %ptr.d9e = addr_of %.loc8_27.3
-// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr.loc8_27.2)
+// CHECK:STDOUT:   %bound_method.loc15: <bound method> = bound_method %.loc15_22.3, %T.as.Destroy.impl.Op.specific_fn.1
+// CHECK:STDOUT:   %addr.loc15_22.3: %ptr.d9e = addr_of %.loc15_22.3
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call.loc15: init %empty_tuple.type = call %bound_method.loc15(%addr.loc15_22.3)
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound.loc12: <bound method> = bound_method %.loc12_3.3, constants.%T.as.Destroy.impl.Op.21b
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc12: <bound method> = bound_method %.loc12_3.3, %T.as.Destroy.impl.Op.specific_fn.2
+// CHECK:STDOUT:   %addr.loc12_3.3: %ptr.d9e = addr_of %.loc12_3.3
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call.loc12: init %empty_tuple.type = call %bound_method.loc12(%addr.loc12_3.3)
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound.loc11: <bound method> = bound_method %.loc11_3.3, constants.%T.as.Destroy.impl.Op.21b
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc11: <bound method> = bound_method %.loc11_3.3, %T.as.Destroy.impl.Op.specific_fn.3
+// CHECK:STDOUT:   %addr.loc11_3.3: %ptr.d9e = addr_of %.loc11_3.3
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call.loc11: init %empty_tuple.type = call %bound_method.loc11(%addr.loc11_3.3)
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound.loc8: <bound method> = bound_method %.loc8_26.3, constants.%T.as.Destroy.impl.Op.21b
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc8: <bound method> = bound_method %.loc8_26.3, %T.as.Destroy.impl.Op.specific_fn.4
+// CHECK:STDOUT:   %addr.loc8_26.2: %ptr.d9e = addr_of %.loc8_26.3
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call.loc8: init %empty_tuple.type = call %bound_method.loc8(%addr.loc8_26.2)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT: