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.
This commit is contained in:
Boaz Brickner
2025-09-08 15:39:08 +00:00
committed by GitHub
parent 049abc638d
commit 471b394c6d
3 changed files with 360 additions and 101 deletions
+25
View File
@@ -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
View File
@@ -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)
+305 -87
View File
@@ -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:
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+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_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: ^~~~~~~~~
// 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 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+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]]: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: ^~~~~~~~
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 or_result: bool = c1 or c2;
// 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_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:
// 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: %c.patt: %pattern_type.217 = binding_pattern c [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: %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_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: %.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: %minus.patt: %pattern_type.217 = binding_pattern minus [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: %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: