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

Implement support for mixed-access overload sets. (#6137)

This turns out to be quite important, as several important standard
library types (such as `std::string`) have mixed-access overload sets
for their constructors as an implementation detail. The overall approach
here is:

- Use the most permissive access to determine the access of the overload
set itself. This affects whether name lookup finds the member name at
all.
- After overload resolution, re-check the access of the selected member,
if it's protected or private.

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
Richard Smith 7 месяцев назад
Родитель
Сommit
3b6d202730

+ 35 - 44
toolchain/check/cpp/import.cpp

@@ -508,18 +508,6 @@ static auto GetDeclContext(Context& context, SemIR::NameScopeId scope_id)
       context.clang_decls().Get(scope_clang_decl_context_id).key.decl);
 }
 
-// Looks up for constructors in the class scope and returns the lookup result.
-static auto ClangConstructorLookup(Context& context,
-                                   SemIR::NameScopeId scope_id)
-    -> clang::DeclContextLookupResult {
-  const SemIR::NameScope& scope = context.name_scopes().Get(scope_id);
-
-  clang::Sema& sema = context.clang_sema();
-  clang::Decl* decl =
-      context.clang_decls().Get(scope.clang_decl_context_id()).key.decl;
-  return sema.LookupConstructors(cast<clang::CXXRecordDecl>(decl));
-}
-
 // Returns true if the given Clang declaration is the implicit injected class
 // name within the class.
 static auto IsDeclInjectedClassName(Context& context,
@@ -2074,12 +2062,14 @@ static auto LookupBuiltinTypes(Context& context, SemIR::LocId loc_id,
 
 auto ImportCppOverloadSet(Context& context, SemIR::NameScopeId scope_id,
                           SemIR::NameId name_id,
-                          const clang::UnresolvedSet<4>& overload_set)
+                          clang::CXXRecordDecl* naming_class,
+                          clang::UnresolvedSet<4>&& overload_set)
     -> SemIR::InstId {
   SemIR::CppOverloadSetId overload_set_id = context.cpp_overload_sets().Add(
       SemIR::CppOverloadSet{.name_id = name_id,
                             .parent_scope_id = scope_id,
-                            .candidate_functions = overload_set});
+                            .naming_class = naming_class,
+                            .candidate_functions = std::move(overload_set)});
 
   auto overload_set_inst_id =
       // TODO: Add a location.
@@ -2093,17 +2083,18 @@ auto ImportCppOverloadSet(Context& context, SemIR::NameScopeId scope_id,
   return overload_set_inst_id;
 }
 
-// Gets the access for an overloaded function set. Returns std::nullopt
-// if the access is not the same for all functions in the overload set.
-// TODO: Fix to support functions with different access levels.
-static auto GetOverloadSetAccess(Context& context, SemIR::LocId loc_id,
-                                 const clang::UnresolvedSet<4>& overload_set)
-    -> std::optional<SemIR::AccessKind> {
+// Gets the best access for an overloaded function set. This is the access that
+// we use for the overload set as a whole. More fine-grained checking is done
+// after overload resolution.
+static auto GetOverloadSetAccess(const clang::UnresolvedSet<4>& overload_set)
+    -> SemIR::AccessKind {
   clang::AccessSpecifier access = overload_set.begin().getAccess();
   for (auto it = overload_set.begin() + 1; it != overload_set.end(); ++it) {
-    if (it.getAccess() != access) {
-      context.TODO(loc_id, "Unsupported: Overloaded set with mixed access");
-      return std::nullopt;
+    CARBON_CHECK(
+        (it.getAccess() == clang::AS_none) == (access == clang::AS_none),
+        "Unexpected mixture of members and non-members");
+    if (it.getAccess() < access) {
+      access = it->getAccess();
     }
   }
   return MapAccess(access);
@@ -2111,32 +2102,31 @@ static auto GetOverloadSetAccess(Context& context, SemIR::LocId loc_id,
 
 // Imports an overload set from Clang to Carbon and adds the name into the
 // `NameScope`.
-static auto ImportOverloadSetIntoScope(
-    Context& context, SemIR::LocId loc_id, SemIR::NameScopeId scope_id,
-    SemIR::NameId name_id, const clang::UnresolvedSet<4>& overload_set)
+static auto ImportOverloadSetIntoScope(Context& context,
+                                       SemIR::NameScopeId scope_id,
+                                       SemIR::NameId name_id,
+                                       clang::CXXRecordDecl* naming_class,
+                                       clang::UnresolvedSet<4>&& overload_set)
     -> SemIR::ScopeLookupResult {
-  std::optional<SemIR::AccessKind> access_kind =
-      GetOverloadSetAccess(context, loc_id, overload_set);
-  if (!access_kind.has_value()) {
-    return SemIR::ScopeLookupResult::MakeError();
-  }
-
-  SemIR::InstId inst_id =
-      ImportCppOverloadSet(context, scope_id, name_id, overload_set);
-  AddNameToScope(context, scope_id, name_id, access_kind.value(), inst_id);
+  SemIR::AccessKind access_kind = GetOverloadSetAccess(overload_set);
+  SemIR::InstId inst_id = ImportCppOverloadSet(
+      context, scope_id, name_id, naming_class, std::move(overload_set));
+  AddNameToScope(context, scope_id, name_id, access_kind, inst_id);
   return SemIR::ScopeLookupResult::MakeWrappedLookupResult(inst_id,
-                                                           access_kind.value());
+                                                           access_kind);
 }
 
 // Imports the constructors for a given class name. The found constructors are
 // imported as part of an overload set into the scope. Currently copy/move
 // constructors are not imported.
-static auto ImportConstructorsIntoScope(Context& context, SemIR::LocId loc_id,
+static auto ImportConstructorsIntoScope(Context& context,
                                         SemIR::NameScopeId scope_id,
                                         SemIR::NameId name_id)
     -> SemIR::ScopeLookupResult {
+  auto* naming_class =
+      cast<clang::CXXRecordDecl>(GetDeclContext(context, scope_id));
   clang::DeclContextLookupResult constructors_lookup =
-      ClangConstructorLookup(context, scope_id);
+      context.clang_sema().LookupConstructors(naming_class);
 
   clang::UnresolvedSet<4> overload_set;
   for (auto* decl : constructors_lookup) {
@@ -2144,14 +2134,14 @@ static auto ImportConstructorsIntoScope(Context& context, SemIR::LocId loc_id,
     if (!info.Constructor || info.Constructor->isCopyOrMoveConstructor()) {
       continue;
     }
-    overload_set.addDecl(info.FoundDecl, info.FoundDecl->getAccess());
+    overload_set.addDecl(info.FoundDecl, info.FoundDecl.getAccess());
   }
   if (overload_set.empty()) {
     return SemIR::ScopeLookupResult::MakeNotFound();
   }
 
-  return ImportOverloadSetIntoScope(context, loc_id, scope_id, name_id,
-                                    overload_set);
+  return ImportOverloadSetIntoScope(context, scope_id, name_id, naming_class,
+                                    std::move(overload_set));
 }
 
 // Imports a builtin type from Clang to Carbon and adds the name into the
@@ -2207,8 +2197,9 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
        lookup->getFoundDecl()->isFunctionOrFunctionTemplate())) {
     clang::UnresolvedSet<4> overload_set;
     overload_set.append(lookup->begin(), lookup->end());
-    return ImportOverloadSetIntoScope(context, loc_id, scope_id, name_id,
-                                      overload_set);
+    return ImportOverloadSetIntoScope(context, scope_id, name_id,
+                                      lookup->getNamingClass(),
+                                      std::move(overload_set));
   }
   if (!lookup->isSingleResult()) {
     // Clang will diagnose ambiguous lookup results for us.
@@ -2224,7 +2215,7 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
   }
   if (IsDeclInjectedClassName(context, scope_id, name_id,
                               lookup->getFoundDecl())) {
-    return ImportConstructorsIntoScope(context, loc_id, scope_id, name_id);
+    return ImportConstructorsIntoScope(context, scope_id, name_id);
   }
   auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(lookup->getFoundDecl());
   return ImportNameDeclIntoScope(context, loc_id, scope_id, name_id, key,

+ 2 - 1
toolchain/check/cpp/import.h

@@ -34,7 +34,8 @@ auto ImportCppFunctionDecl(Context& context, SemIR::LocId loc_id,
 // 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)
+                          clang::CXXRecordDecl* naming_class,
+                          clang::UnresolvedSet<4>&& overload_set)
     -> SemIR::InstId;
 
 // Looks up the given name in the Clang AST generated when importing C++ code

+ 2 - 1
toolchain/check/cpp/operators.cpp

@@ -206,7 +206,8 @@ auto LookupCppOperator(Context& context, SemIR::LocId loc_id, Operator op,
   }
 
   return ImportCppOverloadSet(context, SemIR::NameScopeId::None,
-                              SemIR::NameId::CppOperator, functions);
+                              SemIR::NameId::CppOperator,
+                              /*naming_class=*/nullptr, std::move(functions));
 }
 
 }  // namespace Carbon::Check

+ 41 - 2
toolchain/check/cpp/overload_resolution.cpp

@@ -11,9 +11,12 @@
 #include "toolchain/check/cpp/import.h"
 #include "toolchain/check/cpp/location.h"
 #include "toolchain/check/cpp/type_mapping.h"
+#include "toolchain/check/member_access.h"
+#include "toolchain/check/name_lookup.h"
 #include "toolchain/diagnostics/diagnostic_emitter.h"
 #include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/name_scope.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
@@ -79,6 +82,40 @@ static auto AddOverloadCandidataes(clang::Sema& sema,
   }
 }
 
+// Checks whether a selected overload is accessible and diagnoses if not.
+static auto CheckOverloadAccess(Context& context, SemIR::LocId loc_id,
+                                const SemIR::CppOverloadSet& overload_set,
+                                clang::DeclAccessPair overload,
+                                SemIR::InstId overload_inst_id) -> void {
+  SemIR::AccessKind member_access_kind;
+  switch (overload->getAccess()) {
+    case clang::AS_none:
+    case clang::AS_public: {
+      return;
+    }
+
+    case clang::AS_protected: {
+      member_access_kind = SemIR::AccessKind::Protected;
+      break;
+    }
+
+    case clang::AS_private: {
+      member_access_kind = SemIR::AccessKind::Private;
+      break;
+    }
+  }
+
+  auto name_scope_const_id = context.constant_values().Get(
+      context.name_scopes().Get(overload_set.parent_scope_id).inst_id());
+  SemIR::AccessKind allowed_access_kind =
+      GetHighestAllowedAccess(context, loc_id, name_scope_const_id);
+  CheckAccess(context, loc_id, SemIR::LocId(overload_inst_id),
+              overload_set.name_id, member_access_kind,
+              /*is_parent_access=*/false,
+              {.constant_id = name_scope_const_id,
+               .highest_allowed_access = allowed_access_kind});
+}
+
 // Returns whether the decl is an operator member function.
 static auto IsOperatorMethodDecl(clang::Decl* decl) -> bool {
   auto* clang_method_decl = dyn_cast<clang::CXXMethodDecl>(decl);
@@ -136,12 +173,14 @@ static auto ResolveCalleeId(Context& context, SemIR::LocId loc_id,
       // TODO: Handle the cases when Function is null.
       CARBON_CHECK(best_viable_fn->Function);
       sema.MarkFunctionReferenced(loc, best_viable_fn->Function);
-      SemIR::InstId result = ImportCppFunctionDecl(
+      SemIR::InstId result_id = ImportCppFunctionDecl(
           context, loc_id, best_viable_fn->Function,
           // If this is an operator method, the first arg will be used as self.
           arg_exprs.size() -
               (IsOperatorMethodDecl(best_viable_fn->Function) ? 1 : 0));
-      return result;
+      CheckOverloadAccess(context, loc_id, overload_set,
+                          best_viable_fn->FoundDecl, result_id);
+      return result_id;
     }
     case clang::OverloadingResult::OR_No_Viable_Function: {
       candidate_set.NoteCandidates(

+ 2 - 4
toolchain/check/member_access.cpp

@@ -96,10 +96,8 @@ static auto IsInstanceType(Context& context, SemIR::TypeId type_id) -> bool {
   return false;
 }
 
-// Returns the highest allowed access. For example, if this returns `Protected`
-// then only `Public` and `Protected` accesses are allowed--not `Private`.
-static auto GetHighestAllowedAccess(Context& context, SemIR::LocId loc_id,
-                                    SemIR::ConstantId name_scope_const_id)
+auto GetHighestAllowedAccess(Context& context, SemIR::LocId loc_id,
+                             SemIR::ConstantId name_scope_const_id)
     -> SemIR::AccessKind {
   SemIR::ScopeLookupResult lookup_result =
       LookupUnqualifiedName(context, loc_id, SemIR::NameId::SelfType,

+ 7 - 0
toolchain/check/member_access.h

@@ -10,6 +10,13 @@
 
 namespace Carbon::Check {
 
+// Returns the highest allowed access for members of `name_scope_const_id`. For
+// example, if this returns `Protected` then only `Public` and `Protected`
+// accesses are allowed -- not `Private`.
+auto GetHighestAllowedAccess(Context& context, SemIR::LocId loc_id,
+                             SemIR::ConstantId name_scope_const_id)
+    -> SemIR::AccessKind;
+
 // Creates SemIR to perform a member access with base expression `base_id` and
 // member name `name_id`. When `required`, failing to find the name is a
 // diagnosed error; otherwise, `None` is returned. Returns the result of the

+ 16 - 5
toolchain/check/name_lookup.cpp

@@ -195,7 +195,7 @@ auto LookupNameInExactScope(Context& context, SemIR::LocId loc_id,
 
 // Prints diagnostics on invalid qualified name access.
 static auto DiagnoseInvalidQualifiedNameAccess(
-    Context& context, SemIR::LocId loc_id, SemIR::InstId scope_result_id,
+    Context& context, SemIR::LocId loc_id, SemIR::LocId member_loc_id,
     SemIR::NameId name_id, SemIR::AccessKind access_kind, bool is_parent_access,
     AccessInfo access_info) -> void {
   auto class_type = context.insts().TryGetAs<SemIR::ClassType>(
@@ -231,7 +231,7 @@ static auto DiagnoseInvalidQualifiedNameAccess(
   context.emitter()
       .Build(loc_id, ClassInvalidMemberAccess,
              access_kind == SemIR::AccessKind::Private, name_id, parent_type_id)
-      .Note(scope_result_id, ClassMemberDeclaration)
+      .Note(member_loc_id, ClassMemberDeclaration)
       .Emit();
 }
 
@@ -255,6 +255,17 @@ static auto IsAccessProhibited(std::optional<AccessInfo> access_info,
   }
 }
 
+auto CheckAccess(Context& context, SemIR::LocId loc_id,
+                 SemIR::LocId member_loc_id, SemIR::NameId name_id,
+                 SemIR::AccessKind access_kind, bool is_parent_access,
+                 AccessInfo access_info) -> void {
+  if (IsAccessProhibited(access_info, access_kind, is_parent_access)) {
+    DiagnoseInvalidQualifiedNameAccess(context, loc_id, member_loc_id, name_id,
+                                       access_kind, is_parent_access,
+                                       access_info);
+  }
+}
+
 // Information regarding a prohibited access.
 struct ProhibitedAccessInfo {
   // The resulting inst of the lookup.
@@ -475,9 +486,9 @@ auto LookupQualifiedName(Context& context, SemIR::LocId loc_id,
 
         // Note, `access_info` is guaranteed to have a value here, since
         // `prohibited_accesses` is non-empty.
-        DiagnoseInvalidQualifiedNameAccess(context, loc_id, scope_result_id,
-                                           name_id, access_kind,
-                                           is_parent_access, *access_info);
+        DiagnoseInvalidQualifiedNameAccess(
+            context, loc_id, SemIR::LocId(scope_result_id), name_id,
+            access_kind, is_parent_access, *access_info);
       }
     }
 

+ 7 - 0
toolchain/check/name_lookup.h

@@ -99,6 +99,13 @@ auto LookupQualifiedName(Context& context, SemIR::LocId loc_id,
 auto LookupNameInCore(Context& context, SemIR::LocId loc_id,
                       llvm::StringRef name) -> SemIR::InstId;
 
+// Checks whether a name is accessible in the given access context. Produces a
+// diagnostic if not.
+auto CheckAccess(Context& context, SemIR::LocId loc_id,
+                 SemIR::LocId member_loc_id, SemIR::NameId name_id,
+                 SemIR::AccessKind access_kind, bool is_parent_access,
+                 AccessInfo access_info) -> void;
+
 // Prints a diagnostic for a duplicate name.
 auto DiagnoseDuplicateName(Context& context, SemIR::NameId name_id,
                            SemIR::LocId dup_def, SemIR::LocId prev_def) -> void;

+ 123 - 26
toolchain/check/testdata/interop/cpp/class/access.carbon

@@ -10,8 +10,6 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/class/access.carbon
 
-// TODO: Tests marked as `fail_todo_5891_` to fixed as a follow-up of https://github.com/carbon-language/carbon-lang/pull/5891.
-
 // ============================================================================
 // Public non-function member
 // ============================================================================
@@ -233,14 +231,17 @@ fn F(c: Cpp.C*) {
 
 class C {
  public:
+  C();
   static auto foo() -> void;
  protected:
+  C(int);
   static auto foo(int a) -> void;
  private:
+  C(int, int);
   static auto foo(int a, int b) -> void;
 };
 
-// --- fail_todo_5891_import_overload_set_public.carbon
+// --- import_overload_set_public.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -248,51 +249,109 @@ import Cpp library "overload_set.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_5891_import_overload_set_public.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Overloaded set with mixed access` [SemanticsTodo]
-  // CHECK:STDERR:   Cpp.C.foo();
-  // CHECK:STDERR:   ^~~~~~~~~
-  // CHECK:STDERR: fail_todo_5891_import_overload_set_public.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   Cpp.C.foo();
-  // CHECK:STDERR:   ^~~~~~~~~
-  // CHECK:STDERR:
+  Cpp.C.C();
   Cpp.C.foo();
   //@dump-sem-ir-end
 }
 
-// --- fail_todo_5891_import_overload_set_protected.carbon
+// --- fail_import_overload_set_protected.carbon
 
 library "[[@TEST_NAME]]";
 
 import Cpp library "overload_set.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_5891_import_overload_set_protected.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Overloaded set with mixed access` [SemanticsTodo]
-  // CHECK:STDERR:   Cpp.C.foo(1 as i32);
-  // CHECK:STDERR:   ^~~~~~~~~
-  // CHECK:STDERR: fail_todo_5891_import_overload_set_protected.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR: fail_import_overload_set_protected.carbon:[[@LINE+5]]:3: error: cannot access protected member `C` of type `Cpp.C` [ClassInvalidMemberAccess]
+  // CHECK:STDERR:   Cpp.C.C(1 as i32);
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_import_overload_set_protected.carbon: note: declared here [ClassMemberDeclaration]
+  // CHECK:STDERR:
+  Cpp.C.C(1 as i32);
+
+  // CHECK:STDERR: fail_import_overload_set_protected.carbon:[[@LINE+5]]:3: error: cannot access protected member `foo` of type `Cpp.C` [ClassInvalidMemberAccess]
   // CHECK:STDERR:   Cpp.C.foo(1 as i32);
-  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_import_overload_set_protected.carbon: note: declared here [ClassMemberDeclaration]
   // CHECK:STDERR:
   Cpp.C.foo(1 as i32);
 }
 
-// --- fail_todo_5891_import_overload_set_private.carbon
+// --- import_overload_set_protected_base.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "overload_set.h";
+
+class D {
+  extend base: Cpp.C;
+
+  fn MakePublic() -> D {
+    return {.base = C()};
+  }
+
+  fn MakeProtected() -> D {
+    return {.base = C(1 as i32)};
+  }
+
+  fn CallPublic() {
+    foo();
+  }
+
+  fn CallProtected() {
+    foo(1 as i32);
+  }
+}
+
+// --- fail_import_overload_set_private.carbon
 
 library "[[@TEST_NAME]]";
 
 import Cpp library "overload_set.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_5891_import_overload_set_private.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Overloaded set with mixed access` [SemanticsTodo]
-  // CHECK:STDERR:   Cpp.C.foo(1 as i32, 2 as i32);
-  // CHECK:STDERR:   ^~~~~~~~~
-  // CHECK:STDERR: fail_todo_5891_import_overload_set_private.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR: fail_import_overload_set_private.carbon:[[@LINE+5]]:3: error: cannot access private member `C` of type `Cpp.C` [ClassInvalidMemberAccess]
+  // CHECK:STDERR:   Cpp.C.C(1 as i32, 2 as i32);
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_import_overload_set_private.carbon: note: declared here [ClassMemberDeclaration]
+  // CHECK:STDERR:
+  Cpp.C.C(1 as i32, 2 as i32);
+
+  // CHECK:STDERR: fail_import_overload_set_private.carbon:[[@LINE+5]]:3: error: cannot access private member `foo` of type `Cpp.C` [ClassInvalidMemberAccess]
   // CHECK:STDERR:   Cpp.C.foo(1 as i32, 2 as i32);
-  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_import_overload_set_private.carbon: note: declared here [ClassMemberDeclaration]
   // CHECK:STDERR:
   Cpp.C.foo(1 as i32, 2 as i32);
 }
 
+// --- fail_import_overload_set_private_base.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "overload_set.h";
+
+class D {
+  extend base: Cpp.C;
+
+  fn MakePrivate() -> D {
+    // CHECK:STDERR: fail_import_overload_set_private_base.carbon:[[@LINE+5]]:21: error: cannot access private member `C` of type `Cpp.C` [ClassInvalidMemberAccess]
+    // CHECK:STDERR:     return {.base = C(1 as i32, 2 as i32)};
+    // CHECK:STDERR:                     ^~~~~~~~~~~~~~~~~~~~~
+    // CHECK:STDERR: fail_import_overload_set_private_base.carbon: note: declared here [ClassMemberDeclaration]
+    // CHECK:STDERR:
+    return {.base = C(1 as i32, 2 as i32)};
+  }
+
+  fn CallPrivate() {
+    // CHECK:STDERR: fail_import_overload_set_private_base.carbon:[[@LINE+5]]:5: error: cannot access private member `foo` of type `Cpp.C` [ClassInvalidMemberAccess]
+    // CHECK:STDERR:     foo(1 as i32, 2 as i32);
+    // CHECK:STDERR:     ^~~~~~~~~~~~~~~~~~~~~~~
+    // CHECK:STDERR: fail_import_overload_set_private_base.carbon: note: declared here [ClassMemberDeclaration]
+    // CHECK:STDERR:
+    foo(1 as i32, 2 as i32);
+  }
+}
+
 // ============================================================================
 // Base class
 // ============================================================================
@@ -459,10 +518,24 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_5891_import_overload_set_public.carbon
+// CHECK:STDOUT: --- import_overload_set_public.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:   %.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]
+// CHECK:STDOUT:   %.cd1: type = cpp_overload_set_type @C.foo [concrete]
+// CHECK:STDOUT:   %empty_struct.b85: %.cd1 = struct_value () [concrete]
+// CHECK:STDOUT:   %C.foo.type: type = fn_type @C.foo [concrete]
+// CHECK:STDOUT:   %C.foo: %C.foo.type = struct_value () [concrete]
+// CHECK:STDOUT:   %type_where: type = facet_type <type where .Self impls <CanAggregateDestroy>> [concrete]
+// CHECK:STDOUT:   %facet_value: %type_where = facet_value %C, () [concrete]
+// CHECK:STDOUT:   %AggregateT.as_type.as.Destroy.impl.Op.type.fc1: type = fn_type @AggregateT.as_type.as.Destroy.impl.Op, @AggregateT.as_type.as.Destroy.impl(%facet_value) [concrete]
+// CHECK:STDOUT:   %AggregateT.as_type.as.Destroy.impl.Op.6b9: %AggregateT.as_type.as.Destroy.impl.Op.type.fc1 = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -471,13 +544,37 @@ 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__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:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.e4a: %.cd1 = cpp_overload_set_value @C.foo [concrete = constants.%empty_struct.b85]
+// CHECK:STDOUT:   %C.foo.decl: %C.foo.type = fn_decl @C.foo [concrete = constants.%C.foo] {} {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %C.ref: type = name_ref C, imports.%C.decl [concrete = constants.%C]
-// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %C.ref.loc8_6: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %C.ref.loc8_8: %.d40 = name_ref C, imports.%.40b [concrete = constants.%empty_struct.e73]
+// CHECK:STDOUT:   %.loc8_11.1: ref %C = temporary_storage
+// CHECK:STDOUT:   %addr.loc8_11.1: %ptr.d9e = addr_of %.loc8_11.1
+// CHECK:STDOUT:   %C__carbon_thunk.call: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc8_11.1)
+// CHECK:STDOUT:   %.loc8_11.2: init %C = in_place_init %C__carbon_thunk.call, %.loc8_11.1
+// CHECK:STDOUT:   %.loc8_11.3: ref %C = temporary %.loc8_11.1, %.loc8_11.2
+// CHECK:STDOUT:   %Cpp.ref.loc9: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %C.ref.loc9: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %foo.ref: %.cd1 = name_ref foo, imports.%.e4a [concrete = constants.%empty_struct.b85]
+// CHECK:STDOUT:   %C.foo.call: init %empty_tuple.type = call imports.%C.foo.decl()
+// CHECK:STDOUT:   %facet_value: %type_where = facet_value constants.%C, () [concrete = constants.%facet_value]
+// CHECK:STDOUT:   %.loc8_11.4: %type_where = converted constants.%C, %facet_value [concrete = constants.%facet_value]
+// CHECK:STDOUT:   %AggregateT.as_type.as.Destroy.impl.Op.bound: <bound method> = bound_method %.loc8_11.3, constants.%AggregateT.as_type.as.Destroy.impl.Op.6b9
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %.loc8_11.3, %AggregateT.as_type.as.Destroy.impl.Op.specific_fn
+// CHECK:STDOUT:   %addr.loc8_11.2: %ptr.d9e = addr_of %.loc8_11.3
+// CHECK:STDOUT:   %AggregateT.as_type.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr.loc8_11.2)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 0 - 249
toolchain/check/testdata/interop/cpp/function/overloads.carbon

@@ -238,54 +238,6 @@ fn F() {
   Cpp.foo(1 as i32);
 }
 
-// ============================================================================
-// Overloads access
-// ============================================================================
-
-// --- same_access_level.h
-
-struct S {
-  public:
-    static auto foo(short a) -> void;
-    static auto foo(int a) -> void;
-};
-
-// --- import_same_access_level.carbon
-
-library "[[@TEST_NAME]]";
-
-import Cpp library "same_access_level.h";
-
-fn F() {
-  Cpp.S.foo(1 as i32);
-}
-
-// --- mixed_access_level.h
-
-struct S {
-  public:
-    static auto foo(int a) -> void;
-  protected:
-    static auto foo(short a) -> void;
-};
-
-// --- fail_todo_import_mixed_access_level.carbon
-
-library "[[@TEST_NAME]]";
-
-import Cpp library "mixed_access_level.h";
-
-fn F() {
-  // CHECK:STDERR: fail_todo_import_mixed_access_level.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Overloaded set with mixed access` [SemanticsTodo]
-  // CHECK:STDERR:   Cpp.S.foo(1 as i32);
-  // CHECK:STDERR:   ^~~~~~~~~
-  // CHECK:STDERR: fail_todo_import_mixed_access_level.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   Cpp.S.foo(1 as i32);
-  // CHECK:STDERR:   ^~~~~~~~~
-  // CHECK:STDERR:
-  Cpp.S.foo(1 as i32);
-}
-
 // ============================================================================
 // No viable function found
 // ============================================================================
@@ -1671,207 +1623,6 @@ fn F() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @foo__carbon_thunk(%a.param: %ptr);
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- import_same_access_level.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
-// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
-// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
-// CHECK:STDOUT:   %S: type = class_type @S [concrete]
-// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
-// CHECK:STDOUT:   %complete_type.357: <witness> = complete_type_witness %empty_struct_type [concrete]
-// CHECK:STDOUT:   %.177: type = cpp_overload_set_type @As.Convert [concrete]
-// CHECK:STDOUT:   %empty_struct: %.177 = struct_value () [concrete]
-// CHECK:STDOUT:   %int_1.5b8: Core.IntLiteral = int_value 1 [concrete]
-// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
-// CHECK:STDOUT:   %Int.type: type = generic_class_type @Int [concrete]
-// CHECK:STDOUT:   %Int.generic: %Int.type = struct_value () [concrete]
-// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
-// CHECK:STDOUT:   %As.type.90f: type = generic_interface_type @As [concrete]
-// CHECK:STDOUT:   %As.generic: %As.type.90f = struct_value () [concrete]
-// CHECK:STDOUT:   %As.type.dbd: type = facet_type <@As, @As(%i32)> [concrete]
-// CHECK:STDOUT:   %As.Convert.type.99b: type = fn_type @As.Convert, @As(%i32) [concrete]
-// CHECK:STDOUT:   %To: Core.IntLiteral = bind_symbolic_name To, 0 [symbolic]
-// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.type.565: type = fn_type @Core.IntLiteral.as.As.impl.Convert, @Core.IntLiteral.as.As.impl(%To) [symbolic]
-// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.d2c: %Core.IntLiteral.as.As.impl.Convert.type.565 = struct_value () [symbolic]
-// CHECK:STDOUT:   %As.impl_witness.080: <witness> = impl_witness imports.%As.impl_witness_table.5ad, @Core.IntLiteral.as.As.impl(%int_32) [concrete]
-// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.type.aaf: type = fn_type @Core.IntLiteral.as.As.impl.Convert, @Core.IntLiteral.as.As.impl(%int_32) [concrete]
-// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.414: %Core.IntLiteral.as.As.impl.Convert.type.aaf = struct_value () [concrete]
-// CHECK:STDOUT:   %As.facet: %As.type.dbd = facet_value Core.IntLiteral, (%As.impl_witness.080) [concrete]
-// CHECK:STDOUT:   %.351: type = fn_type_with_self_type %As.Convert.type.99b, %As.facet [concrete]
-// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.bound: <bound method> = bound_method %int_1.5b8, %Core.IntLiteral.as.As.impl.Convert.414 [concrete]
-// CHECK:STDOUT:   %pattern_type.7ce: type = pattern_type %i32 [concrete]
-// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.specific_fn: <specific function> = specific_function %Core.IntLiteral.as.As.impl.Convert.414, @Core.IntLiteral.as.As.impl.Convert(%int_32) [concrete]
-// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %int_1.5b8, %Core.IntLiteral.as.As.impl.Convert.specific_fn [concrete]
-// CHECK:STDOUT:   %int_1.5d2: %i32 = int_value 1 [concrete]
-// CHECK:STDOUT:   %S.foo.type: type = fn_type @S.foo [concrete]
-// CHECK:STDOUT:   %S.foo: %S.foo.type = struct_value () [concrete]
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
-// CHECK:STDOUT:     .Int = %Core.Int
-// CHECK:STDOUT:     .As = %Core.As
-// CHECK:STDOUT:     import Core//prelude
-// CHECK:STDOUT:     import Core//prelude/...
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .S = %S.decl
-// CHECK:STDOUT:     import Cpp//...
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %S.decl: type = class_decl @S [concrete = constants.%S] {} {}
-// CHECK:STDOUT:   %.dcb: %.177 = cpp_overload_set_value @As.Convert [concrete = constants.%empty_struct]
-// CHECK:STDOUT:   %Core.Int: %Int.type = import_ref Core//prelude/parts/int, Int, loaded [concrete = constants.%Int.generic]
-// CHECK:STDOUT:   %Core.As: %As.type.90f = import_ref Core//prelude/parts/as, As, loaded [concrete = constants.%As.generic]
-// CHECK:STDOUT:   %Core.import_ref.99c: @Core.IntLiteral.as.As.impl.%Core.IntLiteral.as.As.impl.Convert.type (%Core.IntLiteral.as.As.impl.Convert.type.565) = import_ref Core//prelude/parts/int, loc32_39, loaded [symbolic = @Core.IntLiteral.as.As.impl.%Core.IntLiteral.as.As.impl.Convert (constants.%Core.IntLiteral.as.As.impl.Convert.d2c)]
-// CHECK:STDOUT:   %As.impl_witness_table.5ad = impl_witness_table (%Core.import_ref.99c), @Core.IntLiteral.as.As.impl [concrete]
-// CHECK:STDOUT:   %S.foo.decl: %S.foo.type = fn_decl @S.foo [concrete = constants.%S.foo] {
-// CHECK:STDOUT:     %a.patt: %pattern_type.7ce = binding_pattern a [concrete]
-// CHECK:STDOUT:     %a.param_patt: %pattern_type.7ce = value_param_pattern %a.patt, call_param0 [concrete]
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %a.param: %i32 = value_param call_param0
-// CHECK:STDOUT:     %.1: type = splice_block %i32 [concrete = constants.%i32] {
-// CHECK:STDOUT:       %int_32: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
-// CHECK:STDOUT:       %i32: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
-// CHECK:STDOUT:     }
-// CHECK:STDOUT:     %a: %i32 = bind_name a, %a.param
-// CHECK:STDOUT:   }
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
-// CHECK:STDOUT:     .Core = imports.%Core
-// CHECK:STDOUT:     .Cpp = imports.%Cpp
-// CHECK:STDOUT:     .F = %F.decl
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Core.import = import Core
-// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
-// CHECK:STDOUT:     import Cpp "same_access_level.h"
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @S {
-// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete = constants.%empty_struct_type]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type.357]
-// CHECK:STDOUT:   complete_type_witness = %complete_type
-// CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .foo = imports.%.dcb
-// CHECK:STDOUT:   import Cpp//...
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @F() {
-// CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %S.ref: type = name_ref S, imports.%S.decl [concrete = constants.%S]
-// CHECK:STDOUT:   %foo.ref: %.177 = name_ref foo, imports.%.dcb [concrete = constants.%empty_struct]
-// CHECK:STDOUT:   %int_1: Core.IntLiteral = int_value 1 [concrete = constants.%int_1.5b8]
-// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
-// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
-// CHECK:STDOUT:   %impl.elem0: %.351 = impl_witness_access constants.%As.impl_witness.080, element0 [concrete = constants.%Core.IntLiteral.as.As.impl.Convert.414]
-// CHECK:STDOUT:   %bound_method.loc7_15.1: <bound method> = bound_method %int_1, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.As.impl.Convert.bound]
-// CHECK:STDOUT:   %specific_fn: <specific function> = specific_function %impl.elem0, @Core.IntLiteral.as.As.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.As.impl.Convert.specific_fn]
-// CHECK:STDOUT:   %bound_method.loc7_15.2: <bound method> = bound_method %int_1, %specific_fn [concrete = constants.%bound_method]
-// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.call: init %i32 = call %bound_method.loc7_15.2(%int_1) [concrete = constants.%int_1.5d2]
-// CHECK:STDOUT:   %.loc7_15.1: %i32 = value_of_initializer %Core.IntLiteral.as.As.impl.Convert.call [concrete = constants.%int_1.5d2]
-// CHECK:STDOUT:   %.loc7_15.2: %i32 = converted %int_1, %.loc7_15.1 [concrete = constants.%int_1.5d2]
-// CHECK:STDOUT:   %S.foo.call: init %empty_tuple.type = call imports.%S.foo.decl(%.loc7_15.2)
-// CHECK:STDOUT:   return
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @S.foo(%a.param: %i32);
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_mixed_access_level.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
-// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
-// CHECK:STDOUT:   %S: type = class_type @S [concrete]
-// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
-// CHECK:STDOUT:   %complete_type.357: <witness> = complete_type_witness %empty_struct_type [concrete]
-// CHECK:STDOUT:   %int_1.5b8: Core.IntLiteral = int_value 1 [concrete]
-// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
-// CHECK:STDOUT:   %Int.type: type = generic_class_type @Int [concrete]
-// CHECK:STDOUT:   %Int.generic: %Int.type = struct_value () [concrete]
-// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
-// CHECK:STDOUT:   %As.type.90f: type = generic_interface_type @As [concrete]
-// CHECK:STDOUT:   %As.generic: %As.type.90f = struct_value () [concrete]
-// CHECK:STDOUT:   %As.type.dbd: type = facet_type <@As, @As(%i32)> [concrete]
-// CHECK:STDOUT:   %As.Convert.type.99b: type = fn_type @As.Convert, @As(%i32) [concrete]
-// CHECK:STDOUT:   %To: Core.IntLiteral = bind_symbolic_name To, 0 [symbolic]
-// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.type.565: type = fn_type @Core.IntLiteral.as.As.impl.Convert, @Core.IntLiteral.as.As.impl(%To) [symbolic]
-// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.d2c: %Core.IntLiteral.as.As.impl.Convert.type.565 = struct_value () [symbolic]
-// CHECK:STDOUT:   %As.impl_witness.080: <witness> = impl_witness imports.%As.impl_witness_table.5ad, @Core.IntLiteral.as.As.impl(%int_32) [concrete]
-// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.type.aaf: type = fn_type @Core.IntLiteral.as.As.impl.Convert, @Core.IntLiteral.as.As.impl(%int_32) [concrete]
-// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.414: %Core.IntLiteral.as.As.impl.Convert.type.aaf = struct_value () [concrete]
-// CHECK:STDOUT:   %As.facet: %As.type.dbd = facet_value Core.IntLiteral, (%As.impl_witness.080) [concrete]
-// CHECK:STDOUT:   %.351: type = fn_type_with_self_type %As.Convert.type.99b, %As.facet [concrete]
-// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.bound: <bound method> = bound_method %int_1.5b8, %Core.IntLiteral.as.As.impl.Convert.414 [concrete]
-// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.specific_fn: <specific function> = specific_function %Core.IntLiteral.as.As.impl.Convert.414, @Core.IntLiteral.as.As.impl.Convert(%int_32) [concrete]
-// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %int_1.5b8, %Core.IntLiteral.as.As.impl.Convert.specific_fn [concrete]
-// CHECK:STDOUT:   %int_1.5d2: %i32 = int_value 1 [concrete]
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
-// CHECK:STDOUT:     .Int = %Core.Int
-// CHECK:STDOUT:     .As = %Core.As
-// CHECK:STDOUT:     import Core//prelude
-// CHECK:STDOUT:     import Core//prelude/...
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .S = %S.decl
-// CHECK:STDOUT:     import Cpp//...
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %S.decl: type = class_decl @S [concrete = constants.%S] {} {}
-// CHECK:STDOUT:   %Core.Int: %Int.type = import_ref Core//prelude/parts/int, Int, loaded [concrete = constants.%Int.generic]
-// CHECK:STDOUT:   %Core.As: %As.type.90f = import_ref Core//prelude/parts/as, As, loaded [concrete = constants.%As.generic]
-// CHECK:STDOUT:   %Core.import_ref.99c: @Core.IntLiteral.as.As.impl.%Core.IntLiteral.as.As.impl.Convert.type (%Core.IntLiteral.as.As.impl.Convert.type.565) = import_ref Core//prelude/parts/int, loc32_39, loaded [symbolic = @Core.IntLiteral.as.As.impl.%Core.IntLiteral.as.As.impl.Convert (constants.%Core.IntLiteral.as.As.impl.Convert.d2c)]
-// CHECK:STDOUT:   %As.impl_witness_table.5ad = impl_witness_table (%Core.import_ref.99c), @Core.IntLiteral.as.As.impl [concrete]
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
-// CHECK:STDOUT:     .Core = imports.%Core
-// CHECK:STDOUT:     .Cpp = imports.%Cpp
-// CHECK:STDOUT:     .F = %F.decl
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %Core.import = import Core
-// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
-// CHECK:STDOUT:     import Cpp "mixed_access_level.h"
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @S {
-// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete = constants.%empty_struct_type]
-// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type.357]
-// CHECK:STDOUT:   complete_type_witness = %complete_type
-// CHECK:STDOUT:
-// CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .foo = <poisoned>
-// CHECK:STDOUT:   import Cpp//...
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @F() {
-// CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   %S.ref: type = name_ref S, imports.%S.decl [concrete = constants.%S]
-// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
-// CHECK:STDOUT:   %int_1: Core.IntLiteral = int_value 1 [concrete = constants.%int_1.5b8]
-// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
-// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
-// CHECK:STDOUT:   %impl.elem0: %.351 = impl_witness_access constants.%As.impl_witness.080, element0 [concrete = constants.%Core.IntLiteral.as.As.impl.Convert.414]
-// CHECK:STDOUT:   %bound_method.loc14_15.1: <bound method> = bound_method %int_1, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.As.impl.Convert.bound]
-// CHECK:STDOUT:   %specific_fn: <specific function> = specific_function %impl.elem0, @Core.IntLiteral.as.As.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.As.impl.Convert.specific_fn]
-// CHECK:STDOUT:   %bound_method.loc14_15.2: <bound method> = bound_method %int_1, %specific_fn [concrete = constants.%bound_method]
-// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.call: init %i32 = call %bound_method.loc14_15.2(%int_1) [concrete = constants.%int_1.5d2]
-// CHECK:STDOUT:   %.loc14_15.1: %i32 = value_of_initializer %Core.IntLiteral.as.As.impl.Convert.call [concrete = constants.%int_1.5d2]
-// CHECK:STDOUT:   %.loc14_15.2: %i32 = converted %int_1, %.loc14_15.1 [concrete = constants.%int_1.5d2]
-// CHECK:STDOUT:   return
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_import_no_viable_function.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {

+ 3 - 0
toolchain/sem_ir/cpp_overload_set.h

@@ -21,6 +21,9 @@ struct CppOverloadSet : public Printable<CppOverloadSet> {
   // The parent scope.
   NameScopeId parent_scope_id;
 
+  // The naming class in the name lookup that found this overload set.
+  clang::CXXRecordDecl* naming_class;
+
   // List of all named decls found at name lookup.
   // TODO: Find a good small size for the UnresolvedSet<size> or rework how we
   // store the candidates.