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

Initial support for interop with class/struct/union fields. (#5849)

Add a new type, `custom_layout_type`, representing a struct type whose
size, alignment, and field offsets can be manually controlled. Use this
as the object representation type for imported C++ class types (which
also includes struct and union types), allowing us to model C++ class
type layouts. In passing, also add support for incomplete C++ class
types, mapping them into incomplete Carbon class types.

Map C++ fields into Carbon field declarations, allowing direct access to
C++ fields from Carbon. So far, no support is added for base classes nor
anonymous struct or union declarations; those will be added in
subsequent PRs. Also, we don't map C++ access control into Carbon yet,
so all C++ fields are accessible regardless of their access control.

For now we still use a `struct_type` as the object representation for
empty C++ classes, in order to continue to support our existing tests
that convert `{}` to empty C++ class types. This is temporary and should
be removed once we support interop with C++ class initialization.
Richard Smith 9 месяцев назад
Родитель
Сommit
36f0a73092
36 измененных файлов с 1769 добавлено и 589 удалено
  1. 1 0
      toolchain/check/BUILD
  2. 3 0
      toolchain/check/context.h
  3. 4 0
      toolchain/check/convert.cpp
  4. 40 0
      toolchain/check/import.cpp
  5. 24 0
      toolchain/check/import.h
  6. 191 68
      toolchain/check/import_cpp.cpp
  7. 1 35
      toolchain/check/import_ref.cpp
  8. 0 24
      toolchain/check/import_ref.h
  9. 66 74
      toolchain/check/testdata/interop/cpp/class/class.carbon
  10. 670 0
      toolchain/check/testdata/interop/cpp/class/field.carbon
  11. 12 12
      toolchain/check/testdata/interop/cpp/class/method.carbon
  12. 129 75
      toolchain/check/testdata/interop/cpp/class/struct.carbon
  13. 49 47
      toolchain/check/testdata/interop/cpp/class/union.carbon
  14. 67 90
      toolchain/check/testdata/interop/cpp/function/class.carbon
  15. 0 1
      toolchain/check/testdata/interop/cpp/function/in_template.carbon
  16. 0 3
      toolchain/check/testdata/interop/cpp/function/pointer.carbon
  17. 60 89
      toolchain/check/testdata/interop/cpp/function/struct.carbon
  18. 50 56
      toolchain/check/testdata/interop/cpp/function/union.carbon
  19. 0 1
      toolchain/check/testdata/interop/cpp/namespace.carbon
  20. 18 0
      toolchain/check/type_completion.cpp
  21. 7 0
      toolchain/lower/file_context.cpp
  22. 2 0
      toolchain/lower/function_context.cpp
  23. 40 9
      toolchain/lower/handle_aggregates.cpp
  24. 234 0
      toolchain/lower/testdata/interop/cpp/field.carbon
  25. 1 0
      toolchain/sem_ir/expr_info.cpp
  26. 9 0
      toolchain/sem_ir/file.h
  27. 17 0
      toolchain/sem_ir/formatter.cpp
  28. 4 2
      toolchain/sem_ir/formatter.h
  29. 1 0
      toolchain/sem_ir/id_kind.h
  30. 20 0
      toolchain/sem_ir/ids.h
  31. 7 3
      toolchain/sem_ir/import_ir.cpp
  32. 11 0
      toolchain/sem_ir/inst_categories.h
  33. 10 0
      toolchain/sem_ir/inst_fingerprinter.cpp
  34. 1 0
      toolchain/sem_ir/inst_kind.def
  35. 6 0
      toolchain/sem_ir/stringify.cpp
  36. 14 0
      toolchain/sem_ir/typed_insts.h

+ 1 - 0
toolchain/check/BUILD

@@ -136,6 +136,7 @@ cc_library(
         "//toolchain/sem_ir:file",
         "//toolchain/sem_ir:formatter",
         "//toolchain/sem_ir:typed_insts",
+        "@llvm-project//clang:ast",
         "@llvm-project//clang:basic",
         "@llvm-project//clang:frontend",
         "@llvm-project//clang:lex",

+ 3 - 0
toolchain/check/context.h

@@ -275,6 +275,9 @@ class Context {
   auto struct_type_fields() -> SemIR::StructTypeFieldsStore& {
     return sem_ir().struct_type_fields();
   }
+  auto custom_layouts() -> SemIR::CustomLayoutStore& {
+    return sem_ir().custom_layouts();
+  }
   auto types() -> SemIR::TypeStore& { return sem_ir().types(); }
   // Instructions should be added with `AddInst` or `AddInstInNoBlock` from
   // `inst.h`. This is `const` to prevent accidental misuse.

+ 4 - 0
toolchain/check/convert.cpp

@@ -574,6 +574,10 @@ static auto ConvertStructToClass(
   if (object_repr_id == SemIR::ErrorInst::TypeId) {
     return SemIR::ErrorInst::InstId;
   }
+  if (context.types().Is<SemIR::CustomLayoutType>(object_repr_id)) {
+    // Builtin conversion does not apply.
+    return value_id;
+  }
   auto dest_struct_type =
       context.types().GetAs<SemIR::StructType>(object_repr_id);
 

+ 40 - 0
toolchain/check/import.cpp

@@ -700,4 +700,44 @@ auto ImportNameFromOtherPackage(
   return result_id;
 }
 
+// Returns whether a parse node associated with an imported instruction of kind
+// `imported_kind` is usable as the location of a corresponding local
+// instruction of kind `local_kind`.
+static auto HasCompatibleImportedNodeKind(SemIR::InstKind imported_kind,
+                                          SemIR::InstKind local_kind) -> bool {
+  if (imported_kind == local_kind) {
+    return true;
+  }
+  if (imported_kind == SemIR::ImportDecl::Kind &&
+      local_kind == SemIR::Namespace::Kind) {
+    static_assert(
+        std::is_convertible_v<decltype(SemIR::ImportDecl::Kind)::TypedNodeId,
+                              decltype(SemIR::Namespace::Kind)::TypedNodeId>);
+    return true;
+  }
+  return false;
+}
+
+namespace Internal {
+
+auto CheckCompatibleImportedNodeKind(Context& context,
+                                     SemIR::ImportIRInstId imported_loc_id,
+                                     SemIR::InstKind kind) -> void {
+  auto& import_ir_inst = context.import_ir_insts().Get(imported_loc_id);
+  if (import_ir_inst.ir_id() == SemIR::ImportIRId::Cpp) {
+    // We don't require a matching node kind if the location is in C++, because
+    // there isn't a node.
+    return;
+  }
+  const auto* import_ir =
+      context.import_irs().Get(import_ir_inst.ir_id()).sem_ir;
+  auto imported_kind = import_ir->insts().Get(import_ir_inst.inst_id()).kind();
+  CARBON_CHECK(
+      HasCompatibleImportedNodeKind(imported_kind, kind),
+      "Node of kind {0} created with location of imported node of kind {1}",
+      kind, imported_kind);
+}
+
+}  // namespace Internal
+
 }  // namespace Carbon::Check

+ 24 - 0
toolchain/check/import.h

@@ -93,6 +93,30 @@ auto ImportNameFromOtherPackage(
         import_ir_scopes,
     SemIR::NameId name_id) -> SemIR::InstId;
 
+namespace Internal {
+
+// Checks that the provided imported location has a node kind that is
+// compatible with that of the given instruction.
+auto CheckCompatibleImportedNodeKind(Context& context,
+                                     SemIR::ImportIRInstId imported_loc_id,
+                                     SemIR::InstKind kind) -> void;
+}  // namespace Internal
+
+// Returns a LocIdAndInst for an instruction with an imported location. Checks
+// that the imported location is compatible with the kind of instruction being
+// created.
+template <typename InstT>
+  requires SemIR::Internal::HasNodeId<InstT>
+auto MakeImportedLocIdAndInst(Context& context,
+                              SemIR::ImportIRInstId imported_loc_id, InstT inst)
+    -> SemIR::LocIdAndInst {
+  if constexpr (!SemIR::Internal::HasUntypedNodeId<InstT>) {
+    Internal::CheckCompatibleImportedNodeKind(context, imported_loc_id,
+                                              InstT::Kind);
+  }
+  return SemIR::LocIdAndInst::UncheckedLoc(imported_loc_id, inst);
+}
+
 }  // namespace Carbon::Check
 
 #endif  // CARBON_TOOLCHAIN_CHECK_IMPORT_H_

+ 191 - 68
toolchain/check/import_cpp.cpp

@@ -10,6 +10,8 @@
 #include <tuple>
 #include <utility>
 
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/RecordLayout.h"
 #include "clang/Basic/FileManager.h"
 #include "clang/Frontend/ASTUnit.h"
 #include "clang/Frontend/CompilerInstance.h"
@@ -42,6 +44,7 @@
 #include "toolchain/parse/node_ids.h"
 #include "toolchain/sem_ir/clang_decl.h"
 #include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/name_scope.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
@@ -105,9 +108,9 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
  public:
   // Creates an instance with the location that triggers calling Clang.
   // `context` must not be null.
-  explicit CarbonClangDiagnosticConsumer(Context* context,
-                                         clang::CompilerInvocation* invocation)
-      : context_(context), invocation_(invocation) {}
+  explicit CarbonClangDiagnosticConsumer(
+      Context* context, std::shared_ptr<clang::CompilerInvocation> invocation)
+      : context_(context), invocation_(std::move(invocation)) {}
 
   // Generates a Carbon warning for each Clang warning and a Carbon error for
   // each Clang error or fatal.
@@ -185,7 +188,7 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer {
   Context* context_;
 
   // The compiler invocation that is producing the diagnostics.
-  clang::CompilerInvocation* invocation_;
+  std::shared_ptr<clang::CompilerInvocation> invocation_;
 
   // Information on a Clang diagnostic that can be converted to a Carbon
   // diagnostic.
@@ -219,11 +222,11 @@ static auto GenerateAst(Context& context,
                         std::shared_ptr<clang::CompilerInvocation> invocation)
     -> std::pair<std::unique_ptr<clang::ASTUnit>, bool> {
   // Build a diagnostics engine.
-  CarbonClangDiagnosticConsumer diagnostics_consumer(&context,
-                                                     invocation.get());
+  auto diagnostics_consumer =
+      std::make_unique<CarbonClangDiagnosticConsumer>(&context, invocation);
   llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags(
       clang::CompilerInstance::createDiagnostics(
-          *fs, invocation->getDiagnosticOpts(), &diagnostics_consumer,
+          *fs, invocation->getDiagnosticOpts(), diagnostics_consumer.get(),
           /*ShouldOwnClient=*/false));
 
   // Extract the input from the frontend invocation and make sure it makes
@@ -247,9 +250,6 @@ static auto GenerateAst(Context& context,
       invocation, std::make_shared<clang::PCHContainerOperations>(), nullptr,
       diags, new clang::FileManager(invocation->getFileSystemOpts(), fs));
 
-  // Remove link to the diagnostics consumer before its destruction.
-  ast->getDiagnostics().setClient(nullptr);
-
   // Remove remapped file before its underlying storage is destroyed.
   invocation->getPreprocessorOpts().clearRemappedFiles();
 
@@ -259,9 +259,15 @@ static auto GenerateAst(Context& context,
   context.sem_ir().set_cpp_ast(ast.get());
 
   // Emit any diagnostics we queued up while building the AST.
-  diagnostics_consumer.EmitDiagnostics();
+  diagnostics_consumer->EmitDiagnostics();
+  bool any_errors = diagnostics_consumer->getNumErrors() > 0;
+
+  // Transfer ownership of the consumer to the AST unit, in case more
+  // diagnostics are produced by AST queries.
+  ast->getDiagnostics().setClient(diagnostics_consumer.release(),
+                                  /*ShouldOwnClient=*/true);
 
-  return {std::move(ast), !ast || diagnostics_consumer.getNumErrors() > 0};
+  return {std::move(ast), !ast || any_errors};
 }
 
 // Adds a namespace for the `Cpp` import and returns its `NameScopeId`.
@@ -373,7 +379,10 @@ static auto ClangLookup(Context& context, SemIR::NameScopeId scope_id,
 // Returns whether `decl` already mapped to an instruction.
 static auto IsClangDeclImported(const Context& context, clang::Decl* decl)
     -> bool {
-  return context.sem_ir().clang_decls().Lookup(decl).has_value();
+  return context.sem_ir()
+      .clang_decls()
+      .Lookup(decl->getCanonicalDecl())
+      .has_value();
 }
 
 // If `decl` already mapped to an instruction, returns that instruction.
@@ -381,7 +390,7 @@ static auto IsClangDeclImported(const Context& context, clang::Decl* decl)
 static auto LookupClangDeclInstId(const Context& context, clang::Decl* decl)
     -> SemIR::InstId {
   const auto& clang_decls = context.sem_ir().clang_decls();
-  if (auto context_clang_decl_id = clang_decls.Lookup(decl);
+  if (auto context_clang_decl_id = clang_decls.Lookup(decl->getCanonicalDecl());
       context_clang_decl_id.has_value()) {
     return clang_decls.Get(context_clang_decl_id).inst_id;
   }
@@ -442,22 +451,27 @@ static auto ImportNamespaceDecl(Context& context,
   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}));
+          {.decl = clang_decl->getCanonicalDecl(), .inst_id = result.inst_id}));
   return result.inst_id;
 }
 
+static auto MapType(Context& context, SemIR::LocId loc_id, clang::QualType type)
+    -> TypeExpr;
+
 // 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,
+static auto BuildClassDecl(Context& context,
+                           SemIR::ImportIRInstId import_ir_inst_id,
+                           SemIR::NameScopeId parent_scope_id,
                            SemIR::NameId name_id)
-    -> std::tuple<SemIR::ClassId, SemIR::InstId> {
+    -> std::tuple<SemIR::ClassId, SemIR::TypeInstId> {
   // 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,
+      SemIR::LocIdAndInst::UncheckedLoc(import_ir_inst_id, class_decl));
   context.imports().push_back(class_decl_id);
 
   SemIR::Class class_info = {
@@ -486,76 +500,159 @@ static auto BuildClassDecl(Context& context, SemIR::NameScopeId parent_scope_id,
 
   SetClassSelfType(context, class_decl.class_id);
 
-  return {class_decl.class_id, class_decl_id};
+  return {class_decl.class_id, context.types().GetAsTypeInstId(class_decl_id)};
+}
+
+// Checks that the specified finished class definition is valid and builds and
+// returns a corresponding complete type witness instruction.
+// TODO: Remove recursion into mapping field types.
+// NOLINTNEXTLINE(misc-no-recursion)
+static auto ImportClassObjectRepr(Context& context,
+                                  SemIR::ImportIRInstId import_ir_inst_id,
+                                  SemIR::TypeInstId class_type_inst_id,
+                                  const clang::CXXRecordDecl* clang_def)
+    -> SemIR::TypeInstId {
+  // For now, if the class is empty, produce an empty struct as the object
+  // representation. This allows our tests to continue to pass while we don't
+  // properly support initializing imported C++ classes.
+  // TODO: Remove this.
+  if (clang_def->isEmpty()) {
+    return context.types().GetAsTypeInstId(AddInst(
+        context,
+        MakeImportedLocIdAndInst(
+            context, import_ir_inst_id,
+            SemIR::StructType{.type_id = SemIR::TypeType::TypeId,
+                              .fields_id = SemIR::StructTypeFieldsId::Empty})));
+  }
+
+  const auto& clang_layout =
+      context.ast_context().getASTRecordLayout(clang_def);
+
+  llvm::SmallVector<uint64_t> layout;
+  llvm::SmallVector<SemIR::StructTypeField> fields;
+
+  static_assert(SemIR::CustomLayoutId::SizeIndex == 0);
+  layout.push_back(clang_layout.getSize().getQuantity());
+
+  static_assert(SemIR::CustomLayoutId::AlignIndex == 1);
+  layout.push_back(clang_layout.getAlignment().getQuantity());
+
+  static_assert(SemIR::CustomLayoutId::FirstFieldIndex == 2);
+
+  // TODO: Import vptr(s).
+  // TODO: Import bases.
+
+  // Import fields.
+  for (auto* field : clang_def->fields()) {
+    if (field->isBitField()) {
+      // TODO: Add a representation for named bitfield members.
+      continue;
+    }
+    if (field->isAnonymousStructOrUnion()) {
+      // TODO: Visit IndirectFieldDecls and add them to the layout.
+      continue;
+    }
+
+    auto field_name_id = AddIdentifierName(context, field->getName());
+    auto [field_type_inst_id, field_type_id] =
+        MapType(context, import_ir_inst_id, field->getType());
+
+    // Create a field now, as we know the index to use.
+    // TODO: Consider doing this lazily instead.
+    auto field_decl_id = AddInst(
+        context, MakeImportedLocIdAndInst(
+                     context, import_ir_inst_id,
+                     SemIR::FieldDecl{
+                         .type_id = GetUnboundElementType(
+                             context, class_type_inst_id, field_type_inst_id),
+                         .name_id = field_name_id,
+                         .index = SemIR::ElementIndex(fields.size())}));
+    context.sem_ir().clang_decls().Add(
+        {.decl = field->getCanonicalDecl(), .inst_id = field_decl_id});
+
+    layout.push_back(context.ast_context()
+                         .toCharUnitsFromBits(clang_layout.getFieldOffset(
+                             field->getFieldIndex()))
+                         .getQuantity());
+    fields.push_back(
+        {.name_id = field_name_id, .type_inst_id = field_type_inst_id});
+  }
+
+  // TODO: Add a field to prevent tail padding reuse if necessary.
+
+  return AddTypeInst<SemIR::CustomLayoutType>(
+      context, import_ir_inst_id,
+      {.type_id = SemIR::TypeType::TypeId,
+       .fields_id = context.struct_type_fields().Add(fields),
+       .layout_id = context.custom_layouts().Add(layout)});
 }
 
 // Creates a class definition based on the information in the given Clang
-// declaration, which is assumed to be for a class definition. Returns the new
-// class id and instruction id.
+// declaration, which is assumed to be for a class definition.
+// TODO: Remove recursion into mapping field types.
+// NOLINTNEXTLINE(misc-no-recursion)
 static auto BuildClassDefinition(Context& context,
-                                 clang::CXXRecordDecl* clang_decl)
-    -> std::tuple<SemIR::ClassId, SemIR::InstId> {
-  auto [class_id, class_inst_id] =
-      BuildClassDecl(context, GetParentNameScopeId(context, clang_decl),
-                     AddIdentifierName(context, clang_decl->getName()));
+                                 SemIR::ImportIRInstId import_ir_inst_id,
+                                 SemIR::ClassId class_id,
+                                 SemIR::TypeInstId class_inst_id,
+                                 SemIR::ClangDeclId clang_decl_id,
+                                 clang::CXXRecordDecl* clang_def) -> void {
   auto& class_info = context.classes().Get(class_id);
   StartClassDefinition(context, class_info, class_inst_id);
 
+  // Name lookup into the Carbon class looks in the C++ class definition.
   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}));
+      .set_clang_decl_context_id(clang_decl_id);
 
-  return {class_id, class_inst_id};
+  context.inst_block_stack().Push();
+
+  // Compute the class's object representation.
+  auto object_repr_id = ImportClassObjectRepr(context, import_ir_inst_id,
+                                              class_inst_id, clang_def);
+  class_info.complete_type_witness_id = AddInst<SemIR::CompleteTypeWitness>(
+      context, import_ir_inst_id,
+      {.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId),
+       .object_repr_type_inst_id = object_repr_id});
+
+  class_info.body_block_id = context.inst_block_stack().Pop();
 }
 
 // Mark the given `Decl` as failed in `clang_decls`.
 static auto MarkFailedDecl(Context& context, clang::Decl* clang_decl) {
-  context.sem_ir().clang_decls().Add(
-      {.decl = clang_decl, .inst_id = SemIR::ErrorInst::InstId});
+  context.sem_ir().clang_decls().Add({.decl = clang_decl->getCanonicalDecl(),
+                                      .inst_id = SemIR::ErrorInst::InstId});
 }
 
 // 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,
+// TODO: Remove recursion into mapping field types.
+// NOLINTNEXTLINE(misc-no-recursion)
+static auto ImportCXXRecordDecl(Context& context,
                                 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");
-    MarkFailedDecl(context, clang_decl);
-    return SemIR::ErrorInst::InstId;
-  }
-
-  if (clang_def->isDynamicClass()) {
-    context.TODO(loc_id, "Unsupported: Dynamic Class");
-    MarkFailedDecl(context, clang_decl);
-    return SemIR::ErrorInst::InstId;
+  if (clang_def) {
+    clang_decl = clang_def;
   }
+  auto import_ir_inst_id = AddImportIRInst(context, clang_decl->getLocation());
 
-  if (clang_def->isUnion() && !clang_def->fields().empty()) {
-    context.TODO(loc_id, "Unsupported: Non-empty union");
-    MarkFailedDecl(context, clang_decl);
-    return SemIR::ErrorInst::InstId;
-  }
+  auto [class_id, class_inst_id] = BuildClassDecl(
+      context, import_ir_inst_id, GetParentNameScopeId(context, clang_decl),
+      AddIdentifierName(context, clang_decl->getName()));
 
-  auto [class_id, class_def_id] = BuildClassDefinition(context, clang_def);
+  // TODO: The caller does the same lookup. Avoid doing it twice.
+  auto clang_decl_id = context.sem_ir().clang_decls().Add(
+      {.decl = clang_decl->getCanonicalDecl(), .inst_id = class_inst_id});
 
-  // 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=*/{});
+  if (clang_def) {
+    BuildClassDefinition(context, import_ir_inst_id, class_id, class_inst_id,
+                         clang_decl_id, clang_def);
+  }
 
-  return class_def_id;
+  return class_inst_id;
 }
 
 // Creates an integer type of the given size.
@@ -587,8 +684,10 @@ static auto MapBuiltinType(Context& context, clang::QualType qual_type,
 
 // 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 {
+// TODO: Remove recursion mapping fields of class types.
+// NOLINTNEXTLINE(misc-no-recursion)
+static auto MapRecordType(Context& context, const clang::RecordType& type)
+    -> TypeExpr {
   auto* record_decl = clang::dyn_cast<clang::CXXRecordDecl>(type.getDecl());
   if (!record_decl) {
     return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
@@ -597,7 +696,7 @@ static auto MapRecordType(Context& context, SemIR::LocId loc_id,
   // Check if the declaration is already mapped.
   SemIR::InstId record_inst_id = LookupClangDeclInstId(context, record_decl);
   if (!record_inst_id.has_value()) {
-    record_inst_id = ImportCXXRecordDecl(context, loc_id, record_decl);
+    record_inst_id = ImportCXXRecordDecl(context, record_decl);
   }
   SemIR::TypeInstId record_type_inst_id =
       context.types().GetAsTypeInstId(record_inst_id);
@@ -609,14 +708,16 @@ static auto MapRecordType(Context& context, SemIR::LocId loc_id,
 // Maps a C++ type that is not a wrapper type such as a pointer to a Carbon
 // type.
 // TODO: Support more types.
-static auto MapNonWrapperType(Context& context, SemIR::LocId loc_id,
-                              clang::QualType type) -> TypeExpr {
+// TODO: Remove recursion mapping fields of class types.
+// NOLINTNEXTLINE(misc-no-recursion)
+static auto MapNonWrapperType(Context& context, clang::QualType type)
+    -> TypeExpr {
   if (const auto* builtin_type = type->getAs<clang::BuiltinType>()) {
     return MapBuiltinType(context, type, *builtin_type);
   }
 
   if (const auto* record_type = type->getAs<clang::RecordType>()) {
-    return MapRecordType(context, loc_id, *record_type);
+    return MapRecordType(context, *record_type);
   }
 
   CARBON_CHECK(!type.hasQualifiers() && !type->isPointerType(),
@@ -673,6 +774,8 @@ static auto MapPointerType(Context& context, SemIR::LocId loc_id,
 // Maps a C++ type to a Carbon type. `type` should not be canonicalized because
 // we check for pointer nullability and nullability will be lost by
 // canonicalization.
+// TODO: Remove recursion mapping fields of class types.
+// NOLINTNEXTLINE(misc-no-recursion)
 static auto MapType(Context& context, SemIR::LocId loc_id, clang::QualType type)
     -> TypeExpr {
   // Unwrap any type modifiers and wrappers.
@@ -689,7 +792,7 @@ static auto MapType(Context& context, SemIR::LocId loc_id, clang::QualType type)
     wrapper_types.push_back(orig_type);
   }
 
-  auto mapped = MapNonWrapperType(context, loc_id, type);
+  auto mapped = MapNonWrapperType(context, type);
 
   for (auto wrapper : llvm::reverse(wrapper_types)) {
     if (!mapped.inst_id.has_value() ||
@@ -961,6 +1064,7 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
     MarkFailedDecl(context, clang_decl);
     return SemIR::ErrorInst::InstId;
   }
+
   if (clang_decl->getTemplatedKind() ==
       clang::FunctionDecl::TK_FunctionTemplate) {
     context.TODO(loc_id, "Unsupported: Template function");
@@ -968,6 +1072,14 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
     return SemIR::ErrorInst::InstId;
   }
 
+  if (auto* method_decl = dyn_cast<clang::CXXMethodDecl>(clang_decl)) {
+    if (method_decl->isVirtual()) {
+      context.TODO(loc_id, "Unsupported: Virtual function");
+      MarkFailedDecl(context, clang_decl);
+      return SemIR::ErrorInst::InstId;
+    }
+  }
+
   context.scope_stack().PushForDeclName();
   context.inst_block_stack().Push();
   context.pattern_block_stack().Push();
@@ -1047,6 +1159,7 @@ static auto GetDependentUnimportedTypeDecls(const Context& context,
       if (!IsClangDeclImported(context, record_decl)) {
         return {record_decl};
       }
+      // TODO: Also collect field types.
     }
   }
 
@@ -1115,6 +1228,16 @@ static auto ImportDeclAfterDependencies(Context& context, SemIR::LocId loc_id,
     }
     return type_inst_id;
   }
+  if (clang::isa<clang::FieldDecl>(clang_decl)) {
+    // Usable fields get imported as a side effect of importing the class.
+    if (SemIR::InstId existing_inst_id =
+            LookupClangDeclInstId(context, clang_decl);
+        existing_inst_id.has_value()) {
+      return existing_inst_id;
+    }
+    context.TODO(loc_id, "Unsupported: Unhandled kind of field declaration");
+    return SemIR::InstId::None;
+  }
 
   context.TODO(loc_id, llvm::formatv("Unsupported: Declaration type {0}",
                                      clang_decl->getDeclKindName())

+ 1 - 35
toolchain/check/import_ref.cpp

@@ -15,6 +15,7 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/eval.h"
 #include "toolchain/check/generic.h"
+#include "toolchain/check/import.h"
 #include "toolchain/check/inst.h"
 #include "toolchain/check/name_lookup.h"
 #include "toolchain/check/type.h"
@@ -3440,39 +3441,4 @@ auto ImportImpl(Context& context, SemIR::ImportIRId import_ir_id,
                        .first_decl_id());
 }
 
-// Returns whether a parse node associated with an imported instruction of kind
-// `imported_kind` is usable as the location of a corresponding local
-// instruction of kind `local_kind`.
-static auto HasCompatibleImportedNodeKind(SemIR::InstKind imported_kind,
-                                          SemIR::InstKind local_kind) -> bool {
-  if (imported_kind == local_kind) {
-    return true;
-  }
-  if (imported_kind == SemIR::ImportDecl::Kind &&
-      local_kind == SemIR::Namespace::Kind) {
-    static_assert(
-        std::is_convertible_v<decltype(SemIR::ImportDecl::Kind)::TypedNodeId,
-                              decltype(SemIR::Namespace::Kind)::TypedNodeId>);
-    return true;
-  }
-  return false;
-}
-
-namespace Internal {
-
-auto CheckCompatibleImportedNodeKind(Context& context,
-                                     SemIR::ImportIRInstId imported_loc_id,
-                                     SemIR::InstKind kind) -> void {
-  auto& import_ir_inst = context.import_ir_insts().Get(imported_loc_id);
-  const auto* import_ir =
-      context.import_irs().Get(import_ir_inst.ir_id()).sem_ir;
-  auto imported_kind = import_ir->insts().Get(import_ir_inst.inst_id()).kind();
-  CARBON_CHECK(
-      HasCompatibleImportedNodeKind(imported_kind, kind),
-      "Node of kind {0} created with location of imported node of kind {1}",
-      kind, imported_kind);
-}
-
-}  // namespace Internal
-
 }  // namespace Carbon::Check

+ 0 - 24
toolchain/check/import_ref.h

@@ -52,30 +52,6 @@ auto ImportImplsFromApiFile(Context& context) -> void;
 auto ImportImpl(Context& context, SemIR::ImportIRId import_ir_id,
                 SemIR::ImplId impl_id) -> void;
 
-namespace Internal {
-
-// Checks that the provided imported location has a node kind that is
-// compatible with that of the given instruction.
-auto CheckCompatibleImportedNodeKind(Context& context,
-                                     SemIR::ImportIRInstId imported_loc_id,
-                                     SemIR::InstKind kind) -> void;
-}  // namespace Internal
-
-// Returns a LocIdAndInst for an instruction with an imported location. Checks
-// that the imported location is compatible with the kind of instruction being
-// created.
-template <typename InstT>
-  requires SemIR::Internal::HasNodeId<InstT>
-auto MakeImportedLocIdAndInst(Context& context,
-                              SemIR::ImportIRInstId imported_loc_id, InstT inst)
-    -> SemIR::LocIdAndInst {
-  if constexpr (!SemIR::Internal::HasUntypedNodeId<InstT>) {
-    Internal::CheckCompatibleImportedNodeKind(context, imported_loc_id,
-                                              InstT::Kind);
-  }
-  return SemIR::LocIdAndInst::UncheckedLoc(imported_loc_id, inst);
-}
-
 }  // namespace Carbon::Check
 
 #endif  // CARBON_TOOLCHAIN_CHECK_IMPORT_REF_H_

+ 66 - 74
toolchain/check/testdata/interop/cpp/class/class.carbon

@@ -18,23 +18,32 @@
 
 class Bar;
 
-// --- fail_todo_import_declaration.carbon
+// --- import_declaration.carbon
 
 library "[[@TEST_NAME]]";
 
 import Cpp library "declaration.h";
 
 //@dump-sem-ir-begin
-// CHECK:STDERR: fail_todo_import_declaration.carbon:[[@LINE+7]]:13: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
-// CHECK:STDERR: fn MyF(bar: Cpp.Bar*);
-// CHECK:STDERR:             ^~~~~~~
-// CHECK:STDERR: fail_todo_import_declaration.carbon:[[@LINE+4]]:13: note: in `Cpp` name lookup for `Bar` [InCppNameLookup]
-// CHECK:STDERR: fn MyF(bar: Cpp.Bar*);
-// CHECK:STDERR:             ^~~~~~~
-// CHECK:STDERR:
 fn MyF(bar: Cpp.Bar*);
 //@dump-sem-ir-end
 
+// --- fail_use_declaration_as_definition.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "declaration.h";
+
+fn MyF() {
+  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE+6]]:12: error: binding pattern has incomplete type `Bar` in name binding declaration [IncompleteTypeInBindingDecl]
+  // CHECK:STDERR:   var bar: Cpp.Bar;
+  // CHECK:STDERR:            ^~~~~~~
+  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE-6]]:1: in import [InImport]
+  // CHECK:STDERR: ./declaration.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR:
+  var bar: Cpp.Bar;
+}
+
 // ============================================================================
 // Definition
 // ============================================================================
@@ -158,28 +167,17 @@ fn MyF() {
 
 class Bar {
  public:
-  Bar* foo;
+  Bar* _Nonnull foo;
 };
 
-// --- fail_todo_import_public_data_member.carbon
+// --- import_public_data_member.carbon
 
 library "[[@TEST_NAME]]";
 
-import Cpp library "public_static_data_member.h";
+import Cpp library "public_data_member.h";
 
 //@dump-sem-ir-begin
 fn MyF(bar : Cpp.Bar*) {
-  // CHECK:STDERR: fail_todo_import_public_data_member.carbon:[[@LINE+11]]:27: error: semantics TODO: `Unsupported: Declaration type Var` [SemanticsTodo]
-  // CHECK:STDERR:   let foo_bar: Cpp.Bar* = bar->foo;
-  // CHECK:STDERR:                           ^~~~~~~~
-  // CHECK:STDERR: fail_todo_import_public_data_member.carbon:[[@LINE+8]]:27: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   let foo_bar: Cpp.Bar* = bar->foo;
-  // CHECK:STDERR:                           ^~~~~~~~
-  // CHECK:STDERR:
-  // CHECK:STDERR: fail_todo_import_public_data_member.carbon:[[@LINE+4]]:27: error: member name `foo` not found in `Cpp.Bar` [MemberNameNotFoundInInstScope]
-  // CHECK:STDERR:   let foo_bar: Cpp.Bar* = bar->foo;
-  // CHECK:STDERR:                           ^~~~~~~~
-  // CHECK:STDERR:
   let foo_bar: Cpp.Bar* = bar->foo;
 }
 //@dump-sem-ir-end
@@ -230,7 +228,7 @@ library "[[@TEST_NAME]]";
 import Cpp library "inheritance_pointers.h";
 
 //@dump-sem-ir-begin
-fn MyF1(bar : Cpp.Bar1*);
+fn MyF1(bar: Cpp.Bar1*);
 // TODO: Support C++ inheritance.
 // CHECK:STDERR: fail_todo_import_inheritance_pointers.carbon:[[@LINE+10]]:33: error: cannot implicitly convert expression of type `Cpp.Bar2*` to `Cpp.Bar1*` [ConversionFailure]
 // CHECK:STDERR: fn MyF2(bar : Cpp.Bar2*) { MyF1(bar); }
@@ -239,8 +237,8 @@ fn MyF1(bar : Cpp.Bar1*);
 // CHECK:STDERR: fn MyF2(bar : Cpp.Bar2*) { MyF1(bar); }
 // CHECK:STDERR:                                 ^~~
 // CHECK:STDERR: fail_todo_import_inheritance_pointers.carbon:[[@LINE-8]]:9: note: initializing function parameter [InCallToFunctionParam]
-// CHECK:STDERR: fn MyF1(bar : Cpp.Bar1*);
-// CHECK:STDERR:         ^~~~~~~~~~~~~~~
+// CHECK:STDERR: fn MyF1(bar: Cpp.Bar1*);
+// CHECK:STDERR:         ^~~~~~~~~~~~~~
 // CHECK:STDERR:
 fn MyF2(bar : Cpp.Bar2*) { MyF1(bar); }
 //@dump-sem-ir-end
@@ -256,21 +254,14 @@ class Bar {
   virtual ~Bar();
 };
 
-// --- fail_todo_import_dynamic.carbon
+// --- import_dynamic.carbon
 
 library "[[@TEST_NAME]]";
 
 import Cpp library "dynamic.h";
 
 //@dump-sem-ir-begin
-// CHECK:STDERR: fail_todo_import_dynamic.carbon:[[@LINE+7]]:14: error: semantics TODO: `Unsupported: Dynamic Class` [SemanticsTodo]
-// CHECK:STDERR: fn MyF(bar : Cpp.Bar*);
-// CHECK:STDERR:              ^~~~~~~
-// CHECK:STDERR: fail_todo_import_dynamic.carbon:[[@LINE+4]]:14: note: in `Cpp` name lookup for `Bar` [InCppNameLookup]
-// CHECK:STDERR: fn MyF(bar : Cpp.Bar*);
-// CHECK:STDERR:              ^~~~~~~
-// CHECK:STDERR:
-fn MyF(bar : Cpp.Bar*);
+fn MyF(bar: Cpp.Bar*);
 //@dump-sem-ir-end
 
 // ============================================================================
@@ -355,36 +346,40 @@ import Cpp library "template.h";
 fn MyF(bar: Cpp.Bar*);
 //@dump-sem-ir-end
 
-// CHECK:STDOUT: --- fail_todo_import_declaration.carbon
+// CHECK:STDOUT: --- import_declaration.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Bar: type = class_type @Bar [concrete]
+// CHECK:STDOUT:   %ptr: type = ptr_type %Bar [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %ptr [concrete]
 // CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
 // CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .Bar = <error>
+// CHECK:STDOUT:     .Bar = %Bar.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Bar.decl: type = class_decl @Bar [concrete = constants.%Bar] {} {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {
-// CHECK:STDOUT:     %bar.patt: <error> = binding_pattern bar [concrete]
-// CHECK:STDOUT:     %bar.param_patt: <error> = value_param_pattern %bar.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %bar.patt: %pattern_type = binding_pattern bar [concrete]
+// CHECK:STDOUT:     %bar.param_patt: %pattern_type = value_param_pattern %bar.patt, call_param0 [concrete]
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %bar.param: <error> = value_param call_param0
-// CHECK:STDOUT:     %.loc14: type = splice_block %ptr [concrete = <error>] {
+// CHECK:STDOUT:     %bar.param: %ptr = value_param call_param0
+// CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       %Bar.ref: <error> = name_ref Bar, <error> [concrete = <error>]
-// CHECK:STDOUT:       %ptr: type = ptr_type <error> [concrete = <error>]
+// CHECK:STDOUT:       %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:       %ptr: type = ptr_type %Bar.ref [concrete = constants.%ptr]
 // CHECK:STDOUT:     }
-// CHECK:STDOUT:     %bar: <error> = bind_name bar, %bar.param
+// CHECK:STDOUT:     %bar: %ptr = bind_name bar, %bar.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @MyF(%bar.param: <error>);
+// CHECK:STDOUT: fn @MyF(%bar.param: %ptr);
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- import_definition.carbon
 // CHECK:STDOUT:
@@ -412,7 +407,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %bar.param: %ptr = value_param call_param0
 // CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       <elided>
 // CHECK:STDOUT:       %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:       %ptr: type = ptr_type %Bar.ref [concrete = constants.%ptr]
 // CHECK:STDOUT:     }
@@ -448,7 +442,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %bar.param: %ptr = value_param call_param0
 // CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       <elided>
 // CHECK:STDOUT:       %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:       %ptr: type = ptr_type %Bar.ref [concrete = constants.%ptr]
 // CHECK:STDOUT:     }
@@ -479,7 +472,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT: fn @MyF() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:   %foo.ref: %Bar.foo.type = name_ref foo, imports.%Bar.foo.decl [concrete = constants.%Bar.foo]
 // CHECK:STDOUT:   %Bar.foo.call: init %empty_tuple.type = call %foo.ref()
@@ -512,7 +504,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
 // CHECK:STDOUT:   %.loc19: type = splice_block %ptr [concrete = constants.%ptr.f68] {
 // CHECK:STDOUT:     %Cpp.ref.loc19_12: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:     %Bar.ref.loc19_15: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:     %ptr: type = ptr_type %Bar.ref.loc19_15 [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:   }
@@ -520,11 +511,12 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_public_data_member.carbon
+// CHECK:STDOUT: --- import_public_data_member.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %Bar: type = class_type @Bar [concrete]
 // CHECK:STDOUT:   %ptr.f68: type = ptr_type %Bar [concrete]
+// CHECK:STDOUT:   %Bar.elem: type = unbound_element_type %Bar, %ptr.f68 [concrete]
 // CHECK:STDOUT:   %pattern_type: type = pattern_type %ptr.f68 [concrete]
 // CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
 // CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
@@ -546,7 +538,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %bar.param: %ptr.f68 = value_param call_param0
 // CHECK:STDOUT:     %.loc7: type = splice_block %ptr.loc7 [concrete = constants.%ptr.f68] {
 // CHECK:STDOUT:       %Cpp.ref.loc7: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       <elided>
 // CHECK:STDOUT:       %Bar.ref.loc7: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:       %ptr.loc7: type = ptr_type %Bar.ref.loc7 [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:     }
@@ -560,14 +551,16 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %foo_bar.patt: %pattern_type = binding_pattern foo_bar [concrete]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %bar.ref: %ptr.f68 = name_ref bar, %bar
-// CHECK:STDOUT:   %.loc19_30: ref %Bar = deref %bar.ref
-// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
-// CHECK:STDOUT:   %.loc19_23: type = splice_block %ptr.loc19 [concrete = constants.%ptr.f68] {
-// CHECK:STDOUT:     %Cpp.ref.loc19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     %Bar.ref.loc19: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
-// CHECK:STDOUT:     %ptr.loc19: type = ptr_type %Bar.ref.loc19 [concrete = constants.%ptr.f68]
+// CHECK:STDOUT:   %.loc8_30.1: ref %Bar = deref %bar.ref
+// CHECK:STDOUT:   %foo.ref: %Bar.elem = name_ref foo, @Bar.%.1 [concrete = @Bar.%.1]
+// CHECK:STDOUT:   %.loc8_30.2: ref %ptr.f68 = class_element_access %.loc8_30.1, element0
+// CHECK:STDOUT:   %.loc8_23: type = splice_block %ptr.loc8 [concrete = constants.%ptr.f68] {
+// CHECK:STDOUT:     %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Bar.ref.loc8: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:     %ptr.loc8: type = ptr_type %Bar.ref.loc8 [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %foo_bar: %ptr.f68 = bind_name foo_bar, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc8_30.3: %ptr.f68 = bind_value %.loc8_30.2
+// CHECK:STDOUT:   %foo_bar: %ptr.f68 = bind_name foo_bar, %.loc8_30.3
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -598,12 +591,10 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT: fn @MyF() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %Bar1.ref: type = name_ref Bar1, imports.%Bar1.decl [concrete = constants.%Bar1]
 // CHECK:STDOUT:   %foo1.ref.loc8: %Bar1.foo1.type = name_ref foo1, imports.%Bar1.foo1.decl [concrete = constants.%Bar1.foo1]
 // CHECK:STDOUT:   %Bar1.foo1.call.loc8: init %empty_tuple.type = call %foo1.ref.loc8()
 // CHECK:STDOUT:   %Cpp.ref.loc9: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %Bar2.ref.loc9: type = name_ref Bar2, imports.%Bar2.decl [concrete = constants.%Bar2]
 // CHECK:STDOUT:   %foo1.ref.loc9: %Bar1.foo1.type = name_ref foo1, imports.%Bar1.foo1.decl [concrete = constants.%Bar1.foo1]
 // CHECK:STDOUT:   %Bar1.foo1.call.loc9: init %empty_tuple.type = call %foo1.ref.loc9()
@@ -648,7 +639,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %bar.param: %ptr.f68 = value_param call_param0
 // CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr.f68] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       <elided>
 // CHECK:STDOUT:       %Bar1.ref: type = name_ref Bar1, imports.%Bar1.decl [concrete = constants.%Bar1]
 // CHECK:STDOUT:       %ptr: type = ptr_type %Bar1.ref [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:     }
@@ -661,7 +651,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %bar.param: %ptr.eca = value_param call_param0
 // CHECK:STDOUT:     %.loc19_23: type = splice_block %ptr [concrete = constants.%ptr.eca] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       <elided>
 // CHECK:STDOUT:       %Bar2.ref: type = name_ref Bar2, imports.%Bar2.decl [concrete = constants.%Bar2]
 // CHECK:STDOUT:       %ptr: type = ptr_type %Bar2.ref [concrete = constants.%ptr.eca]
 // CHECK:STDOUT:     }
@@ -680,36 +669,40 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_dynamic.carbon
+// CHECK:STDOUT: --- import_dynamic.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Bar: type = class_type @Bar [concrete]
+// CHECK:STDOUT:   %ptr: type = ptr_type %Bar [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %ptr [concrete]
 // CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
 // CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .Bar = <error>
+// CHECK:STDOUT:     .Bar = %Bar.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Bar.decl: type = class_decl @Bar [concrete = constants.%Bar] {} {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {
-// CHECK:STDOUT:     %bar.patt: <error> = binding_pattern bar [concrete]
-// CHECK:STDOUT:     %bar.param_patt: <error> = value_param_pattern %bar.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %bar.patt: %pattern_type = binding_pattern bar [concrete]
+// CHECK:STDOUT:     %bar.param_patt: %pattern_type = value_param_pattern %bar.patt, call_param0 [concrete]
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %bar.param: <error> = value_param call_param0
-// CHECK:STDOUT:     %.loc14: type = splice_block %ptr [concrete = <error>] {
+// CHECK:STDOUT:     %bar.param: %ptr = value_param call_param0
+// CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       %Bar.ref: <error> = name_ref Bar, <error> [concrete = <error>]
-// CHECK:STDOUT:       %ptr: type = ptr_type <error> [concrete = <error>]
+// CHECK:STDOUT:       %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:       %ptr: type = ptr_type %Bar.ref [concrete = constants.%ptr]
 // CHECK:STDOUT:     }
-// CHECK:STDOUT:     %bar: <error> = bind_name bar, %bar.param
+// CHECK:STDOUT:     %bar: %ptr = bind_name bar, %bar.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @MyF(%bar.param: <error>);
+// CHECK:STDOUT: fn @MyF(%bar.param: %ptr);
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- import_to_inherit_public.carbon
 // CHECK:STDOUT:
@@ -742,12 +735,11 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Derived {
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:   %.loc8: %Derived.elem = base_decl %Bar.ref, element0 [concrete]
 // CHECK:STDOUT:   %struct_type.base: type = struct_type {.base: %Bar} [concrete = constants.%struct_type.base.36d]
-// CHECK:STDOUT:   %complete_type.loc9: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.fff]
-// CHECK:STDOUT:   complete_type_witness = %complete_type.loc9
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.fff]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%Derived

+ 670 - 0
toolchain/check/testdata/interop/cpp/class/field.carbon

@@ -0,0 +1,670 @@
+// 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-FILE: toolchain/testing/testdata/min_prelude/primitives.carbon
+// EXTRA-ARGS: --dump-sem-ir-ranges=ignore
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/class/field.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/class/field.carbon
+
+// --- struct.h
+
+struct Struct {
+  int a;
+  int b;
+  int* _Nonnull p;
+};
+
+// --- use_struct_fields.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "struct.h";
+
+fn F(s: Cpp.Struct) -> (i32, i32, i32) {
+  //@dump-sem-ir-begin
+  return (s.a, s.b, *s.p);
+  //@dump-sem-ir-end
+}
+
+// --- union.h
+
+union Union {
+  int a;
+  int b;
+  int* _Nonnull p;
+};
+
+// --- use_union_fields.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "union.h";
+
+fn F(u: Cpp.Union) -> i32 {
+  //@dump-sem-ir-begin
+  return u.a;
+  //@dump-sem-ir-end
+}
+
+fn G(u: Cpp.Union) -> i32 {
+  //@dump-sem-ir-begin
+  return u.b;
+  //@dump-sem-ir-end
+}
+
+fn H(u: Cpp.Union) -> i32 {
+  //@dump-sem-ir-begin
+  return *u.p;
+  //@dump-sem-ir-end
+}
+
+// --- with_bitfields.h
+
+struct Struct {
+  int a;
+  int b : 2;
+};
+
+union Union {
+  int c;
+  int d : 3;
+};
+
+// --- use_non_bitfields_in_type_with_bitfields.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "with_bitfields.h";
+
+fn F(s: Cpp.Struct) -> i32 {
+  //@dump-sem-ir-begin
+  return s.a;
+  //@dump-sem-ir-end
+}
+
+fn G(s: Cpp.Union) -> i32 {
+  //@dump-sem-ir-begin
+  return s.c;
+  //@dump-sem-ir-end
+}
+
+// --- fail_todo_use_bitfields.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "with_bitfields.h";
+
+fn F(s: Cpp.Struct) -> i32 {
+  //@dump-sem-ir-begin
+  // CHECK:STDERR: fail_todo_use_bitfields.carbon:[[@LINE+11]]:10: error: semantics TODO: `Unsupported: Unhandled kind of field declaration` [SemanticsTodo]
+  // CHECK:STDERR:   return s.b;
+  // CHECK:STDERR:          ^~~
+  // CHECK:STDERR: fail_todo_use_bitfields.carbon:[[@LINE+8]]:10: note: in `Cpp` name lookup for `b` [InCppNameLookup]
+  // CHECK:STDERR:   return s.b;
+  // CHECK:STDERR:          ^~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_use_bitfields.carbon:[[@LINE+4]]:10: error: member name `b` not found in `Cpp.Struct` [MemberNameNotFoundInInstScope]
+  // CHECK:STDERR:   return s.b;
+  // CHECK:STDERR:          ^~~
+  // CHECK:STDERR:
+  return s.b;
+  //@dump-sem-ir-end
+}
+
+fn G(s: Cpp.Union) -> i32 {
+  //@dump-sem-ir-begin
+  // CHECK:STDERR: fail_todo_use_bitfields.carbon:[[@LINE+11]]:10: error: semantics TODO: `Unsupported: Unhandled kind of field declaration` [SemanticsTodo]
+  // CHECK:STDERR:   return s.d;
+  // CHECK:STDERR:          ^~~
+  // CHECK:STDERR: fail_todo_use_bitfields.carbon:[[@LINE+8]]:10: note: in `Cpp` name lookup for `d` [InCppNameLookup]
+  // CHECK:STDERR:   return s.d;
+  // CHECK:STDERR:          ^~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_use_bitfields.carbon:[[@LINE+4]]:10: error: member name `d` not found in `Cpp.Union` [MemberNameNotFoundInInstScope]
+  // CHECK:STDERR:   return s.d;
+  // CHECK:STDERR:          ^~~
+  // CHECK:STDERR:
+  return s.d;
+  //@dump-sem-ir-end
+}
+
+// CHECK:STDOUT: --- use_struct_fields.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Struct: type = class_type @Struct [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:   %Struct.elem.86b: type = unbound_element_type %Struct, %i32 [concrete]
+// CHECK:STDOUT:   %ptr.235: type = ptr_type %i32 [concrete]
+// CHECK:STDOUT:   %Struct.elem.765: type = unbound_element_type %Struct, %ptr.235 [concrete]
+// CHECK:STDOUT:   %.550: type = custom_layout_type {size=16, align=8, .a@0: %i32, .b@4: %i32, .p@8: %ptr.235} [concrete]
+// CHECK:STDOUT:   %complete_type.8cf: <witness> = complete_type_witness %.550 [concrete]
+// CHECK:STDOUT:   %pattern_type.60c: type = pattern_type %Struct [concrete]
+// CHECK:STDOUT:   %tuple.type.ff9: type = tuple_type (type, type, type) [concrete]
+// CHECK:STDOUT:   %tuple.type.189: type = tuple_type (%i32, %i32, %i32) [concrete]
+// CHECK:STDOUT:   %pattern_type.b5a: type = pattern_type %tuple.type.189 [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.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:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .Struct = %Struct.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Struct.decl: type = class_decl @Struct [concrete = constants.%Struct] {} {}
+// CHECK:STDOUT:   %Core.Int: %Int.type = import_ref Core//prelude/parts/int, Int, loaded [concrete = constants.%Int.generic]
+// 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 "struct.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %s.patt: %pattern_type.60c = binding_pattern s [concrete]
+// CHECK:STDOUT:     %s.param_patt: %pattern_type.60c = value_param_pattern %s.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %return.patt: %pattern_type.b5a = return_slot_pattern [concrete]
+// CHECK:STDOUT:     %return.param_patt: %pattern_type.b5a = out_param_pattern %return.patt, call_param1 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %int_32.loc6_25: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:     %i32.loc6_25: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:     %int_32.loc6_30: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:     %i32.loc6_30: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:     %int_32.loc6_35: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:     %i32.loc6_35: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:     %.loc6_38.1: %tuple.type.ff9 = tuple_literal (%i32.loc6_25, %i32.loc6_30, %i32.loc6_35)
+// CHECK:STDOUT:     %.loc6_38.2: type = converted %.loc6_38.1, constants.%tuple.type.189 [concrete = constants.%tuple.type.189]
+// CHECK:STDOUT:     %s.param: %Struct = value_param call_param0
+// CHECK:STDOUT:     %.loc6_12: type = splice_block %Struct.ref [concrete = constants.%Struct] {
+// CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:       %Struct.ref: type = name_ref Struct, imports.%Struct.decl [concrete = constants.%Struct]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %s: %Struct = bind_name s, %s.param
+// CHECK:STDOUT:     %return.param: ref %tuple.type.189 = out_param call_param1
+// CHECK:STDOUT:     %return: ref %tuple.type.189 = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Struct {
+// CHECK:STDOUT:   %int_32.1: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:   %i32.1: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   %.1: %Struct.elem.86b = field_decl a, element0 [concrete]
+// CHECK:STDOUT:   %int_32.2: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:   %i32.2: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   %.2: %Struct.elem.86b = field_decl b, element1 [concrete]
+// CHECK:STDOUT:   %int_32.3: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:   %i32.3: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   %.3: %Struct.elem.765 = field_decl p, element2 [concrete]
+// CHECK:STDOUT:   %.4: type = custom_layout_type {size=16, align=8, .a@0: %i32, .b@4: %i32, .p@8: %ptr.235} [concrete = constants.%.550]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %.4 [concrete = constants.%complete_type.8cf]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Struct
+// CHECK:STDOUT:   .a = %.1
+// CHECK:STDOUT:   .b = %.2
+// CHECK:STDOUT:   .p = %.3
+// CHECK:STDOUT:   import Cpp//...
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%s.param: %Struct) -> %return.param: %tuple.type.189 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %s.ref.loc8_11: %Struct = name_ref s, %s
+// CHECK:STDOUT:   %a.ref: %Struct.elem.86b = name_ref a, @Struct.%.1 [concrete = @Struct.%.1]
+// CHECK:STDOUT:   %.loc8_12.1: ref %i32 = class_element_access %s.ref.loc8_11, element0
+// CHECK:STDOUT:   %.loc8_12.2: %i32 = bind_value %.loc8_12.1
+// CHECK:STDOUT:   %s.ref.loc8_16: %Struct = name_ref s, %s
+// CHECK:STDOUT:   %b.ref: %Struct.elem.86b = name_ref b, @Struct.%.2 [concrete = @Struct.%.2]
+// CHECK:STDOUT:   %.loc8_17.1: ref %i32 = class_element_access %s.ref.loc8_16, element1
+// CHECK:STDOUT:   %.loc8_17.2: %i32 = bind_value %.loc8_17.1
+// CHECK:STDOUT:   %s.ref.loc8_22: %Struct = name_ref s, %s
+// CHECK:STDOUT:   %p.ref: %Struct.elem.765 = name_ref p, @Struct.%.3 [concrete = @Struct.%.3]
+// CHECK:STDOUT:   %.loc8_23.1: ref %ptr.235 = class_element_access %s.ref.loc8_22, element2
+// CHECK:STDOUT:   %.loc8_23.2: %ptr.235 = bind_value %.loc8_23.1
+// CHECK:STDOUT:   %.loc8_21.1: ref %i32 = deref %.loc8_23.2
+// CHECK:STDOUT:   %.loc8_25.1: %tuple.type.189 = tuple_literal (%.loc8_12.2, %.loc8_17.2, %.loc8_21.1)
+// CHECK:STDOUT:   %tuple.elem0: ref %i32 = tuple_access %return, element0
+// CHECK:STDOUT:   %.loc8_25.2: init %i32 = initialize_from %.loc8_12.2 to %tuple.elem0
+// CHECK:STDOUT:   %tuple.elem1: ref %i32 = tuple_access %return, element1
+// CHECK:STDOUT:   %.loc8_25.3: init %i32 = initialize_from %.loc8_17.2 to %tuple.elem1
+// CHECK:STDOUT:   %.loc8_21.2: %i32 = bind_value %.loc8_21.1
+// CHECK:STDOUT:   %tuple.elem2: ref %i32 = tuple_access %return, element2
+// CHECK:STDOUT:   %.loc8_25.4: init %i32 = initialize_from %.loc8_21.2 to %tuple.elem2
+// CHECK:STDOUT:   %.loc8_25.5: init %tuple.type.189 = tuple_init (%.loc8_25.2, %.loc8_25.3, %.loc8_25.4) to %return
+// CHECK:STDOUT:   %.loc8_26: init %tuple.type.189 = converted %.loc8_25.1, %.loc8_25.5
+// CHECK:STDOUT:   return %.loc8_26 to %return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_union_fields.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Union: type = class_type @Union [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:   %Union.elem.041: type = unbound_element_type %Union, %i32 [concrete]
+// CHECK:STDOUT:   %ptr.235: type = ptr_type %i32 [concrete]
+// CHECK:STDOUT:   %Union.elem.92a: type = unbound_element_type %Union, %ptr.235 [concrete]
+// CHECK:STDOUT:   %.c84: type = custom_layout_type {size=8, align=8, .a@0: %i32, .b@0: %i32, .p@0: %ptr.235} [concrete]
+// CHECK:STDOUT:   %complete_type.1b0: <witness> = complete_type_witness %.c84 [concrete]
+// CHECK:STDOUT:   %pattern_type.a90: type = pattern_type %Union [concrete]
+// CHECK:STDOUT:   %pattern_type.7ce: type = pattern_type %i32 [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
+// CHECK:STDOUT:   %H.type: type = fn_type @H [concrete]
+// CHECK:STDOUT:   %H: %H.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:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .Union = %Union.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Union.decl: type = class_decl @Union [concrete = constants.%Union] {} {}
+// CHECK:STDOUT:   %Core.Int: %Int.type = import_ref Core//prelude/parts/int, Int, loaded [concrete = constants.%Int.generic]
+// 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:     .G = %G.decl
+// CHECK:STDOUT:     .H = %H.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "union.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %u.patt: %pattern_type.a90 = binding_pattern u [concrete]
+// CHECK:STDOUT:     %u.param_patt: %pattern_type.a90 = value_param_pattern %u.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %return.patt: %pattern_type.7ce = return_slot_pattern [concrete]
+// CHECK:STDOUT:     %return.param_patt: %pattern_type.7ce = out_param_pattern %return.patt, call_param1 [concrete]
+// CHECK:STDOUT:   } {
+// 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:     %u.param: %Union = value_param call_param0
+// CHECK:STDOUT:     %.loc6: type = splice_block %Union.ref [concrete = constants.%Union] {
+// CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:       %Union.ref: type = name_ref Union, imports.%Union.decl [concrete = constants.%Union]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %u: %Union = bind_name u, %u.param
+// CHECK:STDOUT:     %return.param: ref %i32 = out_param call_param1
+// CHECK:STDOUT:     %return: ref %i32 = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {
+// CHECK:STDOUT:     %u.patt: %pattern_type.a90 = binding_pattern u [concrete]
+// CHECK:STDOUT:     %u.param_patt: %pattern_type.a90 = value_param_pattern %u.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %return.patt: %pattern_type.7ce = return_slot_pattern [concrete]
+// CHECK:STDOUT:     %return.param_patt: %pattern_type.7ce = out_param_pattern %return.patt, call_param1 [concrete]
+// CHECK:STDOUT:   } {
+// 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:     %u.param: %Union = value_param call_param0
+// CHECK:STDOUT:     %.loc12: type = splice_block %Union.ref [concrete = constants.%Union] {
+// CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:       %Union.ref: type = name_ref Union, imports.%Union.decl [concrete = constants.%Union]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %u: %Union = bind_name u, %u.param
+// CHECK:STDOUT:     %return.param: ref %i32 = out_param call_param1
+// CHECK:STDOUT:     %return: ref %i32 = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %H.decl: %H.type = fn_decl @H [concrete = constants.%H] {
+// CHECK:STDOUT:     %u.patt: %pattern_type.a90 = binding_pattern u [concrete]
+// CHECK:STDOUT:     %u.param_patt: %pattern_type.a90 = value_param_pattern %u.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %return.patt: %pattern_type.7ce = return_slot_pattern [concrete]
+// CHECK:STDOUT:     %return.param_patt: %pattern_type.7ce = out_param_pattern %return.patt, call_param1 [concrete]
+// CHECK:STDOUT:   } {
+// 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:     %u.param: %Union = value_param call_param0
+// CHECK:STDOUT:     %.loc18: type = splice_block %Union.ref [concrete = constants.%Union] {
+// CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:       %Union.ref: type = name_ref Union, imports.%Union.decl [concrete = constants.%Union]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %u: %Union = bind_name u, %u.param
+// CHECK:STDOUT:     %return.param: ref %i32 = out_param call_param1
+// CHECK:STDOUT:     %return: ref %i32 = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Union {
+// CHECK:STDOUT:   %int_32.1: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:   %i32.1: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   %.1: %Union.elem.041 = field_decl a, element0 [concrete]
+// CHECK:STDOUT:   %int_32.2: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:   %i32.2: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   %.2: %Union.elem.041 = field_decl b, element1 [concrete]
+// CHECK:STDOUT:   %int_32.3: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:   %i32.3: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   %.3: %Union.elem.92a = field_decl p, element2 [concrete]
+// CHECK:STDOUT:   %.4: type = custom_layout_type {size=8, align=8, .a@0: %i32, .b@0: %i32, .p@0: %ptr.235} [concrete = constants.%.c84]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %.4 [concrete = constants.%complete_type.1b0]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Union
+// CHECK:STDOUT:   .a = %.1
+// CHECK:STDOUT:   .b = %.2
+// CHECK:STDOUT:   .p = %.3
+// CHECK:STDOUT:   import Cpp//...
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%u.param: %Union) -> %i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %u.ref: %Union = name_ref u, %u
+// CHECK:STDOUT:   %a.ref: %Union.elem.041 = name_ref a, @Union.%.1 [concrete = @Union.%.1]
+// CHECK:STDOUT:   %.loc8_11.1: ref %i32 = class_element_access %u.ref, element0
+// CHECK:STDOUT:   %.loc8_11.2: %i32 = bind_value %.loc8_11.1
+// CHECK:STDOUT:   return %.loc8_11.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G(%u.param: %Union) -> %i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %u.ref: %Union = name_ref u, %u
+// CHECK:STDOUT:   %b.ref: %Union.elem.041 = name_ref b, @Union.%.2 [concrete = @Union.%.2]
+// CHECK:STDOUT:   %.loc14_11.1: ref %i32 = class_element_access %u.ref, element1
+// CHECK:STDOUT:   %.loc14_11.2: %i32 = bind_value %.loc14_11.1
+// CHECK:STDOUT:   return %.loc14_11.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @H(%u.param: %Union) -> %i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %u.ref: %Union = name_ref u, %u
+// CHECK:STDOUT:   %p.ref: %Union.elem.92a = name_ref p, @Union.%.3 [concrete = @Union.%.3]
+// CHECK:STDOUT:   %.loc20_12.1: ref %ptr.235 = class_element_access %u.ref, element2
+// CHECK:STDOUT:   %.loc20_12.2: %ptr.235 = bind_value %.loc20_12.1
+// CHECK:STDOUT:   %.loc20_10.1: ref %i32 = deref %.loc20_12.2
+// CHECK:STDOUT:   %.loc20_10.2: %i32 = bind_value %.loc20_10.1
+// CHECK:STDOUT:   return %.loc20_10.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_non_bitfields_in_type_with_bitfields.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Struct: type = class_type @Struct [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:   %Struct.elem: type = unbound_element_type %Struct, %i32 [concrete]
+// CHECK:STDOUT:   %.f9e: type = custom_layout_type {size=8, align=4, .a@0: %i32} [concrete]
+// CHECK:STDOUT:   %complete_type.d3c: <witness> = complete_type_witness %.f9e [concrete]
+// CHECK:STDOUT:   %pattern_type.60c: type = pattern_type %Struct [concrete]
+// CHECK:STDOUT:   %pattern_type.7ce: type = pattern_type %i32 [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Union: type = class_type @Union [concrete]
+// CHECK:STDOUT:   %Union.elem: type = unbound_element_type %Union, %i32 [concrete]
+// CHECK:STDOUT:   %.891: type = custom_layout_type {size=4, align=4, .c@0: %i32} [concrete]
+// CHECK:STDOUT:   %complete_type.591: <witness> = complete_type_witness %.891 [concrete]
+// CHECK:STDOUT:   %pattern_type.a90: type = pattern_type %Union [concrete]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
+// CHECK:STDOUT:   %G: %G.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:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .Struct = %Struct.decl
+// CHECK:STDOUT:     .Union = %Union.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Struct.decl: type = class_decl @Struct [concrete = constants.%Struct] {} {}
+// CHECK:STDOUT:   %Core.Int: %Int.type = import_ref Core//prelude/parts/int, Int, loaded [concrete = constants.%Int.generic]
+// CHECK:STDOUT:   %Union.decl: type = class_decl @Union [concrete = constants.%Union] {} {}
+// 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:     .G = %G.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "with_bitfields.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %s.patt: %pattern_type.60c = binding_pattern s [concrete]
+// CHECK:STDOUT:     %s.param_patt: %pattern_type.60c = value_param_pattern %s.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %return.patt: %pattern_type.7ce = return_slot_pattern [concrete]
+// CHECK:STDOUT:     %return.param_patt: %pattern_type.7ce = out_param_pattern %return.patt, call_param1 [concrete]
+// CHECK:STDOUT:   } {
+// 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:     %s.param: %Struct = value_param call_param0
+// CHECK:STDOUT:     %.loc6: type = splice_block %Struct.ref [concrete = constants.%Struct] {
+// CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:       %Struct.ref: type = name_ref Struct, imports.%Struct.decl [concrete = constants.%Struct]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %s: %Struct = bind_name s, %s.param
+// CHECK:STDOUT:     %return.param: ref %i32 = out_param call_param1
+// CHECK:STDOUT:     %return: ref %i32 = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {
+// CHECK:STDOUT:     %s.patt: %pattern_type.a90 = binding_pattern s [concrete]
+// CHECK:STDOUT:     %s.param_patt: %pattern_type.a90 = value_param_pattern %s.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %return.patt: %pattern_type.7ce = return_slot_pattern [concrete]
+// CHECK:STDOUT:     %return.param_patt: %pattern_type.7ce = out_param_pattern %return.patt, call_param1 [concrete]
+// CHECK:STDOUT:   } {
+// 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:     %s.param: %Union = value_param call_param0
+// CHECK:STDOUT:     %.loc12: type = splice_block %Union.ref [concrete = constants.%Union] {
+// CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:       %Union.ref: type = name_ref Union, imports.%Union.decl [concrete = constants.%Union]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %s: %Union = bind_name s, %s.param
+// CHECK:STDOUT:     %return.param: ref %i32 = out_param call_param1
+// CHECK:STDOUT:     %return: ref %i32 = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Struct {
+// 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:   %.1: %Struct.elem = field_decl a, element0 [concrete]
+// CHECK:STDOUT:   %.2: type = custom_layout_type {size=8, align=4, .a@0: %i32} [concrete = constants.%.f9e]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %.2 [concrete = constants.%complete_type.d3c]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Struct
+// CHECK:STDOUT:   .a = %.1
+// CHECK:STDOUT:   import Cpp//...
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Union {
+// 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:   %.1: %Union.elem = field_decl c, element0 [concrete]
+// CHECK:STDOUT:   %.2: type = custom_layout_type {size=4, align=4, .c@0: %i32} [concrete = constants.%.891]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %.2 [concrete = constants.%complete_type.591]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Union
+// CHECK:STDOUT:   .c = %.1
+// CHECK:STDOUT:   import Cpp//...
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%s.param: %Struct) -> %i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %s.ref: %Struct = name_ref s, %s
+// CHECK:STDOUT:   %a.ref: %Struct.elem = name_ref a, @Struct.%.1 [concrete = @Struct.%.1]
+// CHECK:STDOUT:   %.loc8_11.1: ref %i32 = class_element_access %s.ref, element0
+// CHECK:STDOUT:   %.loc8_11.2: %i32 = bind_value %.loc8_11.1
+// CHECK:STDOUT:   return %.loc8_11.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G(%s.param: %Union) -> %i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %s.ref: %Union = name_ref s, %s
+// CHECK:STDOUT:   %c.ref: %Union.elem = name_ref c, @Union.%.1 [concrete = @Union.%.1]
+// CHECK:STDOUT:   %.loc14_11.1: ref %i32 = class_element_access %s.ref, element0
+// CHECK:STDOUT:   %.loc14_11.2: %i32 = bind_value %.loc14_11.1
+// CHECK:STDOUT:   return %.loc14_11.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_use_bitfields.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Struct: type = class_type @Struct [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:   %Struct.elem: type = unbound_element_type %Struct, %i32 [concrete]
+// CHECK:STDOUT:   %.f9e: type = custom_layout_type {size=8, align=4, .a@0: %i32} [concrete]
+// CHECK:STDOUT:   %complete_type.d3c: <witness> = complete_type_witness %.f9e [concrete]
+// CHECK:STDOUT:   %pattern_type.60c: type = pattern_type %Struct [concrete]
+// CHECK:STDOUT:   %pattern_type.7ce: type = pattern_type %i32 [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Union: type = class_type @Union [concrete]
+// CHECK:STDOUT:   %Union.elem: type = unbound_element_type %Union, %i32 [concrete]
+// CHECK:STDOUT:   %.891: type = custom_layout_type {size=4, align=4, .c@0: %i32} [concrete]
+// CHECK:STDOUT:   %complete_type.591: <witness> = complete_type_witness %.891 [concrete]
+// CHECK:STDOUT:   %pattern_type.a90: type = pattern_type %Union [concrete]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
+// CHECK:STDOUT:   %G: %G.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:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .Struct = %Struct.decl
+// CHECK:STDOUT:     .Union = %Union.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Struct.decl: type = class_decl @Struct [concrete = constants.%Struct] {} {}
+// CHECK:STDOUT:   %Core.Int: %Int.type = import_ref Core//prelude/parts/int, Int, loaded [concrete = constants.%Int.generic]
+// CHECK:STDOUT:   %Union.decl: type = class_decl @Union [concrete = constants.%Union] {} {}
+// 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:     .G = %G.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "with_bitfields.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %s.patt: %pattern_type.60c = binding_pattern s [concrete]
+// CHECK:STDOUT:     %s.param_patt: %pattern_type.60c = value_param_pattern %s.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %return.patt: %pattern_type.7ce = return_slot_pattern [concrete]
+// CHECK:STDOUT:     %return.param_patt: %pattern_type.7ce = out_param_pattern %return.patt, call_param1 [concrete]
+// CHECK:STDOUT:   } {
+// 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:     %s.param: %Struct = value_param call_param0
+// CHECK:STDOUT:     %.loc6: type = splice_block %Struct.ref [concrete = constants.%Struct] {
+// CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:       %Struct.ref: type = name_ref Struct, imports.%Struct.decl [concrete = constants.%Struct]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %s: %Struct = bind_name s, %s.param
+// CHECK:STDOUT:     %return.param: ref %i32 = out_param call_param1
+// CHECK:STDOUT:     %return: ref %i32 = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {
+// CHECK:STDOUT:     %s.patt: %pattern_type.a90 = binding_pattern s [concrete]
+// CHECK:STDOUT:     %s.param_patt: %pattern_type.a90 = value_param_pattern %s.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %return.patt: %pattern_type.7ce = return_slot_pattern [concrete]
+// CHECK:STDOUT:     %return.param_patt: %pattern_type.7ce = out_param_pattern %return.patt, call_param1 [concrete]
+// CHECK:STDOUT:   } {
+// 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:     %s.param: %Union = value_param call_param0
+// CHECK:STDOUT:     %.loc23: type = splice_block %Union.ref [concrete = constants.%Union] {
+// CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:       %Union.ref: type = name_ref Union, imports.%Union.decl [concrete = constants.%Union]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %s: %Union = bind_name s, %s.param
+// CHECK:STDOUT:     %return.param: ref %i32 = out_param call_param1
+// CHECK:STDOUT:     %return: ref %i32 = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Struct {
+// 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:   %.1: %Struct.elem = field_decl a, element0 [concrete]
+// CHECK:STDOUT:   %.2: type = custom_layout_type {size=8, align=4, .a@0: %i32} [concrete = constants.%.f9e]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %.2 [concrete = constants.%complete_type.d3c]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Struct
+// CHECK:STDOUT:   .b = <poisoned>
+// CHECK:STDOUT:   import Cpp//...
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Union {
+// 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:   %.1: %Union.elem = field_decl c, element0 [concrete]
+// CHECK:STDOUT:   %.2: type = custom_layout_type {size=4, align=4, .c@0: %i32} [concrete = constants.%.891]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %.2 [concrete = constants.%complete_type.591]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Union
+// CHECK:STDOUT:   .d = <poisoned>
+// CHECK:STDOUT:   import Cpp//...
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%s.param: %Struct) -> %i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %s.ref: %Struct = name_ref s, %s
+// CHECK:STDOUT:   %b.ref: <error> = name_ref b, <error> [concrete = <error>]
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G(%s.param: %Union) -> %i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %s.ref: %Union = name_ref s, %s
+// CHECK:STDOUT:   %d.ref: <error> = name_ref d, <error> [concrete = <error>]
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 12 - 12
toolchain/check/testdata/interop/cpp/class/method.carbon

@@ -226,8 +226,6 @@ fn Ref(p: Cpp.HasQualifiers*) {
 // CHECK:STDOUT:     %v.param: %HasQualifiers = value_param call_param0
 // CHECK:STDOUT:     %.loc6_12: type = splice_block %HasQualifiers.ref.loc6_12 [concrete = constants.%HasQualifiers] {
 // CHECK:STDOUT:       %Cpp.ref.loc6_9: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// 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]
 // CHECK:STDOUT:       %HasQualifiers.ref.loc6_12: type = name_ref HasQualifiers, imports.%HasQualifiers.decl [concrete = constants.%HasQualifiers]
 // CHECK:STDOUT:     }
 // CHECK:STDOUT:     %v: %HasQualifiers = bind_name v, %v.param
@@ -242,7 +240,9 @@ fn Ref(p: Cpp.HasQualifiers*) {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @HasQualifiers {
-// CHECK:STDOUT:   complete_type_witness = @F.%complete_type
+// 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]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%HasQualifiers
@@ -378,8 +378,6 @@ fn Ref(p: Cpp.HasQualifiers*) {
 // CHECK:STDOUT:     %v.param: %HasQualifiers = value_param call_param0
 // CHECK:STDOUT:     %.loc6: type = splice_block %HasQualifiers.ref [concrete = constants.%HasQualifiers] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// 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:       %HasQualifiers.ref: type = name_ref HasQualifiers, imports.%HasQualifiers.decl [concrete = constants.%HasQualifiers]
 // CHECK:STDOUT:     }
 // CHECK:STDOUT:     %v: %HasQualifiers = bind_name v, %v.param
@@ -387,7 +385,9 @@ fn Ref(p: Cpp.HasQualifiers*) {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @HasQualifiers {
-// CHECK:STDOUT:   complete_type_witness = @Value.%complete_type
+// 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:   .Self = constants.%HasQualifiers
@@ -500,8 +500,6 @@ fn Ref(p: Cpp.HasQualifiers*) {
 // CHECK:STDOUT:     %p.param: %ptr.ec3 = value_param call_param0
 // CHECK:STDOUT:     %.loc6: type = splice_block %ptr [concrete = constants.%ptr.ec3] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// 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]
 // CHECK:STDOUT:       %HasQualifiers.ref: type = name_ref HasQualifiers, imports.%HasQualifiers.decl [concrete = constants.%HasQualifiers]
 // CHECK:STDOUT:       %ptr: type = ptr_type %HasQualifiers.ref [concrete = constants.%ptr.ec3]
 // CHECK:STDOUT:     }
@@ -510,7 +508,9 @@ fn Ref(p: Cpp.HasQualifiers*) {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @HasQualifiers {
-// CHECK:STDOUT:   complete_type_witness = @Ref.%complete_type
+// 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]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%HasQualifiers
@@ -572,8 +572,6 @@ fn Ref(p: Cpp.HasQualifiers*) {
 // CHECK:STDOUT:     %p.param: %ptr.ec3 = value_param call_param0
 // CHECK:STDOUT:     %.loc6: type = splice_block %ptr [concrete = constants.%ptr.ec3] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// 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]
 // CHECK:STDOUT:       %HasQualifiers.ref: type = name_ref HasQualifiers, imports.%HasQualifiers.decl [concrete = constants.%HasQualifiers]
 // CHECK:STDOUT:       %ptr: type = ptr_type %HasQualifiers.ref [concrete = constants.%ptr.ec3]
 // CHECK:STDOUT:     }
@@ -582,7 +580,9 @@ fn Ref(p: Cpp.HasQualifiers*) {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @HasQualifiers {
-// CHECK:STDOUT:   complete_type_witness = @Ref.%complete_type
+// 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]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%HasQualifiers

+ 129 - 75
toolchain/check/testdata/interop/cpp/class/struct.carbon

@@ -18,23 +18,32 @@
 
 struct Bar;
 
-// --- fail_todo_import_declaration.carbon
+// --- import_declaration.carbon
 
 library "[[@TEST_NAME]]";
 
 import Cpp library "declaration.h";
 
 //@dump-sem-ir-begin
-// CHECK:STDERR: fail_todo_import_declaration.carbon:[[@LINE+7]]:13: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
-// CHECK:STDERR: fn MyF(bar: Cpp.Bar*);
-// CHECK:STDERR:             ^~~~~~~
-// CHECK:STDERR: fail_todo_import_declaration.carbon:[[@LINE+4]]:13: note: in `Cpp` name lookup for `Bar` [InCppNameLookup]
-// CHECK:STDERR: fn MyF(bar: Cpp.Bar*);
-// CHECK:STDERR:             ^~~~~~~
-// CHECK:STDERR:
 fn MyF(bar: Cpp.Bar*);
 //@dump-sem-ir-end
 
+// --- fail_use_declaration_as_definition.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "declaration.h";
+
+fn MyF() {
+  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE+6]]:12: error: binding pattern has incomplete type `Bar` in name binding declaration [IncompleteTypeInBindingDecl]
+  // CHECK:STDERR:   var bar: Cpp.Bar;
+  // CHECK:STDERR:            ^~~~~~~
+  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE-6]]:1: in import [InImport]
+  // CHECK:STDERR: ./declaration.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR:
+  var bar: Cpp.Bar;
+}
+
 // ============================================================================
 // Definition
 // ============================================================================
@@ -112,6 +121,7 @@ library "[[@TEST_NAME]]";
 import Cpp library "private_static_member_function.h";
 
 fn MyF() {
+  // TODO: Should fail because `foo` is inaccessible.
   Cpp.Bar.foo();
 }
 
@@ -122,7 +132,7 @@ fn MyF() {
 // --- public_static_data_member.h
 
 struct Bar {
-  static Bar* foo;
+  static Bar* _Nonnull foo;
 };
 
 // --- fail_todo_import_public_static_data_member.carbon
@@ -155,10 +165,10 @@ fn MyF() {
 // --- public_data_member.h
 
 struct Bar {
-  Bar* foo;
+  Bar* _Nonnull foo;
 };
 
-// --- fail_todo_import_public_data_member.carbon
+// --- import_public_data_member.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -166,17 +176,6 @@ import Cpp library "public_data_member.h";
 
 //@dump-sem-ir-begin
 fn MyF(bar : Cpp.Bar*) {
-  // CHECK:STDERR: fail_todo_import_public_data_member.carbon:[[@LINE+11]]:27: error: semantics TODO: `Unsupported: Declaration type Field` [SemanticsTodo]
-  // CHECK:STDERR:   let foo_bar: Cpp.Bar* = bar->foo;
-  // CHECK:STDERR:                           ^~~~~~~~
-  // CHECK:STDERR: fail_todo_import_public_data_member.carbon:[[@LINE+8]]:27: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   let foo_bar: Cpp.Bar* = bar->foo;
-  // CHECK:STDERR:                           ^~~~~~~~
-  // CHECK:STDERR:
-  // CHECK:STDERR: fail_todo_import_public_data_member.carbon:[[@LINE+4]]:27: error: member name `foo` not found in `Cpp.Bar` [MemberNameNotFoundInInstScope]
-  // CHECK:STDERR:   let foo_bar: Cpp.Bar* = bar->foo;
-  // CHECK:STDERR:                           ^~~~~~~~
-  // CHECK:STDERR:
   let foo_bar: Cpp.Bar* = bar->foo;
 }
 //@dump-sem-ir-end
@@ -225,7 +224,7 @@ library "[[@TEST_NAME]]";
 import Cpp library "inheritance_pointers.h";
 
 //@dump-sem-ir-begin
-fn MyF1(bar : Cpp.Bar1*);
+fn MyF1(bar: Cpp.Bar1*);
 // TODO: Support C++ inheritance.
 // CHECK:STDERR: fail_todo_import_inheritance_pointers.carbon:[[@LINE+10]]:33: error: cannot implicitly convert expression of type `Cpp.Bar2*` to `Cpp.Bar1*` [ConversionFailure]
 // CHECK:STDERR: fn MyF2(bar : Cpp.Bar2*) { MyF1(bar); }
@@ -234,8 +233,8 @@ fn MyF1(bar : Cpp.Bar1*);
 // CHECK:STDERR: fn MyF2(bar : Cpp.Bar2*) { MyF1(bar); }
 // CHECK:STDERR:                                 ^~~
 // CHECK:STDERR: fail_todo_import_inheritance_pointers.carbon:[[@LINE-8]]:9: note: initializing function parameter [InCallToFunctionParam]
-// CHECK:STDERR: fn MyF1(bar : Cpp.Bar1*);
-// CHECK:STDERR:         ^~~~~~~~~~~~~~~
+// CHECK:STDERR: fn MyF1(bar: Cpp.Bar1*);
+// CHECK:STDERR:         ^~~~~~~~~~~~~~
 // CHECK:STDERR:
 fn MyF2(bar : Cpp.Bar2*) { MyF1(bar); }
 //@dump-sem-ir-end
@@ -247,24 +246,36 @@ fn MyF2(bar : Cpp.Bar2*) { MyF1(bar); }
 // --- dynamic.h
 
 struct Bar {
-  virtual ~Bar();
+  virtual void f();
 };
 
-// --- fail_todo_import_dynamic.carbon
+// --- import_dynamic.carbon
 
 library "[[@TEST_NAME]]";
 
 import Cpp library "dynamic.h";
 
 //@dump-sem-ir-begin
-// CHECK:STDERR: fail_todo_import_dynamic.carbon:[[@LINE+7]]:14: error: semantics TODO: `Unsupported: Dynamic Class` [SemanticsTodo]
-// CHECK:STDERR: fn MyF(bar : Cpp.Bar*);
-// CHECK:STDERR:              ^~~~~~~
-// CHECK:STDERR: fail_todo_import_dynamic.carbon:[[@LINE+4]]:14: note: in `Cpp` name lookup for `Bar` [InCppNameLookup]
-// CHECK:STDERR: fn MyF(bar : Cpp.Bar*);
-// CHECK:STDERR:              ^~~~~~~
-// CHECK:STDERR:
-fn MyF(bar : Cpp.Bar*);
+fn MyF(bar: Cpp.Bar*);
+//@dump-sem-ir-end
+
+// --- fail_todo_call_dynamic.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "dynamic.h";
+
+//@dump-sem-ir-begin
+fn MyF(bar: Cpp.Bar*) {
+  // CHECK:STDERR: fail_todo_call_dynamic.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Virtual function` [SemanticsTodo]
+  // CHECK:STDERR:   bar->f();
+  // CHECK:STDERR:   ^~~~~~
+  // CHECK:STDERR: fail_todo_call_dynamic.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `f` [InCppNameLookup]
+  // CHECK:STDERR:   bar->f();
+  // CHECK:STDERR:   ^~~~~~
+  // CHECK:STDERR:
+  bar->f();
+}
 //@dump-sem-ir-end
 
 // ============================================================================
@@ -313,6 +324,7 @@ class Derived {
 }
 
 fn MyF() {
+  // TODO: Should fail because `foo` is inaccessible.
   Derived.foo();
 }
 
@@ -346,36 +358,40 @@ import Cpp library "template.h";
 fn MyF(bar: Cpp.Bar*);
 //@dump-sem-ir-end
 
-// CHECK:STDOUT: --- fail_todo_import_declaration.carbon
+// CHECK:STDOUT: --- import_declaration.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Bar: type = class_type @Bar [concrete]
+// CHECK:STDOUT:   %ptr: type = ptr_type %Bar [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %ptr [concrete]
 // CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
 // CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .Bar = <error>
+// CHECK:STDOUT:     .Bar = %Bar.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Bar.decl: type = class_decl @Bar [concrete = constants.%Bar] {} {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {
-// CHECK:STDOUT:     %bar.patt: <error> = binding_pattern bar [concrete]
-// CHECK:STDOUT:     %bar.param_patt: <error> = value_param_pattern %bar.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %bar.patt: %pattern_type = binding_pattern bar [concrete]
+// CHECK:STDOUT:     %bar.param_patt: %pattern_type = value_param_pattern %bar.patt, call_param0 [concrete]
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %bar.param: <error> = value_param call_param0
-// CHECK:STDOUT:     %.loc14: type = splice_block %ptr [concrete = <error>] {
+// CHECK:STDOUT:     %bar.param: %ptr = value_param call_param0
+// CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       %Bar.ref: <error> = name_ref Bar, <error> [concrete = <error>]
-// CHECK:STDOUT:       %ptr: type = ptr_type <error> [concrete = <error>]
+// CHECK:STDOUT:       %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:       %ptr: type = ptr_type %Bar.ref [concrete = constants.%ptr]
 // CHECK:STDOUT:     }
-// CHECK:STDOUT:     %bar: <error> = bind_name bar, %bar.param
+// CHECK:STDOUT:     %bar: %ptr = bind_name bar, %bar.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @MyF(%bar.param: <error>);
+// CHECK:STDOUT: fn @MyF(%bar.param: %ptr);
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- import_definition.carbon
 // CHECK:STDOUT:
@@ -403,7 +419,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %bar.param: %ptr = value_param call_param0
 // CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       <elided>
 // CHECK:STDOUT:       %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:       %ptr: type = ptr_type %Bar.ref [concrete = constants.%ptr]
 // CHECK:STDOUT:     }
@@ -439,7 +454,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %bar.param: %ptr = value_param call_param0
 // CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       <elided>
 // CHECK:STDOUT:       %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:       %ptr: type = ptr_type %Bar.ref [concrete = constants.%ptr]
 // CHECK:STDOUT:     }
@@ -470,7 +484,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT: fn @MyF() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:   %foo.ref: %Bar.foo.type = name_ref foo, imports.%Bar.foo.decl [concrete = constants.%Bar.foo]
 // CHECK:STDOUT:   %Bar.foo.call: init %empty_tuple.type = call %foo.ref()
@@ -503,7 +516,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
 // CHECK:STDOUT:   %.loc19: type = splice_block %ptr [concrete = constants.%ptr.f68] {
 // CHECK:STDOUT:     %Cpp.ref.loc19_12: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:     %Bar.ref.loc19_15: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:     %ptr: type = ptr_type %Bar.ref.loc19_15 [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:   }
@@ -511,11 +523,12 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_public_data_member.carbon
+// CHECK:STDOUT: --- import_public_data_member.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %Bar: type = class_type @Bar [concrete]
 // CHECK:STDOUT:   %ptr.f68: type = ptr_type %Bar [concrete]
+// CHECK:STDOUT:   %Bar.elem: type = unbound_element_type %Bar, %ptr.f68 [concrete]
 // CHECK:STDOUT:   %pattern_type: type = pattern_type %ptr.f68 [concrete]
 // CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
 // CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
@@ -537,7 +550,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %bar.param: %ptr.f68 = value_param call_param0
 // CHECK:STDOUT:     %.loc7: type = splice_block %ptr.loc7 [concrete = constants.%ptr.f68] {
 // CHECK:STDOUT:       %Cpp.ref.loc7: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       <elided>
 // CHECK:STDOUT:       %Bar.ref.loc7: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:       %ptr.loc7: type = ptr_type %Bar.ref.loc7 [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:     }
@@ -551,14 +563,16 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %foo_bar.patt: %pattern_type = binding_pattern foo_bar [concrete]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %bar.ref: %ptr.f68 = name_ref bar, %bar
-// CHECK:STDOUT:   %.loc19_30: ref %Bar = deref %bar.ref
-// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
-// CHECK:STDOUT:   %.loc19_23: type = splice_block %ptr.loc19 [concrete = constants.%ptr.f68] {
-// CHECK:STDOUT:     %Cpp.ref.loc19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     %Bar.ref.loc19: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
-// CHECK:STDOUT:     %ptr.loc19: type = ptr_type %Bar.ref.loc19 [concrete = constants.%ptr.f68]
+// CHECK:STDOUT:   %.loc8_30.1: ref %Bar = deref %bar.ref
+// CHECK:STDOUT:   %foo.ref: %Bar.elem = name_ref foo, @Bar.%.1 [concrete = @Bar.%.1]
+// CHECK:STDOUT:   %.loc8_30.2: ref %ptr.f68 = class_element_access %.loc8_30.1, element0
+// CHECK:STDOUT:   %.loc8_23: type = splice_block %ptr.loc8 [concrete = constants.%ptr.f68] {
+// CHECK:STDOUT:     %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Bar.ref.loc8: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:     %ptr.loc8: type = ptr_type %Bar.ref.loc8 [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %foo_bar: %ptr.f68 = bind_name foo_bar, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc8_30.3: %ptr.f68 = bind_value %.loc8_30.2
+// CHECK:STDOUT:   %foo_bar: %ptr.f68 = bind_name foo_bar, %.loc8_30.3
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -589,12 +603,10 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT: fn @MyF() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %Bar1.ref: type = name_ref Bar1, imports.%Bar1.decl [concrete = constants.%Bar1]
 // CHECK:STDOUT:   %foo1.ref.loc8: %Bar1.foo1.type = name_ref foo1, imports.%Bar1.foo1.decl [concrete = constants.%Bar1.foo1]
 // CHECK:STDOUT:   %Bar1.foo1.call.loc8: init %empty_tuple.type = call %foo1.ref.loc8()
 // CHECK:STDOUT:   %Cpp.ref.loc9: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %Bar2.ref.loc9: type = name_ref Bar2, imports.%Bar2.decl [concrete = constants.%Bar2]
 // CHECK:STDOUT:   %foo1.ref.loc9: %Bar1.foo1.type = name_ref foo1, imports.%Bar1.foo1.decl [concrete = constants.%Bar1.foo1]
 // CHECK:STDOUT:   %Bar1.foo1.call.loc9: init %empty_tuple.type = call %foo1.ref.loc9()
@@ -639,7 +651,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %bar.param: %ptr.f68 = value_param call_param0
 // CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr.f68] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       <elided>
 // CHECK:STDOUT:       %Bar1.ref: type = name_ref Bar1, imports.%Bar1.decl [concrete = constants.%Bar1]
 // CHECK:STDOUT:       %ptr: type = ptr_type %Bar1.ref [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:     }
@@ -652,7 +663,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %bar.param: %ptr.eca = value_param call_param0
 // CHECK:STDOUT:     %.loc19_23: type = splice_block %ptr [concrete = constants.%ptr.eca] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       <elided>
 // CHECK:STDOUT:       %Bar2.ref: type = name_ref Bar2, imports.%Bar2.decl [concrete = constants.%Bar2]
 // CHECK:STDOUT:       %ptr: type = ptr_type %Bar2.ref [concrete = constants.%ptr.eca]
 // CHECK:STDOUT:     }
@@ -671,36 +681,81 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_dynamic.carbon
+// CHECK:STDOUT: --- import_dynamic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Bar: type = class_type @Bar [concrete]
+// CHECK:STDOUT:   %ptr: type = ptr_type %Bar [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %ptr [concrete]
+// CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
+// CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .Bar = %Bar.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Bar.decl: type = class_decl @Bar [concrete = constants.%Bar] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {
+// CHECK:STDOUT:     %bar.patt: %pattern_type = binding_pattern bar [concrete]
+// CHECK:STDOUT:     %bar.param_patt: %pattern_type = value_param_pattern %bar.patt, call_param0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %bar.param: %ptr = value_param call_param0
+// CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr] {
+// CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:       %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:       %ptr: type = ptr_type %Bar.ref [concrete = constants.%ptr]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %bar: %ptr = bind_name bar, %bar.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyF(%bar.param: %ptr);
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_call_dynamic.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Bar: type = class_type @Bar [concrete]
+// CHECK:STDOUT:   %ptr.f68: type = ptr_type %Bar [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %ptr.f68 [concrete]
 // CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
 // CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .Bar = <error>
+// CHECK:STDOUT:     .Bar = %Bar.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Bar.decl: type = class_decl @Bar [concrete = constants.%Bar] {} {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {
-// CHECK:STDOUT:     %bar.patt: <error> = binding_pattern bar [concrete]
-// CHECK:STDOUT:     %bar.param_patt: <error> = value_param_pattern %bar.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %bar.patt: %pattern_type = binding_pattern bar [concrete]
+// CHECK:STDOUT:     %bar.param_patt: %pattern_type = value_param_pattern %bar.patt, call_param0 [concrete]
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %bar.param: <error> = value_param call_param0
-// CHECK:STDOUT:     %.loc14: type = splice_block %ptr [concrete = <error>] {
+// CHECK:STDOUT:     %bar.param: %ptr.f68 = value_param call_param0
+// CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr.f68] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       %Bar.ref: <error> = name_ref Bar, <error> [concrete = <error>]
-// CHECK:STDOUT:       %ptr: type = ptr_type <error> [concrete = <error>]
+// CHECK:STDOUT:       %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:       %ptr: type = ptr_type %Bar.ref [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:     }
-// CHECK:STDOUT:     %bar: <error> = bind_name bar, %bar.param
+// CHECK:STDOUT:     %bar: %ptr.f68 = bind_name bar, %bar.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @MyF(%bar.param: <error>);
+// CHECK:STDOUT: fn @MyF(%bar.param: %ptr.f68) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %bar.ref: %ptr.f68 = name_ref bar, %bar
+// CHECK:STDOUT:   %.loc15: ref %Bar = deref %bar.ref
+// CHECK:STDOUT:   %f.ref: <error> = name_ref f, <error> [concrete = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- import_to_inherit_public.carbon
 // CHECK:STDOUT:
@@ -733,12 +788,11 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Derived {
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:   %.loc8: %Derived.elem = base_decl %Bar.ref, element0 [concrete]
 // CHECK:STDOUT:   %struct_type.base: type = struct_type {.base: %Bar} [concrete = constants.%struct_type.base.36d]
-// CHECK:STDOUT:   %complete_type.loc9: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.fff]
-// CHECK:STDOUT:   complete_type_witness = %complete_type.loc9
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.fff]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .Self = constants.%Derived

+ 49 - 47
toolchain/check/testdata/interop/cpp/class/union.carbon

@@ -2,7 +2,7 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
-// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/none.carbon
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/convert.carbon
 //
 // AUTOUPDATE
 // TIP: To test this file alone, run:
@@ -18,23 +18,32 @@
 
 union Bar;
 
-// --- fail_todo_import_declaration.carbon
+// --- import_declaration.carbon
 
 library "[[@TEST_NAME]]";
 
 import Cpp library "declaration.h";
 
 //@dump-sem-ir-begin
-// CHECK:STDERR: fail_todo_import_declaration.carbon:[[@LINE+7]]:13: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
-// CHECK:STDERR: fn MyF(bar: Cpp.Bar*);
-// CHECK:STDERR:             ^~~~~~~
-// CHECK:STDERR: fail_todo_import_declaration.carbon:[[@LINE+4]]:13: note: in `Cpp` name lookup for `Bar` [InCppNameLookup]
-// CHECK:STDERR: fn MyF(bar: Cpp.Bar*);
-// CHECK:STDERR:             ^~~~~~~
-// CHECK:STDERR:
 fn MyF(bar: Cpp.Bar*);
 //@dump-sem-ir-end
 
+// --- fail_use_declaration_as_definition.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "declaration.h";
+
+fn MyF() {
+  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE+6]]:12: error: binding pattern has incomplete type `Bar` in name binding declaration [IncompleteTypeInBindingDecl]
+  // CHECK:STDERR:   var bar: Cpp.Bar;
+  // CHECK:STDERR:            ^~~~~~~
+  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE-6]]:1: in import [InImport]
+  // CHECK:STDERR: ./declaration.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR:
+  var bar: Cpp.Bar;
+}
+
 // ============================================================================
 // Definition
 // ============================================================================
@@ -113,6 +122,7 @@ library "[[@TEST_NAME]]";
 import Cpp library "private_static_member_function.h";
 
 fn MyF() {
+  // TODO: Should fail because `foo` is inaccessible.
   Cpp.Bar.foo();
 }
 
@@ -124,7 +134,7 @@ fn MyF() {
 
 union Bar {
  public:
-  static Bar* foo;
+  static Bar* _Nonnull foo;
 };
 
 // --- fail_todo_import_public_static_data_member.carbon
@@ -158,28 +168,17 @@ fn MyF() {
 
 union Bar {
  public:
-  Bar* foo;
+  Bar* _Nonnull foo;
 };
 
-// --- fail_todo_import_public_data_member.carbon
+// --- import_public_data_member.carbon
 
 library "[[@TEST_NAME]]";
 
-import Cpp library "public_static_data_member.h";
+import Cpp library "public_data_member.h";
 
 //@dump-sem-ir-begin
 fn MyF(bar : Cpp.Bar*) {
-  // CHECK:STDERR: fail_todo_import_public_data_member.carbon:[[@LINE+11]]:27: error: semantics TODO: `Unsupported: Declaration type Var` [SemanticsTodo]
-  // CHECK:STDERR:   let foo_bar: Cpp.Bar* = bar->foo;
-  // CHECK:STDERR:                           ^~~~~~~~
-  // CHECK:STDERR: fail_todo_import_public_data_member.carbon:[[@LINE+8]]:27: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
-  // CHECK:STDERR:   let foo_bar: Cpp.Bar* = bar->foo;
-  // CHECK:STDERR:                           ^~~~~~~~
-  // CHECK:STDERR:
-  // CHECK:STDERR: fail_todo_import_public_data_member.carbon:[[@LINE+4]]:27: error: member name `foo` not found in `Cpp.Bar` [MemberNameNotFoundInInstScope]
-  // CHECK:STDERR:   let foo_bar: Cpp.Bar* = bar->foo;
-  // CHECK:STDERR:                           ^~~~~~~~
-  // CHECK:STDERR:
   let foo_bar: Cpp.Bar* = bar->foo;
 }
 //@dump-sem-ir-end
@@ -202,6 +201,7 @@ library "[[@TEST_NAME]]";
 import Cpp library "to_inherit_public.h";
 
 class Derived {
+  // TODO: Presumably we should disallow deriving from a union.
   extend base: Cpp.Bar;
 }
 
@@ -239,36 +239,40 @@ import Cpp library "template.h";
 fn MyF(bar: Cpp.Bar*);
 //@dump-sem-ir-end
 
-// CHECK:STDOUT: --- fail_todo_import_declaration.carbon
+// CHECK:STDOUT: --- import_declaration.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Bar: type = class_type @Bar [concrete]
+// CHECK:STDOUT:   %ptr: type = ptr_type %Bar [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %ptr [concrete]
 // CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
 // CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .Bar = <error>
+// CHECK:STDOUT:     .Bar = %Bar.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Bar.decl: type = class_decl @Bar [concrete = constants.%Bar] {} {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {
-// CHECK:STDOUT:     %bar.patt: <error> = binding_pattern bar [concrete]
-// CHECK:STDOUT:     %bar.param_patt: <error> = value_param_pattern %bar.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %bar.patt: %pattern_type = binding_pattern bar [concrete]
+// CHECK:STDOUT:     %bar.param_patt: %pattern_type = value_param_pattern %bar.patt, call_param0 [concrete]
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %bar.param: <error> = value_param call_param0
-// CHECK:STDOUT:     %.loc14: type = splice_block %ptr [concrete = <error>] {
+// CHECK:STDOUT:     %bar.param: %ptr = value_param call_param0
+// CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       %Bar.ref: <error> = name_ref Bar, <error> [concrete = <error>]
-// CHECK:STDOUT:       %ptr: type = ptr_type <error> [concrete = <error>]
+// CHECK:STDOUT:       %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:       %ptr: type = ptr_type %Bar.ref [concrete = constants.%ptr]
 // CHECK:STDOUT:     }
-// CHECK:STDOUT:     %bar: <error> = bind_name bar, %bar.param
+// CHECK:STDOUT:     %bar: %ptr = bind_name bar, %bar.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @MyF(%bar.param: <error>);
+// CHECK:STDOUT: fn @MyF(%bar.param: %ptr);
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- import_definition.carbon
 // CHECK:STDOUT:
@@ -296,7 +300,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %bar.param: %ptr = value_param call_param0
 // CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       <elided>
 // CHECK:STDOUT:       %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:       %ptr: type = ptr_type %Bar.ref [concrete = constants.%ptr]
 // CHECK:STDOUT:     }
@@ -332,7 +335,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %bar.param: %ptr = value_param call_param0
 // CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr] {
 // CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       <elided>
 // CHECK:STDOUT:       %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:       %ptr: type = ptr_type %Bar.ref [concrete = constants.%ptr]
 // CHECK:STDOUT:     }
@@ -363,7 +365,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT: fn @MyF() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %Bar.ref: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:   %foo.ref: %Bar.foo.type = name_ref foo, imports.%Bar.foo.decl [concrete = constants.%Bar.foo]
 // CHECK:STDOUT:   %Bar.foo.call: init %empty_tuple.type = call %foo.ref()
@@ -396,7 +397,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
 // CHECK:STDOUT:   %.loc19: type = splice_block %ptr [concrete = constants.%ptr.f68] {
 // CHECK:STDOUT:     %Cpp.ref.loc19_12: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:     %Bar.ref.loc19_15: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:     %ptr: type = ptr_type %Bar.ref.loc19_15 [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:   }
@@ -404,11 +404,12 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_public_data_member.carbon
+// CHECK:STDOUT: --- import_public_data_member.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %Bar: type = class_type @Bar [concrete]
 // CHECK:STDOUT:   %ptr.f68: type = ptr_type %Bar [concrete]
+// CHECK:STDOUT:   %Bar.elem: type = unbound_element_type %Bar, %ptr.f68 [concrete]
 // CHECK:STDOUT:   %pattern_type: type = pattern_type %ptr.f68 [concrete]
 // CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
 // CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
@@ -430,7 +431,6 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %bar.param: %ptr.f68 = value_param call_param0
 // CHECK:STDOUT:     %.loc7: type = splice_block %ptr.loc7 [concrete = constants.%ptr.f68] {
 // CHECK:STDOUT:       %Cpp.ref.loc7: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:       <elided>
 // CHECK:STDOUT:       %Bar.ref.loc7: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
 // CHECK:STDOUT:       %ptr.loc7: type = ptr_type %Bar.ref.loc7 [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:     }
@@ -444,14 +444,16 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:     %foo_bar.patt: %pattern_type = binding_pattern foo_bar [concrete]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %bar.ref: %ptr.f68 = name_ref bar, %bar
-// CHECK:STDOUT:   %.loc19_30: ref %Bar = deref %bar.ref
-// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
-// CHECK:STDOUT:   %.loc19_23: type = splice_block %ptr.loc19 [concrete = constants.%ptr.f68] {
-// CHECK:STDOUT:     %Cpp.ref.loc19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     %Bar.ref.loc19: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
-// CHECK:STDOUT:     %ptr.loc19: type = ptr_type %Bar.ref.loc19 [concrete = constants.%ptr.f68]
+// CHECK:STDOUT:   %.loc8_30.1: ref %Bar = deref %bar.ref
+// CHECK:STDOUT:   %foo.ref: %Bar.elem = name_ref foo, @Bar.%.1 [concrete = @Bar.%.1]
+// CHECK:STDOUT:   %.loc8_30.2: ref %ptr.f68 = class_element_access %.loc8_30.1, element0
+// CHECK:STDOUT:   %.loc8_23: type = splice_block %ptr.loc8 [concrete = constants.%ptr.f68] {
+// CHECK:STDOUT:     %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Bar.ref.loc8: type = name_ref Bar, imports.%Bar.decl [concrete = constants.%Bar]
+// CHECK:STDOUT:     %ptr.loc8: type = ptr_type %Bar.ref.loc8 [concrete = constants.%ptr.f68]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %foo_bar: %ptr.f68 = bind_name foo_bar, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc8_30.3: %ptr.f68 = bind_value %.loc8_30.2
+// CHECK:STDOUT:   %foo_bar: %ptr.f68 = bind_name foo_bar, %.loc8_30.3
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 67 - 90
toolchain/check/testdata/interop/cpp/function/class.carbon

@@ -2,7 +2,7 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
-// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/destroy.carbon
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/convert.carbon
 //
 // AUTOUPDATE
 // TIP: To test this file alone, run:
@@ -27,12 +27,12 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+7]]:11: error: forming value of incomplete type `Cpp.C` [IncompleteTypeInValueConversion]
   // 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:           ^~
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE-6]]:1: in import [InImport]
+  // CHECK:STDERR: ./decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo({});
 }
@@ -44,12 +44,11 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+11]]:10: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
-  // CHECK:STDERR:   let c: Cpp.C;
-  // CHECK:STDERR:          ^~~~~
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+8]]:10: note: in `Cpp` name lookup for `C` [InCppNameLookup]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+10]]:10: error: binding pattern has incomplete type `C` in name binding declaration [IncompleteTypeInBindingDecl]
   // CHECK:STDERR:   let c: Cpp.C;
   // CHECK:STDERR:          ^~~~~
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE-6]]:1: in import [InImport]
+  // CHECK:STDERR: ./decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
   // CHECK:STDERR:
   // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+4]]:15: error: expected `=`; `let` declaration must have an initializer [ExpectedInitializerAfterLet]
   // CHECK:STDERR:   let c: Cpp.C;
@@ -77,14 +76,21 @@ library "[[@TEST_NAME]]";
 import Cpp library "double_decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:12: error: forming value of incomplete type `Cpp.C` [IncompleteTypeInValueConversion]
   // CHECK:STDERR:   Cpp.foo1({});
-  // CHECK:STDERR:   ^~~~~~~~
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo1` [InCppNameLookup]
-  // CHECK:STDERR:   Cpp.foo1({});
-  // CHECK:STDERR:   ^~~~~~~~
+  // CHECK:STDERR:            ^~
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-6]]:1: in import [InImport]
+  // CHECK:STDERR: ./double_decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo1({});
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:12: error: forming value of incomplete type `Cpp.C` [IncompleteTypeInValueConversion]
+  // CHECK:STDERR:   Cpp.foo2({});
+  // CHECK:STDERR:            ^~
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-14]]:1: in import [InImport]
+  // CHECK:STDERR: ./double_decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
+  // CHECK:STDERR:
   Cpp.foo2({});
 }
 
@@ -124,7 +130,7 @@ class C {
 
 auto foo(C) -> void;
 
-// --- import_definition_single_data_member_value_param_type.carbon
+// --- fail_todo_import_definition_single_data_member_value_param_type.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -132,6 +138,14 @@ 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+8]]:11: error: cannot implicitly convert expression of type `{}` to `Cpp.C` [ConversionFailure]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:           ^~
+  // CHECK:STDERR: fail_todo_import_definition_single_data_member_value_param_type.carbon:[[@LINE+5]]:11: note: type `{}` does not implement interface `Core.ImplicitAs(Cpp.C)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:           ^~
+  // CHECK:STDERR: fail_todo_import_definition_single_data_member_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
+  // CHECK:STDERR:
   Cpp.foo({});
   //@dump-sem-ir-end
 }
@@ -152,7 +166,7 @@ class C {
 
 auto foo(C) -> void;
 
-// --- import_definition_multiple_data_members_value_param_type.carbon
+// --- fail_todo_import_definition_multiple_data_members_value_param_type.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -160,6 +174,14 @@ 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+8]]:11: error: cannot implicitly convert expression of type `{}` to `Cpp.C` [ConversionFailure]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:           ^~
+  // CHECK:STDERR: fail_todo_import_definition_multiple_data_members_value_param_type.carbon:[[@LINE+5]]:11: note: type `{}` does not implement interface `Core.ImplicitAs(Cpp.C)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:           ^~
+  // CHECK:STDERR: fail_todo_import_definition_multiple_data_members_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
+  // CHECK:STDERR:
   Cpp.foo({});
   //@dump-sem-ir-end
 }
@@ -285,19 +307,12 @@ class C;
 
 auto foo(C* _Nonnull) -> void;
 
-// --- fail_todo_import_decl_pointer_param_type.carbon
+// --- 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(c: 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(c: Cpp.C*) {
-// CHECK:STDERR:         ^~~~~
-// CHECK:STDERR:
 fn F(c: Cpp.C*) {
   //@dump-sem-ir-begin
   Cpp.foo(c);
@@ -343,12 +358,12 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_return_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+7]]: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+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+7]]:3: error: function returns incomplete type `Cpp.C` [IncompleteTypeInFunctionReturnType]
   // CHECK:STDERR:   Cpp.foo();
-  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE-6]]:1: in import [InImport]
+  // CHECK:STDERR: ./decl_value_return_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon: note: return type declared here [IncompleteReturnTypeHere]
   // CHECK:STDERR:
   Cpp.foo();
 }
@@ -385,7 +400,7 @@ class C;
 
 auto foo() -> C* _Nonnull;
 
-// --- fail_todo_import_decl_pointer_return_type.carbon
+// --- import_decl_pointer_return_type.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -393,13 +408,6 @@ 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: Record declarations without a definition` [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
 }
@@ -455,7 +463,6 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // 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 %C = temporary_storage
@@ -472,7 +479,7 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- import_definition_single_data_member_value_param_type.carbon
+// CHECK:STDOUT: --- fail_todo_import_definition_single_data_member_value_param_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
@@ -480,10 +487,6 @@ fn F() {
 // 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:   %C.val: %C = struct_value () [concrete]
-// CHECK:STDOUT:   %T.as.Destroy.impl.Op.type.1b3: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%C) [concrete]
-// CHECK:STDOUT:   %T.as.Destroy.impl.Op.21b: %T.as.Destroy.impl.Op.type.1b3 = struct_value () [concrete]
-// CHECK:STDOUT:   %ptr.d9e: type = ptr_type %C [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -501,24 +504,14 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // 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 %C = temporary_storage
-// CHECK:STDOUT:   %.loc8_12.3: init %C = class_init (), %.loc8_12.2 [concrete = constants.%C.val]
-// CHECK:STDOUT:   %.loc8_12.4: ref %C = temporary %.loc8_12.2, %.loc8_12.3
-// CHECK:STDOUT:   %.loc8_12.5: ref %C = converted %.loc8_12.1, %.loc8_12.4
-// CHECK:STDOUT:   %.loc8_12.6: %C = bind_value %.loc8_12.5
-// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(%.loc8_12.6)
-// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound: <bound method> = bound_method %.loc8_12.2, constants.%T.as.Destroy.impl.Op.21b
-// CHECK:STDOUT:   <elided>
-// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %.loc8_12.2, %T.as.Destroy.impl.Op.specific_fn
-// CHECK:STDOUT:   %addr: %ptr.d9e = addr_of %.loc8_12.2
-// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr)
+// CHECK:STDOUT:   %.loc16_12.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc16_12.2: %C = converted %.loc16_12.1, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(<error>)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- import_definition_multiple_data_members_value_param_type.carbon
+// CHECK:STDOUT: --- fail_todo_import_definition_multiple_data_members_value_param_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
@@ -526,10 +519,6 @@ fn F() {
 // 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:   %C.val: %C = struct_value () [concrete]
-// CHECK:STDOUT:   %T.as.Destroy.impl.Op.type.1b3: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%C) [concrete]
-// CHECK:STDOUT:   %T.as.Destroy.impl.Op.21b: %T.as.Destroy.impl.Op.type.1b3 = struct_value () [concrete]
-// CHECK:STDOUT:   %ptr.d9e: type = ptr_type %C [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -547,20 +536,10 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // 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 %C = temporary_storage
-// CHECK:STDOUT:   %.loc8_12.3: init %C = class_init (), %.loc8_12.2 [concrete = constants.%C.val]
-// CHECK:STDOUT:   %.loc8_12.4: ref %C = temporary %.loc8_12.2, %.loc8_12.3
-// CHECK:STDOUT:   %.loc8_12.5: ref %C = converted %.loc8_12.1, %.loc8_12.4
-// CHECK:STDOUT:   %.loc8_12.6: %C = bind_value %.loc8_12.5
-// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(%.loc8_12.6)
-// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound: <bound method> = bound_method %.loc8_12.2, constants.%T.as.Destroy.impl.Op.21b
-// CHECK:STDOUT:   <elided>
-// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %.loc8_12.2, %T.as.Destroy.impl.Op.specific_fn
-// CHECK:STDOUT:   %addr: %ptr.d9e = addr_of %.loc8_12.2
-// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr)
+// CHECK:STDOUT:   %.loc16_12.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc16_12.2: %C = converted %.loc16_12.1, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(<error>)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -600,7 +579,6 @@ fn F() {
 // 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:   %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 %C = temporary_storage
@@ -667,7 +645,6 @@ 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:   <elided>
 // 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 %C = temporary_storage
@@ -720,7 +697,6 @@ fn F() {
 // 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:   %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 %C = temporary_storage
@@ -786,7 +762,6 @@ fn F() {
 // 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: %C.bar.type = name_ref bar, imports.%C.bar.decl [concrete = constants.%C.bar]
 // CHECK:STDOUT:   %C.bar.call: init %empty_tuple.type = call %bar.ref()
@@ -841,7 +816,6 @@ fn F() {
 // 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:   %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 %C = temporary_storage
@@ -862,9 +836,11 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_decl_pointer_param_type.carbon
+// CHECK:STDOUT: --- import_decl_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:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
 // CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
@@ -872,10 +848,11 @@ fn F() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .C = <error>
+// CHECK:STDOUT:     .C = %C.decl
 // CHECK:STDOUT:     .foo = %foo.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
 // CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   } {
@@ -883,12 +860,12 @@ fn F() {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%c.param: <error>) {
+// CHECK:STDOUT: fn @F(%c.param: %ptr) {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %Cpp.ref.loc15: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// 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:   %c.ref: <error> = name_ref c, %c
-// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(<error>)
+// CHECK:STDOUT:   %c.ref: %ptr = name_ref c, %c
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(%c.ref)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -952,7 +929,6 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
 // CHECK:STDOUT:   %.loc8_11.1: ref %C = temporary_storage
 // CHECK:STDOUT:   %foo.call: init %C = call %foo.ref() to %.loc8_11.1
@@ -965,9 +941,11 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_decl_pointer_return_type.carbon
+// CHECK:STDOUT: --- import_decl_pointer_return_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:   %foo.type: type = fn_type @foo [concrete]
 // CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
 // CHECK:STDOUT: }
@@ -988,7 +966,7 @@ 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:   %foo.call: init <error> = call %foo.ref()
+// CHECK:STDOUT:   %foo.call: init %ptr = call %foo.ref()
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -1016,7 +994,6 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
 // CHECK:STDOUT:   %foo.call: init %ptr = call %foo.ref()
 // CHECK:STDOUT:   <elided>

+ 0 - 1
toolchain/check/testdata/interop/cpp/function/in_template.carbon

@@ -77,7 +77,6 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %Y.ref: type = name_ref Y, imports.%X.decl [concrete = constants.%X]
 // CHECK:STDOUT:   %f.ref: %X.f.type = name_ref f, imports.%X.f.decl [concrete = constants.%X.f]
 // CHECK:STDOUT:   %int_42: Core.IntLiteral = int_value 42 [concrete = constants.%int_42.20e]

+ 0 - 3
toolchain/check/testdata/interop/cpp/function/pointer.carbon

@@ -229,7 +229,6 @@ fn F() {
 // CHECK:STDOUT:   assign %s.var, %.loc8_3
 // CHECK:STDOUT:   %.loc8_13: type = splice_block %S.ref [concrete = constants.%S] {
 // 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:   }
 // CHECK:STDOUT:   %s: ref %S = bind_name s, %s.var
@@ -292,7 +291,6 @@ fn F() {
 // CHECK:STDOUT:   assign %s.var, %.loc8_3
 // CHECK:STDOUT:   %.loc8_13: type = splice_block %S.ref.loc8 [concrete = constants.%S] {
 // CHECK:STDOUT:     %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:     %S.ref.loc8: type = name_ref S, imports.%S.decl [concrete = constants.%S]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %s: ref %S = bind_name s, %s.var
@@ -560,7 +558,6 @@ fn F() {
 // CHECK:STDOUT:   assign %s.var, %.loc8_3
 // CHECK:STDOUT:   %.loc8_13: type = splice_block %S.ref [concrete = constants.%S] {
 // 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:   }
 // CHECK:STDOUT:   %s: ref %S = bind_name s, %s.var

+ 60 - 89
toolchain/check/testdata/interop/cpp/function/struct.carbon

@@ -27,12 +27,12 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+7]]:11: error: forming value of incomplete type `Cpp.S` [IncompleteTypeInValueConversion]
   // 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:           ^~
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE-6]]:1: in import [InImport]
+  // CHECK:STDERR: ./decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo({});
 }
@@ -44,12 +44,11 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+11]]:10: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
-  // CHECK:STDERR:   let s: Cpp.S;
-  // CHECK:STDERR:          ^~~~~
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+8]]:10: note: in `Cpp` name lookup for `S` [InCppNameLookup]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+10]]:10: error: binding pattern has incomplete type `S` in name binding declaration [IncompleteTypeInBindingDecl]
   // CHECK:STDERR:   let s: Cpp.S;
   // CHECK:STDERR:          ^~~~~
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE-6]]:1: in import [InImport]
+  // CHECK:STDERR: ./decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
   // CHECK:STDERR:
   // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+4]]:15: error: expected `=`; `let` declaration must have an initializer [ExpectedInitializerAfterLet]
   // CHECK:STDERR:   let s: Cpp.S;
@@ -77,14 +76,21 @@ library "[[@TEST_NAME]]";
 import Cpp library "double_decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:12: error: forming value of incomplete type `Cpp.S` [IncompleteTypeInValueConversion]
   // CHECK:STDERR:   Cpp.foo1({});
-  // CHECK:STDERR:   ^~~~~~~~
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo1` [InCppNameLookup]
-  // CHECK:STDERR:   Cpp.foo1({});
-  // CHECK:STDERR:   ^~~~~~~~
+  // CHECK:STDERR:            ^~
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-6]]:1: in import [InImport]
+  // CHECK:STDERR: ./double_decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo1({});
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:12: error: forming value of incomplete type `Cpp.S` [IncompleteTypeInValueConversion]
+  // CHECK:STDERR:   Cpp.foo2({});
+  // CHECK:STDERR:            ^~
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-14]]:1: in import [InImport]
+  // CHECK:STDERR: ./double_decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
+  // CHECK:STDERR:
   Cpp.foo2({});
 }
 
@@ -124,7 +130,7 @@ struct S {
 
 auto foo(S) -> void;
 
-// --- import_definition_single_data_member_value_param_type.carbon
+// --- fail_todo_import_definition_single_data_member_value_param_type.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -132,6 +138,11 @@ 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+5]]:11: error: name `Core.ImplicitAs` implicitly referenced here, but not found [CoreNameNotFound]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:           ^~
+  // CHECK:STDERR: fail_todo_import_definition_single_data_member_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
+  // CHECK:STDERR:
   Cpp.foo({});
   //@dump-sem-ir-end
 }
@@ -152,7 +163,7 @@ struct S {
 
 auto foo(S) -> void;
 
-// --- import_definition_multiple_data_members_value_param_type.carbon
+// --- fail_todo_import_definition_multiple_data_members_value_param_type.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -160,6 +171,11 @@ 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+5]]:11: error: name `Core.ImplicitAs` implicitly referenced here, but not found [CoreNameNotFound]
+  // CHECK:STDERR:   Cpp.foo({});
+  // CHECK:STDERR:           ^~
+  // CHECK:STDERR: fail_todo_import_definition_multiple_data_members_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
+  // CHECK:STDERR:
   Cpp.foo({});
   //@dump-sem-ir-end
 }
@@ -284,19 +300,12 @@ struct S;
 
 auto foo(S* _Nonnull) -> void;
 
-// --- fail_todo_import_decl_pointer_param_type.carbon
+// --- 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.S*) {
-// CHECK:STDERR:         ^~~~~
-// CHECK:STDERR: fail_todo_import_decl_pointer_param_type.carbon:[[@LINE+4]]:9: note: in `Cpp` name lookup for `S` [InCppNameLookup]
-// CHECK:STDERR: fn F(s: Cpp.S*) {
-// CHECK:STDERR:         ^~~~~
-// CHECK:STDERR:
 fn F(s: Cpp.S*) {
   //@dump-sem-ir-begin
   Cpp.foo(s);
@@ -342,12 +351,12 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_return_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+7]]:3: error: function returns incomplete type `Cpp.S` [IncompleteTypeInFunctionReturnType]
   // 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:   ^~~~~~~~~
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE-6]]:1: in import [InImport]
+  // CHECK:STDERR: ./decl_value_return_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon: note: return type declared here [IncompleteReturnTypeHere]
   // CHECK:STDERR:
   Cpp.foo();
 }
@@ -384,7 +393,7 @@ struct S;
 
 auto foo() -> S* _Nonnull;
 
-// --- fail_todo_import_decl_pointer_return_type.carbon
+// --- import_decl_pointer_return_type.carbon
 
 library "[[@TEST_NAME]]";
 
@@ -392,13 +401,6 @@ 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: Record declarations without a definition` [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
 }
@@ -454,7 +456,6 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // 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
@@ -471,7 +472,7 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- import_definition_single_data_member_value_param_type.carbon
+// CHECK:STDOUT: --- fail_todo_import_definition_single_data_member_value_param_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
@@ -479,10 +480,6 @@ fn F() {
 // 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:   %T.as.Destroy.impl.Op.type.642: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%S) [concrete]
-// CHECK:STDOUT:   %T.as.Destroy.impl.Op.ab5: %T.as.Destroy.impl.Op.type.642 = struct_value () [concrete]
-// CHECK:STDOUT:   %ptr.5c7: type = ptr_type %S [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -500,24 +497,14 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // 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:   %T.as.Destroy.impl.Op.bound: <bound method> = bound_method %.loc8_12.2, constants.%T.as.Destroy.impl.Op.ab5
-// CHECK:STDOUT:   <elided>
-// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %.loc8_12.2, %T.as.Destroy.impl.Op.specific_fn
-// CHECK:STDOUT:   %addr: %ptr.5c7 = addr_of %.loc8_12.2
-// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr)
+// CHECK:STDOUT:   %.loc13_12.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc13_12.2: %S = converted %.loc13_12.1, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(<error>)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- import_definition_multiple_data_members_value_param_type.carbon
+// CHECK:STDOUT: --- fail_todo_import_definition_multiple_data_members_value_param_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
@@ -525,10 +512,6 @@ fn F() {
 // 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:   %T.as.Destroy.impl.Op.type.642: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%S) [concrete]
-// CHECK:STDOUT:   %T.as.Destroy.impl.Op.ab5: %T.as.Destroy.impl.Op.type.642 = struct_value () [concrete]
-// CHECK:STDOUT:   %ptr.5c7: type = ptr_type %S [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -546,20 +529,10 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // 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:   %T.as.Destroy.impl.Op.bound: <bound method> = bound_method %.loc8_12.2, constants.%T.as.Destroy.impl.Op.ab5
-// CHECK:STDOUT:   <elided>
-// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %.loc8_12.2, %T.as.Destroy.impl.Op.specific_fn
-// CHECK:STDOUT:   %addr: %ptr.5c7 = addr_of %.loc8_12.2
-// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr)
+// CHECK:STDOUT:   %.loc13_12.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc13_12.2: %S = converted %.loc13_12.1, <error> [concrete = <error>]
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(<error>)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -599,7 +572,6 @@ fn F() {
 // 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:   %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
@@ -666,7 +638,6 @@ 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:   <elided>
 // 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
@@ -719,7 +690,6 @@ fn F() {
 // 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:   %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
@@ -785,7 +755,6 @@ fn F() {
 // 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: %S.bar.type = name_ref bar, imports.%S.bar.decl [concrete = constants.%S.bar]
 // CHECK:STDOUT:   %S.bar.call: init %empty_tuple.type = call %bar.ref()
@@ -840,7 +809,6 @@ fn F() {
 // 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:   %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
@@ -861,9 +829,11 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_decl_pointer_param_type.carbon
+// CHECK:STDOUT: --- import_decl_pointer_param_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %S: type = class_type @S [concrete]
+// CHECK:STDOUT:   %ptr: type = ptr_type %S [concrete]
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
 // CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
@@ -871,10 +841,11 @@ fn F() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .S = <error>
+// 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:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   } {
@@ -882,12 +853,12 @@ fn F() {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%s.param: <error>) {
+// 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:   %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:   %s.ref: <error> = name_ref s, %s
-// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(<error>)
+// CHECK:STDOUT:   %s.ref: %ptr = name_ref s, %s
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(%s.ref)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -951,7 +922,6 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // 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
@@ -964,9 +934,11 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_decl_pointer_return_type.carbon
+// CHECK:STDOUT: --- import_decl_pointer_return_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %S: type = class_type @S [concrete]
+// CHECK:STDOUT:   %ptr: type = ptr_type %S [concrete]
 // CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
 // CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
 // CHECK:STDOUT: }
@@ -987,7 +959,7 @@ 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:   %foo.call: init <error> = call %foo.ref()
+// CHECK:STDOUT:   %foo.call: init %ptr = call %foo.ref()
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -1015,7 +987,6 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
 // CHECK:STDOUT:   %foo.call: init %ptr = call %foo.ref()
 // CHECK:STDOUT:   <elided>

+ 50 - 56
toolchain/check/testdata/interop/cpp/function/union.carbon

@@ -28,12 +28,12 @@ import Cpp library "decl_value_param_type.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE+7]]:11: error: forming value of incomplete type `Cpp.U` [IncompleteTypeInValueConversion]
   // 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:           ^~
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon:[[@LINE-7]]:1: in import [InImport]
+  // CHECK:STDERR: ./decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo({});
   //@dump-sem-ir-end
@@ -46,12 +46,11 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+11]]:10: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
-  // CHECK:STDERR:   let u: Cpp.U;
-  // CHECK:STDERR:          ^~~~~
-  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+8]]:10: note: in `Cpp` name lookup for `U` [InCppNameLookup]
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+10]]:10: error: binding pattern has incomplete type `U` in name binding declaration [IncompleteTypeInBindingDecl]
   // CHECK:STDERR:   let u: Cpp.U;
   // CHECK:STDERR:          ^~~~~
+  // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE-6]]:1: in import [InImport]
+  // CHECK:STDERR: ./decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
   // CHECK:STDERR:
   // CHECK:STDERR: fail_todo_import_decl_value_param_type_previously_imported.carbon:[[@LINE+4]]:15: error: expected `=`; `let` declaration must have an initializer [ExpectedInitializerAfterLet]
   // CHECK:STDERR:   let u: Cpp.U;
@@ -79,14 +78,21 @@ library "[[@TEST_NAME]]";
 import Cpp library "double_decl_value_param_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
-  // CHECK:STDERR:   Cpp.foo1({});
-  // CHECK:STDERR:   ^~~~~~~~
-  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo1` [InCppNameLookup]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:12: error: forming value of incomplete type `Cpp.U` [IncompleteTypeInValueConversion]
   // CHECK:STDERR:   Cpp.foo1({});
-  // CHECK:STDERR:   ^~~~~~~~
+  // CHECK:STDERR:            ^~
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-6]]:1: in import [InImport]
+  // CHECK:STDERR: ./double_decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo1({});
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE+7]]:12: error: forming value of incomplete type `Cpp.U` [IncompleteTypeInValueConversion]
+  // CHECK:STDERR:   Cpp.foo2({});
+  // CHECK:STDERR:            ^~
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon:[[@LINE-14]]:1: in import [InImport]
+  // CHECK:STDERR: ./double_decl_value_param_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_double_decl_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
+  // CHECK:STDERR:
   Cpp.foo2({});
 }
 
@@ -134,12 +140,10 @@ import Cpp library "definition_single_data_member_value_param_type.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_import_definition_single_data_member_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Non-empty union` [SemanticsTodo]
-  // CHECK:STDERR:   Cpp.foo({});
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR: fail_import_definition_single_data_member_value_param_type.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR: fail_import_definition_single_data_member_value_param_type.carbon:[[@LINE+5]]:11: error: name `Core.ImplicitAs` implicitly referenced here, but not found [CoreNameNotFound]
   // CHECK:STDERR:   Cpp.foo({});
-  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:           ^~
+  // CHECK:STDERR: fail_import_definition_single_data_member_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo({});
   //@dump-sem-ir-end
@@ -169,12 +173,10 @@ import Cpp library "definition_multiple_data_members_value_param_type.h";
 
 fn F() {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_import_definition_multiple_data_members_value_param_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Non-empty union` [SemanticsTodo]
+  // CHECK:STDERR: fail_import_definition_multiple_data_members_value_param_type.carbon:[[@LINE+5]]:11: error: name `Core.ImplicitAs` implicitly referenced here, but not found [CoreNameNotFound]
   // CHECK:STDERR:   Cpp.foo({});
-  // CHECK:STDERR:   ^~~~~~~
-  // CHECK:STDERR: fail_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:           ^~
+  // CHECK:STDERR: fail_import_definition_multiple_data_members_value_param_type.carbon: note: initializing function parameter [InCallToFunctionParam]
   // CHECK:STDERR:
   Cpp.foo({});
   //@dump-sem-ir-end
@@ -301,19 +303,12 @@ union U;
 
 auto foo(U* _Nonnull) -> void;
 
-// --- fail_todo_import_decl_pointer_param_type.carbon
+// --- 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(u: Cpp.U*) {
-// CHECK:STDERR:         ^~~~~
-// CHECK:STDERR: fail_todo_import_decl_pointer_param_type.carbon:[[@LINE+4]]:9: note: in `Cpp` name lookup for `U` [InCppNameLookup]
-// CHECK:STDERR: fn F(u: Cpp.U*) {
-// CHECK:STDERR:         ^~~~~
-// CHECK:STDERR:
 fn F(u: Cpp.U*) {
   //@dump-sem-ir-begin
   Cpp.foo(u);
@@ -359,12 +354,12 @@ library "[[@TEST_NAME]]";
 import Cpp library "decl_value_return_type.h";
 
 fn F() {
-  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Record declarations without a definition` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE+7]]:3: error: function returns incomplete type `Cpp.U` [IncompleteTypeInFunctionReturnType]
   // 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:   ^~~~~~~~~
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon:[[@LINE-6]]:1: in import [InImport]
+  // CHECK:STDERR: ./decl_value_return_type.h:2: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: fail_todo_import_decl_value_return_type.carbon: note: return type declared here [IncompleteReturnTypeHere]
   // CHECK:STDERR:
   Cpp.foo();
 }
@@ -409,7 +404,7 @@ 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: Record declarations without a definition` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_import_decl_pointer_return_type.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: nullable pointer: U *` [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]
@@ -501,7 +496,6 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // 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 %U = temporary_storage
@@ -522,9 +516,10 @@ fn F() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %U: type = class_type @U [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:   %empty_struct_type: type = struct_type {} [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -543,7 +538,8 @@ 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:   %.loc15: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc13_12.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc13_12.2: %U = converted %.loc13_12.1, <error> [concrete = <error>]
 // CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(<error>)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
@@ -552,9 +548,10 @@ fn F() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %U: type = class_type @U [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:   %empty_struct_type: type = struct_type {} [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -573,7 +570,8 @@ 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:   %.loc15: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc13_12.1: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc13_12.2: %U = converted %.loc13_12.1, <error> [concrete = <error>]
 // CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(<error>)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
@@ -614,7 +612,6 @@ fn F() {
 // 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:   %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 %U = temporary_storage
@@ -681,7 +678,6 @@ 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:   <elided>
 // 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 %U = temporary_storage
@@ -734,7 +730,6 @@ fn F() {
 // 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:   %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 %U = temporary_storage
@@ -800,7 +795,6 @@ fn F() {
 // 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:   %U.ref: type = name_ref U, imports.%U.decl [concrete = constants.%U]
 // CHECK:STDOUT:   %bar.ref: %U.bar.type = name_ref bar, imports.%U.bar.decl [concrete = constants.%U.bar]
 // CHECK:STDOUT:   %U.bar.call: init %empty_tuple.type = call %bar.ref()
@@ -855,7 +849,6 @@ fn F() {
 // 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:   %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 %U = temporary_storage
@@ -876,9 +869,11 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_decl_pointer_param_type.carbon
+// CHECK:STDOUT: --- import_decl_pointer_param_type.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %U: type = class_type @U [concrete]
+// CHECK:STDOUT:   %ptr: type = ptr_type %U [concrete]
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
 // CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
 // CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
@@ -886,10 +881,11 @@ fn F() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
 // CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
-// CHECK:STDOUT:     .U = <error>
+// CHECK:STDOUT:     .U = %U.decl
 // CHECK:STDOUT:     .foo = %foo.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %U.decl: type = class_decl @U [concrete = constants.%U] {} {}
 // CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {
 // CHECK:STDOUT:     <elided>
 // CHECK:STDOUT:   } {
@@ -897,12 +893,12 @@ fn F() {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F(%u.param: <error>) {
+// CHECK:STDOUT: fn @F(%u.param: %ptr) {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %Cpp.ref.loc15: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// 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:   %u.ref: <error> = name_ref u, %u
-// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(<error>)
+// CHECK:STDOUT:   %u.ref: %ptr = name_ref u, %u
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref(%u.ref)
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -966,7 +962,6 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
 // CHECK:STDOUT:   %.loc8_11.1: ref %U = temporary_storage
 // CHECK:STDOUT:   %foo.call: init %U = call %foo.ref() to %.loc8_11.1
@@ -1030,7 +1025,6 @@ fn F() {
 // CHECK:STDOUT: fn @F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
 // CHECK:STDOUT:   %foo.call: init %ptr = call %foo.ref()
 // CHECK:STDOUT:   <elided>

+ 0 - 1
toolchain/check/testdata/interop/cpp/namespace.carbon

@@ -331,7 +331,6 @@ fn MyF() {
 // CHECK:STDOUT: fn @MyF() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
-// CHECK:STDOUT:   <elided>
 // CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo]
 // CHECK:STDOUT:   %.loc8_11.1: ref %X = temporary_storage
 // CHECK:STDOUT:   %foo.call: init %X = call %foo.ref() to %.loc8_11.1

+ 18 - 0
toolchain/check/type_completion.cpp

@@ -140,6 +140,10 @@ class TypeCompleter {
   auto BuildInfoForInst(SemIR::TypeId /*type_id*/, SemIR::ConstType inst) const
       -> SemIR::CompleteTypeInfo;
 
+  auto BuildInfoForInst(SemIR::TypeId type_id,
+                        SemIR::CustomLayoutType inst) const
+      -> SemIR::CompleteTypeInfo;
+
   auto BuildInfoForInst(SemIR::TypeId /*type_id*/,
                         SemIR::PartialType inst) const
       -> SemIR::CompleteTypeInfo;
@@ -294,6 +298,12 @@ auto TypeCompleter::AddNestedIncompleteTypes(SemIR::Inst type_inst) -> bool {
       Push(context_->types().GetTypeIdForTypeInstId(inst.inner_id));
       break;
     }
+    case CARBON_KIND(SemIR::CustomLayoutType inst): {
+      for (auto field : context_->struct_type_fields().Get(inst.fields_id)) {
+        Push(context_->types().GetTypeIdForTypeInstId(field.type_inst_id));
+      }
+      break;
+    }
     case CARBON_KIND(SemIR::PartialType inst): {
       Push(context_->types().GetTypeIdForTypeInstId(inst.inner_id));
       break;
@@ -520,6 +530,14 @@ auto TypeCompleter::BuildInfoForInst(SemIR::TypeId /*type_id*/,
   return GetNestedInfo(context_->types().GetTypeIdForTypeInstId(inst.inner_id));
 }
 
+auto TypeCompleter::BuildInfoForInst(SemIR::TypeId type_id,
+                                     SemIR::CustomLayoutType /*inst*/) const
+    -> SemIR::CompleteTypeInfo {
+  // TODO: Should we support other value representations for custom layout
+  // types?
+  return {.value_repr = MakePointerValueRepr(type_id)};
+}
+
 auto TypeCompleter::BuildInfoForInst(SemIR::TypeId /*type_id*/,
                                      SemIR::PartialType inst) const
     -> SemIR::CompleteTypeInfo {

+ 7 - 0
toolchain/lower/file_context.cpp

@@ -768,6 +768,13 @@ static auto BuildTypeForInst(FileContext& context, SemIR::ConstType inst)
       context.sem_ir().types().GetTypeIdForTypeInstId(inst.inner_id));
 }
 
+static auto BuildTypeForInst(FileContext& context, SemIR::CustomLayoutType inst)
+    -> llvm::Type* {
+  auto layout = context.sem_ir().custom_layouts().Get(inst.layout_id);
+  return llvm::ArrayType::get(llvm::Type::getInt8Ty(context.llvm_context()),
+                              layout[SemIR::CustomLayoutId::SizeIndex]);
+}
+
 static auto BuildTypeForInst(FileContext& context, SemIR::PartialType inst)
     -> llvm::Type* {
   return context.GetType(

+ 2 - 0
toolchain/lower/function_context.cpp

@@ -218,6 +218,8 @@ auto FunctionContext::CreateAlloca(llvm::Type* type, const llvm::Twine& name)
     builder().SetCurrentDebugLocation(debug_loc);
 
     // Create an alloca for this variable in the entry block.
+    // TODO: Compute alignment of the type, which may be greater than the
+    // alignment computed by LLVM.
     alloca = builder().CreateAlloca(type, /*ArraySize=*/nullptr, name);
   }
 

+ 40 - 9
toolchain/lower/handle_aggregates.cpp

@@ -10,6 +10,7 @@
 #include "toolchain/lower/function_context.h"
 #include "toolchain/sem_ir/expr_info.h"
 #include "toolchain/sem_ir/file.h"
+#include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
@@ -26,6 +27,30 @@ static auto GetPointeeType(FunctionContext::TypeInFile type)
           .type_id = type.file->GetPointeeType(type.type_id)};
 }
 
+// Given an index within a SemIR aggregate type, returns the corresponding index
+// of the element within the LLVM type suitable for use with the getelementptr
+// instruction.
+static auto GetElementIndex(FunctionContext::TypeInFile type,
+                            SemIR::ElementIndex idx) -> unsigned int {
+  auto type_inst = type.file->types().GetAsInst(type.type_id);
+
+  if (auto custom_layout_type = type_inst.TryAs<SemIR::CustomLayoutType>()) {
+    // For custom layout types, we form an array of i8 as the LLVM type, so the
+    // offset in the type is the getelementptr index.
+    // TODO: This offset might not fit into an `unsigned int`.
+    return type.file->custom_layouts().Get(
+        custom_layout_type
+            ->layout_id)[SemIR::CustomLayoutId::FirstFieldIndex + idx.index];
+  }
+
+  // For now, struct and tuple types map directly into LLVM struct types with
+  // identical field numbering.
+  CARBON_CHECK(
+      type_inst.Is<SemIR::StructType>() || type_inst.Is<SemIR::TupleType>(),
+      "Indexing unexpected aggregate type {0}", type_inst);
+  return idx.index;
+}
+
 // Extracts an element of an aggregate, such as a struct, tuple, or class, by
 // index. Depending on the expression category and value representation of the
 // aggregate input, this will either produce a value or a reference.
@@ -58,14 +83,16 @@ static auto GetAggregateElement(FunctionContext& context,
           return aggr_value;
         case SemIR::ValueRepr::Copy:
           // We are holding the values of the aggregate directly, elementwise.
-          return context.builder().CreateExtractValue(aggr_value, idx.index,
-                                                      name);
+          return context.builder().CreateExtractValue(
+              aggr_value, GetElementIndex(value_repr.type(), idx), name);
         case SemIR::ValueRepr::Pointer: {
           // The value representation is a pointer to an aggregate that we want
           // to index into.
-          auto* value_type = context.GetType(GetPointeeType(value_repr.type()));
+          auto value_rep_type = GetPointeeType(value_repr.type());
+          auto* value_type = context.GetType(value_rep_type);
           auto* elem_ptr = context.builder().CreateStructGEP(
-              value_type, aggr_value, idx.index, name);
+              value_type, aggr_value, GetElementIndex(value_rep_type, idx),
+              name);
 
           if (!value_repr.repr.elements_are_values()) {
             // `elem_ptr` points to an object representation, which is our
@@ -88,17 +115,21 @@ static auto GetAggregateElement(FunctionContext& context,
     case SemIR::ExprCategory::DurableRef:
     case SemIR::ExprCategory::EphemeralRef: {
       // Just locate the aggregate element.
-      auto* aggr_type = context.GetTypeOfInst(aggr_inst_id);
-      return context.builder().CreateStructGEP(aggr_type, aggr_value, idx.index,
-                                               name);
+      auto aggr_type = context.GetTypeIdOfInst(aggr_inst_id);
+      auto object_repr = FunctionContext::TypeInFile{
+          .file = aggr_type.file,
+          .type_id = aggr_type.file->types().GetObjectRepr(aggr_type.type_id)};
+      return context.builder().CreateStructGEP(
+          context.GetType(object_repr), aggr_value,
+          GetElementIndex(object_repr, idx), name);
     }
   }
 }
 
 static auto GetStructFieldName(FunctionContext::TypeInFile struct_type,
                                SemIR::ElementIndex index) -> llvm::StringRef {
-  auto struct_type_inst =
-      struct_type.file->types().GetAs<SemIR::StructType>(struct_type.type_id);
+  auto struct_type_inst = struct_type.file->types().GetAs<SemIR::AnyStructType>(
+      struct_type.type_id);
   auto fields =
       struct_type.file->struct_type_fields().Get(struct_type_inst.fields_id);
   // We intentionally don't add this to the fingerprint because it's only used

+ 234 - 0
toolchain/lower/testdata/interop/cpp/field.carbon

@@ -0,0 +1,234 @@
+// 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-FILE: toolchain/testing/testdata/min_prelude/int.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/interop/cpp/field.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/field.carbon
+
+// --- struct.h
+
+struct A {
+  int n;
+  alignas(16) int m;
+};
+
+// --- access_struct.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "struct.h";
+
+// TODO: We generate 4-byte-aligned loads here, and should generate
+// 16-byte-aligned loads as Clang does.
+fn AccessN(a: Cpp.A) -> i32 {
+  return a.n;
+}
+
+fn AccessM(a: Cpp.A) -> i32 {
+  return a.m;
+}
+
+// --- assign_struct.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "struct.h";
+
+// TODO: We generate 4-byte-aligned stores here, and should generate
+// 16-byte-aligned stores as Clang does.
+fn AssignN(p: Cpp.A*) {
+  p->n = 1;
+}
+
+fn AssignM(p: Cpp.A*) {
+  p->m = 1;
+}
+
+// --- union.h
+
+union A {
+  int n;
+  int m;
+};
+
+// --- access_union.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "union.h";
+
+fn AccessN(a: Cpp.A) -> i32 {
+  return a.n;
+}
+
+fn AccessM(a: Cpp.A) -> i32 {
+  return a.m;
+}
+
+// --- assign_union.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "union.h";
+
+fn AssignN(p: Cpp.A*) {
+  p->n = 1;
+}
+
+fn AssignM(p: Cpp.A*) {
+  p->m = 1;
+}
+
+// CHECK:STDOUT: ; ModuleID = 'access_struct.carbon'
+// CHECK:STDOUT: source_filename = "access_struct.carbon"
+// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @_CAccessN.Main(ptr %a) !dbg !7 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc9_11.1.n = getelementptr inbounds nuw [32 x i8], ptr %a, i32 0, i32 0, !dbg !10
+// CHECK:STDOUT:   %.loc9_11.2 = load i32, ptr %.loc9_11.1.n, align 4, !dbg !10
+// CHECK:STDOUT:   ret i32 %.loc9_11.2, !dbg !11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @_CAccessM.Main(ptr %a) !dbg !12 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc13_11.1.m = getelementptr inbounds nuw [32 x i8], ptr %a, i32 0, i32 16, !dbg !13
+// CHECK:STDOUT:   %.loc13_11.2 = load i32, ptr %.loc13_11.1.m, align 4, !dbg !13
+// CHECK:STDOUT:   ret i32 %.loc13_11.2, !dbg !14
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4}
+// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
+// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "access_struct.carbon", directory: "")
+// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "AccessN", linkageName: "_CAccessN.Main", scope: null, file: !6, line: 8, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !8 = !DISubroutineType(types: !9)
+// CHECK:STDOUT: !9 = !{}
+// CHECK:STDOUT: !10 = !DILocation(line: 9, column: 10, scope: !7)
+// CHECK:STDOUT: !11 = !DILocation(line: 9, column: 3, scope: !7)
+// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "AccessM", linkageName: "_CAccessM.Main", scope: null, file: !6, line: 12, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !13 = !DILocation(line: 13, column: 10, scope: !12)
+// CHECK:STDOUT: !14 = !DILocation(line: 13, column: 3, scope: !12)
+// CHECK:STDOUT: ; ModuleID = 'assign_struct.carbon'
+// CHECK:STDOUT: source_filename = "assign_struct.carbon"
+// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CAssignN.Main(ptr %p) !dbg !7 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc9_4.2.n = getelementptr inbounds nuw [32 x i8], ptr %p, i32 0, i32 0, !dbg !10
+// CHECK:STDOUT:   store i32 1, ptr %.loc9_4.2.n, align 4, !dbg !10
+// CHECK:STDOUT:   ret void, !dbg !11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CAssignM.Main(ptr %p) !dbg !12 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc13_4.2.m = getelementptr inbounds nuw [32 x i8], ptr %p, i32 0, i32 16, !dbg !13
+// CHECK:STDOUT:   store i32 1, ptr %.loc13_4.2.m, align 4, !dbg !13
+// CHECK:STDOUT:   ret void, !dbg !14
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4}
+// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
+// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "assign_struct.carbon", directory: "")
+// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "AssignN", linkageName: "_CAssignN.Main", scope: null, file: !6, line: 8, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !8 = !DISubroutineType(types: !9)
+// CHECK:STDOUT: !9 = !{}
+// CHECK:STDOUT: !10 = !DILocation(line: 9, column: 3, scope: !7)
+// CHECK:STDOUT: !11 = !DILocation(line: 8, column: 1, scope: !7)
+// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "AssignM", linkageName: "_CAssignM.Main", scope: null, file: !6, line: 12, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !13 = !DILocation(line: 13, column: 3, scope: !12)
+// CHECK:STDOUT: !14 = !DILocation(line: 12, column: 1, scope: !12)
+// CHECK:STDOUT: ; ModuleID = 'access_union.carbon'
+// CHECK:STDOUT: source_filename = "access_union.carbon"
+// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @_CAccessN.Main(ptr %a) !dbg !7 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc7_11.1.n = getelementptr inbounds nuw [4 x i8], ptr %a, i32 0, i32 0, !dbg !10
+// CHECK:STDOUT:   %.loc7_11.2 = load i32, ptr %.loc7_11.1.n, align 4, !dbg !10
+// CHECK:STDOUT:   ret i32 %.loc7_11.2, !dbg !11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @_CAccessM.Main(ptr %a) !dbg !12 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc11_11.1.m = getelementptr inbounds nuw [4 x i8], ptr %a, i32 0, i32 0, !dbg !13
+// CHECK:STDOUT:   %.loc11_11.2 = load i32, ptr %.loc11_11.1.m, align 4, !dbg !13
+// CHECK:STDOUT:   ret i32 %.loc11_11.2, !dbg !14
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4}
+// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
+// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "access_union.carbon", directory: "")
+// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "AccessN", linkageName: "_CAccessN.Main", scope: null, file: !6, line: 6, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !8 = !DISubroutineType(types: !9)
+// CHECK:STDOUT: !9 = !{}
+// CHECK:STDOUT: !10 = !DILocation(line: 7, column: 10, scope: !7)
+// CHECK:STDOUT: !11 = !DILocation(line: 7, column: 3, scope: !7)
+// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "AccessM", linkageName: "_CAccessM.Main", scope: null, file: !6, line: 10, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !13 = !DILocation(line: 11, column: 10, scope: !12)
+// CHECK:STDOUT: !14 = !DILocation(line: 11, column: 3, scope: !12)
+// CHECK:STDOUT: ; ModuleID = 'assign_union.carbon'
+// CHECK:STDOUT: source_filename = "assign_union.carbon"
+// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CAssignN.Main(ptr %p) !dbg !7 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc7_4.2.n = getelementptr inbounds nuw [4 x i8], ptr %p, i32 0, i32 0, !dbg !10
+// CHECK:STDOUT:   store i32 1, ptr %.loc7_4.2.n, align 4, !dbg !10
+// CHECK:STDOUT:   ret void, !dbg !11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CAssignM.Main(ptr %p) !dbg !12 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc11_4.2.m = getelementptr inbounds nuw [4 x i8], ptr %p, i32 0, i32 0, !dbg !13
+// CHECK:STDOUT:   store i32 1, ptr %.loc11_4.2.m, align 4, !dbg !13
+// CHECK:STDOUT:   ret void, !dbg !14
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4}
+// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
+// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "assign_union.carbon", directory: "")
+// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "AssignN", linkageName: "_CAssignN.Main", scope: null, file: !6, line: 6, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !8 = !DISubroutineType(types: !9)
+// CHECK:STDOUT: !9 = !{}
+// CHECK:STDOUT: !10 = !DILocation(line: 7, column: 3, scope: !7)
+// CHECK:STDOUT: !11 = !DILocation(line: 6, column: 1, scope: !7)
+// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "AssignM", linkageName: "_CAssignM.Main", scope: null, file: !6, line: 10, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !13 = !DILocation(line: 11, column: 3, scope: !12)
+// CHECK:STDOUT: !14 = !DILocation(line: 10, column: 1, scope: !12)

+ 1 - 0
toolchain/sem_ir/expr_info.cpp

@@ -109,6 +109,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case CompleteTypeWitness::Kind:
       case ConstType::Kind:
       case ConvertToValueAction::Kind:
+      case CustomLayoutType::Kind:
       case FacetAccessType::Kind:
       case FacetType::Kind:
       case FacetValue::Kind:

+ 9 - 0
toolchain/sem_ir/file.h

@@ -61,6 +61,8 @@ struct ExprRegion {
 
 using ExprRegionStore = ValueStore<ExprRegionId, ExprRegion>;
 
+using CustomLayoutStore = BlockValueStore<CustomLayoutId, uint64_t>;
+
 // Provides semantic analysis on a Parse::Tree.
 class File : public Printable<File> {
  public:
@@ -210,6 +212,10 @@ class File : public Printable<File> {
   auto struct_type_fields() const -> const StructTypeFieldsStore& {
     return struct_type_fields_;
   }
+  auto custom_layouts() -> CustomLayoutStore& { return custom_layouts_; }
+  auto custom_layouts() const -> const CustomLayoutStore& {
+    return custom_layouts_;
+  }
   auto types() -> TypeStore& { return types_; }
   auto types() const -> const TypeStore& { return types_; }
   auto insts() -> InstStore& { return insts_; }
@@ -361,6 +367,9 @@ class File : public Printable<File> {
   // Storage for StructTypeField lists.
   StructTypeFieldsStore struct_type_fields_ = StructTypeFieldsStore(allocator_);
 
+  // Storage for custom layouts.
+  CustomLayoutStore custom_layouts_ = CustomLayoutStore(allocator_);
+
   // Descriptions of types used in this file.
   TypeStore types_ = TypeStore(this);
 

+ 17 - 0
toolchain/sem_ir/formatter.cpp

@@ -1006,6 +1006,23 @@ auto Formatter::FormatInstRhs(Inst inst) -> void {
       return;
     }
 
+    case CARBON_KIND(CustomLayoutType type): {
+      out_ << " {";
+      auto layout = sem_ir_->custom_layouts().Get(type.layout_id);
+      out_ << "size=" << layout[CustomLayoutId::SizeIndex]
+           << ", align=" << layout[CustomLayoutId::AlignIndex];
+      for (auto [field, offset] :
+           llvm::zip(sem_ir_->struct_type_fields().Get(type.fields_id),
+                     layout.drop_front(CustomLayoutId::FirstFieldIndex))) {
+        out_ << ", .";
+        FormatName(field.name_id);
+        out_ << "@" << offset << ": ";
+        FormatInstAsType(field.type_inst_id);
+      }
+      out_ << "}";
+      return;
+    }
+
     case CARBON_KIND(FloatLiteral value): {
       llvm::SmallVector<char, 16> buffer;
       sem_ir_->floats().Get(value.float_id).toString(buffer);

+ 4 - 2
toolchain/sem_ir/formatter.h

@@ -401,8 +401,10 @@ auto Formatter::FormatEntityStart(llvm::StringRef entity_kind,
     if (import_ir_inst_id.has_value()) {
       auto import_ir_id =
           sem_ir_->import_ir_insts().Get(import_ir_inst_id).ir_id();
-      const auto* import_file = sem_ir_->import_irs().Get(import_ir_id).sem_ir;
-      pending_imported_from_ = import_file->filename();
+      if (const auto* import_file =
+              sem_ir_->import_irs().Get(import_ir_id).sem_ir) {
+        pending_imported_from_ = import_file->filename();
+      }
     }
   }
 

+ 1 - 0
toolchain/sem_ir/id_kind.h

@@ -35,6 +35,7 @@ using IdKind = TypeEnum<
     ClassId,
     CompileTimeBindIndex,
     ConstantId,
+    CustomLayoutId,
     DeclInstBlockId,
     DestInstId,
     ElementIndex,

+ 20 - 0
toolchain/sem_ir/ids.h

@@ -728,6 +728,26 @@ struct StructTypeFieldsId : public IdBase<StructTypeFieldsId> {
 
 constexpr StructTypeFieldsId StructTypeFieldsId::Empty = StructTypeFieldsId(0);
 
+// The ID of a `CustomLayout` block.
+struct CustomLayoutId : public IdBase<CustomLayoutId> {
+  static constexpr llvm::StringLiteral Label = "custom_layout";
+
+  // The canonical empty block. This is never used, but needed by
+  // BlockValueStore.
+  static const CustomLayoutId Empty;
+
+  // The index in a custom layout of the overall size field.
+  static constexpr int SizeIndex = 0;
+  // The index in a custom layout of the overall alignment field.
+  static constexpr int AlignIndex = 1;
+  // The index in a custom layout of the offset of the first struct field.
+  static constexpr int FirstFieldIndex = 2;
+
+  using IdBase::IdBase;
+};
+
+constexpr CustomLayoutId CustomLayoutId::Empty = CustomLayoutId(0);
+
 // The ID of a type.
 struct TypeId : public IdBase<TypeId> {
   static constexpr llvm::StringLiteral Label = "type";

+ 7 - 3
toolchain/sem_ir/import_ir.cpp

@@ -33,9 +33,13 @@ auto GetCanonicalFileAndInstId(const File* sem_ir, SemIR::InstId inst_id)
     if (auto import_ir_inst_id = sem_ir->insts().GetImportSource(inst_id);
         import_ir_inst_id.has_value()) {
       auto import_ir_inst = sem_ir->import_ir_insts().Get(import_ir_inst_id);
-      sem_ir = sem_ir->import_irs().Get(import_ir_inst.ir_id()).sem_ir;
-      inst_id = import_ir_inst.inst_id();
-      continue;
+      // TODO: For imports from C++, we return the importing instruction, which
+      // isn't necessarily canonical.
+      if (import_ir_inst.ir_id() != ImportIRId::Cpp) {
+        sem_ir = sem_ir->import_irs().Get(import_ir_inst.ir_id()).sem_ir;
+        inst_id = import_ir_inst.inst_id();
+        continue;
+      }
     }
 
     // Step through export declarations to their exported value.

+ 11 - 0
toolchain/sem_ir/inst_categories.h

@@ -167,6 +167,17 @@ struct AnyParamPattern {
   CallParamIndex index;
 };
 
+// A struct-like type with a list of named fields.
+struct AnyStructType {
+  using CategoryInfo = CategoryOf<StructType, CustomLayoutType>;
+
+  InstKind kind;
+
+  TypeId type_id;
+  StructTypeFieldsId fields_id;
+  AnyRawId arg1;
+};
+
 }  // namespace Carbon::SemIR
 
 #endif  // CARBON_TOOLCHAIN_SEM_IR_INST_CATEGORIES_H_

+ 10 - 0
toolchain/sem_ir/inst_fingerprinter.cpp

@@ -150,6 +150,16 @@ struct Worklist {
     AddBlock(sem_ir->struct_type_fields().Get(struct_type_fields_id));
   }
 
+  auto Add(CustomLayoutId custom_layout_id) -> void {
+    if (!custom_layout_id.has_value()) {
+      AddInvalid();
+      return;
+    }
+    auto block = sem_ir->custom_layouts().Get(custom_layout_id);
+    contents.push_back(block.size());
+    contents.insert(contents.end(), block.begin(), block.end());
+  }
+
   auto Add(NameScopeId name_scope_id) -> void {
     if (!name_scope_id.has_value()) {
       AddInvalid();

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -55,6 +55,7 @@ CARBON_SEM_IR_INST_KIND(CompleteTypeWitness)
 CARBON_SEM_IR_INST_KIND(ConstType)
 CARBON_SEM_IR_INST_KIND(ConvertToValueAction)
 CARBON_SEM_IR_INST_KIND(Converted)
+CARBON_SEM_IR_INST_KIND(CustomLayoutType)
 CARBON_SEM_IR_INST_KIND(Deref)
 CARBON_SEM_IR_INST_KIND(ErrorInst)
 CARBON_SEM_IR_INST_KIND(ExportDecl)

+ 6 - 0
toolchain/sem_ir/stringify.cpp

@@ -299,6 +299,12 @@ class Stringifier {
     step_stack_->PushInstId(inst.inner_id);
   }
 
+  auto StringifyInst(InstId /*inst_id*/, CustomLayoutType inst) -> void {
+    auto layout = sem_ir_->custom_layouts().Get(inst.layout_id);
+    *out_ << "<size " << layout[CustomLayoutId::SizeIndex] << ", align "
+          << layout[CustomLayoutId::AlignIndex] << ">";
+  }
+
   auto StringifyInst(InstId /*inst_id*/, FacetAccessType inst) -> void {
     // Given `T:! I`, print `T as type` as simply `T`.
     step_stack_->PushInstId(inst.facet_value_inst_id);

+ 14 - 0
toolchain/sem_ir/typed_insts.h

@@ -552,6 +552,20 @@ struct ConvertToValueAction {
   TypeInstId target_type_inst_id;
 };
 
+// A type whose layout is determined externally. This is used as the object
+// representation of class types imported from C++.
+struct CustomLayoutType {
+  static constexpr auto Kind = InstKind::CustomLayoutType.Define<Parse::NodeId>(
+      {.ir_name = "custom_layout_type",
+       .is_type = InstIsType::Always,
+       .constant_kind = InstConstantKind::WheneverPossible,
+       .deduce_through = true});
+
+  TypeId type_id;
+  StructTypeFieldsId fields_id;
+  CustomLayoutId layout_id;
+};
+
 // The `*` dereference operator, as in `*pointer`.
 struct Deref {
   static constexpr auto Kind =