Ver Fonte

When using a C++ struct as a parameter, map its type to a Carbon class type (#5538)

This doesn't support actually passing the value of the struct, which is
planned to be implemented using thunks.

`ClangDeclId` value is now `ClangDecl` which includes the mapped Carbon
instruction in addition to the Clang declaration. This allows finding
the Carbon instruction for a given Clang declaration, which is necessary
for mapping a Clang struct parameter type to the Carbon class without
doing name lookup. We don't take the instruction as part of the hash
key, as discussed in
[Discord](https://discord.com/channels/655572317891461132/768530752592805919/1380575881050718469).

To map the type, we also need to map namespaces. To avoid recursion for
inner namespaces, we use a vector.

Note that the first commit just changes the order of functions in the
file to make review easier.

C++ Interop Demo (that shows missing behavior):

```c++
// hello_world.h

struct S {
  S(const S&) { x = 1; }
  int x;
};

void hello_world(S s);
```

```c++
// hello_world.cpp

#include "hello_world.h"

#include <cstdio>

void hello_world2(S s) { printf("hello_world2: %d\n", s.x); }

void hello_world(S s) {
  printf("hello_world: %d\n", s.x);
  hello_world2(s);
}
```

```carbon
// main.carbon

library "Main";

import Cpp library "hello_world.h";

fn Run() -> i32 {
  var s : Cpp.S;
  Cpp.hello_world(s);
  return 0;
}
```

```shell
$ clang -c hello_world.cpp
$ bazel-bin/toolchain/carbon compile main.carbon
$ bazel-bin/toolchain/carbon link hello_world.o main.o --output=demo
$ ./demo
hello_world: -1108224096
hello_world2: 1
```

Part of #5533.
Boaz Brickner há 10 meses atrás
pai
commit
cc698d78f5

+ 273 - 143
toolchain/check/import_cpp.cpp

@@ -52,7 +52,21 @@ static auto GenerateCppIncludesHeaderCode(
   return code;
 }
 
-namespace {
+// Adds the name to the scope with the given `inst_id`, if the `inst_id` is not
+// `None`.
+static auto AddNameToScope(Context& context, SemIR::NameScopeId scope_id,
+                           SemIR::NameId name_id, SemIR::InstId inst_id)
+    -> void {
+  if (inst_id.has_value()) {
+    context.name_scopes().AddRequiredName(scope_id, name_id, inst_id);
+  }
+}
+
+// Maps a Clang name to a Carbon `NameId`.
+static auto AddIdentifierName(Context& context, llvm::StringRef name)
+    -> SemIR::NameId {
+  return SemIR::NameId::ForIdentifier(context.identifiers().Add(name));
+}
 
 // Adds the given source location and an `ImportIRInst` referring to it in
 // `ImportIRId::Cpp`.
@@ -65,6 +79,8 @@ static auto AddImportIRInst(Context& context,
       SemIR::ImportIRInst(clang_source_loc_id));
 }
 
+namespace {
+
 // Used to convert Clang diagnostics to Carbon diagnostics.
 class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
  public:
@@ -255,7 +271,8 @@ auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
   SemIR::NameScope& name_scope = context.name_scopes().Get(name_scope_id);
   name_scope.set_is_closed_import(true);
   name_scope.set_clang_decl_context_id(context.sem_ir().clang_decls().Add(
-      generated_ast->getASTContext().getTranslationUnitDecl()));
+      {.decl = generated_ast->getASTContext().getTranslationUnitDecl(),
+       .inst_id = name_scope.inst_id()}));
 
   if (ast_has_error) {
     name_scope.set_has_error();
@@ -292,10 +309,14 @@ static auto ClangLookup(Context& context, SemIR::NameScopeId scope_id,
   // `TextDiagnosticPrinter` assumes we're processing a C++ source file.
   lookup.suppressDiagnostics();
 
+  auto scope_clang_decl_context_id =
+      context.name_scopes().Get(scope_id).clang_decl_context_id();
   bool found = sema.LookupQualifiedName(
       lookup,
-      clang::dyn_cast<clang::DeclContext>(context.sem_ir().clang_decls().Get(
-          context.name_scopes().Get(scope_id).clang_decl_context_id())));
+      clang::dyn_cast<clang::DeclContext>(context.sem_ir()
+                                              .clang_decls()
+                                              .Get(scope_clang_decl_context_id)
+                                              .decl));
 
   if (!found) {
     return std::nullopt;
@@ -304,6 +325,174 @@ static auto ClangLookup(Context& context, SemIR::NameScopeId scope_id,
   return lookup;
 }
 
+// Imports a namespace declaration from Clang to Carbon. If successful, returns
+// the new Carbon namespace declaration `InstId`.
+static auto ImportNamespaceDecl(Context& context,
+                                SemIR::NameScopeId parent_scope_id,
+                                SemIR::NameId name_id,
+                                clang::NamespaceDecl* clang_decl)
+    -> SemIR::InstId {
+  auto result = AddImportNamespace(
+      context, GetSingletonType(context, SemIR::NamespaceType::TypeInstId),
+      name_id, parent_scope_id, /*import_id=*/SemIR::InstId::None);
+  context.name_scopes()
+      .Get(result.name_scope_id)
+      .set_clang_decl_context_id(context.sem_ir().clang_decls().Add(
+          {.decl = clang_decl, .inst_id = result.inst_id}));
+  return result.inst_id;
+}
+
+// Maps a C++ declaration context to a Carbon namespace.
+static auto AsCarbonNamespace(Context& context,
+                              clang::DeclContext* decl_context)
+    -> SemIR::InstId {
+  CARBON_CHECK(decl_context);
+  auto& clang_decls = context.sem_ir().clang_decls();
+
+  // Check if the decl context is already mapped to a Carbon namespace.
+  if (auto context_clang_decl_id = clang_decls.Lookup(
+          {.decl = clang::dyn_cast<clang::Decl>(decl_context),
+           .inst_id = SemIR::InstId::None});
+      context_clang_decl_id.has_value()) {
+    return clang_decls.Get(context_clang_decl_id).inst_id;
+  }
+
+  // We know we have at least one context to map, add all decl contexts we need
+  // to map.
+  llvm::SmallVector<clang::DeclContext*> decl_contexts;
+  auto parent_decl_id = SemIR::ClangDeclId::None;
+  do {
+    decl_contexts.push_back(decl_context);
+    decl_context = decl_context->getParent();
+    parent_decl_id =
+        clang_decls.Lookup({.decl = clang::dyn_cast<clang::Decl>(decl_context),
+                            .inst_id = SemIR::InstId::None});
+  } while (!parent_decl_id.has_value());
+
+  // We know the parent of the last decl context is mapped, map the rest.
+  auto namespace_inst_id = SemIR::InstId::None;
+  do {
+    decl_context = decl_contexts.pop_back_val();
+    auto parent_inst_id = clang_decls.Get(parent_decl_id).inst_id;
+    auto parent_namespace =
+        context.insts().GetAs<SemIR::Namespace>(parent_inst_id);
+    namespace_inst_id = ImportNamespaceDecl(
+        context, parent_namespace.name_scope_id,
+        AddIdentifierName(
+            context, llvm::dyn_cast<clang::NamedDecl>(decl_context)->getName()),
+        clang::dyn_cast<clang::NamespaceDecl>(decl_context));
+    parent_decl_id = clang_decls.Add({
+        .decl = clang::dyn_cast<clang::Decl>(decl_context),
+        .inst_id = namespace_inst_id,
+    });
+  } while (!decl_contexts.empty());
+
+  return namespace_inst_id;
+}
+
+// Creates a class declaration for the given class name in the given scope.
+// Returns the `InstId` for the declaration.
+static auto BuildClassDecl(Context& context, SemIR::NameScopeId parent_scope_id,
+                           SemIR::NameId name_id)
+    -> std::tuple<SemIR::ClassId, SemIR::InstId> {
+  // Add the class declaration.
+  auto class_decl = SemIR::ClassDecl{.type_id = SemIR::TypeType::TypeId,
+                                     .class_id = SemIR::ClassId::None,
+                                     .decl_block_id = SemIR::InstBlockId::None};
+  // TODO: Consider setting a proper location.
+  auto class_decl_id = AddPlaceholderInstInNoBlock(
+      context, SemIR::LocIdAndInst::NoLoc(class_decl));
+  context.imports().push_back(class_decl_id);
+
+  SemIR::Class class_info = {
+      {.name_id = name_id,
+       .parent_scope_id = parent_scope_id,
+       .generic_id = SemIR::GenericId::None,
+       .first_param_node_id = Parse::NodeId::None,
+       .last_param_node_id = Parse::NodeId::None,
+       .pattern_block_id = SemIR::InstBlockId::None,
+       .implicit_param_patterns_id = SemIR::InstBlockId::None,
+       .param_patterns_id = SemIR::InstBlockId::None,
+       .is_extern = false,
+       .extern_library_id = SemIR::LibraryNameId::None,
+       .non_owning_decl_id = SemIR::InstId::None,
+       .first_owning_decl_id = class_decl_id},
+      {// `.self_type_id` depends on the ClassType, so is set below.
+       .self_type_id = SemIR::TypeId::None,
+       // TODO: Support Dynamic classes.
+       // TODO: Support Final classes.
+       .inheritance_kind = SemIR::Class::Base}};
+
+  class_decl.class_id = context.classes().Add(class_info);
+
+  // Write the class ID into the ClassDecl.
+  ReplaceInstBeforeConstantUse(context, class_decl_id, class_decl);
+
+  SetClassSelfType(context, class_decl.class_id);
+
+  return {class_decl.class_id, class_decl_id};
+}
+
+// Creates a class definition for the given class name in the given scope based
+// on the information in the given Clang declaration. Returns the `InstId` for
+// the declaration, which is assumed to be for a class definition. Returns the
+// new class id and instruction id.
+static auto BuildClassDefinition(Context& context,
+                                 SemIR::NameScopeId parent_scope_id,
+                                 SemIR::NameId name_id,
+                                 clang::CXXRecordDecl* clang_decl)
+    -> std::tuple<SemIR::ClassId, SemIR::InstId> {
+  auto [class_id, class_inst_id] =
+      BuildClassDecl(context, parent_scope_id, name_id);
+  auto& class_info = context.classes().Get(class_id);
+  StartClassDefinition(context, class_info, class_inst_id);
+
+  context.name_scopes()
+      .Get(class_info.scope_id)
+      .set_clang_decl_context_id(context.sem_ir().clang_decls().Add(
+          {.decl = clang_decl, .inst_id = class_inst_id}));
+
+  return {class_id, class_inst_id};
+}
+
+// Imports a record declaration from Clang to Carbon. If successful, returns
+// the new Carbon class declaration `InstId`.
+// TODO: Change `clang_decl` to `const &` when lookup is using `clang::DeclID`
+// and we don't need to store the decl for lookup context.
+static auto ImportCXXRecordDecl(Context& context, SemIR::LocId loc_id,
+                                SemIR::NameScopeId parent_scope_id,
+                                SemIR::NameId name_id,
+                                clang::CXXRecordDecl* clang_decl)
+    -> SemIR::InstId {
+  clang::CXXRecordDecl* clang_def = clang_decl->getDefinition();
+  if (!clang_def) {
+    context.TODO(loc_id,
+                 "Unsupported: Record declarations without a definition");
+    return SemIR::ErrorInst::InstId;
+  }
+
+  if (clang_def->isDynamicClass()) {
+    context.TODO(loc_id, "Unsupported: Dynamic Class");
+    return SemIR::ErrorInst::InstId;
+  }
+
+  auto [class_id, class_def_id] =
+      BuildClassDefinition(context, parent_scope_id, name_id, clang_def);
+
+  // The class type is now fully defined. Compute its object representation.
+  ComputeClassObjectRepr(context,
+                         // TODO: Consider having a proper location here.
+                         Parse::ClassDefinitionId::None, class_id,
+                         // TODO: Set fields.
+                         /*field_decls=*/{},
+                         // TODO: Set vtable.
+                         /*vtable_contents=*/{},
+                         // TODO: Set block.
+                         /*body=*/{});
+
+  return class_def_id;
+}
+
 // Creates an integer type of the given size.
 static auto MakeIntType(Context& context, IntId size_id) -> TypeExpr {
   // TODO: Fill in a location for the type once available.
@@ -312,23 +501,19 @@ static auto MakeIntType(Context& context, IntId size_id) -> TypeExpr {
   return ExprAsType(context, Parse::NodeId::None, type_inst_id);
 }
 
-// Maps a C++ type to a Carbon type.
-// TODO: Support more types.
-static auto MapType(Context& context, clang::QualType type) -> TypeExpr {
-  const auto* builtin_type = dyn_cast<clang::BuiltinType>(type);
-  if (!builtin_type) {
-    return {.inst_id = SemIR::ErrorInst::TypeInstId,
-            .type_id = SemIR::ErrorInst::TypeId};
-  }
+// Maps a C++ builtin type to a Carbon type.
+// TODO: Support more builtin types.
+static auto MapBuiltinType(Context& context, const clang::BuiltinType& type)
+    -> TypeExpr {
   // TODO: Refactor to avoid duplication.
-  switch (builtin_type->getKind()) {
+  switch (type.getKind()) {
     case clang::BuiltinType::Short:
-      if (context.ast_context().getTypeSize(type) == 16) {
+      if (context.ast_context().getTypeSize(&type) == 16) {
         return MakeIntType(context, context.ints().Add(16));
       }
       break;
     case clang::BuiltinType::Int:
-      if (context.ast_context().getTypeSize(type) == 32) {
+      if (context.ast_context().getTypeSize(&type) == 32) {
         return MakeIntType(context, context.ints().Add(32));
       }
       break;
@@ -339,6 +524,57 @@ static auto MapType(Context& context, clang::QualType type) -> TypeExpr {
           .type_id = SemIR::ErrorInst::TypeId};
 }
 
+// Maps a C++ record type to a Carbon type.
+// TODO: Support more record types.
+static auto MapRecordType(Context& context, SemIR::LocId loc_id,
+                          const clang::RecordType& type) -> TypeExpr {
+  auto* record_decl = clang::dyn_cast<clang::CXXRecordDecl>(type.getDecl());
+  if (record_decl && record_decl->isStruct()) {
+    auto& clang_decls = context.sem_ir().clang_decls();
+    SemIR::InstId struct_inst_id = SemIR::InstId::None;
+    if (auto struct_clang_decl_id = clang_decls.Lookup(
+            {.decl = record_decl, .inst_id = SemIR::InstId::None});
+        struct_clang_decl_id.has_value()) {
+      struct_inst_id = clang_decls.Get(struct_clang_decl_id).inst_id;
+    } else {
+      auto parent_inst_id =
+          AsCarbonNamespace(context, record_decl->getDeclContext());
+      auto parent_name_scope_id =
+          context.insts().GetAs<SemIR::Namespace>(parent_inst_id).name_scope_id;
+      SemIR::NameId struct_name_id =
+          AddIdentifierName(context, record_decl->getName());
+      struct_inst_id = ImportCXXRecordDecl(
+          context, loc_id, parent_name_scope_id, struct_name_id, record_decl);
+      AddNameToScope(context, parent_name_scope_id, struct_name_id,
+                     struct_inst_id);
+    }
+    SemIR::TypeInstId struct_type_inst_id =
+        context.types().GetAsTypeInstId(struct_inst_id);
+    return {
+        .inst_id = struct_type_inst_id,
+        .type_id = context.types().GetTypeIdForTypeInstId(struct_type_inst_id)};
+  }
+
+  return {.inst_id = SemIR::ErrorInst::TypeInstId,
+          .type_id = SemIR::ErrorInst::TypeId};
+}
+
+// Maps a C++ type to a Carbon type.
+// TODO: Support more types.
+static auto MapType(Context& context, SemIR::LocId loc_id, clang::QualType type)
+    -> TypeExpr {
+  if (const auto* builtin_type = dyn_cast<clang::BuiltinType>(type)) {
+    return MapBuiltinType(context, *builtin_type);
+  }
+
+  if (const auto* record_type = clang::dyn_cast<clang::RecordType>(type)) {
+    return MapRecordType(context, loc_id, *record_type);
+  }
+
+  return {.inst_id = SemIR::ErrorInst::TypeInstId,
+          .type_id = SemIR::ErrorInst::TypeId};
+}
+
 // Returns a block id for the explicit parameters of the given function
 // declaration. If the function declaration has no parameters, it returns
 // `SemIR::InstBlockId::Empty`. In the case of an unsupported parameter type, it
@@ -359,7 +595,7 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
     // Mark the start of a region of insts, needed for the type expression
     // created later with the call of `EndSubpatternAsExpr()`.
     BeginSubpattern(context);
-    auto [type_inst_id, type_id] = MapType(context, param_type);
+    auto [type_inst_id, type_id] = MapType(context, loc_id, param_type);
     // Type expression of the binding pattern - a single-entry/single-exit
     // region that allows control flow in the type expression e.g. fn F(x: if C
     // then i32 else i64).
@@ -378,8 +614,7 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
             // Translate an unnamed parameter to an underscore to
             // match Carbon's naming of unnamed/unused function params.
             ? SemIR::NameId::Underscore
-            : SemIR::NameId::ForIdentifier(
-                  context.sem_ir().identifiers().Add(param_name));
+            : AddIdentifierName(context, param_name);
 
     // TODO: Fix this once templates are supported.
     bool is_template = false;
@@ -413,7 +648,7 @@ static auto GetReturnType(Context& context, SemIR::LocId loc_id,
     return SemIR::InstId::None;
   }
 
-  auto [type_inst_id, type_id] = MapType(context, ret_type);
+  auto [type_inst_id, type_id] = MapType(context, loc_id, ret_type);
   if (type_id == SemIR::ErrorInst::TypeId) {
     context.TODO(loc_id, llvm::formatv("Unsupported: return type: {0}",
                                        ret_type.getAsString()));
@@ -531,7 +766,8 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
        .return_slot_pattern_id = function_params_insts->return_slot_pattern_id,
        .virtual_modifier = SemIR::FunctionFields::VirtualModifier::None,
        .self_param_id = SemIR::InstId::None,
-       .clang_decl_id = context.sem_ir().clang_decls().Add(clang_decl)}};
+       .clang_decl_id = context.sem_ir().clang_decls().Add(
+           {.decl = clang_decl, .inst_id = decl_id})}};
 
   function_decl.function_id = context.functions().Add(function_info);
 
@@ -542,127 +778,6 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
 
   return decl_id;
 }
-
-// Imports a namespace declaration from Clang to Carbon. If successful, returns
-// the new Carbon namespace declaration `InstId`.
-static auto ImportNamespaceDecl(Context& context,
-                                SemIR::NameScopeId parent_scope_id,
-                                SemIR::NameId name_id,
-                                clang::NamespaceDecl* clang_decl)
-    -> SemIR::InstId {
-  auto result = AddImportNamespace(
-      context, GetSingletonType(context, SemIR::NamespaceType::TypeInstId),
-      name_id, parent_scope_id, /*import_id=*/SemIR::InstId::None);
-  context.name_scopes()
-      .Get(result.name_scope_id)
-      .set_clang_decl_context_id(
-          context.sem_ir().clang_decls().Add(clang_decl));
-  return result.inst_id;
-}
-
-// Creates a class declaration for the given class name in the given scope.
-// Returns the `InstId` for the declaration.
-static auto BuildClassDecl(Context& context, SemIR::NameScopeId parent_scope_id,
-                           SemIR::NameId name_id)
-    -> std::tuple<SemIR::ClassId, SemIR::InstId> {
-  // Add the class declaration.
-  auto class_decl = SemIR::ClassDecl{.type_id = SemIR::TypeType::TypeId,
-                                     .class_id = SemIR::ClassId::None,
-                                     .decl_block_id = SemIR::InstBlockId::None};
-  // TODO: Consider setting a proper location.
-  auto class_decl_id = AddPlaceholderInstInNoBlock(
-      context, SemIR::LocIdAndInst::NoLoc(class_decl));
-  context.imports().push_back(class_decl_id);
-
-  SemIR::Class class_info = {
-      {.name_id = name_id,
-       .parent_scope_id = parent_scope_id,
-       .generic_id = SemIR::GenericId::None,
-       .first_param_node_id = Parse::NodeId::None,
-       .last_param_node_id = Parse::NodeId::None,
-       .pattern_block_id = SemIR::InstBlockId::None,
-       .implicit_param_patterns_id = SemIR::InstBlockId::None,
-       .param_patterns_id = SemIR::InstBlockId::None,
-       .is_extern = false,
-       .extern_library_id = SemIR::LibraryNameId::None,
-       .non_owning_decl_id = SemIR::InstId::None,
-       .first_owning_decl_id = class_decl_id},
-      {// `.self_type_id` depends on the ClassType, so is set below.
-       .self_type_id = SemIR::TypeId::None,
-       // TODO: Support Dynamic classes.
-       // TODO: Support Final classes.
-       .inheritance_kind = SemIR::Class::Base}};
-
-  class_decl.class_id = context.classes().Add(class_info);
-
-  // Write the class ID into the ClassDecl.
-  ReplaceInstBeforeConstantUse(context, class_decl_id, class_decl);
-
-  SetClassSelfType(context, class_decl.class_id);
-
-  return {class_decl.class_id, class_decl_id};
-}
-
-// Creates a class definition for the given class name in the given scope based
-// on the information in the given Clang declaration. Returns the `InstId` for
-// the declaration, which is assumed to be for a class definition. Returns the
-// new class id and instruction id.
-static auto BuildClassDefinition(Context& context,
-                                 SemIR::NameScopeId parent_scope_id,
-                                 SemIR::NameId name_id,
-                                 clang::CXXRecordDecl* clang_decl)
-    -> std::tuple<SemIR::ClassId, SemIR::InstId> {
-  auto [class_id, class_decl_id] =
-      BuildClassDecl(context, parent_scope_id, name_id);
-  auto& class_info = context.classes().Get(class_id);
-  StartClassDefinition(context, class_info, class_decl_id);
-
-  context.name_scopes()
-      .Get(class_info.scope_id)
-      .set_clang_decl_context_id(
-          context.sem_ir().clang_decls().Add(clang_decl));
-
-  return {class_id, class_decl_id};
-}
-
-// Imports a record declaration from Clang to Carbon. If successful, returns
-// the new Carbon class declaration `InstId`.
-// TODO: Change `clang_decl` to `const &` when lookup is using `clang::DeclID`
-// and we don't need to store the decl for lookup context.
-static auto ImportCXXRecordDecl(Context& context, SemIR::LocId loc_id,
-                                SemIR::NameScopeId parent_scope_id,
-                                SemIR::NameId name_id,
-                                clang::CXXRecordDecl* clang_decl)
-    -> SemIR::InstId {
-  clang::CXXRecordDecl* clang_def = clang_decl->getDefinition();
-  if (!clang_def) {
-    context.TODO(loc_id,
-                 "Unsupported: Record declarations without a definition");
-    return SemIR::ErrorInst::InstId;
-  }
-
-  if (clang_def->isDynamicClass()) {
-    context.TODO(loc_id, "Unsupported: Dynamic Class");
-    return SemIR::ErrorInst::InstId;
-  }
-
-  auto [class_id, class_def_id] =
-      BuildClassDefinition(context, parent_scope_id, name_id, clang_def);
-
-  // The class type is now fully defined. Compute its object representation.
-  ComputeClassObjectRepr(context,
-                         // TODO: Consider having a proper location here.
-                         Parse::ClassDefinitionId::None, class_id,
-                         // TODO: Set fields.
-                         /*field_decls=*/{},
-                         // TODO: Set vtable.
-                         /*vtable_contents=*/{},
-                         // TODO: Set block.
-                         /*body=*/{});
-
-  return class_def_id;
-}
-
 // Imports a declaration from Clang to Carbon. If successful, returns the
 // instruction for the new Carbon declaration.
 static auto ImportNameDecl(Context& context, SemIR::LocId loc_id,
@@ -690,6 +805,19 @@ static auto ImportNameDecl(Context& context, SemIR::LocId loc_id,
   return SemIR::InstId::None;
 }
 
+// Imports a `clang::NamedDecl` into Carbon and adds that name into the
+// `NameScope`.
+static auto ImportNameDeclIntoScope(Context& context, SemIR::LocId loc_id,
+                                    SemIR::NameScopeId scope_id,
+                                    SemIR::NameId name_id,
+                                    clang::NamedDecl* clang_decl)
+    -> SemIR::InstId {
+  SemIR::InstId inst_id =
+      ImportNameDecl(context, loc_id, scope_id, name_id, clang_decl);
+  AddNameToScope(context, scope_id, name_id, inst_id);
+  return inst_id;
+}
+
 auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
                        SemIR::NameScopeId scope_id, SemIR::NameId name_id)
     -> SemIR::InstId {
@@ -711,11 +839,13 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
                                "find a single result; LookupResultKind: {0}",
                                static_cast<int>(lookup->getResultKind()))
                      .str());
+    context.name_scopes().AddRequiredName(scope_id, name_id,
+                                          SemIR::ErrorInst::InstId);
     return SemIR::ErrorInst::InstId;
   }
 
-  return ImportNameDecl(context, loc_id, scope_id, name_id,
-                        lookup->getFoundDecl());
+  return ImportNameDeclIntoScope(context, loc_id, scope_id, name_id,
+                                 lookup->getFoundDecl());
 }
 
 }  // namespace Carbon::Check

+ 2 - 4
toolchain/check/name_lookup.cpp

@@ -190,10 +190,8 @@ auto LookupNameInExactScope(Context& context, SemIR::LocId loc_id,
     SemIR::InstId imported_inst_id =
         ImportNameFromCpp(context, loc_id, scope_id, name_id);
     if (imported_inst_id.has_value()) {
-      SemIR::ScopeLookupResult result = SemIR::ScopeLookupResult::MakeFound(
-          imported_inst_id, SemIR::AccessKind::Public);
-      scope.AddRequired({.name_id = name_id, .result = result});
-      return result;
+      return SemIR::ScopeLookupResult::MakeFound(imported_inst_id,
+                                                 SemIR::AccessKind::Public);
     }
   }
 

+ 729 - 0
toolchain/check/testdata/interop/cpp/function/class.carbon

@@ -0,0 +1,729 @@
+// 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
+//
+// EXTRA-ARGS: --no-prelude-import
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/function/class.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/function/class.carbon
+
+// ============================================================================
+// Forward-declared class as parameter type
+// ============================================================================
+
+// --- decl_value_param_type.h
+
+class C;
+
+auto foo(C) -> void;
+
+// --- fail_todo_import_decl_value_param_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "decl_value_param_type.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  // TODO: This should fail on the fact `C` is declared and not defined.
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: class C` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo({});
+  //@dump-sem-ir-end
+}
+
+// ============================================================================
+// Defined class without data members as parameter type
+// ============================================================================
+
+// --- definition_no_data_members_value_param_type.h
+
+class C {};
+
+auto foo(C) -> void;
+
+// --- fail_todo_import_definition_no_data_members_value_param_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "definition_no_data_members_value_param_type.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  // CHECK:STDERR: fail_todo_import_definition_no_data_members_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: class C` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_definition_no_data_members_value_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo({});
+  //@dump-sem-ir-end
+}
+
+// ============================================================================
+// Defined class with a single data member as parameter type
+// ============================================================================
+
+// --- definition_single_data_member_value_param_type.h
+
+class D {};
+
+class C {
+  D d;
+};
+
+auto foo(C) -> void;
+
+// --- fail_todo_import_definition_single_data_member_value_param_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "definition_single_data_member_value_param_type.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  // CHECK:STDERR: fail_todo_import_definition_single_data_member_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: class C` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_definition_single_data_member_value_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo({});
+  //@dump-sem-ir-end
+}
+
+// ============================================================================
+// Defined class with multiple data members as parameter type
+// ============================================================================
+
+// --- definition_multiple_data_members_value_param_type.h
+
+class D {};
+
+class C {
+  D d1;
+  D d2;
+  D d3;
+};
+
+auto foo(C) -> void;
+
+// --- fail_todo_import_definition_multiple_data_members_value_param_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "definition_multiple_data_members_value_param_type.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  // CHECK:STDERR: fail_todo_import_definition_multiple_data_members_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: class C` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_definition_multiple_data_members_value_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo({});
+  //@dump-sem-ir-end
+}
+
+// ============================================================================
+// Defined class in namespace
+// ============================================================================
+
+// --- definition_in_namespace_value_param_type.h
+
+namespace N { class C {}; }
+
+auto foo(N::C) -> void;
+
+// --- fail_todo_import_definition_in_namespace_value_param_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "definition_in_namespace_value_param_type.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  // CHECK:STDERR: fail_todo_import_definition_in_namespace_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: class N::C` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_definition_in_namespace_value_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo({});
+  //@dump-sem-ir-end
+}
+
+// ============================================================================
+// Defined class in relative namespace
+// ============================================================================
+
+// --- definition_in_relative_namespace_value_param_type.h
+
+namespace N1 {
+  namespace N2 { class C {}; }
+  auto foo(N2::C) -> void;
+}
+
+// --- fail_todo_import_definition_in_relative_namespace_value_param_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "definition_in_relative_namespace_value_param_type.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  // CHECK:STDERR: fail_todo_import_definition_in_relative_namespace_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: class N1::N2::C` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.N1.foo({});
+  // CHECK:STDERR:   ^~~~~~~~~~
+  // CHECK:STDERR: fail_todo_import_definition_in_relative_namespace_value_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.N1.foo({});
+  // CHECK:STDERR:   ^~~~~~~~~~
+  // CHECK:STDERR:
+  Cpp.N1.foo({});
+  //@dump-sem-ir-end
+}
+
+// ============================================================================
+// Defined class and explicitly used
+// ============================================================================
+
+// --- definition_with_static_method.h
+
+class C {
+  static void bar();
+};
+
+auto foo(C) -> void;
+
+// --- fail_todo_import_definition_and_static_method_call_before.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "definition_with_static_method.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  Cpp.C.bar();
+  // CHECK:STDERR: fail_todo_import_definition_and_static_method_call_before.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: class C` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_definition_and_static_method_call_before.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo({});
+  //@dump-sem-ir-end
+}
+
+// --- fail_todo_import_definition_and_static_method_call_after.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "definition_with_static_method.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  // CHECK:STDERR: fail_todo_import_definition_and_static_method_call_after.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: class C` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_definition_and_static_method_call_after.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo({});
+  Cpp.C.bar();
+  //@dump-sem-ir-end
+}
+
+// ============================================================================
+// Pointer to forward-declared class as parameter type
+// ============================================================================
+
+// --- decl_pointer_param_type.h
+
+class C;
+
+auto foo(C*) -> void;
+
+// --- fail_todo_import_decl_pointer_param_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "decl_pointer_param_type.h";
+
+// CHECK:STDERR: fail_todo_import_decl_pointer_param_type.carbon:[[@LINE+7]]:9: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
+// CHECK:STDERR: fn F(s: Cpp.C*) {
+// CHECK:STDERR:         ^~~~~
+// CHECK:STDERR: fail_todo_import_decl_pointer_param_type.carbon:[[@LINE+4]]:9: note: in `Cpp` name lookup for `C` [InCppNameLookup]
+// CHECK:STDERR: fn F(s: Cpp.C*) {
+// CHECK:STDERR:         ^~~~~
+// CHECK:STDERR:
+fn F(s: Cpp.C*) {
+  //@dump-sem-ir-begin
+  // CHECK:STDERR: fail_todo_import_decl_pointer_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: class C *` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo(s);
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_decl_pointer_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo(s);
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo(s);
+  //@dump-sem-ir-end
+}
+
+// ============================================================================
+// Pointer to defined class as parameter type
+// ============================================================================
+
+// --- definition_pointer_param_type.h
+
+class C {};
+
+auto foo(C*) -> void;
+
+// --- fail_todo_import_definition_pointer_param_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "definition_pointer_param_type.h";
+
+fn F(s: Cpp.C*) {
+  //@dump-sem-ir-begin
+  // CHECK:STDERR: fail_todo_import_definition_pointer_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: class C *` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo(s);
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_definition_pointer_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo(s);
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo(s);
+  //@dump-sem-ir-end
+}
+
+// ============================================================================
+// Forward-declared class as return type
+// ============================================================================
+
+// --- decl_value_return_type.h
+
+class C;
+
+auto foo() -> C;
+
+// --- fail_todo_import_decl_value_return_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "decl_value_return_type.h";
+
+fn F() {
+  // TODO: This should fail on the fact `C` is declared and not defined.
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: return type: class C` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo();
+}
+
+// ============================================================================
+// Defined class as return type
+// ============================================================================
+
+// --- definition_value_return_type.h
+
+class C {};
+
+auto foo() -> C;
+
+// --- fail_todo_import_definition_value_return_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "definition_value_return_type.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  // CHECK:STDERR: fail_todo_import_definition_value_return_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: return type: class C` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_definition_value_return_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo();
+  //@dump-sem-ir-end
+}
+
+// ============================================================================
+// Pointer to forward-declared class as return type
+// ============================================================================
+
+// --- decl_pointer_return_type.h
+
+class C;
+
+auto foo() -> C*;
+
+// --- fail_todo_import_decl_pointer_return_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "decl_pointer_return_type.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  // CHECK:STDERR: fail_todo_import_decl_pointer_return_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: return type: class C *` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_decl_pointer_return_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo();
+  //@dump-sem-ir-end
+}
+
+// ============================================================================
+// Pointer to defined class as return type
+// ============================================================================
+
+// --- definition_pointer_return_type.h
+
+class C {};
+
+auto foo() -> C*;
+
+// --- fail_todo_import_definition_pointer_return_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "definition_pointer_return_type.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  // CHECK:STDERR: fail_todo_import_definition_pointer_return_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: return type: class C *` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_definition_pointer_return_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo();
+  //@dump-sem-ir-end
+}
+
+// CHECK:STDOUT: --- fail_todo_import_decl_value_param_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// 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:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc16: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_import_definition_no_data_members_value_param_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// 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:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc15: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_import_definition_single_data_member_value_param_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// 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:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc15: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_import_definition_multiple_data_members_value_param_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// 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:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc15: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_import_definition_in_namespace_value_param_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// 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:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc15: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_import_definition_in_relative_namespace_value_param_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .N1 = %N1
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %N1: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// 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:   %N1.ref: <namespace> = name_ref N1, imports.%N1 [concrete = imports.%N1]
+// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc15: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_import_definition_and_static_method_call_before.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:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %bar.type: type = fn_type @bar [concrete]
+// CHECK:STDOUT:   %bar: %bar.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
+// CHECK:STDOUT:   %bar.decl: %bar.type = fn_decl @bar [concrete = constants.%bar] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %C.ref: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %bar.ref: %bar.type = name_ref bar, imports.%bar.decl [concrete = constants.%bar]
+// CHECK:STDOUT:   %bar.call: init %empty_tuple.type = call %bar.ref()
+// CHECK:STDOUT:   %Cpp.ref.loc16: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc16: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_import_definition_and_static_method_call_after.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT:   %bar.type: type = fn_type @bar [concrete]
+// CHECK:STDOUT:   %bar: %bar.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
+// CHECK:STDOUT:   %bar.decl: %bar.type = fn_decl @bar [concrete = constants.%bar] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref.loc15: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc15: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %Cpp.ref.loc16: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %C.ref: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %bar.ref: %bar.type = name_ref bar, imports.%bar.decl [concrete = constants.%bar]
+// CHECK:STDOUT:   %bar.call: init %empty_tuple.type = call %bar.ref()
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_import_decl_pointer_param_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .C = <error>
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%s.param: <error>) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref.loc22: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %s.ref: <error> = name_ref s, %s [concrete = <error>]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_import_definition_pointer_param_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT:   %ptr: type = ptr_type %C [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%s.param: %ptr) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref.loc15: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %s.ref: %ptr = name_ref s, %s
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_import_definition_value_return_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// 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:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_import_decl_pointer_return_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// 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:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_import_definition_pointer_return_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// 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:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 358 - 48
toolchain/check/testdata/interop/cpp/function/struct.carbon

@@ -29,6 +29,13 @@ import Cpp library "decl_value_param_type.h";
 fn F() {
   //@dump-sem-ir-begin
   // TODO: This should fail on the fact `S` is declared and not defined.
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+14]]:3: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+11]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
   // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: struct S` [SemanticsTodo]
   // CHECK:STDERR:   Cpp.foo({});
   // CHECK:STDERR:   ^~~~~~~
@@ -50,7 +57,7 @@ struct S {};
 
 auto foo(S) -> void;
 
-// --- fail_todo_import_definition_no_data_members_value_param_type.carbon
+// --- import_definition_no_data_members_value_param_type.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -58,13 +65,6 @@ import Cpp library "definition_no_data_members_value_param_type.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_definition_no_data_members_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: struct S` [SemanticsTodo]
-  // CHECK:STDERR:   Cpp.foo({});
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR: fail_todo_import_definition_no_data_members_value_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   Cpp.foo({});
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR:
   Cpp.foo({});
   //@dump-sem-ir-end
 }
@@ -83,7 +83,7 @@ struct S {
 
 auto foo(S) -> void;
 
-// --- fail_todo_import_definition_single_data_member_value_param_type.carbon
+// --- import_definition_single_data_member_value_param_type.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -91,13 +91,6 @@ import Cpp library "definition_single_data_member_value_param_type.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_definition_single_data_member_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: struct S` [SemanticsTodo]
-  // CHECK:STDERR:   Cpp.foo({});
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR: fail_todo_import_definition_single_data_member_value_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   Cpp.foo({});
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR:
   Cpp.foo({});
   //@dump-sem-ir-end
 }
@@ -118,7 +111,7 @@ struct S {
 
 auto foo(S) -> void;
 
-// --- fail_todo_import_definition_multiple_data_members_value_param_type.carbon
+// --- import_definition_multiple_data_members_value_param_type.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -126,17 +119,93 @@ import Cpp library "definition_multiple_data_members_value_param_type.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_definition_multiple_data_members_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: parameter type: struct S` [SemanticsTodo]
-  // CHECK:STDERR:   Cpp.foo({});
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR: fail_todo_import_definition_multiple_data_members_value_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   Cpp.foo({});
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR:
   Cpp.foo({});
   //@dump-sem-ir-end
 }
 
+// ============================================================================
+// Defined struct in namespace
+// ============================================================================
+
+// --- definition_in_namespace_value_param_type.h
+
+namespace N { struct S {}; }
+
+auto foo(N::S) -> void;
+
+// --- import_definition_in_namespace_value_param_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "definition_in_namespace_value_param_type.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  Cpp.foo({});
+  //@dump-sem-ir-end
+}
+
+// ============================================================================
+// Defined struct in relative namespace
+// ============================================================================
+
+// --- definition_in_relative_namespace_value_param_type.h
+
+namespace N1 {
+  namespace N2 { struct S {}; }
+  auto foo(N2::S) -> void;
+}
+
+// --- import_definition_in_relative_namespace_value_param_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "definition_in_relative_namespace_value_param_type.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  Cpp.N1.foo({});
+  //@dump-sem-ir-end
+}
+
+// ============================================================================
+// Defined struct and explicitly used
+// ============================================================================
+
+// --- definition_with_static_method.h
+
+struct S {
+  static void bar();
+};
+
+auto foo(S) -> void;
+
+// --- import_definition_and_static_method_call_before.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "definition_with_static_method.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  Cpp.S.bar();
+  Cpp.foo({});
+  //@dump-sem-ir-end
+}
+
+// --- import_definition_and_static_method_call_after.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "definition_with_static_method.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  Cpp.foo({});
+  Cpp.S.bar();
+  //@dump-sem-ir-end
+}
+
 // ============================================================================
 // Pointer to forward-declared struct as parameter type
 // ============================================================================
@@ -221,6 +290,13 @@ import Cpp library "decl_value_return_type.h";
 fn F() {
   //@dump-sem-ir-begin
   // TODO: This should fail on the fact `S` is declared and not defined.
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+14]]:3: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+11]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
   // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: return type: struct S` [SemanticsTodo]
   // CHECK:STDERR:   Cpp.foo();
   // CHECK:STDERR:   ^~~~~~~
@@ -242,7 +318,7 @@ struct S {};
 
 auto foo() -> S;
 
-// --- fail_todo_import_definition_value_return_type.carbon
+// --- import_definition_value_return_type.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -250,13 +326,6 @@ import Cpp library "definition_value_return_type.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_definition_value_return_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: return type: struct S` [SemanticsTodo]
-  // CHECK:STDERR:   Cpp.foo();
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR: fail_todo_import_definition_value_return_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   Cpp.foo();
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR:
   Cpp.foo();
   //@dump-sem-ir-end
 }
@@ -328,6 +397,7 @@ fn F() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     .S = <error>
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
@@ -336,70 +406,296 @@ fn F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
 // CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
-// CHECK:STDOUT:   %.loc16: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc23: %empty_struct_type = struct_literal ()
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_definition_no_data_members_value_param_type.carbon
+// CHECK:STDOUT: --- import_definition_no_data_members_value_param_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %S: type = class_type @S [concrete]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT:   %S.val: %S = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     .foo = %foo.decl
+// CHECK:STDOUT:     .S = %S.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %S.decl: type = class_decl @S [concrete = constants.%S] {} {}
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
 // 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:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
-// CHECK:STDOUT:   %.loc15: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %.loc8_12.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc8_12.2: ref %S = temporary_storage
+// CHECK:STDOUT:   %.loc8_12.3: init %S = class_init (), %.loc8_12.2 [concrete = constants.%S.val]
+// CHECK:STDOUT:   %.loc8_12.4: ref %S = temporary %.loc8_12.2, %.loc8_12.3
+// CHECK:STDOUT:   %.loc8_12.5: ref %S = converted %.loc8_12.1, %.loc8_12.4
+// CHECK:STDOUT:   %.loc8_12.6: %S = bind_value %.loc8_12.5
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(%.loc8_12.6)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_definition_single_data_member_value_param_type.carbon
+// CHECK:STDOUT: --- import_definition_single_data_member_value_param_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %S: type = class_type @S [concrete]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT:   %S.val: %S = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     .foo = %foo.decl
+// CHECK:STDOUT:     .S = %S.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %S.decl: type = class_decl @S [concrete = constants.%S] {} {}
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
 // 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:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
-// CHECK:STDOUT:   %.loc15: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %.loc8_12.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc8_12.2: ref %S = temporary_storage
+// CHECK:STDOUT:   %.loc8_12.3: init %S = class_init (), %.loc8_12.2 [concrete = constants.%S.val]
+// CHECK:STDOUT:   %.loc8_12.4: ref %S = temporary %.loc8_12.2, %.loc8_12.3
+// CHECK:STDOUT:   %.loc8_12.5: ref %S = converted %.loc8_12.1, %.loc8_12.4
+// CHECK:STDOUT:   %.loc8_12.6: %S = bind_value %.loc8_12.5
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(%.loc8_12.6)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_definition_multiple_data_members_value_param_type.carbon
+// CHECK:STDOUT: --- import_definition_multiple_data_members_value_param_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %S: type = class_type @S [concrete]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT:   %S.val: %S = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     .foo = %foo.decl
+// CHECK:STDOUT:     .S = %S.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %S.decl: type = class_decl @S [concrete = constants.%S] {} {}
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
 // 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:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
-// CHECK:STDOUT:   %.loc15: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %.loc8_12.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc8_12.2: ref %S = temporary_storage
+// CHECK:STDOUT:   %.loc8_12.3: init %S = class_init (), %.loc8_12.2 [concrete = constants.%S.val]
+// CHECK:STDOUT:   %.loc8_12.4: ref %S = temporary %.loc8_12.2, %.loc8_12.3
+// CHECK:STDOUT:   %.loc8_12.5: ref %S = converted %.loc8_12.1, %.loc8_12.4
+// CHECK:STDOUT:   %.loc8_12.6: %S = bind_value %.loc8_12.5
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(%.loc8_12.6)
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_definition_in_namespace_value_param_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %S: type = class_type @S [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT:   %S.val: %S = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = %foo.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// 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:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %.loc8_12.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc8_12.2: ref %S = temporary_storage
+// CHECK:STDOUT:   %.loc8_12.3: init %S = class_init (), %.loc8_12.2 [concrete = constants.%S.val]
+// CHECK:STDOUT:   %.loc8_12.4: ref %S = temporary %.loc8_12.2, %.loc8_12.3
+// CHECK:STDOUT:   %.loc8_12.5: ref %S = converted %.loc8_12.1, %.loc8_12.4
+// CHECK:STDOUT:   %.loc8_12.6: %S = bind_value %.loc8_12.5
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(%.loc8_12.6)
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_definition_in_relative_namespace_value_param_type.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %S: type = class_type @S [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT:   %S.val: %S = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .N1 = %N1
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %N1: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .foo = %foo.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// 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:   %N1.ref: <namespace> = name_ref N1, imports.%N1 [concrete = imports.%N1]
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %.loc8_15.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc8_15.2: ref %S = temporary_storage
+// CHECK:STDOUT:   %.loc8_15.3: init %S = class_init (), %.loc8_15.2 [concrete = constants.%S.val]
+// CHECK:STDOUT:   %.loc8_15.4: ref %S = temporary %.loc8_15.2, %.loc8_15.3
+// CHECK:STDOUT:   %.loc8_15.5: ref %S = converted %.loc8_15.1, %.loc8_15.4
+// CHECK:STDOUT:   %.loc8_15.6: %S = bind_value %.loc8_15.5
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(%.loc8_15.6)
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_definition_and_static_method_call_before.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %S: type = class_type @S [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %bar.type: type = fn_type @bar [concrete]
+// CHECK:STDOUT:   %bar: %bar.type = struct_value () [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT:   %S.val: %S = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .S = %S.decl
+// CHECK:STDOUT:     .foo = %foo.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %S.decl: type = class_decl @S [concrete = constants.%S] {} {}
+// CHECK:STDOUT:   %bar.decl: %bar.type = fn_decl @bar [concrete = constants.%bar] {} {}
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %S.ref: type = name_ref S, imports.%S.decl [concrete = constants.%S]
+// CHECK:STDOUT:   %bar.ref: %bar.type = name_ref bar, imports.%bar.decl [concrete = constants.%bar]
+// CHECK:STDOUT:   %bar.call: init %empty_tuple.type = call %bar.ref()
+// CHECK:STDOUT:   %Cpp.ref.loc9: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %.loc9_12.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc9_12.2: ref %S = temporary_storage
+// CHECK:STDOUT:   %.loc9_12.3: init %S = class_init (), %.loc9_12.2 [concrete = constants.%S.val]
+// CHECK:STDOUT:   %.loc9_12.4: ref %S = temporary %.loc9_12.2, %.loc9_12.3
+// CHECK:STDOUT:   %.loc9_12.5: ref %S = converted %.loc9_12.1, %.loc9_12.4
+// CHECK:STDOUT:   %.loc9_12.6: %S = bind_value %.loc9_12.5
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(%.loc9_12.6)
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_definition_and_static_method_call_after.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %S: type = class_type @S [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT:   %S.val: %S = struct_value () [concrete]
+// CHECK:STDOUT:   %bar.type: type = fn_type @bar [concrete]
+// CHECK:STDOUT:   %bar: %bar.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = %foo.decl
+// CHECK:STDOUT:     .S = %S.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %S.decl: type = class_decl @S [concrete = constants.%S] {} {}
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %bar.decl: %bar.type = fn_decl @bar [concrete = constants.%bar] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %.loc8_12.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc8_12.2: ref %S = temporary_storage
+// CHECK:STDOUT:   %.loc8_12.3: init %S = class_init (), %.loc8_12.2 [concrete = constants.%S.val]
+// CHECK:STDOUT:   %.loc8_12.4: ref %S = temporary %.loc8_12.2, %.loc8_12.3
+// CHECK:STDOUT:   %.loc8_12.5: ref %S = converted %.loc8_12.1, %.loc8_12.4
+// CHECK:STDOUT:   %.loc8_12.6: %S = bind_value %.loc8_12.5
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(%.loc8_12.6)
+// CHECK:STDOUT:   %Cpp.ref.loc9: <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:   %bar.ref: %bar.type = name_ref bar, imports.%bar.decl [concrete = constants.%bar]
+// CHECK:STDOUT:   %bar.call: init %empty_tuple.type = call %bar.ref()
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -456,6 +752,7 @@ fn F() {
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
 // CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     .S = <error>
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
@@ -467,22 +764,35 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_definition_value_return_type.carbon
+// CHECK:STDOUT: --- import_definition_value_return_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %S: type = class_type @S [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:     .foo = %foo.decl
+// CHECK:STDOUT:     .S = %S.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %S.decl: type = class_decl @S [concrete = constants.%S] {} {}
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
 // 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:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %.loc8_11.1: ref %S = temporary_storage
+// CHECK:STDOUT:   %foo.call: init %S = call %foo.ref() to %.loc8_11.1
+// CHECK:STDOUT:   %.loc8_11.2: ref %S = temporary %.loc8_11.1, %foo.call
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/lower/file_context.cpp

@@ -675,7 +675,7 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id,
     CARBON_CHECK(!specific_id.has_value(),
                  "Specific functions cannot have C++ definitions");
     HandleReferencedCppFunction(clang::dyn_cast<clang::FunctionDecl>(
-        sem_ir().clang_decls().Get(clang_decl_id)));
+        sem_ir().clang_decls().Get(clang_decl_id).decl));
     // TODO: Check that the signature and mangling generated by Clang and the
     // one we generated are the same.
   }

+ 1 - 1
toolchain/lower/mangler.cpp

@@ -152,7 +152,7 @@ auto Mangler::Mangle(SemIR::FunctionId function_id,
   }
   if (function.clang_decl_id.has_value()) {
     return MangleCppClang(llvm::dyn_cast<clang::NamedDecl>(
-        sem_ir().clang_decls().Get(function.clang_decl_id)));
+        sem_ir().clang_decls().Get(function.clang_decl_id).decl));
   }
   RawStringOstream os;
   os << "_C";

+ 13 - 0
toolchain/sem_ir/BUILD

@@ -57,6 +57,18 @@ cc_test(
     ],
 )
 
+cc_library(
+    name = "clang_decl",
+    srcs = ["clang_decl.cpp"],
+    hdrs = ["clang_decl.h"],
+    deps = [
+        ":typed_insts",
+        "//common:hashtable_key_context",
+        "//common:ostream",
+        "@llvm-project//clang:ast",
+    ],
+)
+
 cc_library(
     name = "file",
     srcs = [
@@ -107,6 +119,7 @@ cc_library(
     ],
     deps = [
         ":block_value_store",
+        ":clang_decl",
         ":typed_insts",
         "//common:check",
         "//common:enum_base",

+ 17 - 0
toolchain/sem_ir/clang_decl.cpp

@@ -0,0 +1,17 @@
+// 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/sem_ir/clang_decl.h"
+
+#include "clang/AST/DeclBase.h"
+
+namespace Carbon::SemIR {
+
+auto ClangDecl::Print(llvm::raw_ostream& out) const -> void {
+  out << "{decl: ";
+  decl->print(out);
+  out << ", inst_id: " << inst_id << "}";
+}
+
+}  // namespace Carbon::SemIR

+ 62 - 0
toolchain/sem_ir/clang_decl.h

@@ -0,0 +1,62 @@
+// 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_SEM_IR_CLANG_DECL_H_
+#define CARBON_TOOLCHAIN_SEM_IR_CLANG_DECL_H_
+
+#include "common/hashtable_key_context.h"
+#include "common/ostream.h"
+#include "toolchain/sem_ir/ids.h"
+
+// NOLINTNEXTLINE(readability-identifier-naming)
+namespace clang {
+
+// Forward declare indexed types, for integration with ValueStore.
+class Decl;
+
+}  // namespace clang
+
+namespace Carbon::SemIR {
+
+// A Clang declaration mapped to a Carbon instruction.
+// Using custom hashing since the declaration is keyed by the `decl` member for
+// lookup.
+// TODO: Avoid custom hashing by either having the data structure support keying
+// or create a dedicated mapping. See
+// https://discord.com/channels/655572317891461132/768530752592805919/1384999468293947537
+struct ClangDecl : public Printable<ClangDecl> {
+  auto Print(llvm::raw_ostream& out) const -> void;
+
+  friend auto CarbonHashtableEq(const ClangDecl& lhs, const ClangDecl& rhs)
+      -> bool {
+    return HashtableEq(lhs.decl, rhs.decl);
+  }
+
+  // Hashing for ClangDecl. See common/hashing.h.
+  friend auto CarbonHashValue(const ClangDecl& value, uint64_t seed)
+      -> HashCode {
+    return HashValue(value.decl, seed);
+  }
+
+  // The Clang declaration pointing to the Clang AST.
+  // TODO: Ensure we can easily serialize/deserialize this. Consider
+  // `clang::LazyDeclPtr`.
+  clang::Decl* decl = nullptr;
+
+  // The instruction the Clang declaration is mapped to.
+  InstId inst_id;
+};
+
+// The ID of a `ClangDecl`.
+struct ClangDeclId : public IdBase<ClangDeclId> {
+  static constexpr llvm::StringLiteral Label = "clang_decl_id";
+
+  using ValueType = ClangDecl;
+
+  using IdBase::IdBase;
+};
+
+}  // namespace Carbon::SemIR
+
+#endif  // CARBON_TOOLCHAIN_SEM_IR_CLANG_DECL_H_

+ 3 - 1
toolchain/sem_ir/file.h

@@ -335,7 +335,9 @@ class File : public Printable<File> {
   // `Cpp` imports.
   clang::ASTUnit* cpp_ast_ = nullptr;
 
-  // Clang AST declarations pointing to the AST.
+  // Clang AST declarations pointing to the AST and their mapped Carbon
+  // instructions. When calling `Lookup()`, `inst_id` is ignored. `Add()` will
+  // not add multiple entries with the same `decl` and different `inst_id`.
   CanonicalValueStore<ClangDeclId> clang_decls_;
 
   // All instructions. The first entries will always be the singleton

+ 1 - 0
toolchain/sem_ir/function.h

@@ -7,6 +7,7 @@
 
 #include "clang/AST/Decl.h"
 #include "toolchain/sem_ir/builtin_function_kind.h"
+#include "toolchain/sem_ir/clang_decl.h"
 #include "toolchain/sem_ir/entity_with_params_base.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst_categories.h"

+ 0 - 12
toolchain/sem_ir/ids.h

@@ -18,7 +18,6 @@
 namespace clang {
 
 // Forward declare indexed types, for integration with ValueStore.
-class Decl;
 class SourceLocation;
 
 }  // namespace clang
@@ -788,17 +787,6 @@ struct TypeId : public IdBase<TypeId> {
   auto Print(llvm::raw_ostream& out) const -> void;
 };
 
-// The ID of a Clang `Decl` pointer, pointing to the Clang AST.
-struct ClangDeclId : public IdBase<ClangDeclId> {
-  static constexpr llvm::StringLiteral Label = "clang_decl_id";
-
-  // TODO: Ensure we can easily serialize/deserialize this. Consider
-  // `clang::LazyDeclPtr`.
-  using ValueType = clang::Decl*;
-
-  using IdBase::IdBase;
-};
-
 // The ID of a Clang Source Location.
 struct ClangSourceLocId : public IdBase<ClangSourceLocId> {
   static constexpr llvm::StringLiteral Label = "clang_source_loc";

+ 1 - 0
toolchain/sem_ir/name_scope.h

@@ -7,6 +7,7 @@
 
 #include "clang/AST/DeclBase.h"
 #include "common/map.h"
+#include "toolchain/sem_ir/clang_decl.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"