Pārlūkot izejas kodu

Support for importing C++ enum types. (#5978)

We import C++ enum types as Carbon class types as adapters for the
corresponding builtin integer type, and we import enumerator constants
as integer constants of that class type.

No operators are supported on such values for now; eventually once we
start asking Clang to implement operators on C++-owned types, these
types should be handled in the same way. However, they can be converted
to the corresponding integer type with `as` via adapter conversion, and
integer builtin functions can operate on them.
Richard Smith 8 mēneši atpakaļ
vecāks
revīzija
82ba1a43a1

+ 5 - 0
toolchain/base/int.h

@@ -255,6 +255,11 @@ class IntStore {
     return AddLarge(value);
   }
 
+  // Returns the ID corresponding to this integer value.
+  auto Add(llvm::APSInt value) -> IntId {
+    return value.isSigned() ? AddSigned(value) : AddUnsigned(value);
+  }
+
   // Returns the ID corresponding to this signed integer value, storing an
   // `APInt` if necessary to represent it.
   auto AddSigned(llvm::APInt value) -> IntId {

+ 133 - 43
toolchain/check/import_cpp.cpp

@@ -656,8 +656,11 @@ static auto LookupClangDeclInstId(const Context& context, clang::Decl* decl)
 // Returns the parent of the given declaration. Skips declaration types we
 // ignore.
 static auto GetParentDecl(clang::Decl* clang_decl) -> clang::Decl* {
-  return cast<clang::Decl>(
-      clang_decl->getDeclContext()->getNonTransparentContext());
+  auto* parent_dc = clang_decl->getDeclContext();
+  while (!parent_dc->isLookupContext()) {
+    parent_dc = parent_dc->getParent();
+  }
+  return cast<clang::Decl>(parent_dc);
 }
 
 // Returns the given declaration's parent scope. Assumes the parent declaration
@@ -756,10 +759,10 @@ static auto BuildClassDecl(Context& context,
   return {class_decl.class_id, context.types().GetAsTypeInstId(class_decl_id)};
 }
 
-// Imports a record declaration from Clang to Carbon. If successful, returns
-// the new Carbon class declaration `InstId`.
-static auto ImportCXXRecordDecl(Context& context,
-                                clang::CXXRecordDecl* clang_decl)
+// Imports a tag declaration from Clang to Carbon. This covers classes (which
+// includes structs and unions) as well as enums. If successful, returns the new
+// Carbon class declaration `InstId`.
+static auto ImportTagDecl(Context& context, clang::TagDecl* clang_decl)
     -> SemIR::InstId {
   auto import_ir_inst_id =
       AddImportIRInst(context.sem_ir(), clang_decl->getLocation());
@@ -973,8 +976,8 @@ static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
        .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.
+// Creates a Carbon class definition based on the information in the given Clang
+// class declaration, which is assumed to be for a class definition.
 static auto BuildClassDefinition(Context& context,
                                  SemIR::ImportIRInstId import_ir_inst_id,
                                  SemIR::ClassId class_id,
@@ -999,13 +1002,70 @@ static auto BuildClassDefinition(Context& context,
   class_info.body_block_id = context.inst_block_stack().Pop();
 }
 
-auto ImportCppClassDefinition(Context& context, SemIR::LocId loc_id,
-                              SemIR::ClassId class_id,
-                              SemIR::ClangDeclId clang_decl_id) -> bool {
+// Computes and returns the Carbon type to use as the object representation of
+// the given C++ enum type. This is a builtin int type matching the enum's
+// representation.
+static auto ImportEnumObjectRepresentation(
+    Context& context, SemIR::ImportIRInstId import_ir_inst_id,
+    clang::EnumDecl* enum_decl) -> SemIR::TypeInstId {
+  auto int_type = enum_decl->getIntegerType();
+  CARBON_CHECK(!int_type.isNull(), "incomplete enum type {0}",
+               enum_decl->getNameAsString());
+
+  auto int_kind = int_type->isSignedIntegerType() ? SemIR::IntKind::Signed
+                                                  : SemIR::IntKind::Unsigned;
+  auto bit_width_id = GetOrAddInst<SemIR::IntValue>(
+      context, import_ir_inst_id,
+      {.type_id = GetSingletonType(context, SemIR::IntLiteralType::TypeInstId),
+       .int_id = context.ints().AddUnsigned(
+           llvm::APInt(64, context.ast_context().getIntWidth(int_type)))});
+  return context.types().GetAsTypeInstId(
+      GetOrAddInst(context, SemIR::LocIdAndInst::NoLoc(SemIR::IntType{
+                                .type_id = SemIR::TypeType::TypeId,
+                                .int_kind = int_kind,
+                                .bit_width_id = bit_width_id})));
+}
+
+// Creates a Carbon class definition based on the information in the given Clang
+// enum declaration.
+static auto BuildEnumDefinition(Context& context,
+                                SemIR::ImportIRInstId import_ir_inst_id,
+                                SemIR::ClassId class_id,
+                                SemIR::TypeInstId class_inst_id,
+                                clang::EnumDecl* enum_decl) -> void {
+  auto& class_info = context.classes().Get(class_id);
+  CARBON_CHECK(!class_info.has_definition_started());
+  class_info.definition_id = class_inst_id;
+
+  context.inst_block_stack().Push();
+
+  // Don't allow inheritance from C++ enums, to match the behavior in C++.
+  class_info.inheritance_kind = SemIR::Class::Final;
+
+  // Compute the enum type's object representation. An enum is an adapter for
+  // the corresponding builtin integer type.
+  auto object_repr_id =
+      ImportEnumObjectRepresentation(context, import_ir_inst_id, enum_decl);
+  class_info.adapt_id = AddInst(
+      context, SemIR::LocIdAndInst::UncheckedLoc(
+                   import_ir_inst_id,
+                   SemIR::AdaptDecl{.adapted_type_inst_id = object_repr_id}));
+  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();
+}
+
+auto ImportClassDefinitionForClangDecl(Context& context, SemIR::LocId loc_id,
+                                       SemIR::ClassId class_id,
+                                       SemIR::ClangDeclId clang_decl_id)
+    -> bool {
   clang::ASTUnit* ast = context.sem_ir().clang_ast_unit();
   CARBON_CHECK(ast);
 
-  auto* clang_decl = cast<clang::CXXRecordDecl>(
+  auto* clang_decl = cast<clang::TagDecl>(
       context.sem_ir().clang_decls().Get(clang_decl_id).decl);
   auto class_inst_id = context.types().GetAsTypeInstId(
       context.classes().Get(class_id).first_owning_decl_id);
@@ -1017,7 +1077,7 @@ auto ImportCppClassDefinition(Context& context, SemIR::LocId loc_id,
   Diagnostics::AnnotationScope annotate_diagnostics(
       &context.emitter(), [&](auto& builder) {
         CARBON_DIAGNOSTIC(InCppTypeCompletion, Note,
-                          "while completing C++ class type {0}", SemIR::TypeId);
+                          "while completing C++ type {0}", SemIR::TypeId);
         builder.Note(loc_id, InCppTypeCompletion,
                      context.classes().Get(class_id).self_type_id);
       });
@@ -1026,33 +1086,62 @@ auto ImportCppClassDefinition(Context& context, SemIR::LocId loc_id,
   // instantiation if necessary.
   clang::DiagnosticErrorTrap trap(ast->getDiagnostics());
   if (!ast->getSema().isCompleteType(
-          loc, context.ast_context().getRecordType(clang_decl))) {
+          loc, context.ast_context().getTypeDeclType(clang_decl))) {
     // Type is incomplete. Nothing more to do, but tell the caller if we
     // produced an error.
     return !trap.hasErrorOccurred();
   }
 
-  clang::CXXRecordDecl* clang_def = clang_decl->getDefinition();
-  CARBON_CHECK(clang_def, "Complete type has no definition");
-
-  if (clang_def->getNumVBases()) {
-    // TODO: Handle virtual bases. We don't actually know where they go in the
-    // layout. We may also want to use a different size in the layout for
-    // `partial C`, excluding the virtual base. It's also not entirely safe to
-    // just skip over the virtual base, as the type we would construct would
-    // have a misleading size. For now, treat a C++ class with vbases as
-    // incomplete in Carbon.
-    context.TODO(loc_id, "class with virtual bases");
-    return false;
-  }
-
   auto import_ir_inst_id =
       context.insts().GetCanonicalLocId(class_inst_id).import_ir_inst_id();
-  BuildClassDefinition(context, import_ir_inst_id, class_id, class_inst_id,
-                       clang_def);
+
+  if (auto* class_decl = dyn_cast<clang::CXXRecordDecl>(clang_decl)) {
+    auto* class_def = class_decl->getDefinition();
+    CARBON_CHECK(class_def, "Complete type has no definition");
+
+    if (class_def->getNumVBases()) {
+      // TODO: Handle virtual bases. We don't actually know where they go in the
+      // layout. We may also want to use a different size in the layout for
+      // `partial C`, excluding the virtual base. It's also not entirely safe to
+      // just skip over the virtual base, as the type we would construct would
+      // have a misleading size. For now, treat a C++ class with vbases as
+      // incomplete in Carbon.
+      context.TODO(loc_id, "class with virtual bases");
+      return false;
+    }
+
+    BuildClassDefinition(context, import_ir_inst_id, class_id, class_inst_id,
+                         class_def);
+  } else if (auto* enum_decl = dyn_cast<clang::EnumDecl>(clang_decl)) {
+    BuildEnumDefinition(context, import_ir_inst_id, class_id, class_inst_id,
+                        enum_decl);
+  }
   return true;
 }
 
+// Imports an enumerator declaration from Clang to Carbon.
+static auto ImportEnumConstantDecl(Context& context,
+                                   clang::EnumConstantDecl* enumerator_decl)
+    -> SemIR::InstId {
+  CARBON_CHECK(!IsClangDeclImported(context, enumerator_decl));
+
+  // Find the enclosing enum type.
+  auto type_inst_id = LookupClangDeclInstId(
+      context, cast<clang::EnumDecl>(enumerator_decl->getDeclContext()));
+  auto type_id = context.types().GetTypeIdForTypeInstId(type_inst_id);
+
+  // Build a corresponding IntValue.
+  auto int_id = context.ints().Add(enumerator_decl->getInitVal());
+  auto loc_id =
+      AddImportIRInst(context.sem_ir(), enumerator_decl->getLocation());
+  auto inst_id = AddInstInNoBlock<SemIR::IntValue>(
+      context, loc_id, {.type_id = type_id, .int_id = int_id});
+  context.imports().push_back(inst_id);
+  context.sem_ir().clang_decls().Add(
+      {.decl = enumerator_decl->getCanonicalDecl(), .inst_id = inst_id});
+  return inst_id;
+}
+
 // 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->getCanonicalDecl(),
@@ -1114,18 +1203,16 @@ static auto MapBuiltinType(Context& context, SemIR::LocId loc_id,
   return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
 }
 
-// Maps a C++ record type to a Carbon type.
-static auto MapRecordType(Context& context, const clang::RecordType& type)
+// Maps a C++ tag type (class, struct, union, enum) to a Carbon type.
+static auto MapTagType(Context& context, const clang::TagType& type)
     -> TypeExpr {
-  auto* record_decl = dyn_cast<clang::CXXRecordDecl>(type.getDecl());
-  if (!record_decl) {
-    return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
-  }
+  auto* tag_decl = type.getDecl();
+  CARBON_CHECK(tag_decl);
 
   // Check if the declaration is already mapped.
-  SemIR::InstId record_inst_id = LookupClangDeclInstId(context, record_decl);
+  SemIR::InstId record_inst_id = LookupClangDeclInstId(context, tag_decl);
   if (!record_inst_id.has_value()) {
-    record_inst_id = ImportCXXRecordDecl(context, record_decl);
+    record_inst_id = ImportTagDecl(context, tag_decl);
   }
   SemIR::TypeInstId record_type_inst_id =
       context.types().GetAsTypeInstId(record_inst_id);
@@ -1143,8 +1230,8 @@ static auto MapNonWrapperType(Context& context, SemIR::LocId loc_id,
     return MapBuiltinType(context, loc_id, type, *builtin_type);
   }
 
-  if (const auto* record_type = type->getAs<clang::RecordType>()) {
-    return MapRecordType(context, *record_type);
+  if (const auto* tag_type = type->getAs<clang::TagType>()) {
+    return MapTagType(context, *tag_type);
   }
 
   CARBON_CHECK(!type.hasQualifiers() && !type->isPointerType(),
@@ -1640,8 +1727,8 @@ static auto AddDependentUnimportedTypeDecls(const Context& context,
     }
   }
 
-  if (const auto* record_type = type->getAs<clang::RecordType>()) {
-    AddDependentDecl(context, record_type->getDecl(), worklist);
+  if (const auto* tag_type = type->getAs<clang::TagType>()) {
+    AddDependentDecl(context, tag_type->getDecl(), worklist);
   }
 }
 
@@ -1666,7 +1753,7 @@ static auto AddDependentUnimportedDecls(const Context& context,
     AddDependentUnimportedFunctionDecls(context, *clang_function_decl,
                                         worklist);
   } else if (auto* type_decl = dyn_cast<clang::TypeDecl>(clang_decl)) {
-    if (!isa<clang::RecordDecl>(clang_decl)) {
+    if (!isa<clang::TagDecl>(clang_decl)) {
       AddDependentUnimportedTypeDecls(
           context, type_decl->getASTContext().getTypeDeclType(type_decl),
           worklist);
@@ -1713,6 +1800,9 @@ static auto ImportDeclAfterDependencies(Context& context, SemIR::LocId loc_id,
                  "Unsupported: field declaration has unhandled type or kind");
     return SemIR::ErrorInst::InstId;
   }
+  if (auto* enum_const_decl = dyn_cast<clang::EnumConstantDecl>(clang_decl)) {
+    return ImportEnumConstantDecl(context, enum_const_decl);
+  }
 
   context.TODO(AddImportIRInst(context.sem_ir(), clang_decl->getLocation()),
                llvm::formatv("Unsupported: Declaration type {0}",

+ 8 - 6
toolchain/check/import_cpp.h

@@ -31,12 +31,14 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
                        SemIR::NameScopeId scope_id, SemIR::NameId name_id)
     -> SemIR::ScopeLookupResult;
 
-// Given a class declaration that was imported from C++, attempt to import a
-// corresponding class definition. Returns true if nothing went wrong (whether
-// or not a definition could be imported), false if a diagnostic was produced.
-auto ImportCppClassDefinition(Context& context, SemIR::LocId loc_id,
-                              SemIR::ClassId class_id,
-                              SemIR::ClangDeclId clang_decl_id) -> bool;
+// Given a Carbon class declaration that was imported from some kind of C++
+// declaration, such as a class or enum, attempt to import a corresponding class
+// definition. Returns true if nothing went wrong (whether or not a definition
+// could be imported), false if a diagnostic was produced.
+auto ImportClassDefinitionForClangDecl(Context& context, SemIR::LocId loc_id,
+                                       SemIR::ClassId class_id,
+                                       SemIR::ClangDeclId clang_decl_id)
+    -> bool;
 
 }  // namespace Carbon::Check
 

+ 1 - 1
toolchain/check/testdata/interop/cpp/class/base.carbon

@@ -181,7 +181,7 @@ fn Convert(p: Cpp.B*) -> Cpp.A* {
   // CHECK:STDERR: fail_todo_use_virtual_inheritance.carbon:[[@LINE+14]]:3: error: semantics TODO: `class with virtual bases` [SemanticsTodo]
   // CHECK:STDERR:   return p;
   // CHECK:STDERR:   ^~~~~~~~~
-  // CHECK:STDERR: fail_todo_use_virtual_inheritance.carbon:[[@LINE+11]]:3: note: while completing C++ class type `Cpp.B` [InCppTypeCompletion]
+  // CHECK:STDERR: fail_todo_use_virtual_inheritance.carbon:[[@LINE+11]]:3: note: while completing C++ type `Cpp.B` [InCppTypeCompletion]
   // CHECK:STDERR:   return p;
   // CHECK:STDERR:   ^~~~~~~~~
   // CHECK:STDERR:

+ 1 - 1
toolchain/check/testdata/interop/cpp/class/template.carbon

@@ -73,7 +73,7 @@ import Cpp library "instantiation_error.h";
 //@dump-sem-ir-begin
 var x: Cpp.XY.r#type = 0;
 
-// CHECK:STDERR: fail_use_instantiation_error.carbon:[[@LINE+4]]:8: note: while completing C++ class type `Cpp.X` [InCppTypeCompletion]
+// CHECK:STDERR: fail_use_instantiation_error.carbon:[[@LINE+4]]:8: note: while completing C++ type `Cpp.X` [InCppTypeCompletion]
 // CHECK:STDERR: var y: Cpp.Xint.r#type = 0;
 // CHECK:STDERR:        ^~~~~~~~~~~~~
 // CHECK:STDERR:

+ 165 - 0
toolchain/check/testdata/interop/cpp/enum/anonymous.carbon

@@ -0,0 +1,165 @@
+// 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/convert.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/enum/anonymous.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/enum/anonymous.carbon
+
+// --- anon.h
+
+enum { a, b, c };
+
+void F(decltype(a));
+
+class C {
+ public:
+  enum { d, e, f };
+
+  void F(decltype(d));
+};
+
+// --- copy_enum.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "anon.h";
+
+//@dump-sem-ir-begin
+fn G() {
+  Cpp.F(Cpp.b);
+
+  Cpp.C.C().F(Cpp.C.e);
+}
+//@dump-sem-ir-end
+
+// CHECK:STDOUT: --- copy_enum.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
+// CHECK:STDOUT:   %.4f0: type = class_type @.1 [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.793: type = ptr_type %.4f0 [concrete]
+// CHECK:STDOUT:   %F__carbon_thunk.type.eda1ac.1: type = fn_type @F__carbon_thunk.1 [concrete]
+// CHECK:STDOUT:   %F__carbon_thunk.0cd6a8.1: %F__carbon_thunk.type.eda1ac.1 = struct_value () [concrete]
+// CHECK:STDOUT:   %int_1.81a: %.4f0 = int_value 1 [concrete]
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT:   %C.C.type: type = fn_type @C.C [concrete]
+// CHECK:STDOUT:   %C.C: %C.C.type = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.d9e: type = ptr_type %C [concrete]
+// CHECK:STDOUT:   %C__carbon_thunk.type: type = fn_type @C__carbon_thunk [concrete]
+// CHECK:STDOUT:   %C__carbon_thunk: %C__carbon_thunk.type = struct_value () [concrete]
+// CHECK:STDOUT:   %.bb7: type = class_type @.2 [concrete]
+// CHECK:STDOUT:   %C.F.type: type = fn_type @C.F [concrete]
+// CHECK:STDOUT:   %C.F: %C.F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.73d: type = ptr_type %.bb7 [concrete]
+// CHECK:STDOUT:   %F__carbon_thunk.type.eda1ac.2: type = fn_type @F__carbon_thunk.2 [concrete]
+// CHECK:STDOUT:   %F__carbon_thunk.0cd6a8.2: %F__carbon_thunk.type.eda1ac.2 = struct_value () [concrete]
+// CHECK:STDOUT:   %int_1.1d6: %.bb7 = int_value 1 [concrete]
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.type.8e9: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%.bb7) [concrete]
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.e52: %T.as.Destroy.impl.Op.type.8e9 = 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:   %T.as.Destroy.impl.Op.type.d1b: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%.4f0) [concrete]
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.002: %T.as.Destroy.impl.Op.type.d1b = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:     .b = %int_1.81a
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F__carbon_thunk.decl.e1b8ec.1: %F__carbon_thunk.type.eda1ac.1 = fn_decl @F__carbon_thunk.1 [concrete = constants.%F__carbon_thunk.0cd6a8.1] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %int_1.81a: %.4f0 = int_value 1 [concrete = constants.%int_1.81a]
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
+// CHECK:STDOUT:   %C.C.decl: %C.C.type = fn_decl @C.C [concrete = constants.%C.C] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C__carbon_thunk.decl: %C__carbon_thunk.type = fn_decl @C__carbon_thunk [concrete = constants.%C__carbon_thunk] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.F.decl: %C.F.type = fn_decl @C.F [concrete = constants.%C.F] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F__carbon_thunk.decl.e1b8ec.2: %F__carbon_thunk.type.eda1ac.2 = fn_decl @F__carbon_thunk.2 [concrete = constants.%F__carbon_thunk.0cd6a8.2] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %int_1.1d6: %.bb7 = int_value 1 [concrete = constants.%int_1.1d6]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref.loc8_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %F.ref.loc8: %F.type = name_ref F, imports.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:   %Cpp.ref.loc8_9: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %b.ref: %.4f0 = name_ref b, imports.%int_1.81a [concrete = constants.%int_1.81a]
+// CHECK:STDOUT:   %.loc8_12.1: ref %.4f0 = temporary_storage
+// CHECK:STDOUT:   %.loc8_12.2: ref %.4f0 = temporary %.loc8_12.1, %b.ref
+// CHECK:STDOUT:   %addr.loc8_14: %ptr.793 = addr_of %.loc8_12.2
+// CHECK:STDOUT:   %F__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%F__carbon_thunk.decl.e1b8ec.1(%addr.loc8_14)
+// CHECK:STDOUT:   %Cpp.ref.loc10_3: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %C.ref.loc10_6: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %C.ref.loc10_8: %C.C.type = name_ref C, imports.%C.C.decl [concrete = constants.%C.C]
+// CHECK:STDOUT:   %.loc10_11.1: ref %C = temporary_storage
+// CHECK:STDOUT:   %addr.loc10_11.1: %ptr.d9e = addr_of %.loc10_11.1
+// CHECK:STDOUT:   %C__carbon_thunk.call: init %empty_tuple.type = call imports.%C__carbon_thunk.decl(%addr.loc10_11.1)
+// CHECK:STDOUT:   %.loc10_11.2: init %C = in_place_init %C__carbon_thunk.call, %.loc10_11.1
+// CHECK:STDOUT:   %.loc10_11.3: ref %C = temporary %.loc10_11.1, %.loc10_11.2
+// CHECK:STDOUT:   %F.ref.loc10: %C.F.type = name_ref F, imports.%C.F.decl [concrete = constants.%C.F]
+// CHECK:STDOUT:   %C.F.bound: <bound method> = bound_method %.loc10_11.3, %F.ref.loc10
+// CHECK:STDOUT:   %Cpp.ref.loc10_15: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %C.ref.loc10_18: type = name_ref C, imports.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:   %e.ref: %.bb7 = name_ref e, imports.%int_1.1d6 [concrete = constants.%int_1.1d6]
+// CHECK:STDOUT:   %addr.loc10_11.2: %ptr.d9e = addr_of %.loc10_11.3
+// CHECK:STDOUT:   %.loc10_20.1: ref %.bb7 = temporary_storage
+// CHECK:STDOUT:   %.loc10_20.2: ref %.bb7 = temporary %.loc10_20.1, %e.ref
+// CHECK:STDOUT:   %addr.loc10_22: %ptr.73d = addr_of %.loc10_20.2
+// CHECK:STDOUT:   %F__carbon_thunk.call.loc10: init %empty_tuple.type = call imports.%F__carbon_thunk.decl.e1b8ec.2(%addr.loc10_11.2, %addr.loc10_22)
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound.loc10_20: <bound method> = bound_method %.loc10_20.1, constants.%T.as.Destroy.impl.Op.e52
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc10_20: <bound method> = bound_method %.loc10_20.1, %T.as.Destroy.impl.Op.specific_fn.1
+// CHECK:STDOUT:   %addr.loc10_20: %ptr.73d = addr_of %.loc10_20.1
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call.loc10_20: init %empty_tuple.type = call %bound_method.loc10_20(%addr.loc10_20)
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound.loc10_11: <bound method> = bound_method %.loc10_11.1, constants.%T.as.Destroy.impl.Op.21b
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc10_11: <bound method> = bound_method %.loc10_11.1, %T.as.Destroy.impl.Op.specific_fn.2
+// CHECK:STDOUT:   %addr.loc10_11.3: %ptr.d9e = addr_of %.loc10_11.1
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call.loc10_11: init %empty_tuple.type = call %bound_method.loc10_11(%addr.loc10_11.3)
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound.loc8: <bound method> = bound_method %.loc8_12.1, constants.%T.as.Destroy.impl.Op.002
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method.loc8: <bound method> = bound_method %.loc8_12.1, %T.as.Destroy.impl.Op.specific_fn.3
+// CHECK:STDOUT:   %addr.loc8_12: %ptr.793 = addr_of %.loc8_12.1
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call.loc8: init %empty_tuple.type = call %bound_method.loc8(%addr.loc8_12)
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 194 - 0
toolchain/check/testdata/interop/cpp/enum/convert.carbon

@@ -0,0 +1,194 @@
+// 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/check/testdata/interop/cpp/enum/convert.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/enum/convert.carbon
+
+// --- enum.h
+
+enum Enum : short { a, b, c };
+
+enum Other : short {};
+
+enum DifferentSize : long {};
+
+// --- convert_enum.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "enum.h";
+
+//@dump-sem-ir-begin
+fn F() {
+  let a: i16 = Cpp.Enum.a as i16;
+  let b: Cpp.Enum = (42 as i16) as Cpp.Enum;
+  let c: Cpp.Other = Cpp.a as Cpp.Other;
+}
+//@dump-sem-ir-end
+
+// --- fail_wrong_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "enum.h";
+
+fn F() {
+  // CHECK:STDERR: fail_wrong_type.carbon:[[@LINE+7]]:3: error: cannot convert expression of type `Cpp.Enum` to `i8` with `as` [ConversionFailure]
+  // CHECK:STDERR:   Cpp.Enum.a as i8;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_wrong_type.carbon:[[@LINE+4]]:3: note: type `Cpp.Enum` does not implement interface `Core.As(i8)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   Cpp.Enum.a as i8;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  Cpp.Enum.a as i8;
+
+  // CHECK:STDERR: fail_wrong_type.carbon:[[@LINE+7]]:3: error: cannot convert expression of type `Cpp.Enum` to `i32` with `as` [ConversionFailure]
+  // CHECK:STDERR:   Cpp.Enum.a as i32;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_wrong_type.carbon:[[@LINE+4]]:3: note: type `Cpp.Enum` does not implement interface `Core.As(i32)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   Cpp.Enum.a as i32;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  Cpp.Enum.a as i32;
+
+  // CHECK:STDERR: fail_wrong_type.carbon:[[@LINE+7]]:3: error: cannot convert expression of type `i8` to `Cpp.Enum` with `as` [ConversionFailure]
+  // CHECK:STDERR:   (42 as i8) as Cpp.Enum;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_wrong_type.carbon:[[@LINE+4]]:3: note: type `i8` does not implement interface `Core.As(Cpp.Enum)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   (42 as i8) as Cpp.Enum;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  (42 as i8) as Cpp.Enum;
+
+  // CHECK:STDERR: fail_wrong_type.carbon:[[@LINE+7]]:3: error: cannot convert expression of type `i32` to `Cpp.Enum` with `as` [ConversionFailure]
+  // CHECK:STDERR:   (42 as i32) as Cpp.Enum;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_wrong_type.carbon:[[@LINE+4]]:3: note: type `i32` does not implement interface `Core.As(Cpp.Enum)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   (42 as i32) as Cpp.Enum;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  (42 as i32) as Cpp.Enum;
+
+  // CHECK:STDERR: fail_wrong_type.carbon:[[@LINE+7]]:3: error: cannot convert expression of type `Cpp.Enum` to `Cpp.DifferentSize` with `as` [ConversionFailure]
+  // CHECK:STDERR:   Cpp.a as Cpp.DifferentSize;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_wrong_type.carbon:[[@LINE+4]]:3: note: type `Cpp.Enum` does not implement interface `Core.As(Cpp.DifferentSize)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   Cpp.a as Cpp.DifferentSize;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  Cpp.a as Cpp.DifferentSize;
+}
+
+// CHECK:STDOUT: --- convert_enum.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %int_16: Core.IntLiteral = int_value 16 [concrete]
+// CHECK:STDOUT:   %i16: type = class_type @Int, @Int(%int_16) [concrete]
+// CHECK:STDOUT:   %pattern_type.2f8: type = pattern_type %i16 [concrete]
+// CHECK:STDOUT:   %Enum: type = class_type @Enum [concrete]
+// CHECK:STDOUT:   %int_0.420: %Enum = int_value 0 [concrete]
+// CHECK:STDOUT:   %int_0.320: %i16 = int_value 0 [concrete]
+// CHECK:STDOUT:   %pattern_type.ebf: type = pattern_type %Enum [concrete]
+// CHECK:STDOUT:   %int_42.20e: Core.IntLiteral = int_value 42 [concrete]
+// CHECK:STDOUT:   %As.type.a96: type = facet_type <@As, @As(%i16)> [concrete]
+// CHECK:STDOUT:   %As.Convert.type.be5: type = fn_type @As.Convert, @As(%i16) [concrete]
+// CHECK:STDOUT:   %To: Core.IntLiteral = bind_symbolic_name To, 0 [symbolic]
+// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.type.062: type = fn_type @Core.IntLiteral.as.As.impl.Convert, @Core.IntLiteral.as.As.impl(%To) [symbolic]
+// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.527: %Core.IntLiteral.as.As.impl.Convert.type.062 = struct_value () [symbolic]
+// CHECK:STDOUT:   %As.impl_witness.0ef: <witness> = impl_witness imports.%As.impl_witness_table.eb4, @Core.IntLiteral.as.As.impl(%int_16) [concrete]
+// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.type.172: type = fn_type @Core.IntLiteral.as.As.impl.Convert, @Core.IntLiteral.as.As.impl(%int_16) [concrete]
+// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.489: %Core.IntLiteral.as.As.impl.Convert.type.172 = struct_value () [concrete]
+// CHECK:STDOUT:   %As.facet: %As.type.a96 = facet_value Core.IntLiteral, (%As.impl_witness.0ef) [concrete]
+// CHECK:STDOUT:   %.91d: type = fn_type_with_self_type %As.Convert.type.be5, %As.facet [concrete]
+// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.bound: <bound method> = bound_method %int_42.20e, %Core.IntLiteral.as.As.impl.Convert.489 [concrete]
+// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.specific_fn: <specific function> = specific_function %Core.IntLiteral.as.As.impl.Convert.489, @Core.IntLiteral.as.As.impl.Convert(%int_16) [concrete]
+// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %int_42.20e, %Core.IntLiteral.as.As.impl.Convert.specific_fn [concrete]
+// CHECK:STDOUT:   %int_42.a0e: %i16 = int_value 42 [concrete]
+// CHECK:STDOUT:   %int_42.c3a: %Enum = int_value 42 [concrete]
+// CHECK:STDOUT:   %Other: type = class_type @Other [concrete]
+// CHECK:STDOUT:   %pattern_type.c06: type = pattern_type %Other [concrete]
+// CHECK:STDOUT:   %int_0.fa2: %Other = int_value 0 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .Enum = %Enum.decl
+// CHECK:STDOUT:     .Other = %Other.decl
+// CHECK:STDOUT:     .a = %int_0
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Enum.decl: type = class_decl @Enum [concrete = constants.%Enum] {} {}
+// CHECK:STDOUT:   %int_0: %Enum = int_value 0 [concrete = constants.%int_0.420]
+// CHECK:STDOUT:   %Core.import_ref.78a: @Core.IntLiteral.as.As.impl.%Core.IntLiteral.as.As.impl.Convert.type (%Core.IntLiteral.as.As.impl.Convert.type.062) = import_ref Core//prelude/parts/int, loc25_39, loaded [symbolic = @Core.IntLiteral.as.As.impl.%Core.IntLiteral.as.As.impl.Convert (constants.%Core.IntLiteral.as.As.impl.Convert.527)]
+// CHECK:STDOUT:   %As.impl_witness_table.eb4 = impl_witness_table (%Core.import_ref.78a), @Core.IntLiteral.as.As.impl [concrete]
+// CHECK:STDOUT:   %Other.decl: type = class_decl @Other [concrete = constants.%Other] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.2f8 = binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Enum.ref.loc8: type = name_ref Enum, imports.%Enum.decl [concrete = constants.%Enum]
+// CHECK:STDOUT:   %a.ref.loc8: %Enum = name_ref a, imports.%int_0 [concrete = constants.%int_0.420]
+// CHECK:STDOUT:   %int_16.loc8_30: Core.IntLiteral = int_value 16 [concrete = constants.%int_16]
+// CHECK:STDOUT:   %i16.loc8_30: type = class_type @Int, @Int(constants.%int_16) [concrete = constants.%i16]
+// CHECK:STDOUT:   %.loc8_27.1: %i16 = as_compatible %a.ref.loc8 [concrete = constants.%int_0.320]
+// CHECK:STDOUT:   %.loc8_27.2: %i16 = converted %a.ref.loc8, %.loc8_27.1 [concrete = constants.%int_0.320]
+// CHECK:STDOUT:   %.loc8_10: type = splice_block %i16.loc8_10 [concrete = constants.%i16] {
+// CHECK:STDOUT:     %int_16.loc8_10: Core.IntLiteral = int_value 16 [concrete = constants.%int_16]
+// CHECK:STDOUT:     %i16.loc8_10: type = class_type @Int, @Int(constants.%int_16) [concrete = constants.%i16]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %i16 = bind_name a, %.loc8_27.2
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b.patt: %pattern_type.ebf = binding_pattern b [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %int_42: Core.IntLiteral = int_value 42 [concrete = constants.%int_42.20e]
+// CHECK:STDOUT:   %int_16.loc9: Core.IntLiteral = int_value 16 [concrete = constants.%int_16]
+// CHECK:STDOUT:   %i16.loc9: type = class_type @Int, @Int(constants.%int_16) [concrete = constants.%i16]
+// CHECK:STDOUT:   %impl.elem0: %.91d = impl_witness_access constants.%As.impl_witness.0ef, element0 [concrete = constants.%Core.IntLiteral.as.As.impl.Convert.489]
+// CHECK:STDOUT:   %bound_method.loc9_25.1: <bound method> = bound_method %int_42, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.As.impl.Convert.bound]
+// CHECK:STDOUT:   %specific_fn: <specific function> = specific_function %impl.elem0, @Core.IntLiteral.as.As.impl.Convert(constants.%int_16) [concrete = constants.%Core.IntLiteral.as.As.impl.Convert.specific_fn]
+// CHECK:STDOUT:   %bound_method.loc9_25.2: <bound method> = bound_method %int_42, %specific_fn [concrete = constants.%bound_method]
+// CHECK:STDOUT:   %Core.IntLiteral.as.As.impl.Convert.call: init %i16 = call %bound_method.loc9_25.2(%int_42) [concrete = constants.%int_42.a0e]
+// CHECK:STDOUT:   %.loc9_25.1: %i16 = value_of_initializer %Core.IntLiteral.as.As.impl.Convert.call [concrete = constants.%int_42.a0e]
+// CHECK:STDOUT:   %.loc9_25.2: %i16 = converted %int_42, %.loc9_25.1 [concrete = constants.%int_42.a0e]
+// CHECK:STDOUT:   %Cpp.ref.loc9_36: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Enum.ref.loc9_39: type = name_ref Enum, imports.%Enum.decl [concrete = constants.%Enum]
+// CHECK:STDOUT:   %.loc9_33.1: %Enum = as_compatible %.loc9_25.2 [concrete = constants.%int_42.c3a]
+// CHECK:STDOUT:   %.loc9_33.2: %Enum = converted %.loc9_25.2, %.loc9_33.1 [concrete = constants.%int_42.c3a]
+// CHECK:STDOUT:   %.loc9_13: type = splice_block %Enum.ref.loc9_13 [concrete = constants.%Enum] {
+// CHECK:STDOUT:     %Cpp.ref.loc9_10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Enum.ref.loc9_13: type = name_ref Enum, imports.%Enum.decl [concrete = constants.%Enum]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b: %Enum = bind_name b, %.loc9_33.2
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %c.patt: %pattern_type.c06 = binding_pattern c [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc10_22: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %a.ref.loc10: %Enum = name_ref a, imports.%int_0 [concrete = constants.%int_0.420]
+// CHECK:STDOUT:   %Cpp.ref.loc10_31: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Other.ref.loc10_34: type = name_ref Other, imports.%Other.decl [concrete = constants.%Other]
+// CHECK:STDOUT:   %.loc10_28.1: %Other = as_compatible %a.ref.loc10 [concrete = constants.%int_0.fa2]
+// CHECK:STDOUT:   %.loc10_28.2: %Other = converted %a.ref.loc10, %.loc10_28.1 [concrete = constants.%int_0.fa2]
+// CHECK:STDOUT:   %.loc10_13: type = splice_block %Other.ref.loc10_13 [concrete = constants.%Other] {
+// CHECK:STDOUT:     %Cpp.ref.loc10_10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Other.ref.loc10_13: type = name_ref Other, imports.%Other.decl [concrete = constants.%Other]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %c: %Other = bind_name c, %.loc10_28.2
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 88 - 0
toolchain/check/testdata/interop/cpp/enum/copy.carbon

@@ -0,0 +1,88 @@
+// 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/convert.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/enum/copy.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/enum/copy.carbon
+
+// --- enum.h
+
+enum Enum : short { a, b, c };
+
+// --- copy_enum.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "enum.h";
+
+//@dump-sem-ir-begin
+fn F() {
+  var a: Cpp.Enum = Cpp.Enum.a;
+
+  a = Cpp.Enum.b;
+}
+//@dump-sem-ir-end
+
+// CHECK:STDOUT: --- copy_enum.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Enum: type = class_type @Enum [concrete]
+// CHECK:STDOUT:   %pattern_type.ebf: type = pattern_type %Enum [concrete]
+// CHECK:STDOUT:   %int_0: %Enum = int_value 0 [concrete]
+// CHECK:STDOUT:   %int_1: %Enum = int_value 1 [concrete]
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.type.6f4: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%Enum) [concrete]
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.911: %T.as.Destroy.impl.Op.type.6f4 = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.47b: type = ptr_type %Enum [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .Enum = %Enum.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Enum.decl: type = class_decl @Enum [concrete = constants.%Enum] {} {}
+// CHECK:STDOUT:   %int_0: %Enum = int_value 0 [concrete = constants.%int_0]
+// CHECK:STDOUT:   %int_1: %Enum = int_value 1 [concrete = constants.%int_1]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.ebf = binding_pattern a [concrete]
+// CHECK:STDOUT:     %a.var_patt: %pattern_type.ebf = var_pattern %a.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a.var: ref %Enum = var %a.var_patt
+// CHECK:STDOUT:   %Cpp.ref.loc8_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Enum.ref.loc8_24: type = name_ref Enum, imports.%Enum.decl [concrete = constants.%Enum]
+// CHECK:STDOUT:   %a.ref.loc8: %Enum = name_ref a, imports.%int_0 [concrete = constants.%int_0]
+// CHECK:STDOUT:   assign %a.var, %a.ref.loc8
+// CHECK:STDOUT:   %.loc8: type = splice_block %Enum.ref.loc8_13 [concrete = constants.%Enum] {
+// CHECK:STDOUT:     %Cpp.ref.loc8_10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Enum.ref.loc8_13: type = name_ref Enum, imports.%Enum.decl [concrete = constants.%Enum]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: ref %Enum = bind_name a, %a.var
+// CHECK:STDOUT:   %a.ref.loc10: ref %Enum = name_ref a, %a
+// CHECK:STDOUT:   %Cpp.ref.loc10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Enum.ref.loc10: type = name_ref Enum, imports.%Enum.decl [concrete = constants.%Enum]
+// CHECK:STDOUT:   %b.ref: %Enum = name_ref b, imports.%int_1 [concrete = constants.%int_1]
+// CHECK:STDOUT:   assign %a.ref.loc10, %b.ref
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.bound: <bound method> = bound_method %a.var, constants.%T.as.Destroy.impl.Op.911
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %bound_method: <bound method> = bound_method %a.var, %T.as.Destroy.impl.Op.specific_fn
+// CHECK:STDOUT:   %addr: %ptr.47b = addr_of %a.var
+// CHECK:STDOUT:   %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr)
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 81 - 0
toolchain/check/testdata/interop/cpp/enum/fixed.carbon

@@ -0,0 +1,81 @@
+// 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/convert.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/enum/fixed.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/enum/fixed.carbon
+
+// --- enum.h
+
+enum Enum : short { a, b, c };
+
+// --- import_enum.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "enum.h";
+
+//@dump-sem-ir-begin
+fn F() {
+  let a: Cpp.Enum = Cpp.Enum.a;
+  let b: Cpp.Enum = Cpp.b;
+}
+//@dump-sem-ir-end
+
+// CHECK:STDOUT: --- import_enum.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Enum: type = class_type @Enum [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %Enum [concrete]
+// CHECK:STDOUT:   %int_0: %Enum = int_value 0 [concrete]
+// CHECK:STDOUT:   %int_1: %Enum = int_value 1 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .Enum = %Enum.decl
+// CHECK:STDOUT:     .b = %int_1
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Enum.decl: type = class_decl @Enum [concrete = constants.%Enum] {} {}
+// CHECK:STDOUT:   %int_0: %Enum = int_value 0 [concrete = constants.%int_0]
+// CHECK:STDOUT:   %int_1: %Enum = int_value 1 [concrete = constants.%int_1]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type = binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc8_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Enum.ref.loc8_24: type = name_ref Enum, imports.%Enum.decl [concrete = constants.%Enum]
+// CHECK:STDOUT:   %a.ref: %Enum = name_ref a, imports.%int_0 [concrete = constants.%int_0]
+// CHECK:STDOUT:   %.loc8: type = splice_block %Enum.ref.loc8_13 [concrete = constants.%Enum] {
+// CHECK:STDOUT:     %Cpp.ref.loc8_10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Enum.ref.loc8_13: type = name_ref Enum, imports.%Enum.decl [concrete = constants.%Enum]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %Enum = bind_name a, %a.ref
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b.patt: %pattern_type = binding_pattern b [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc9_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %b.ref: %Enum = name_ref b, imports.%int_1 [concrete = constants.%int_1]
+// CHECK:STDOUT:   %.loc9: type = splice_block %Enum.ref.loc9 [concrete = constants.%Enum] {
+// CHECK:STDOUT:     %Cpp.ref.loc9_10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Enum.ref.loc9: type = name_ref Enum, imports.%Enum.decl [concrete = constants.%Enum]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b: %Enum = bind_name b, %b.ref
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 92 - 0
toolchain/check/testdata/interop/cpp/enum/incomplete.carbon

@@ -0,0 +1,92 @@
+// 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/convert.carbon
+// EXTRA-ARGS: --clang-arg=-fms-compatibility
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/enum/incomplete.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/enum/incomplete.carbon
+
+// --- declaration.h
+
+// This is not valid in standard C++, but MSVC allows it as an extension, and so
+// Clang allows it in MSVC compatibility mode.
+enum UnsizedEnum;
+
+// --- import_declaration.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: import_declaration.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./declaration.h:4:6: warning: forward references to 'enum' types are a Microsoft extension [CppInteropParseWarning]
+// CHECK:STDERR:     4 | enum UnsizedEnum;
+// CHECK:STDERR:       |      ^
+// CHECK:STDERR:
+import Cpp library "declaration.h";
+
+//@dump-sem-ir-begin
+fn F(e: Cpp.UnsizedEnum);
+//@dump-sem-ir-end
+
+// --- fail_use_declaration_as_definition.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./declaration.h:4:6: warning: forward references to 'enum' types are a Microsoft extension [CppInteropParseWarning]
+// CHECK:STDERR:     4 | enum UnsizedEnum;
+// CHECK:STDERR:       |      ^
+// CHECK:STDERR:
+import Cpp library "declaration.h";
+
+fn MyF() {
+  // TODO: It's a bit surprising to refer to this type as a class, even though
+  // we model it as a class in Carbon. Consider customizing the diagnostic.
+  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE+8]]:10: error: binding pattern has incomplete type `UnsizedEnum` in name binding declaration [IncompleteTypeInBindingDecl]
+  // CHECK:STDERR:   var e: Cpp.UnsizedEnum;
+  // CHECK:STDERR:          ^~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_use_declaration_as_definition.carbon:[[@LINE-8]]:10: in file included here [InCppInclude]
+  // CHECK:STDERR: ./declaration.h:4:6: note: class was forward declared here [ClassForwardDeclaredHere]
+  // CHECK:STDERR: enum UnsizedEnum;
+  // CHECK:STDERR:      ^
+  // CHECK:STDERR:
+  var e: Cpp.UnsizedEnum;
+}
+
+// CHECK:STDOUT: --- import_declaration.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %UnsizedEnum: type = class_type @UnsizedEnum [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %UnsizedEnum [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:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .UnsizedEnum = %UnsizedEnum.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %UnsizedEnum.decl: type = class_decl @UnsizedEnum [concrete = constants.%UnsizedEnum] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %e.patt: %pattern_type = binding_pattern e [concrete]
+// CHECK:STDOUT:     %e.param_patt: %pattern_type = value_param_pattern %e.patt, call_param0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %e.param: %UnsizedEnum = value_param call_param0
+// CHECK:STDOUT:     %.loc12: type = splice_block %UnsizedEnum.ref [concrete = constants.%UnsizedEnum] {
+// CHECK:STDOUT:       %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:       %UnsizedEnum.ref: type = name_ref UnsizedEnum, imports.%UnsizedEnum.decl [concrete = constants.%UnsizedEnum]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %e: %UnsizedEnum = bind_name e, %e.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%e.param: %UnsizedEnum);
+// CHECK:STDOUT:

+ 89 - 0
toolchain/check/testdata/interop/cpp/enum/member.carbon

@@ -0,0 +1,89 @@
+// 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/convert.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/enum/member.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/enum/member.carbon
+
+// --- enum_member.h
+
+class A {
+public:
+  enum E { a, b, c };
+};
+
+// --- import_enum_member.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "enum_member.h";
+
+//@dump-sem-ir-begin
+fn F() {
+  let a: Cpp.A.E = Cpp.A.a;
+  let b: Cpp.A.E = Cpp.A.E.b;
+}
+//@dump-sem-ir-end
+
+// CHECK:STDOUT: --- import_enum_member.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %A: type = class_type @A [concrete]
+// CHECK:STDOUT:   %E: type = class_type @E [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %E [concrete]
+// CHECK:STDOUT:   %int_0: %E = int_value 0 [concrete]
+// CHECK:STDOUT:   %int_1: %E = int_value 1 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .A = %A.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %A.decl: type = class_decl @A [concrete = constants.%A] {} {}
+// CHECK:STDOUT:   %E.decl: type = class_decl @E [concrete = constants.%E] {} {}
+// CHECK:STDOUT:   %int_0: %E = int_value 0 [concrete = constants.%int_0]
+// CHECK:STDOUT:   %int_1: %E = int_value 1 [concrete = constants.%int_1]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type = binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc8_20: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %A.ref.loc8_23: type = name_ref A, imports.%A.decl [concrete = constants.%A]
+// CHECK:STDOUT:   %a.ref: %E = name_ref a, imports.%int_0 [concrete = constants.%int_0]
+// CHECK:STDOUT:   %.loc8: type = splice_block %E.ref.loc8 [concrete = constants.%E] {
+// CHECK:STDOUT:     %Cpp.ref.loc8_10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %A.ref.loc8_13: type = name_ref A, imports.%A.decl [concrete = constants.%A]
+// CHECK:STDOUT:     %E.ref.loc8: type = name_ref E, imports.%E.decl [concrete = constants.%E]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %E = bind_name a, %a.ref
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b.patt: %pattern_type = binding_pattern b [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc9_20: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %A.ref.loc9_23: type = name_ref A, imports.%A.decl [concrete = constants.%A]
+// CHECK:STDOUT:   %E.ref.loc9_25: type = name_ref E, imports.%E.decl [concrete = constants.%E]
+// CHECK:STDOUT:   %b.ref: %E = name_ref b, imports.%int_1 [concrete = constants.%int_1]
+// CHECK:STDOUT:   %.loc9: type = splice_block %E.ref.loc9_15 [concrete = constants.%E] {
+// CHECK:STDOUT:     %Cpp.ref.loc9_10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %A.ref.loc9_13: type = name_ref A, imports.%A.decl [concrete = constants.%A]
+// CHECK:STDOUT:     %E.ref.loc9_15: type = name_ref E, imports.%E.decl [concrete = constants.%E]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b: %E = bind_name b, %b.ref
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 81 - 0
toolchain/check/testdata/interop/cpp/enum/scoped.carbon

@@ -0,0 +1,81 @@
+// 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/convert.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/enum/scoped.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/enum/scoped.carbon
+
+// --- enum.h
+
+enum class Enum : short { a, b, c };
+
+// --- import_enum.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "enum.h";
+
+//@dump-sem-ir-begin
+fn F() {
+  let a: Cpp.Enum = Cpp.Enum.a;
+}
+//@dump-sem-ir-end
+
+// --- fail_name_without_scope.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "enum.h";
+
+fn F() {
+  // CHECK:STDERR: fail_name_without_scope.carbon:[[@LINE+4]]:21: error: member name `b` not found in `Cpp` [MemberNameNotFoundInInstScope]
+  // CHECK:STDERR:   let b: Cpp.Enum = Cpp.b;
+  // CHECK:STDERR:                     ^~~~~
+  // CHECK:STDERR:
+  let b: Cpp.Enum = Cpp.b;
+}
+
+// CHECK:STDOUT: --- import_enum.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Enum: type = class_type @Enum [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %Enum [concrete]
+// CHECK:STDOUT:   %int_0: %Enum = int_value 0 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .Enum = %Enum.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Enum.decl: type = class_decl @Enum [concrete = constants.%Enum] {} {}
+// CHECK:STDOUT:   %int_0: %Enum = int_value 0 [concrete = constants.%int_0]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type = binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc8_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Enum.ref.loc8_24: type = name_ref Enum, imports.%Enum.decl [concrete = constants.%Enum]
+// CHECK:STDOUT:   %a.ref: %Enum = name_ref a, imports.%int_0 [concrete = constants.%int_0]
+// CHECK:STDOUT:   %.loc8: type = splice_block %Enum.ref.loc8_13 [concrete = constants.%Enum] {
+// CHECK:STDOUT:     %Cpp.ref.loc8_10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Enum.ref.loc8_13: type = name_ref Enum, imports.%Enum.decl [concrete = constants.%Enum]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %Enum = bind_name a, %a.ref
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 123 - 0
toolchain/check/testdata/interop/cpp/enum/unscoped.carbon

@@ -0,0 +1,123 @@
+// 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/convert.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/enum/unscoped.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/enum/unscoped.carbon
+
+// --- enum.h
+
+enum Enum { a, b, c };
+
+enum Other { d };
+
+// --- import_enum.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "enum.h";
+
+//@dump-sem-ir-begin
+fn F() {
+  let a: Cpp.Enum = Cpp.Enum.a;
+  let b: Cpp.Enum = Cpp.b;
+}
+//@dump-sem-ir-end
+
+// --- fail_wrong_enum.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "enum.h";
+
+fn F() {
+  // CHECK:STDERR: fail_wrong_enum.carbon:[[@LINE+7]]:22: error: cannot implicitly convert expression of type `Cpp.Enum` to `Cpp.Other` [ConversionFailure]
+  // CHECK:STDERR:   let c: Cpp.Other = Cpp.Enum.c;
+  // CHECK:STDERR:                      ^~~~~~~~~~
+  // CHECK:STDERR: fail_wrong_enum.carbon:[[@LINE+4]]:22: note: type `Cpp.Enum` does not implement interface `Core.ImplicitAs(Cpp.Other)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   let c: Cpp.Other = Cpp.Enum.c;
+  // CHECK:STDERR:                      ^~~~~~~~~~
+  // CHECK:STDERR:
+  let c: Cpp.Other = Cpp.Enum.c;
+
+  // CHECK:STDERR: fail_wrong_enum.carbon:[[@LINE+7]]:23: error: cannot implicitly convert expression of type `Cpp.Enum` to `Cpp.Other` [ConversionFailure]
+  // CHECK:STDERR:   let c2: Cpp.Other = Cpp.c;
+  // CHECK:STDERR:                       ^~~~~
+  // CHECK:STDERR: fail_wrong_enum.carbon:[[@LINE+4]]:23: note: type `Cpp.Enum` does not implement interface `Core.ImplicitAs(Cpp.Other)` [MissingImplInMemberAccessNote]
+  // CHECK:STDERR:   let c2: Cpp.Other = Cpp.c;
+  // CHECK:STDERR:                       ^~~~~
+  // CHECK:STDERR:
+  let c2: Cpp.Other = Cpp.c;
+}
+
+// --- fail_non_enum_member.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "enum.h";
+
+fn F() {
+  // CHECK:STDERR: fail_non_enum_member.carbon:[[@LINE+4]]:22: error: member name `d` not found in `Cpp.Enum` [MemberNameNotFoundInInstScope]
+  // CHECK:STDERR:   let c: Cpp.Other = Cpp.Enum.d;
+  // CHECK:STDERR:                      ^~~~~~~~~~
+  // CHECK:STDERR:
+  let c: Cpp.Other = Cpp.Enum.d;
+}
+
+// CHECK:STDOUT: --- import_enum.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Enum: type = class_type @Enum [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %Enum [concrete]
+// CHECK:STDOUT:   %int_0: %Enum = int_value 0 [concrete]
+// CHECK:STDOUT:   %int_1: %Enum = int_value 1 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .Enum = %Enum.decl
+// CHECK:STDOUT:     .b = %int_1
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Enum.decl: type = class_decl @Enum [concrete = constants.%Enum] {} {}
+// CHECK:STDOUT:   %int_0: %Enum = int_value 0 [concrete = constants.%int_0]
+// CHECK:STDOUT:   %int_1: %Enum = int_value 1 [concrete = constants.%int_1]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type = binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc8_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Enum.ref.loc8_24: type = name_ref Enum, imports.%Enum.decl [concrete = constants.%Enum]
+// CHECK:STDOUT:   %a.ref: %Enum = name_ref a, imports.%int_0 [concrete = constants.%int_0]
+// CHECK:STDOUT:   %.loc8: type = splice_block %Enum.ref.loc8_13 [concrete = constants.%Enum] {
+// CHECK:STDOUT:     %Cpp.ref.loc8_10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Enum.ref.loc8_13: type = name_ref Enum, imports.%Enum.decl [concrete = constants.%Enum]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %Enum = bind_name a, %a.ref
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b.patt: %pattern_type = binding_pattern b [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc9_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %b.ref: %Enum = name_ref b, imports.%int_1 [concrete = constants.%int_1]
+// CHECK:STDOUT:   %.loc9: type = splice_block %Enum.ref.loc9 [concrete = constants.%Enum] {
+// CHECK:STDOUT:     %Cpp.ref.loc9_10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Enum.ref.loc9: type = name_ref Enum, imports.%Enum.decl [concrete = constants.%Enum]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b: %Enum = bind_name b, %b.ref
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 3 - 2
toolchain/check/type_completion.cpp

@@ -276,8 +276,9 @@ auto TypeCompleter::AddNestedIncompleteTypes(SemIR::Inst type_inst) -> bool {
       if (!class_info.is_complete() && class_info.scope_id.has_value()) {
         auto& scope = context_->name_scopes().Get(class_info.scope_id);
         if (scope.clang_decl_context_id().has_value()) {
-          if (!ImportCppClassDefinition(*context_, loc_id_, inst.class_id,
-                                        scope.clang_decl_context_id())) {
+          if (!ImportClassDefinitionForClangDecl(
+                  *context_, loc_id_, inst.class_id,
+                  scope.clang_decl_context_id())) {
             // Clang produced a diagnostic. Don't produce one of our own.
             return false;
           }

+ 262 - 0
toolchain/lower/testdata/interop/cpp/enum.carbon

@@ -0,0 +1,262 @@
+// 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/full.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/enum.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/enum.carbon
+
+// --- enum_member.h
+
+struct C {
+  enum E : short {
+    a,
+    b = 7,
+    c
+  };
+
+  static void F(E);
+};
+
+// --- pass_as_arg.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "enum_member.h";
+
+fn Pass() {
+  Cpp.C.F(Cpp.C.a);
+  Cpp.C.F(Cpp.C.b);
+  Cpp.C.F(Cpp.C.c);
+  Cpp.C.F(Cpp.C.E.a);
+  Cpp.C.F(Cpp.C.E.b);
+  Cpp.C.F(Cpp.C.E.c);
+}
+
+// --- convert.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "enum_member.h";
+
+fn TakeI16(n: i16);
+
+fn PassEnum() {
+  TakeI16(Cpp.C.b as i16);
+}
+
+fn ConvertEnumToI16(e: Cpp.C.E) {
+  TakeI16(e as i16);
+}
+
+fn ConvertI16ToEnum(n: i16) {
+  Cpp.C.F(n as Cpp.C.E);
+}
+
+// --- bitmask.h
+
+enum Bits {
+  A = 1 << 0,
+  B = 1 << 1,
+  C = 1 << 2,
+};
+
+void Take(Bits b);
+
+// --- use_bitmask.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "bitmask.h";
+
+// TODO: Once we support interop with C++-defined operators, we should be able
+// to use `|` below rather than declaring our own builtin.
+fn BitOr(a: Cpp.Bits, b: Cpp.Bits) -> Cpp.Bits = "int.or";
+
+fn Call() {
+  Cpp.Take(BitOr(Cpp.A, Cpp.C));
+}
+
+// CHECK:STDOUT: ; ModuleID = 'pass_as_arg.carbon'
+// CHECK:STDOUT: source_filename = "pass_as_arg.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 @_CPass.Main() !dbg !7 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc7_16.1.temp = alloca i16, align 2, !dbg !10
+// CHECK:STDOUT:   %.loc8_16.1.temp = alloca i16, align 2, !dbg !11
+// CHECK:STDOUT:   %.loc9_16.1.temp = alloca i16, align 2, !dbg !12
+// CHECK:STDOUT:   %.loc10_18.1.temp = alloca i16, align 2, !dbg !13
+// CHECK:STDOUT:   %.loc11_18.1.temp = alloca i16, align 2, !dbg !14
+// CHECK:STDOUT:   %.loc12_18.1.temp = alloca i16, align 2, !dbg !15
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc7_16.1.temp), !dbg !10
+// CHECK:STDOUT:   store i16 0, ptr %.loc7_16.1.temp, align 2, !dbg !10
+// CHECK:STDOUT:   call void @_ZN1C1FENS_1EE.carbon_thunk(ptr %.loc7_16.1.temp), !dbg !16
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc8_16.1.temp), !dbg !11
+// CHECK:STDOUT:   store i16 7, ptr %.loc8_16.1.temp, align 2, !dbg !11
+// CHECK:STDOUT:   call void @_ZN1C1FENS_1EE.carbon_thunk(ptr %.loc8_16.1.temp), !dbg !17
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc9_16.1.temp), !dbg !12
+// CHECK:STDOUT:   store i16 8, ptr %.loc9_16.1.temp, align 2, !dbg !12
+// CHECK:STDOUT:   call void @_ZN1C1FENS_1EE.carbon_thunk(ptr %.loc9_16.1.temp), !dbg !18
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc10_18.1.temp), !dbg !13
+// CHECK:STDOUT:   store i16 0, ptr %.loc10_18.1.temp, align 2, !dbg !13
+// CHECK:STDOUT:   call void @_ZN1C1FENS_1EE.carbon_thunk(ptr %.loc10_18.1.temp), !dbg !19
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc11_18.1.temp), !dbg !14
+// CHECK:STDOUT:   store i16 7, ptr %.loc11_18.1.temp, align 2, !dbg !14
+// CHECK:STDOUT:   call void @_ZN1C1FENS_1EE.carbon_thunk(ptr %.loc11_18.1.temp), !dbg !20
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc12_18.1.temp), !dbg !15
+// CHECK:STDOUT:   store i16 8, ptr %.loc12_18.1.temp, align 2, !dbg !15
+// CHECK:STDOUT:   call void @_ZN1C1FENS_1EE.carbon_thunk(ptr %.loc12_18.1.temp), !dbg !21
+// CHECK:STDOUT:   ret void, !dbg !22
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_ZN1C1FENS_1EE(i16)
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress
+// CHECK:STDOUT: define dso_local void @_ZN1C1FENS_1EE.carbon_thunk(ptr %0) #1 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.addr = alloca ptr, align 8
+// CHECK:STDOUT:   store ptr %0, ptr %.addr, align 8
+// CHECK:STDOUT:   %1 = load ptr, ptr %.addr, align 8
+// CHECK:STDOUT:   %2 = load i16, ptr %1, align 2
+// CHECK:STDOUT:   call void @_ZN1C1FENS_1EE(i16 signext %2)
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 5, 4, 3, 2, 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT: attributes #1 = { alwaysinline mustprogress "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+// 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: "pass_as_arg.carbon", directory: "")
+// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "Pass", linkageName: "_CPass.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: 11, scope: !7)
+// CHECK:STDOUT: !11 = !DILocation(line: 8, column: 11, scope: !7)
+// CHECK:STDOUT: !12 = !DILocation(line: 9, column: 11, scope: !7)
+// CHECK:STDOUT: !13 = !DILocation(line: 10, column: 11, scope: !7)
+// CHECK:STDOUT: !14 = !DILocation(line: 11, column: 11, scope: !7)
+// CHECK:STDOUT: !15 = !DILocation(line: 12, column: 11, scope: !7)
+// CHECK:STDOUT: !16 = !DILocation(line: 7, column: 3, scope: !7)
+// CHECK:STDOUT: !17 = !DILocation(line: 8, column: 3, scope: !7)
+// CHECK:STDOUT: !18 = !DILocation(line: 9, column: 3, scope: !7)
+// CHECK:STDOUT: !19 = !DILocation(line: 10, column: 3, scope: !7)
+// CHECK:STDOUT: !20 = !DILocation(line: 11, column: 3, scope: !7)
+// CHECK:STDOUT: !21 = !DILocation(line: 12, column: 3, scope: !7)
+// CHECK:STDOUT: !22 = !DILocation(line: 6, column: 1, scope: !7)
+// CHECK:STDOUT: ; ModuleID = 'convert.carbon'
+// CHECK:STDOUT: source_filename = "convert.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: declare void @_CTakeI16.Main(i16)
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CPassEnum.Main() !dbg !7 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CTakeI16.Main(i16 7), !dbg !10
+// CHECK:STDOUT:   ret void, !dbg !11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CConvertEnumToI16.Main(i16 %e) !dbg !12 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CTakeI16.Main(i16 %e), !dbg !13
+// CHECK:STDOUT:   ret void, !dbg !14
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CConvertI16ToEnum.Main(i16 %n) !dbg !15 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc17_13.3.temp = alloca i16, align 2, !dbg !16
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %.loc17_13.3.temp), !dbg !16
+// CHECK:STDOUT:   store i16 %n, ptr %.loc17_13.3.temp, align 2, !dbg !16
+// CHECK:STDOUT:   call void @_ZN1C1FENS_1EE.carbon_thunk(ptr %.loc17_13.3.temp), !dbg !17
+// CHECK:STDOUT:   ret void, !dbg !18
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_ZN1C1FENS_1EE(i16)
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress
+// CHECK:STDOUT: define dso_local void @_ZN1C1FENS_1EE.carbon_thunk(ptr %0) #1 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.addr = alloca ptr, align 8
+// CHECK:STDOUT:   store ptr %0, ptr %.addr, align 8
+// CHECK:STDOUT:   %1 = load ptr, ptr %.addr, align 8
+// CHECK:STDOUT:   %2 = load i16, ptr %1, align 2
+// CHECK:STDOUT:   call void @_ZN1C1FENS_1EE(i16 signext %2)
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT: attributes #1 = { alwaysinline mustprogress "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+// 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: "convert.carbon", directory: "")
+// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "PassEnum", linkageName: "_CPassEnum.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: "ConvertEnumToI16", linkageName: "_CConvertEnumToI16.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: !15 = distinct !DISubprogram(name: "ConvertI16ToEnum", linkageName: "_CConvertI16ToEnum.Main", scope: null, file: !6, line: 16, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !16 = !DILocation(line: 17, column: 11, scope: !15)
+// CHECK:STDOUT: !17 = !DILocation(line: 17, column: 3, scope: !15)
+// CHECK:STDOUT: !18 = !DILocation(line: 16, column: 1, scope: !15)
+// CHECK:STDOUT: ; ModuleID = 'use_bitmask.carbon'
+// CHECK:STDOUT: source_filename = "use_bitmask.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 @_CCall.Main() !dbg !7 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_Z4Take4Bits(i32 5), !dbg !10
+// CHECK:STDOUT:   ret void, !dbg !11
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_Z4Take4Bits(i32)
+// 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: "use_bitmask.carbon", directory: "")
+// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "Call", linkageName: "_CCall.Main", scope: null, file: !6, line: 10, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !8 = !DISubroutineType(types: !9)
+// CHECK:STDOUT: !9 = !{}
+// CHECK:STDOUT: !10 = !DILocation(line: 11, column: 3, scope: !7)
+// CHECK:STDOUT: !11 = !DILocation(line: 10, column: 1, scope: !7)