Quellcode durchsuchen

Overload resolution for C++ operators (#6092)

Multiple overloads for the same operator are now resolved using overload
resolution.
This change doesn't try to solve all issues with operator lookup.

Moved the operator lookup logic from `import` to `operators` and changed
it to take the args into account.
Use `Sema::LookupOverloadedBinOp()` (with ADL) when looking up operator
functions to create an overload set.

Verified all demos in #6017, #6020 and #6024 still work.

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_;
};

class NotMyNumber {};

auto operator+(MyNumber lhs, MyNumber rhs) -> MyNumber;
auto operator+(NotMyNumber lhs, NotMyNumber rhs) -> NotMyNumber;
```

```c++
// my_number.cpp

#include "my_number.h"

auto operator+(MyNumber lhs, MyNumber rhs) -> MyNumber {
  return MyNumber(lhs.value() + rhs.value());
}

auto operator+(NotMyNumber lhs, NotMyNumber /*rhs*/) -> NotMyNumber {
  return lhs;
}
```

```carbon
// main.carbon

library "Main";

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

fn Run() -> i32 {
  // Arithmetic
  var num1: Cpp.MyNumber = Cpp.MyNumber.MyNumber(14);
  var num2: Cpp.MyNumber = Cpp.MyNumber.MyNumber(5);
  Core.Print(num1.value());
  Core.Print(num2.value());
  Core.Print((num1 + num2).value());

  return 0;
}
```

**After this change:**

```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
5
19
```

**Before this change**

```shell
$ bazel-bin/toolchain/carbon compile main.carbon
main.carbon:14:15: error: semantics TODO: `Unsupported: Lookup succeeded but couldn't find a single result; LookupResultKind: 3`
  Core.Print((num1 + num2).value());
              ^~~~~~~~~~~
main.carbon:14:15: note: in `Cpp` operator `AddWith` lookup
  Core.Print((num1 + num2).value());
              ^~~~~~~~~~~
```

Part of https://github.com/carbon-language/carbon-lang/issues/5995.
Boaz Brickner vor 7 Monaten
Ursprung
Commit
ef488f00fa

+ 2 - 0
toolchain/check/BUILD

@@ -23,6 +23,7 @@ cc_library(
         "convert.cpp",
         "cpp/custom_type_mapping.cpp",
         "cpp/import.cpp",
+        "cpp/operators.cpp",
         "cpp/overload_resolution.cpp",
         "cpp/thunk.cpp",
         "cpp/type_mapping.cpp",
@@ -72,6 +73,7 @@ cc_library(
         "convert.h",
         "cpp/custom_type_mapping.h",
         "cpp/import.h",
+        "cpp/operators.h",
         "cpp/overload_resolution.h",
         "cpp/thunk.h",
         "cpp/type_mapping.h",

+ 6 - 226
toolchain/check/cpp/import.cpp

@@ -527,41 +527,6 @@ static auto ClangLookup(Context& context, SemIR::NameScopeId scope_id,
   return lookup;
 }
 
-// Looks up the given declaration name in the Clang AST in a specific scope.
-// Returns the found declaration and its access. If not found, returns
-// `nullopt`. If there's not a single result, returns `nullptr` and default
-// access.
-static auto ClangLookupDeclarationName(Context& context, SemIR::LocId loc_id,
-                                       SemIR::NameScopeId scope_id,
-                                       clang::DeclarationName name)
-    -> std::optional<std::tuple<clang::NamedDecl*, clang::AccessSpecifier>> {
-  auto lookup = ClangLookup(context, scope_id, name);
-  if (!lookup) {
-    return std::nullopt;
-  }
-
-  std::tuple<clang::NamedDecl*, clang::AccessSpecifier> result{
-      nullptr, clang::AccessSpecifier::AS_none};
-
-  // Access checks are performed separately by the Carbon name lookup logic.
-  lookup->suppressAccessDiagnostics();
-
-  if (!lookup->isSingleResult()) {
-    // Clang will diagnose ambiguous lookup results for us.
-    if (!lookup->isAmbiguous()) {
-      context.TODO(loc_id,
-                   llvm::formatv("Unsupported: Lookup succeeded but couldn't "
-                                 "find a single result; LookupResultKind: {0}",
-                                 static_cast<int>(lookup->getResultKind())));
-    }
-
-    return result;
-  }
-
-  result = {lookup->getFoundDecl(), lookup->begin().getAccess()};
-  return result;
-}
-
 // Looks up for constructors in the class scope and returns the lookup result.
 static auto ClangConstructorLookup(Context& context,
                                    SemIR::NameScopeId scope_id)
@@ -1894,11 +1859,8 @@ static auto ImportDeclAfterDependencies(Context& context, SemIR::LocId loc_id,
 
 // Attempts to import a set of declarations. Returns `false` if an error was
 // produced, `true` otherwise.
-// TODO: Merge overload set and operators and remove the `is_overload_set`
-// param.
 static auto ImportDeclSet(Context& context, SemIR::LocId loc_id,
-                          ImportWorklist& worklist,
-                          bool is_overload_set = false) -> bool {
+                          ImportWorklist& worklist) -> bool {
   // Walk the dependency graph in depth-first order, and import declarations
   // once we've imported all of their dependencies.
   while (!worklist.empty()) {
@@ -1924,7 +1886,7 @@ static auto ImportDeclSet(Context& context, SemIR::LocId loc_id,
       // Functions that are part of the overload set are imported at a later
       // point, once the overload resolution has selected the suitable function
       // for the call.
-      if (is_overload_set && decl->getAsFunction()) {
+      if (decl->getAsFunction()) {
         continue;
       }
       auto inst_id = ImportDeclAfterDependencies(context, loc_id, decl);
@@ -2056,10 +2018,9 @@ static auto LookupBuiltinTypes(Context& context, SemIR::LocId loc_id,
   return inst_id;
 }
 
-// Imports an overloaded function set from Clang to Carbon.
-static auto ImportCppOverloadSet(Context& context, SemIR::NameScopeId scope_id,
-                                 SemIR::NameId name_id,
-                                 const clang::UnresolvedSet<4>& overload_set)
+auto ImportCppOverloadSet(Context& context, SemIR::NameScopeId scope_id,
+                          SemIR::NameId name_id,
+                          const clang::UnresolvedSet<4>& overload_set)
     -> SemIR::InstId {
   SemIR::CppOverloadSetId overload_set_id = context.cpp_overload_sets().Add(
       SemIR::CppOverloadSet{.name_id = name_id,
@@ -2102,7 +2063,7 @@ static auto ImportOverloadSetAndDependencies(
   for (clang::NamedDecl* fn_decl : overloaded_set) {
     AddDependentDecl(context, fn_decl, worklist);
   }
-  if (!ImportDeclSet(context, loc_id, worklist, true)) {
+  if (!ImportDeclSet(context, loc_id, worklist)) {
     return SemIR::ErrorInst::InstId;
   }
   return ImportCppOverloadSet(context, scope_id, name_id, overloaded_set);
@@ -2213,187 +2174,6 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
                                     overload_set);
 }
 
-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");
-    return clang::OO_Plus;
-  }
-  if (interface_name == "SubWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Minus;
-  }
-  if (interface_name == "MulWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Star;
-  }
-  if (interface_name == "DivWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Slash;
-  }
-  if (interface_name == "ModWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Percent;
-  }
-
-  // Bitwise Operators.
-  if (interface_name == "BitAndWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Amp;
-  }
-  if (interface_name == "BitOrWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Pipe;
-  }
-  if (interface_name == "BitXorWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_Caret;
-  }
-  if (interface_name == "LeftShiftWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_LessLess;
-  }
-  if (interface_name == "RightShiftWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_GreaterGreater;
-  }
-
-  // Compound Assignment Arithmetic Operators.
-  if (interface_name == "AddAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_PlusEqual;
-  }
-  if (interface_name == "SubAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_MinusEqual;
-  }
-  if (interface_name == "MulAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_StarEqual;
-  }
-  if (interface_name == "DivAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_SlashEqual;
-  }
-  if (interface_name == "ModAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_PercentEqual;
-  }
-
-  // Compound Assignment Bitwise Operators.
-  if (interface_name == "BitAndAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_AmpEqual;
-  }
-  if (interface_name == "BitOrAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_PipeEqual;
-  }
-  if (interface_name == "BitXorAssignWith") {
-    CARBON_CHECK(op_name == "Op");
-    return clang::OO_CaretEqual;
-  }
-  // TODO: Add support for `LeftShiftAssignWith` (`OO_LessLessEqual`) and
-  // `RightShiftAssignWith` (`OO_GreaterGreaterEqual`) when references are
-  // supported.
-
-  // Relational Operators.
-  if (interface_name == "EqWith") {
-    if (op_name == "Equal") {
-      return clang::OO_EqualEqual;
-    }
-    CARBON_CHECK(op_name == "NotEqual");
-    return clang::OO_ExclaimEqual;
-  }
-  if (interface_name == "OrderedWith") {
-    if (op_name == "Less") {
-      return clang::OO_Less;
-    }
-    if (op_name == "Greater") {
-      return clang::OO_Greater;
-    }
-    if (op_name == "LessOrEquivalent") {
-      return clang::OO_LessEqual;
-    }
-    CARBON_CHECK(op_name == "GreaterOrEquivalent");
-    return clang::OO_GreaterEqual;
-  }
-
-  context.TODO(loc_id, llvm::formatv("Unsupported operator interface `{0}`",
-                                     interface_name));
-  return std::nullopt;
-}
-
-auto ImportOperatorFromCpp(Context& context, SemIR::LocId loc_id,
-                           SemIR::NameScopeId scope_id, Operator op)
-    -> SemIR::ScopeLookupResult {
-  Diagnostics::AnnotationScope annotate_diagnostics(
-      &context.emitter(), [&](auto& builder) {
-        CARBON_DIAGNOSTIC(InCppOperatorLookup, Note,
-                          "in `Cpp` operator `{0}` lookup", std::string);
-        builder.Note(loc_id, InCppOperatorLookup, op.interface_name.str());
-      });
-
-  auto op_kind =
-      GetClangOperatorKind(context, loc_id, op.interface_name, op.op_name);
-  if (!op_kind) {
-    return SemIR::ScopeLookupResult::MakeNotFound();
-  }
-
-  // TODO: We should do ADL-only lookup for operators
-  // (`Sema::ArgumentDependentLookup`), when we support mapping Carbon types
-  // into C++ types. See
-  // https://github.com/carbon-language/carbon-lang/pull/5996/files/5d01fa69511b76f87efbc0387f5e40abcf4c911a#r2316950123
-  auto decl_and_access = ClangLookupDeclarationName(
-      context, loc_id, scope_id,
-      context.ast_context().DeclarationNames.getCXXOperatorName(*op_kind));
-
-  if (!decl_and_access) {
-    return SemIR::ScopeLookupResult::MakeNotFound();
-  }
-  auto [decl, access] = *decl_and_access;
-  if (!decl) {
-    return SemIR::ScopeLookupResult::MakeError();
-  }
-
-  SemIR::InstId inst_id = ImportDeclAndDependencies(context, loc_id, decl);
-  if (!inst_id.has_value()) {
-    return SemIR::ScopeLookupResult::MakeNotFound();
-  }
-
-  SemIR::AccessKind access_kind = MapAccess(access);
-  return SemIR::ScopeLookupResult::MakeWrappedLookupResult(inst_id,
-                                                           access_kind);
-}
-
 auto ImportClassDefinitionForClangDecl(Context& context, SemIR::LocId loc_id,
                                        SemIR::ClassId class_id,
                                        SemIR::ClangDeclId clang_decl_id)

+ 6 - 7
toolchain/check/cpp/import.h

@@ -11,7 +11,6 @@
 #include "llvm/Support/VirtualFileSystem.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/diagnostic_helpers.h"
-#include "toolchain/check/operator.h"
 #include "toolchain/diagnostics/diagnostic_emitter.h"
 
 namespace Carbon::Check {
@@ -31,6 +30,12 @@ auto ImportCppFiles(Context& context,
 auto ImportCppFunctionDecl(Context& context, SemIR::LocId loc_id,
                            clang::FunctionDecl* clang_decl) -> SemIR::InstId;
 
+// Imports an overloaded function set from Clang to Carbon.
+auto ImportCppOverloadSet(Context& context, SemIR::NameScopeId scope_id,
+                          SemIR::NameId name_id,
+                          const clang::UnresolvedSet<4>& overload_set)
+    -> SemIR::InstId;
+
 // Looks up the given name in the Clang AST generated when importing C++ code
 // and returns a lookup result. If using the injected class name (`X.X()`),
 // imports the class constructor as a function named as the class.
@@ -38,12 +43,6 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
                        SemIR::NameScopeId scope_id, SemIR::NameId name_id)
     -> SemIR::ScopeLookupResult;
 
-// Looks up the given operator in the Clang AST generated when importing C++
-// code and returns a lookup result.
-auto ImportOperatorFromCpp(Context& context, SemIR::LocId loc_id,
-                           SemIR::NameScopeId scope_id, Operator op)
-    -> SemIR::ScopeLookupResult;
-
 // Given a Carbon class declaration that was imported from some kind of C++
 // declaration, such as a class or enum, attempt to import a corresponding class
 // definition. Returns true if nothing went wrong (whether or not a definition

+ 197 - 0
toolchain/check/cpp/operators.cpp

@@ -0,0 +1,197 @@
+// 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
+
+#include "toolchain/check/cpp/operators.h"
+
+#include "clang/Sema/Overload.h"
+#include "clang/Sema/Sema.h"
+#include "toolchain/check/cpp/import.h"
+#include "toolchain/check/cpp/type_mapping.h"
+#include "toolchain/check/inst.h"
+#include "toolchain/check/type.h"
+#include "toolchain/sem_ir/ids.h"
+
+namespace Carbon::Check {
+
+// Maps Carbon operator interface and operator names to Clang operator kinds.
+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");
+    return clang::OO_Plus;
+  }
+  if (interface_name == "SubWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_Minus;
+  }
+  if (interface_name == "MulWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_Star;
+  }
+  if (interface_name == "DivWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_Slash;
+  }
+  if (interface_name == "ModWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_Percent;
+  }
+
+  // Bitwise Operators.
+  if (interface_name == "BitAndWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_Amp;
+  }
+  if (interface_name == "BitOrWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_Pipe;
+  }
+  if (interface_name == "BitXorWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_Caret;
+  }
+  if (interface_name == "LeftShiftWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_LessLess;
+  }
+  if (interface_name == "RightShiftWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_GreaterGreater;
+  }
+
+  // Compound Assignment Arithmetic Operators.
+  if (interface_name == "AddAssignWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_PlusEqual;
+  }
+  if (interface_name == "SubAssignWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_MinusEqual;
+  }
+  if (interface_name == "MulAssignWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_StarEqual;
+  }
+  if (interface_name == "DivAssignWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_SlashEqual;
+  }
+  if (interface_name == "ModAssignWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_PercentEqual;
+  }
+
+  // Compound Assignment Bitwise Operators.
+  if (interface_name == "BitAndAssignWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_AmpEqual;
+  }
+  if (interface_name == "BitOrAssignWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_PipeEqual;
+  }
+  if (interface_name == "BitXorAssignWith") {
+    CARBON_CHECK(op_name == "Op");
+    return clang::OO_CaretEqual;
+  }
+  // TODO: Add support for `LeftShiftAssignWith` (`OO_LessLessEqual`) and
+  // `RightShiftAssignWith` (`OO_GreaterGreaterEqual`) when references are
+  // supported.
+
+  // Relational Operators.
+  if (interface_name == "EqWith") {
+    if (op_name == "Equal") {
+      return clang::OO_EqualEqual;
+    }
+    CARBON_CHECK(op_name == "NotEqual");
+    return clang::OO_ExclaimEqual;
+  }
+  if (interface_name == "OrderedWith") {
+    if (op_name == "Less") {
+      return clang::OO_Less;
+    }
+    if (op_name == "Greater") {
+      return clang::OO_Greater;
+    }
+    if (op_name == "LessOrEquivalent") {
+      return clang::OO_LessEqual;
+    }
+    CARBON_CHECK(op_name == "GreaterOrEquivalent");
+    return clang::OO_GreaterEqual;
+  }
+
+  context.TODO(loc_id, llvm::formatv("Unsupported operator interface `{0}`",
+                                     interface_name));
+  return std::nullopt;
+}
+
+auto LookupCppOperator(Context& context, SemIR::LocId loc_id, Operator op,
+                       llvm::ArrayRef<SemIR::InstId> arg_ids) -> SemIR::InstId {
+  Diagnostics::AnnotationScope annotate_diagnostics(
+      &context.emitter(), [&](auto& builder) {
+        CARBON_DIAGNOSTIC(InCppOperatorLookup, Note,
+                          "in `Cpp` operator `{0}` lookup", std::string);
+        builder.Note(loc_id, InCppOperatorLookup, op.interface_name.str());
+      });
+
+  auto op_kind =
+      GetClangOperatorKind(context, loc_id, op.interface_name, op.op_name);
+  if (!op_kind) {
+    return SemIR::InstId::None;
+  }
+
+  auto arg_exprs = InventClangArgs(context, arg_ids);
+  if (!arg_exprs.has_value()) {
+    return SemIR::ErrorInst::InstId;
+  }
+
+  clang::Sema& sema = context.sem_ir().clang_ast_unit()->getSema();
+
+  clang::UnresolvedSet<4> functions;
+  // TODO: Add location accordingly.
+  clang::OverloadCandidateSet candidate_set(
+      clang::SourceLocation(), clang::OverloadCandidateSet::CSK_Operator);
+  // This works for both unary and binary operators.
+  sema.LookupOverloadedBinOp(candidate_set, *op_kind, functions, *arg_exprs);
+
+  for (auto& it : candidate_set) {
+    if (!it.Function) {
+      continue;
+    }
+    functions.addDecl(it.Function, it.FoundDecl.getAccess());
+  }
+
+  return ImportCppOverloadSet(context, SemIR::NameScopeId::None,
+                              SemIR::NameId::CppOperator, functions);
+}
+
+}  // namespace Carbon::Check

+ 22 - 0
toolchain/check/cpp/operators.h

@@ -0,0 +1,22 @@
+// 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
+
+#ifndef CARBON_TOOLCHAIN_CHECK_CPP_OPERATORS_H_
+#define CARBON_TOOLCHAIN_CHECK_CPP_OPERATORS_H_
+
+#include "toolchain/check/context.h"
+#include "toolchain/check/operator.h"
+#include "toolchain/sem_ir/ids.h"
+
+namespace Carbon::Check {
+
+// Looks up the given operator in the Clang AST generated when importing C++
+// code using argument dependent lookup (ADL) and return overload set
+// instruction.
+auto LookupCppOperator(Context& context, SemIR::LocId loc_id, Operator op,
+                       llvm::ArrayRef<SemIR::InstId> arg_ids) -> SemIR::InstId;
+
+}  // namespace Carbon::Check
+
+#endif  // CARBON_TOOLCHAIN_CHECK_CPP_OPERATORS_H_

+ 5 - 62
toolchain/check/cpp/overload_resolution.cpp

@@ -8,63 +8,11 @@
 #include "clang/Sema/Sema.h"
 #include "toolchain/check/cpp/import.h"
 #include "toolchain/check/cpp/type_mapping.h"
-#include "toolchain/sem_ir/expr_info.h"
+#include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
 
-// Invents a Clang argument expression to use in overload resolution to
-// represent the given Carbon argument instruction.
-static auto InventClangArg(Context& context, SemIR::InstId arg_id)
-    -> clang::Expr* {
-  clang::ExprValueKind value_kind;
-  switch (SemIR::GetExprCategory(context.sem_ir(), arg_id)) {
-    case SemIR::ExprCategory::NotExpr:
-      CARBON_FATAL("Should not see these here");
-
-    case SemIR::ExprCategory::Error:
-      return nullptr;
-
-    case SemIR::ExprCategory::DurableRef:
-      value_kind = clang::ExprValueKind::VK_LValue;
-      break;
-
-    case SemIR::ExprCategory::EphemeralRef:
-      value_kind = clang::ExprValueKind::VK_XValue;
-      break;
-
-    case SemIR::ExprCategory::Value:
-    case SemIR::ExprCategory::Initializing:
-      value_kind = clang::ExprValueKind::VK_PRValue;
-      break;
-
-    case SemIR::ExprCategory::Mixed:
-      // TODO: Handle this by creating an InitListExpr.
-      value_kind = clang::ExprValueKind::VK_PRValue;
-      break;
-  }
-
-  if (context.insts().Get(arg_id).type_id() == SemIR::ErrorInst::TypeId) {
-    // The argument error has already been diagnosed.
-    return nullptr;
-  }
-
-  clang::QualType arg_cpp_type = MapToCppType(context, arg_id);
-  if (arg_cpp_type.isNull()) {
-    CARBON_DIAGNOSTIC(CppCallArgTypeNotSupported, Error,
-                      "call argument of type {0} is not supported",
-                      TypeOfInstId);
-    context.emitter().Emit(arg_id, CppCallArgTypeNotSupported, arg_id);
-    return nullptr;
-  }
-
-  // TODO: Avoid heap allocating more of these on every call. Either cache them
-  // somewhere or put them on the stack.
-  return new (context.ast_context()) clang::OpaqueValueExpr(
-      // TODO: Add location accordingly.
-      clang::SourceLocation(), arg_cpp_type.getNonReferenceType(), value_kind);
-}
-
 // Adds the given overload candidates to the candidate set.
 static auto AddOverloadCandidataes(clang::Sema& sema,
                                    clang::OverloadCandidateSet& candidate_set,
@@ -134,14 +82,9 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
       return SemIR::ErrorInst::InstId;
     }
   }
-  llvm::SmallVector<clang::Expr*> arg_exprs;
-  arg_exprs.reserve(arg_ids.size());
-  for (SemIR::InstId arg_id : arg_ids) {
-    auto* arg_expr = InventClangArg(context, arg_id);
-    if (!arg_expr) {
-      return SemIR::ErrorInst::InstId;
-    }
-    arg_exprs.push_back(arg_expr);
+  auto arg_exprs = InventClangArgs(context, arg_ids);
+  if (!arg_exprs.has_value()) {
+    return SemIR::ErrorInst::InstId;
   }
 
   const SemIR::CppOverloadSet& overload_set =
@@ -158,7 +101,7 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
   clang::Sema& sema = ast->getSema();
 
   AddOverloadCandidataes(sema, candidate_set, overload_set.candidate_functions,
-                         self_expr, arg_exprs);
+                         self_expr, *arg_exprs);
 
   // Find best viable function among the candidates.
   clang::OverloadCandidateSet::iterator best_viable_fn;

+ 69 - 1
toolchain/check/cpp/type_mapping.cpp

@@ -18,6 +18,7 @@
 #include "toolchain/check/convert.h"
 #include "toolchain/check/literal.h"
 #include "toolchain/sem_ir/class.h"
+#include "toolchain/sem_ir/expr_info.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/type.h"
@@ -177,8 +178,12 @@ static auto MapNonWrapperType(Context& context, SemIR::InstId inst_id,
   }
 }
 
+// Maps a Carbon type to a C++ type. Accepts an InstId, representing a value
+// whose type is mapped to a C++ type. Returns `clang::QualType` if the mapping
+// succeeds, or `clang::QualType::isNull()` if the type is not supported.
 // TODO: unify this with the C++ to Carbon type mapping function.
-auto MapToCppType(Context& context, SemIR::InstId inst_id) -> clang::QualType {
+static auto MapToCppType(Context& context, SemIR::InstId inst_id)
+    -> clang::QualType {
   auto type_id = context.insts().Get(inst_id).type_id();
   llvm::SmallVector<SemIR::TypeId> wrapper_types;
   while (true) {
@@ -223,4 +228,67 @@ auto MapToCppType(Context& context, SemIR::InstId inst_id) -> clang::QualType {
   return mapped_type;
 }
 
+auto InventClangArg(Context& context, SemIR::InstId arg_id) -> clang::Expr* {
+  clang::ExprValueKind value_kind;
+  switch (SemIR::GetExprCategory(context.sem_ir(), arg_id)) {
+    case SemIR::ExprCategory::NotExpr:
+      CARBON_FATAL("Should not see these here");
+
+    case SemIR::ExprCategory::Error:
+      return nullptr;
+
+    case SemIR::ExprCategory::DurableRef:
+      value_kind = clang::ExprValueKind::VK_LValue;
+      break;
+
+    case SemIR::ExprCategory::EphemeralRef:
+      value_kind = clang::ExprValueKind::VK_XValue;
+      break;
+
+    case SemIR::ExprCategory::Value:
+    case SemIR::ExprCategory::Initializing:
+      value_kind = clang::ExprValueKind::VK_PRValue;
+      break;
+
+    case SemIR::ExprCategory::Mixed:
+      // TODO: Handle this by creating an InitListExpr.
+      value_kind = clang::ExprValueKind::VK_PRValue;
+      break;
+  }
+
+  if (context.insts().Get(arg_id).type_id() == SemIR::ErrorInst::TypeId) {
+    // The argument error has already been diagnosed.
+    return nullptr;
+  }
+
+  clang::QualType arg_cpp_type = MapToCppType(context, arg_id);
+  if (arg_cpp_type.isNull()) {
+    CARBON_DIAGNOSTIC(CppCallArgTypeNotSupported, Error,
+                      "call argument of type {0} is not supported",
+                      TypeOfInstId);
+    context.emitter().Emit(arg_id, CppCallArgTypeNotSupported, arg_id);
+    return nullptr;
+  }
+
+  // TODO: Avoid heap allocating more of these on every call. Either cache them
+  // somewhere or put them on the stack.
+  return new (context.ast_context()) clang::OpaqueValueExpr(
+      // TODO: Add location accordingly.
+      clang::SourceLocation(), arg_cpp_type.getNonReferenceType(), value_kind);
+}
+
+auto InventClangArgs(Context& context, llvm::ArrayRef<SemIR::InstId> arg_ids)
+    -> std::optional<llvm::SmallVector<clang::Expr*>> {
+  llvm::SmallVector<clang::Expr*> arg_exprs;
+  arg_exprs.reserve(arg_ids.size());
+  for (SemIR::InstId arg_id : arg_ids) {
+    auto* arg_expr = InventClangArg(context, arg_id);
+    if (!arg_expr) {
+      return std::nullopt;
+    }
+    arg_exprs.push_back(arg_expr);
+  }
+  return arg_exprs;
+}
+
 }  // namespace Carbon::Check

+ 9 - 4
toolchain/check/cpp/type_mapping.h

@@ -11,10 +11,15 @@
 
 namespace Carbon::Check {
 
-// Maps a Carbon type to a C++ type. Accepts an InstId, representing a value
-// whose type is mapped to a C++ type. Returns `clang::QualType` if the mapping
-// succeeds, or `clang::QualType::isNull()` if the type is not supported.
-auto MapToCppType(Context& context, SemIR::InstId inst_id) -> clang::QualType;
+// Invents a Clang argument expression to use in overload resolution to
+// represent the given Carbon argument instruction.
+auto InventClangArg(Context& context, SemIR::InstId arg_id) -> clang::Expr*;
+
+// For each arg, invents a Clang argument expression to use in overload
+// resolution or argument dependent lookup (ADL) to represent the given Carbon
+// argument instructions. Returns std::nullopt if any arg failed.
+auto InventClangArgs(Context& context, llvm::ArrayRef<SemIR::InstId> arg_ids)
+    -> std::optional<llvm::SmallVector<clang::Expr*>>;
 
 }  // namespace Carbon::Check
 

+ 16 - 49
toolchain/check/operator.cpp

@@ -8,7 +8,7 @@
 
 #include "toolchain/check/call.h"
 #include "toolchain/check/context.h"
-#include "toolchain/check/cpp/import.h"
+#include "toolchain/check/cpp/operators.h"
 #include "toolchain/check/generic.h"
 #include "toolchain/check/member_access.h"
 #include "toolchain/check/name_lookup.h"
@@ -39,35 +39,18 @@ static auto GetOperatorOpFunction(Context& context, SemIR::LocId loc_id,
                              op_name_id);
 }
 
-// If the instruction is a C++ class, returns its parent scope id. Otherwise
-// returns `std::nullopt`.
-static auto GetCppClassTypeParentScope(Context& context, SemIR::InstId inst_id)
-    -> std::optional<SemIR::NameScopeId> {
+// Returns whether the instruction is a C++ class.
+static auto IsCppClassType(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 std::nullopt;
+    return false;
   }
 
   const SemIR::Class& class_info = context.classes().Get(class_type->class_id);
-  if (!class_info.is_complete() ||
-      !context.name_scopes().Get(class_info.scope_id).is_cpp_scope()) {
-    // Not a C++ class.
-    return std::nullopt;
-  }
-
-  SemIR::NameScopeId parent_scope_id = class_info.parent_scope_id;
-  do {
-    SemIR::NameScope& scope = context.name_scopes().Get(parent_scope_id);
-    if (context.insts().Is<SemIR::Namespace>(scope.inst_id())) {
-      break;
-    }
-    parent_scope_id = scope.parent_scope_id();
-
-  } while (parent_scope_id.has_value());
-
-  return parent_scope_id;
+  return class_info.is_complete() &&
+         context.name_scopes().Get(class_info.scope_id).is_cpp_scope();
 }
 
 auto BuildUnaryOperator(Context& context, SemIR::LocId loc_id, Operator op,
@@ -78,16 +61,11 @@ auto BuildUnaryOperator(Context& context, SemIR::LocId loc_id, Operator op,
   // the C++ operator.
   // TODO: Change impl lookup instead. See
   // https://github.com/carbon-language/carbon-lang/blob/db0a00d713015436844c55e7ac190a0f95556499/toolchain/check/operator.cpp#L76
-  // TODO: We should do ADL-only lookup for operators
-  // (`Sema::ArgumentDependentLookup`), when we support mapping Carbon types
-  // into C++ types.
-  auto cpp_parent_scope_id = GetCppClassTypeParentScope(context, operand_id);
-  if (cpp_parent_scope_id) {
-    SemIR::ScopeLookupResult cpp_lookup_result =
-        ImportOperatorFromCpp(context, loc_id, *cpp_parent_scope_id, op);
-    if (cpp_lookup_result.is_found()) {
-      return PerformCall(context, loc_id, cpp_lookup_result.target_inst_id(),
-                         {operand_id});
+  if (IsCppClassType(context, operand_id)) {
+    SemIR::InstId cpp_inst_id =
+        LookupCppOperator(context, loc_id, op, {operand_id});
+    if (cpp_inst_id.has_value() && cpp_inst_id != SemIR::ErrorInst::InstId) {
+      return PerformCall(context, loc_id, cpp_inst_id, {operand_id});
     }
   }
 
@@ -117,22 +95,11 @@ auto BuildBinaryOperator(Context& context, SemIR::LocId loc_id, Operator op,
   // https://github.com/carbon-language/carbon-lang/pull/5996/files/5d01fa69511b76f87efbc0387f5e40abcf4c911a#r2308666348
   // and
   // https://github.com/carbon-language/carbon-lang/pull/5996/files/5d01fa69511b76f87efbc0387f5e40abcf4c911a#r2308664536
-  // TODO: We should do ADL-only lookup for operators
-  // (`Sema::ArgumentDependentLookup`), when we support mapping Carbon types
-  // into C++ types.
-  llvm::SmallVector<SemIR::NameScopeId, 2> cpp_operand_parent_scope_ids;
-  for (SemIR::InstId operand_id : {lhs_id, rhs_id}) {
-    auto cpp_parent_scope_id = GetCppClassTypeParentScope(context, operand_id);
-    if (!cpp_parent_scope_id || llvm::is_contained(cpp_operand_parent_scope_ids,
-                                                   *cpp_parent_scope_id)) {
-      continue;
-    }
-    cpp_operand_parent_scope_ids.push_back(*cpp_parent_scope_id);
-    SemIR::ScopeLookupResult cpp_lookup_result =
-        ImportOperatorFromCpp(context, loc_id, *cpp_parent_scope_id, op);
-    if (cpp_lookup_result.is_found()) {
-      return PerformCall(context, loc_id, cpp_lookup_result.target_inst_id(),
-                         {lhs_id, rhs_id});
+  if (IsCppClassType(context, lhs_id) || IsCppClassType(context, rhs_id)) {
+    SemIR::InstId cpp_inst_id =
+        LookupCppOperator(context, loc_id, op, {lhs_id, rhs_id});
+    if (cpp_inst_id.has_value() && cpp_inst_id != SemIR::ErrorInst::InstId) {
+      return PerformCall(context, loc_id, cpp_inst_id, {lhs_id, rhs_id});
     }
   }
 

+ 113 - 77
toolchain/check/testdata/interop/cpp/function/operators.carbon

@@ -95,16 +95,20 @@ 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: fail_postfix_calling_prefix.carbon:[[@LINE+7]]:3: error: no matching function for call to `<C++ operator>` [CppOverloadingNoViableFunctionFound]
+  // CHECK:STDERR:   ++postfix;
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR: fail_postfix_calling_prefix.carbon:[[@LINE+4]]:3: note: in call to Cpp function here [InCallToCppFunction]
   // 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: fail_postfix_calling_prefix.carbon:[[@LINE+7]]:3: error: no matching function for call to `<C++ operator>` [CppOverloadingNoViableFunctionFound]
+  // CHECK:STDERR:   --postfix;
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR: fail_postfix_calling_prefix.carbon:[[@LINE+4]]:3: note: in call to Cpp function here [InCallToCppFunction]
   // CHECK:STDERR:   --postfix;
   // CHECK:STDERR:   ^~~~~~~~~
-  // CHECK:STDERR: fail_postfix_calling_prefix.carbon: note: calling function declared here [InCallToEntity]
   // CHECK:STDERR:
   --postfix;
 }
@@ -225,22 +229,20 @@ import Cpp library "binary_operators.h";
 
 fn F() {
   let c1: Cpp.C = Cpp.C.C();
-  // CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+8]]:24: error: cannot implicitly convert expression of type `Core.IntLiteral` to `Cpp.C` [ConversionFailure]
+  // CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+7]]:19: error: no matching function for call to `<C++ operator>` [CppOverloadingNoViableFunctionFound]
   // CHECK:STDERR:   let c2: Cpp.C = c1 + 5;
-  // CHECK:STDERR:                        ^
-  // CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+5]]:24: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(Cpp.C)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:                   ^~~~~~
+  // CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+4]]:19: note: in call to Cpp function here [InCallToCppFunction]
   // CHECK:STDERR:   let c2: Cpp.C = c1 + 5;
-  // CHECK:STDERR:                        ^
-  // CHECK:STDERR: fail_call_with_wrong_type.carbon: note: initializing function parameter [InCallToFunctionParam]
+  // CHECK:STDERR:                   ^~~~~~
   // CHECK:STDERR:
   let c2: Cpp.C = c1 + 5;
-  // CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+8]]:19: error: cannot implicitly convert expression of type `Core.IntLiteral` to `Cpp.C` [ConversionFailure]
+  // CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+7]]:19: error: no matching function for call to `<C++ operator>` [CppOverloadingNoViableFunctionFound]
   // CHECK:STDERR:   let c3: Cpp.C = 6 + c1;
-  // CHECK:STDERR:                   ^
-  // CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+5]]:19: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(Cpp.C)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:                   ^~~~~~
+  // CHECK:STDERR: fail_call_with_wrong_type.carbon:[[@LINE+4]]:19: note: in call to Cpp function here [InCallToCppFunction]
   // CHECK:STDERR:   let c3: Cpp.C = 6 + c1;
-  // CHECK:STDERR:                   ^
-  // CHECK:STDERR: fail_call_with_wrong_type.carbon: note: initializing function parameter [InCallToFunctionParam]
+  // CHECK:STDERR:                   ^~~~~~
   // CHECK:STDERR:
   let c3: Cpp.C = 6 + c1;
 }
@@ -600,7 +602,10 @@ fn F() {
   //@dump-sem-ir-begin
   let c1: Cpp.N.C = Cpp.N.C.C();
   let c2: Cpp.N.C = Cpp.N.C.C();
-  // CHECK:STDERR: fail_todo_import_operands_in_namespace_operator_in_global.carbon:[[@LINE+4]]:21: error: cannot access member of interface `Core.AddWith(Cpp.N.C)` in type `Cpp.N.C` that does not implement that interface [MissingImplInMemberAccess]
+  // CHECK:STDERR: fail_todo_import_operands_in_namespace_operator_in_global.carbon:[[@LINE+7]]:21: error: no matching function for call to `<C++ operator>` [CppOverloadingNoViableFunctionFound]
+  // CHECK:STDERR:   let c3: Cpp.N.C = c1 + c2;
+  // CHECK:STDERR:                     ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_operands_in_namespace_operator_in_global.carbon:[[@LINE+4]]:21: note: in call to Cpp function here [InCallToCppFunction]
   // CHECK:STDERR:   let c3: Cpp.N.C = c1 + c2;
   // CHECK:STDERR:                     ^~~~~~~
   // CHECK:STDERR:
@@ -672,7 +677,7 @@ class C {
   auto operator+(C rhs) -> C;
 };
 
-// --- fail_todo_import_member__add_with.carbon
+// --- fail_todo_import_member_add_with.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -682,7 +687,10 @@ fn F() {
   //@dump-sem-ir-begin
   let c1: Cpp.C = Cpp.C.C();
   let c2: Cpp.C = Cpp.C.C();
-  // CHECK:STDERR: fail_todo_import_member__add_with.carbon:[[@LINE+4]]:19: error: cannot access member of interface `Core.AddWith(Cpp.C)` in type `Cpp.C` that does not implement that interface [MissingImplInMemberAccess]
+  // CHECK:STDERR: fail_todo_import_member_add_with.carbon:[[@LINE+7]]:19: error: no matching function for call to `<C++ operator>` [CppOverloadingNoViableFunctionFound]
+  // CHECK:STDERR:   let c3: Cpp.C = c1 + c2;
+  // CHECK:STDERR:                   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_member_add_with.carbon:[[@LINE+4]]:19: note: in call to Cpp function here [InCallToCppFunction]
   // CHECK:STDERR:   let c3: Cpp.C = c1 + c2;
   // CHECK:STDERR:                   ^~~~~~~
   // CHECK:STDERR:
@@ -737,13 +745,48 @@ import Cpp library "not_found.h";
 fn F() {
   let c1: Cpp.C = Cpp.C.C();
   let c2: Cpp.C = Cpp.C.C();
-  // CHECK:STDERR: fail_import_not_found.carbon:[[@LINE+4]]:19: error: cannot access member of interface `Core.AddWith(Cpp.C)` in type `Cpp.C` that does not implement that interface [MissingImplInMemberAccess]
+  // CHECK:STDERR: fail_import_not_found.carbon:[[@LINE+7]]:19: error: no matching function for call to `<C++ operator>` [CppOverloadingNoViableFunctionFound]
+  // CHECK:STDERR:   let c3: Cpp.C = c1 + c2;
+  // CHECK:STDERR:                   ^~~~~~~
+  // CHECK:STDERR: fail_import_not_found.carbon:[[@LINE+4]]:19: note: in call to Cpp function here [InCallToCppFunction]
   // CHECK:STDERR:   let c3: Cpp.C = c1 + c2;
   // CHECK:STDERR:                   ^~~~~~~
   // CHECK:STDERR:
   let c3: Cpp.C = c1 + c2;
 }
 
+// ============================================================================
+// Incomplete operand
+// ============================================================================
+
+// --- incomplete.h
+
+class Incomplete;
+class Complete {};
+
+auto operator+(Complete lhs, Incomplete rhs) -> Complete;
+
+auto foo(Complete complete) -> void;
+
+// --- fail_import_incomplete.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "incomplete.h";
+
+fn F() {
+  var c1: Cpp.Complete = Cpp.Complete.Complete();
+  // CHECK:STDERR: fail_import_incomplete.carbon:[[@LINE+8]]:40: error: invalid use of incomplete type `Cpp.Incomplete` [IncompleteTypeInConversion]
+  // CHECK:STDERR:   let c3: Cpp.Complete = Cpp.foo(c1 + ({} as Cpp.Incomplete));
+  // CHECK:STDERR:                                        ^~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_import_incomplete.carbon:[[@LINE-7]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./incomplete.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: class Incomplete;
+  // CHECK:STDERR:       ^
+  // CHECK:STDERR:
+  let c3: Cpp.Complete = Cpp.foo(c1 + ({} as Cpp.Incomplete));
+}
+
 // ============================================================================
 // Operator overloading
 // ============================================================================
@@ -755,7 +798,7 @@ auto operator+(C lhs, C rhs) -> C;
 class D {};
 auto operator+(D lhs, D rhs) -> D;
 
-// --- fail_todo_import_overloading.carbon
+// --- import_overloading.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -764,13 +807,6 @@ import Cpp library "overloading.h";
 fn F() {
   let c1: Cpp.C = Cpp.C.C();
   let c2: Cpp.C = Cpp.C.C();
-  // CHECK:STDERR: fail_todo_import_overloading.carbon:[[@LINE+7]]:19: error: semantics TODO: `Unsupported: Lookup succeeded but couldn't find a single result; LookupResultKind: 3` [SemanticsTodo]
-  // CHECK:STDERR:   let c3: Cpp.C = c1 + c2;
-  // CHECK:STDERR:                   ^~~~~~~
-  // CHECK:STDERR: fail_todo_import_overloading.carbon:[[@LINE+4]]:19: note: in `Cpp` operator `AddWith` lookup [InCppOperatorLookup]
-  // CHECK:STDERR:   let c3: Cpp.C = c1 + c2;
-  // CHECK:STDERR:                   ^~~~~~~
-  // CHECK:STDERR:
   let c3: Cpp.C = c1 + c2;
 }
 
@@ -780,8 +816,8 @@ fn F() {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %C: type = class_type @C [concrete]
 // CHECK:STDOUT:   %pattern_type.217: type = pattern_type %C [concrete]
-// CHECK:STDOUT:   %.d40: type = cpp_overload_set_type @C.C [concrete]
-// CHECK:STDOUT:   %empty_struct: %.d40 = struct_value () [concrete]
+// CHECK:STDOUT:   %.d40: type = cpp_overload_set_type @operator++__carbon_thunk [concrete]
+// CHECK:STDOUT:   %empty_struct.e73: %.d40 = struct_value () [concrete]
 // 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]
@@ -803,7 +839,7 @@ fn F() {
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
-// CHECK:STDOUT:   %.40b: %.d40 = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.40b: %.d40 = cpp_overload_set_value @operator++__carbon_thunk [concrete = constants.%empty_struct.e73]
 // CHECK:STDOUT:   %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   } {
@@ -833,7 +869,7 @@ fn F() {
 // 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: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc8_23: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
 // 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)
@@ -915,8 +951,8 @@ fn F() {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %C: type = class_type @C [concrete]
 // CHECK:STDOUT:   %pattern_type.217: type = pattern_type %C [concrete]
-// CHECK:STDOUT:   %.d40: type = cpp_overload_set_type @C.C [concrete]
-// CHECK:STDOUT:   %empty_struct: %.d40 = struct_value () [concrete]
+// CHECK:STDOUT:   %.d40: type = cpp_overload_set_type @Copy.Op [concrete]
+// CHECK:STDOUT:   %empty_struct.e73: %.d40 = struct_value () [concrete]
 // 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]
@@ -1005,7 +1041,7 @@ fn F() {
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
-// CHECK:STDOUT:   %.40b: %.d40 = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.40b: %.d40 = cpp_overload_set_value @Copy.Op [concrete = constants.%empty_struct.e73]
 // CHECK:STDOUT:   %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   } {
@@ -1144,7 +1180,7 @@ fn F() {
 // CHECK:STDOUT:   %c1.var: ref %C = var %c1.var_patt
 // 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: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc8_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
 // CHECK:STDOUT:   %.loc8_3.1: ref %C = splice_block %c1.var {}
 // CHECK:STDOUT:   %addr.loc8_27: %ptr.d9e = addr_of %.loc8_3.1
 // CHECK:STDOUT:   %C__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_27)
@@ -1162,7 +1198,7 @@ fn F() {
 // CHECK:STDOUT:   %c2.var: ref %C = var %c2.var_patt
 // CHECK:STDOUT:   %Cpp.ref.loc9_19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %C.ref.loc9_22: type = name_ref C, imports.%C.decl [concrete = constants.%C]
-// CHECK:STDOUT:   %C.ref.loc9_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc9_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
 // CHECK:STDOUT:   %.loc9_3.1: ref %C = splice_block %c2.var {}
 // CHECK:STDOUT:   %addr.loc9_27: %ptr.d9e = addr_of %.loc9_3.1
 // CHECK:STDOUT:   %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_27)
@@ -1792,8 +1828,8 @@ fn F() {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %C: type = class_type @C [concrete]
 // CHECK:STDOUT:   %pattern_type.217: type = pattern_type %C [concrete]
-// CHECK:STDOUT:   %.d40: type = cpp_overload_set_type @C.C [concrete]
-// CHECK:STDOUT:   %empty_struct: %.d40 = struct_value () [concrete]
+// CHECK:STDOUT:   %.d40: type = cpp_overload_set_type @operator+__carbon_thunk [concrete]
+// CHECK:STDOUT:   %empty_struct.e73: %.d40 = struct_value () [concrete]
 // 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]
@@ -1811,7 +1847,7 @@ fn F() {
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
-// CHECK:STDOUT:   %.40b: %.d40 = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.40b: %.d40 = cpp_overload_set_value @operator+__carbon_thunk [concrete = constants.%empty_struct.e73]
 // CHECK:STDOUT:   %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   } {
@@ -1831,7 +1867,7 @@ fn F() {
 // 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: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc8_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
 // 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.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_27.1)
@@ -1848,7 +1884,7 @@ fn F() {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Cpp.ref.loc9_19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %C.ref.loc9_22: type = name_ref C, imports.%C.decl [concrete = constants.%C]
-// CHECK:STDOUT:   %C.ref.loc9_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc9_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
 // CHECK:STDOUT:   %.loc9_27.1: ref %C = temporary_storage
 // CHECK:STDOUT:   %addr.loc9_27.1: %ptr.d9e = addr_of %.loc9_27.1
 // CHECK:STDOUT:   %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_27.1)
@@ -1964,8 +2000,8 @@ fn F() {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %C: type = class_type @C [concrete]
 // CHECK:STDOUT:   %pattern_type.69f: type = pattern_type %C [concrete]
-// CHECK:STDOUT:   %.169: type = cpp_overload_set_type @C.C [concrete]
-// CHECK:STDOUT:   %empty_struct: %.169 = struct_value () [concrete]
+// CHECK:STDOUT:   %.169: type = cpp_overload_set_type @C__carbon_thunk [concrete]
+// CHECK:STDOUT:   %empty_struct.41c: %.169 = struct_value () [concrete]
 // CHECK:STDOUT:   %ptr.838: 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]
@@ -1987,7 +2023,7 @@ fn F() {
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
-// CHECK:STDOUT:   %.c52: %.169 = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.c52: %.169 = cpp_overload_set_value @C__carbon_thunk [concrete = constants.%empty_struct.41c]
 // CHECK:STDOUT:   %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   } {
@@ -2008,7 +2044,7 @@ fn F() {
 // CHECK:STDOUT:   %Cpp.ref.loc8_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %N.ref.loc8_24: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
 // CHECK:STDOUT:   %C.ref.loc8_26: type = name_ref C, imports.%C.decl [concrete = constants.%C]
-// CHECK:STDOUT:   %C.ref.loc8_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc8_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct.41c]
 // CHECK:STDOUT:   %.loc8_31.1: ref %C = temporary_storage
 // CHECK:STDOUT:   %addr.loc8_31.1: %ptr.838 = addr_of %.loc8_31.1
 // CHECK:STDOUT:   %C__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_31.1)
@@ -2027,7 +2063,7 @@ fn F() {
 // CHECK:STDOUT:   %Cpp.ref.loc9_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %N.ref.loc9_24: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
 // CHECK:STDOUT:   %C.ref.loc9_26: type = name_ref C, imports.%C.decl [concrete = constants.%C]
-// CHECK:STDOUT:   %C.ref.loc9_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc9_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct.41c]
 // CHECK:STDOUT:   %.loc9_31.1: ref %C = temporary_storage
 // CHECK:STDOUT:   %addr.loc9_31.1: %ptr.838 = addr_of %.loc9_31.1
 // CHECK:STDOUT:   %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_31.1)
@@ -2091,14 +2127,14 @@ fn F() {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %C1: type = class_type @C1 [concrete]
 // CHECK:STDOUT:   %pattern_type.20f: type = pattern_type %C1 [concrete]
-// CHECK:STDOUT:   %.eb7: type = cpp_overload_set_type @C1__carbon_thunk [concrete]
+// CHECK:STDOUT:   %.eb7: type = cpp_overload_set_type @C2__carbon_thunk [concrete]
 // CHECK:STDOUT:   %empty_struct.56f: %.eb7 = struct_value () [concrete]
 // CHECK:STDOUT:   %ptr.087: type = ptr_type %C1 [concrete]
 // CHECK:STDOUT:   %C1__carbon_thunk.type: type = fn_type @C1__carbon_thunk [concrete]
 // CHECK:STDOUT:   %C1__carbon_thunk: %C1__carbon_thunk.type = struct_value () [concrete]
 // CHECK:STDOUT:   %C2: type = class_type @C2 [concrete]
 // CHECK:STDOUT:   %pattern_type.846: type = pattern_type %C2 [concrete]
-// CHECK:STDOUT:   %.74f: type = cpp_overload_set_type @C2.C2 [concrete]
+// CHECK:STDOUT:   %.74f: type = cpp_overload_set_type @cpp_operator.1 [concrete]
 // CHECK:STDOUT:   %empty_struct.c81: %.74f = struct_value () [concrete]
 // CHECK:STDOUT:   %ptr.51f: type = ptr_type %C2 [concrete]
 // CHECK:STDOUT:   %C2__carbon_thunk.type: type = fn_type @C2__carbon_thunk [concrete]
@@ -2127,7 +2163,7 @@ fn F() {
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %C1.decl: type = class_decl @C1 [concrete = constants.%C1] {} {}
-// CHECK:STDOUT:   %.91f: %.eb7 = cpp_overload_set_value @C1__carbon_thunk [concrete = constants.%empty_struct.56f]
+// CHECK:STDOUT:   %.91f: %.eb7 = cpp_overload_set_value @C2__carbon_thunk [concrete = constants.%empty_struct.56f]
 // CHECK:STDOUT:   %C1__carbon_thunk.decl: %C1__carbon_thunk.type = fn_decl @C1__carbon_thunk [concrete = constants.%C1__carbon_thunk] {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   } {
@@ -2138,7 +2174,7 @@ fn F() {
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %C2.decl: type = class_decl @C2 [concrete = constants.%C2] {} {}
-// CHECK:STDOUT:   %.ed5: %.74f = cpp_overload_set_value @C2.C2 [concrete = constants.%empty_struct.c81]
+// CHECK:STDOUT:   %.ed5: %.74f = cpp_overload_set_value @cpp_operator.1 [concrete = constants.%empty_struct.c81]
 // CHECK:STDOUT:   %C2__carbon_thunk.decl: %C2__carbon_thunk.type = fn_decl @C2__carbon_thunk [concrete = constants.%C2__carbon_thunk] {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   } {
@@ -2275,8 +2311,8 @@ fn F() {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %C: type = class_type @C [concrete]
 // CHECK:STDOUT:   %pattern_type.69f: type = pattern_type %C [concrete]
-// CHECK:STDOUT:   %.169: type = cpp_overload_set_type @C.C [concrete]
-// CHECK:STDOUT:   %empty_struct: %.169 = struct_value () [concrete]
+// CHECK:STDOUT:   %.169: type = cpp_overload_set_type @C__carbon_thunk [concrete]
+// CHECK:STDOUT:   %empty_struct.41c: %.169 = struct_value () [concrete]
 // CHECK:STDOUT:   %ptr.838: 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]
@@ -2296,7 +2332,7 @@ fn F() {
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
-// CHECK:STDOUT:   %.c52: %.169 = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.c52: %.169 = cpp_overload_set_value @C__carbon_thunk [concrete = constants.%empty_struct.41c]
 // CHECK:STDOUT:   %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   } {
@@ -2312,7 +2348,7 @@ fn F() {
 // CHECK:STDOUT:   %Cpp.ref.loc8_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %N.ref.loc8_24: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
 // CHECK:STDOUT:   %C.ref.loc8_26: type = name_ref C, imports.%C.decl [concrete = constants.%C]
-// CHECK:STDOUT:   %C.ref.loc8_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc8_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct.41c]
 // CHECK:STDOUT:   %.loc8_31.1: ref %C = temporary_storage
 // CHECK:STDOUT:   %addr.loc8_31.1: %ptr.838 = addr_of %.loc8_31.1
 // CHECK:STDOUT:   %C__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_31.1)
@@ -2331,7 +2367,7 @@ fn F() {
 // CHECK:STDOUT:   %Cpp.ref.loc9_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %N.ref.loc9_24: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
 // CHECK:STDOUT:   %C.ref.loc9_26: type = name_ref C, imports.%C.decl [concrete = constants.%C]
-// CHECK:STDOUT:   %C.ref.loc9_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc9_28: %.169 = name_ref C, imports.%.c52 [concrete = constants.%empty_struct.41c]
 // CHECK:STDOUT:   %.loc9_31.1: ref %C = temporary_storage
 // CHECK:STDOUT:   %addr.loc9_31.1: %ptr.838 = addr_of %.loc9_31.1
 // CHECK:STDOUT:   %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_31.1)
@@ -2349,10 +2385,10 @@ fn F() {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %c1.ref: %C = name_ref c1, %c1
 // CHECK:STDOUT:   %c2.ref: %C = name_ref c2, %c2
-// CHECK:STDOUT:   %.loc14: type = splice_block %C.ref.loc14 [concrete = constants.%C] {
-// CHECK:STDOUT:     %Cpp.ref.loc14: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     %N.ref.loc14: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
-// CHECK:STDOUT:     %C.ref.loc14: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %.loc17: type = splice_block %C.ref.loc17 [concrete = constants.%C] {
+// CHECK:STDOUT:     %Cpp.ref.loc17: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %N.ref.loc17: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
+// CHECK:STDOUT:     %C.ref.loc17: type = name_ref C, imports.%C.decl [concrete = constants.%C]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %c3: %C = bind_name c3, <error> [concrete = <error>]
 // CHECK:STDOUT:   %facet_value.loc9: %type_where = facet_value constants.%C, () [concrete = constants.%facet_value]
@@ -2379,8 +2415,8 @@ fn F() {
 // CHECK:STDOUT:   %O: type = class_type @O [concrete]
 // CHECK:STDOUT:   %C: type = class_type @C [concrete]
 // CHECK:STDOUT:   %pattern_type.b28: type = pattern_type %C [concrete]
-// CHECK:STDOUT:   %.d80: type = cpp_overload_set_type @C.C [concrete]
-// CHECK:STDOUT:   %empty_struct: %.d80 = struct_value () [concrete]
+// CHECK:STDOUT:   %.d80: type = cpp_overload_set_type @C__carbon_thunk [concrete]
+// CHECK:STDOUT:   %empty_struct.06f: %.d80 = struct_value () [concrete]
 // CHECK:STDOUT:   %ptr.de2: 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]
@@ -2399,7 +2435,7 @@ fn F() {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %O.decl: type = class_decl @O [concrete = constants.%O] {} {}
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
-// CHECK:STDOUT:   %.7a9: %.d80 = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.7a9: %.d80 = cpp_overload_set_value @C__carbon_thunk [concrete = constants.%empty_struct.06f]
 // CHECK:STDOUT:   %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   } {
@@ -2420,7 +2456,7 @@ fn F() {
 // CHECK:STDOUT:   %Cpp.ref.loc8_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %O.ref.loc8_24: type = name_ref O, imports.%O.decl [concrete = constants.%O]
 // CHECK:STDOUT:   %C.ref.loc8_26: type = name_ref C, imports.%C.decl [concrete = constants.%C]
-// CHECK:STDOUT:   %C.ref.loc8_28: %.d80 = name_ref C, imports.%.7a9 [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc8_28: %.d80 = name_ref C, imports.%.7a9 [concrete = constants.%empty_struct.06f]
 // CHECK:STDOUT:   %.loc8_31.1: ref %C = temporary_storage
 // CHECK:STDOUT:   %addr.loc8_31.1: %ptr.de2 = addr_of %.loc8_31.1
 // CHECK:STDOUT:   %C__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_31.1)
@@ -2439,7 +2475,7 @@ fn F() {
 // CHECK:STDOUT:   %Cpp.ref.loc9_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %O.ref.loc9_24: type = name_ref O, imports.%O.decl [concrete = constants.%O]
 // CHECK:STDOUT:   %C.ref.loc9_26: type = name_ref C, imports.%C.decl [concrete = constants.%C]
-// CHECK:STDOUT:   %C.ref.loc9_28: %.d80 = name_ref C, imports.%.7a9 [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc9_28: %.d80 = name_ref C, imports.%.7a9 [concrete = constants.%empty_struct.06f]
 // CHECK:STDOUT:   %.loc9_31.1: ref %C = temporary_storage
 // CHECK:STDOUT:   %addr.loc9_31.1: %ptr.de2 = addr_of %.loc9_31.1
 // CHECK:STDOUT:   %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_31.1)
@@ -2504,8 +2540,8 @@ fn F() {
 // CHECK:STDOUT:   %O: type = class_type @O [concrete]
 // CHECK:STDOUT:   %C: type = class_type @C [concrete]
 // CHECK:STDOUT:   %pattern_type.84b: type = pattern_type %C [concrete]
-// CHECK:STDOUT:   %.62f: type = cpp_overload_set_type @C.C [concrete]
-// CHECK:STDOUT:   %empty_struct: %.62f = struct_value () [concrete]
+// CHECK:STDOUT:   %.62f: type = cpp_overload_set_type @C__carbon_thunk [concrete]
+// CHECK:STDOUT:   %empty_struct.e94: %.62f = struct_value () [concrete]
 // CHECK:STDOUT:   %ptr.4b2: 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]
@@ -2528,7 +2564,7 @@ fn F() {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %O.decl: type = class_decl @O [concrete = constants.%O] {} {}
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
-// CHECK:STDOUT:   %.f56: %.62f = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.f56: %.62f = cpp_overload_set_value @C__carbon_thunk [concrete = constants.%empty_struct.e94]
 // CHECK:STDOUT:   %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   } {
@@ -2550,7 +2586,7 @@ fn F() {
 // CHECK:STDOUT:   %N.ref.loc8_26: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
 // CHECK:STDOUT:   %O.ref.loc8_28: type = name_ref O, imports.%O.decl [concrete = constants.%O]
 // CHECK:STDOUT:   %C.ref.loc8_30: type = name_ref C, imports.%C.decl [concrete = constants.%C]
-// CHECK:STDOUT:   %C.ref.loc8_32: %.62f = name_ref C, imports.%.f56 [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc8_32: %.62f = name_ref C, imports.%.f56 [concrete = constants.%empty_struct.e94]
 // CHECK:STDOUT:   %.loc8_35.1: ref %C = temporary_storage
 // CHECK:STDOUT:   %addr.loc8_35.1: %ptr.4b2 = addr_of %.loc8_35.1
 // CHECK:STDOUT:   %C__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_35.1)
@@ -2571,7 +2607,7 @@ fn F() {
 // CHECK:STDOUT:   %N.ref.loc9_26: <namespace> = name_ref N, imports.%N [concrete = imports.%N]
 // CHECK:STDOUT:   %O.ref.loc9_28: type = name_ref O, imports.%O.decl [concrete = constants.%O]
 // CHECK:STDOUT:   %C.ref.loc9_30: type = name_ref C, imports.%C.decl [concrete = constants.%C]
-// CHECK:STDOUT:   %C.ref.loc9_32: %.62f = name_ref C, imports.%.f56 [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc9_32: %.62f = name_ref C, imports.%.f56 [concrete = constants.%empty_struct.e94]
 // CHECK:STDOUT:   %.loc9_35.1: ref %C = temporary_storage
 // CHECK:STDOUT:   %addr.loc9_35.1: %ptr.4b2 = addr_of %.loc9_35.1
 // CHECK:STDOUT:   %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_35.1)
@@ -2631,14 +2667,14 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_member__add_with.carbon
+// CHECK:STDOUT: --- fail_todo_import_member_add_with.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %C: type = class_type @C [concrete]
 // CHECK:STDOUT:   %pattern_type.217: type = pattern_type %C [concrete]
-// CHECK:STDOUT:   %.d40: type = cpp_overload_set_type @C.C [concrete]
-// CHECK:STDOUT:   %empty_struct: %.d40 = struct_value () [concrete]
+// CHECK:STDOUT:   %.d40: type = cpp_overload_set_type @C__carbon_thunk [concrete]
+// CHECK:STDOUT:   %empty_struct.e73: %.d40 = struct_value () [concrete]
 // 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]
@@ -2654,7 +2690,7 @@ fn F() {
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
-// CHECK:STDOUT:   %.40b: %.d40 = cpp_overload_set_value @C.C [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %.40b: %.d40 = cpp_overload_set_value @C__carbon_thunk [concrete = constants.%empty_struct.e73]
 // CHECK:STDOUT:   %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   } {
@@ -2669,7 +2705,7 @@ fn F() {
 // 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: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc8_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
 // 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.loc8: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_27.1)
@@ -2686,7 +2722,7 @@ fn F() {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Cpp.ref.loc9_19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %C.ref.loc9_22: type = name_ref C, imports.%C.decl [concrete = constants.%C]
-// CHECK:STDOUT:   %C.ref.loc9_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %C.ref.loc9_24: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
 // CHECK:STDOUT:   %.loc9_27.1: ref %C = temporary_storage
 // CHECK:STDOUT:   %addr.loc9_27.1: %ptr.d9e = addr_of %.loc9_27.1
 // CHECK:STDOUT:   %C__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc9_27.1)
@@ -2703,9 +2739,9 @@ fn F() {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %c1.ref: %C = name_ref c1, %c1
 // CHECK:STDOUT:   %c2.ref: %C = name_ref c2, %c2
-// CHECK:STDOUT:   %.loc14: type = splice_block %C.ref.loc14 [concrete = constants.%C] {
-// CHECK:STDOUT:     %Cpp.ref.loc14: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     %C.ref.loc14: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %.loc17: type = splice_block %C.ref.loc17 [concrete = constants.%C] {
+// CHECK:STDOUT:     %Cpp.ref.loc17: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %C.ref.loc17: type = name_ref C, imports.%C.decl [concrete = constants.%C]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %c3: %C = bind_name c3, <error> [concrete = <error>]
 // CHECK:STDOUT:   %facet_value.loc9: %type_where = facet_value constants.%C, () [concrete = constants.%facet_value]