Browse Source

Add interop support for naming and "calling" C++ templates. (#6474)

Expose C++ class templates, variable templates, alias templates, and
concepts as callable values in Carbon, and map calls to them into
template-id formation, mirroring how Carbon generics behave. For now,
only type template parameters are supported; non-type and template
template parameters produce a TODO error.
Richard Smith 4 months ago
parent
commit
6b28213b36
36 changed files with 1409 additions and 124 deletions
  1. 4 0
      toolchain/check/call.cpp
  2. 149 0
      toolchain/check/cpp/call.cpp
  3. 10 0
      toolchain/check/cpp/call.h
  4. 42 24
      toolchain/check/cpp/import.cpp
  5. 21 3
      toolchain/check/cpp/import.h
  6. 2 5
      toolchain/check/cpp/type_mapping.cpp
  7. 7 0
      toolchain/check/cpp/type_mapping.h
  8. 4 8
      toolchain/check/handle_literal.cpp
  9. 8 0
      toolchain/check/literal.cpp
  10. 4 0
      toolchain/check/literal.h
  11. 27 21
      toolchain/check/testdata/interop/cpp/class/class.carbon
  12. 27 21
      toolchain/check/testdata/interop/cpp/class/union.carbon
  13. 147 0
      toolchain/check/testdata/interop/cpp/template/alias_template.carbon
  14. 182 0
      toolchain/check/testdata/interop/cpp/template/argument_count.carbon
  15. 110 0
      toolchain/check/testdata/interop/cpp/template/class_template.carbon
  16. 126 0
      toolchain/check/testdata/interop/cpp/template/concept.carbon
  17. 38 0
      toolchain/check/testdata/interop/cpp/template/generic_call.carbon
  18. 121 0
      toolchain/check/testdata/interop/cpp/template/non_type_param.carbon
  19. 63 0
      toolchain/check/testdata/interop/cpp/template/template_template_param.carbon
  20. 75 0
      toolchain/check/testdata/interop/cpp/template/type_param.carbon
  21. 139 0
      toolchain/check/testdata/interop/cpp/template/var_template.carbon
  22. 25 23
      toolchain/check/testdata/interop/cpp/unsupported_decl_type.carbon
  23. 6 0
      toolchain/check/type.cpp
  24. 4 0
      toolchain/check/type.h
  25. 4 4
      toolchain/check/type_completion.cpp
  26. 2 1
      toolchain/lower/file_context.cpp
  27. 0 14
      toolchain/sem_ir/clang_decl.h
  28. 7 0
      toolchain/sem_ir/formatter.cpp
  29. 1 0
      toolchain/sem_ir/id_kind.h
  30. 14 0
      toolchain/sem_ir/ids.h
  31. 7 0
      toolchain/sem_ir/inst_fingerprinter.cpp
  32. 1 0
      toolchain/sem_ir/inst_kind.def
  33. 10 0
      toolchain/sem_ir/inst_namer.cpp
  34. 5 0
      toolchain/sem_ir/stringify.cpp
  35. 1 0
      toolchain/sem_ir/type_iterator.cpp
  36. 16 0
      toolchain/sem_ir/typed_insts.h

+ 4 - 0
toolchain/check/call.cpp

@@ -324,6 +324,10 @@ static auto PerformCallToNonFunction(Context& context, SemIR::LocId loc_id,
   auto type_inst =
       context.types().GetAsInst(context.insts().Get(callee_id).type_id());
   CARBON_KIND_SWITCH(type_inst) {
+    case CARBON_KIND(SemIR::CppTemplateNameType template_name): {
+      return PerformCallToCppTemplateName(context, loc_id,
+                                          template_name.decl_id, arg_ids);
+    }
     case CARBON_KIND(SemIR::GenericClassType generic_class): {
       return PerformCallToGenericClass(context, loc_id, generic_class.class_id,
                                        generic_class.enclosing_specific_id,

+ 149 - 0
toolchain/check/cpp/call.cpp

@@ -4,10 +4,15 @@
 
 #include "toolchain/check/cpp/call.h"
 
+#include "clang/Sema/Sema.h"
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/call.h"
+#include "toolchain/check/cpp/import.h"
+#include "toolchain/check/cpp/location.h"
 #include "toolchain/check/cpp/operators.h"
 #include "toolchain/check/cpp/overload_resolution.h"
+#include "toolchain/check/cpp/type_mapping.h"
+#include "toolchain/check/literal.h"
 #include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/typed_insts.h"
@@ -43,4 +48,148 @@ auto PerformCallToCppFunction(Context& context, SemIR::LocId loc_id,
   }
 }
 
+// Converts an argument in a call to a C++ template name into a corresponding
+// clang template argument, given the template parameter it will be matched
+// against.
+static auto ConvertArgToTemplateArg(Context& context,
+                                    const clang::NamedDecl* param_decl,
+                                    SemIR::InstId arg_id)
+    -> std::optional<clang::TemplateArgumentLoc> {
+  if (isa<clang::TemplateTypeParmDecl>(param_decl)) {
+    auto type = ExprAsType(context, SemIR::LocId(arg_id), arg_id);
+    if (type.type_id == SemIR::ErrorInst::TypeId) {
+      return std::nullopt;
+    }
+    auto clang_type = MapToCppType(context, type.type_id);
+    if (clang_type.isNull()) {
+      context.TODO(arg_id, "unsupported type used as template argument");
+      return std::nullopt;
+    }
+    return clang::TemplateArgumentLoc(
+        clang_type,
+        context.ast_context().getTrivialTypeSourceInfo(
+            clang_type, GetCppLocation(context, SemIR::LocId(arg_id))));
+  }
+
+  if (isa<clang::TemplateTemplateParmDecl>(param_decl)) {
+    // TODO: Check the type of the argument `CppTemplateNameType` and
+    // convert it to a `clang::TemplateName`.
+    context.TODO(arg_id, "argument for template template parameter");
+    return std::nullopt;
+  }
+
+  if (isa<clang::NonTypeTemplateParmDecl>(param_decl)) {
+    // TODO: Check the argument has a concrete constant value, and convert it to
+    // a Clang constant value.
+    context.TODO(arg_id, "argument for non-type template parameter");
+    return std::nullopt;
+  }
+
+  CARBON_FATAL("Unknown declaration kind for template parameter");
+}
+
+// Converts a call argument list into a Clang template argument list for a given
+// template. Returns true on success, or false if an error was diagnosed.
+static auto ConvertArgsToTemplateArgs(Context& context,
+                                      clang::TemplateDecl* template_decl,
+                                      llvm::ArrayRef<SemIR::InstId> arg_ids,
+                                      clang::TemplateArgumentListInfo& arg_list)
+    -> bool {
+  for (auto* param_decl : template_decl->getTemplateParameters()->asArray()) {
+    if (arg_ids.empty()) {
+      return true;
+    }
+
+    // A parameter pack consumes all remaining arguments; otherwise, it consumes
+    // a single argument.
+    // TODO: Handle expanded template parameter packs, which have a known, fixed
+    // arity.
+    llvm::ArrayRef<SemIR::InstId> args_for_param =
+        param_decl->isTemplateParameterPack() ? std::exchange(arg_ids, {})
+                                              : arg_ids.consume_front();
+    for (auto arg_id : args_for_param) {
+      if (auto arg = ConvertArgToTemplateArg(context, param_decl, arg_id)) {
+        arg_list.addArgument(*arg);
+      } else {
+        return false;
+      }
+    }
+  }
+
+  // If there are any remaining arguments, that's an error; convert them to
+  // placeholder template arguments so that Clang will diagnose it for us.
+  for (auto arg_id : arg_ids) {
+    // Synthesize a placeholder `void{}` template argument.
+    auto arg_loc = GetCppLocation(context, SemIR::LocId(arg_id));
+    auto void_type = context.ast_context().VoidTy;
+    auto* arg = new (context.ast_context()) clang::CXXScalarValueInitExpr(
+        void_type,
+        context.ast_context().getTrivialTypeSourceInfo(void_type, arg_loc),
+        arg_loc);
+    arg_list.addArgument(clang::TemplateArgumentLoc(
+        clang::TemplateArgument(arg, /*IsCanonical=*/false), arg));
+  }
+
+  return true;
+}
+
+// Given a template and an template argument list, builds a Carbon value
+// describing the corresponding C++ template-id.
+static auto BuildTemplateId(Context& context, SemIR::LocId loc_id,
+                            clang::SourceLocation loc,
+                            clang::TemplateDecl* template_decl,
+                            clang::TemplateArgumentListInfo& arg_list)
+    -> SemIR::InstId {
+  if (auto* var_template_decl =
+          dyn_cast<clang::VarTemplateDecl>(template_decl)) {
+    auto decl_result = context.clang_sema().CheckVarTemplateId(
+        var_template_decl, /*TemplateLoc=*/clang::SourceLocation(), loc,
+        arg_list, /*SetWrittenArgs=*/false);
+    return decl_result.isInvalid()
+               ? SemIR::ErrorInst::InstId
+               : ImportCppDecl(context, loc_id,
+                               SemIR::ClangDeclKey::ForNonFunctionDecl(
+                                   decl_result.get()));
+  }
+
+  if (auto* concept_decl = dyn_cast<clang::ConceptDecl>(template_decl)) {
+    auto expr_result = context.clang_sema().CheckConceptTemplateId(
+        clang::CXXScopeSpec(), /*TemplateKWLoc=*/clang::SourceLocation(),
+        clang::DeclarationNameInfo(concept_decl->getDeclName(), loc),
+        concept_decl, concept_decl, &arg_list);
+    if (expr_result.isInvalid()) {
+      return SemIR::ErrorInst::InstId;
+    }
+    auto* expr = expr_result.getAs<clang::ConceptSpecializationExpr>();
+    return MakeBoolLiteral(context, loc_id,
+                           SemIR::BoolValue::From(expr->isSatisfied()));
+  }
+
+  clang::TemplateName template_name(template_decl);
+  auto clang_type = context.clang_sema().CheckTemplateIdType(
+      clang::ElaboratedTypeKeyword::None, template_name, loc, arg_list,
+      /*Scope=*/nullptr, /*ForNestedNameSpecifier=*/false);
+  if (clang_type.isNull()) {
+    return SemIR::ErrorInst::InstId;
+  }
+  return ImportCppType(context, loc_id, clang_type).inst_id;
+}
+
+auto PerformCallToCppTemplateName(Context& context, SemIR::LocId loc_id,
+                                  SemIR::ClangDeclId template_decl_id,
+                                  llvm::ArrayRef<SemIR::InstId> arg_ids)
+    -> SemIR::InstId {
+  auto* template_decl = dyn_cast<clang::TemplateDecl>(
+      context.clang_decls().Get(template_decl_id).key.decl);
+  auto loc = GetCppLocation(context, loc_id);
+
+  // Form a template argument list for this template.
+  clang::TemplateArgumentListInfo arg_list(loc, loc);
+  if (!ConvertArgsToTemplateArgs(context, template_decl, arg_ids, arg_list)) {
+    return SemIR::ErrorInst::InstId;
+  }
+
+  return BuildTemplateId(context, loc_id, loc, template_decl, arg_list);
+}
+
 }  // namespace Carbon::Check

+ 10 - 0
toolchain/check/cpp/call.h

@@ -31,6 +31,16 @@ auto PerformCallToCppFunction(Context& context, SemIR::LocId loc_id,
                               llvm::ArrayRef<SemIR::InstId> arg_ids)
     -> SemIR::InstId;
 
+// Checks and builds SemIR for a call to a C++ template name with arguments
+// `arg_ids`.
+//
+// Converts the arguments to a C++ template argument list and attempts to
+// instantiate a template specialization and import a declaration of it.
+auto PerformCallToCppTemplateName(Context& context, SemIR::LocId loc_id,
+                                  SemIR::ClangDeclId template_decl_id,
+                                  llvm::ArrayRef<SemIR::InstId> arg_ids)
+    -> SemIR::InstId;
+
 }  // namespace Carbon::Check
 
 #endif  // CARBON_TOOLCHAIN_CHECK_CPP_CALL_H_

+ 42 - 24
toolchain/check/cpp/import.cpp

@@ -293,9 +293,6 @@ static auto ImportNamespaceDecl(Context& context,
   return result.inst_id;
 }
 
-static auto ImportTypeAndDependencies(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,
@@ -470,7 +467,7 @@ static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
     }
 
     auto [base_type_inst_id, base_type_id] =
-        ImportTypeAndDependencies(context, import_ir_inst_id, base.getType());
+        ImportCppType(context, import_ir_inst_id, base.getType());
     if (!base_type_id.has_value()) {
       // TODO: If the base class's type can't be mapped, skip it.
       continue;
@@ -552,7 +549,7 @@ static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
 
     auto field_name_id = AddIdentifierName(context, field->getName());
     auto [field_type_inst_id, field_type_id] =
-        ImportTypeAndDependencies(context, import_ir_inst_id, field->getType());
+        ImportCppType(context, import_ir_inst_id, field->getType());
     if (!field_type_inst_id.has_value()) {
       // TODO: For now, just skip over fields whose types we can't map.
       continue;
@@ -1631,6 +1628,38 @@ static auto ImportVarDecl(Context& context, SemIR::LocId loc_id,
   return var_storage_inst_id;
 }
 
+static auto ImportTemplateDecl(Context& context,
+                               clang::TemplateDecl* template_decl)
+    -> SemIR::InstId {
+  auto key = SemIR::ClangDeclKey(template_decl);
+
+  // TODO: Avoid doing this lookup both here and in the insertion below.
+  if (SemIR::InstId existing_inst_id = LookupClangDeclInstId(context, key);
+      existing_inst_id.has_value()) {
+    return existing_inst_id;
+  }
+
+  // Add a placeholder instruction to resolve cycle between the clang
+  // declaration and the type.
+  auto import_loc_id =
+      AddImportIRInst(context.sem_ir(), template_decl->getLocation());
+  SemIR::StructValue value = {.type_id = SemIR::TypeId::None,
+                              .elements_id = SemIR::InstBlockId::Empty};
+  auto inst_id = AddPlaceholderImportedInstInNoBlock(
+      context, MakeImportedLocIdAndInst(context, import_loc_id, value));
+
+  // Create a type for the constant value.
+  auto name_id = context.entity_names().Add(
+      {.name_id = AddIdentifierName(context, template_decl->getName()),
+       .parent_scope_id = GetParentNameScopeId(context, template_decl)});
+  auto decl_id = context.clang_decls().Add({.key = key, .inst_id = inst_id});
+  value.type_id = GetCppTemplateNameType(context, name_id, decl_id);
+
+  // Update the value with its type.
+  ReplaceInstBeforeConstantUse(context, inst_id, value);
+  return inst_id;
+}
+
 // Imports a declaration from Clang to Carbon. Returns the instruction for the
 // new Carbon declaration, which will be an ErrorInst on failure. Assumes all
 // dependencies have already been imported.
@@ -1673,6 +1702,9 @@ static auto ImportDeclAfterDependencies(Context& context, SemIR::LocId loc_id,
   if (auto* var_decl = dyn_cast<clang::VarDecl>(clang_decl)) {
     return ImportVarDecl(context, loc_id, var_decl);
   }
+  if (auto* template_decl = dyn_cast<clang::TemplateDecl>(clang_decl)) {
+    return ImportTemplateDecl(context, template_decl);
+  }
 
   context.TODO(AddImportIRInst(context.sem_ir(), clang_decl->getLocation()),
                llvm::formatv("Unsupported: Declaration type {0}",
@@ -1718,12 +1750,8 @@ static auto ImportDeclSet(Context& context, SemIR::LocId loc_id,
   return true;
 }
 
-// Imports a declaration from Clang to Carbon. If successful, returns the
-// instruction for the new Carbon declaration. All unimported dependencies are
-// imported first.
-static auto ImportDeclAndDependencies(Context& context, SemIR::LocId loc_id,
-                                      SemIR::ClangDeclKey key)
-    -> SemIR::InstId {
+auto ImportCppDecl(Context& context, SemIR::LocId loc_id,
+                   SemIR::ClangDeclKey key) -> SemIR::InstId {
   // Collect dependencies by walking the dependency graph in depth-first order.
   ImportWorklist worklist;
   AddDependentDecl(context, key, worklist);
@@ -1733,10 +1761,8 @@ static auto ImportDeclAndDependencies(Context& context, SemIR::LocId loc_id,
   return LookupClangDeclInstId(context, key);
 }
 
-// Imports a type from Clang to Carbon. If successful, returns the imported
-// TypeId. All unimported dependencies are imported first.
-static auto ImportTypeAndDependencies(Context& context, SemIR::LocId loc_id,
-                                      clang::QualType type) -> TypeExpr {
+auto ImportCppType(Context& context, SemIR::LocId loc_id, clang::QualType type)
+    -> TypeExpr {
   // Collect dependencies by walking the dependency graph in depth-first order.
   ImportWorklist worklist;
   AddDependentUnimportedTypeDecls(context, type, worklist);
@@ -1747,14 +1773,6 @@ static auto ImportTypeAndDependencies(Context& context, SemIR::LocId loc_id,
   return MapType(context, loc_id, type);
 }
 
-auto ImportCppFunctionDecl(Context& context, SemIR::LocId loc_id,
-                           clang::FunctionDecl* clang_decl, int num_params)
-    -> SemIR::InstId {
-  return ImportDeclAndDependencies(
-      context, loc_id,
-      SemIR::ClangDeclKey::ForFunctionDecl(clang_decl, num_params));
-}
-
 // Imports a Clang declaration into Carbon and adds that name into the
 // `NameScope`.
 static auto ImportNameDeclIntoScope(Context& context, SemIR::LocId loc_id,
@@ -1763,7 +1781,7 @@ static auto ImportNameDeclIntoScope(Context& context, SemIR::LocId loc_id,
                                     SemIR::ClangDeclKey key,
                                     SemIR::AccessKind access_kind)
     -> SemIR::ScopeLookupResult {
-  SemIR::InstId inst_id = ImportDeclAndDependencies(context, loc_id, key);
+  SemIR::InstId inst_id = ImportCppDecl(context, loc_id, key);
   if (!inst_id.has_value()) {
     return SemIR::ScopeLookupResult::MakeNotFound();
   }

+ 21 - 3
toolchain/check/cpp/import.h

@@ -10,6 +10,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/VirtualFileSystem.h"
 #include "toolchain/check/context.h"
+#include "toolchain/check/convert.h"
 #include "toolchain/check/diagnostic_helpers.h"
 #include "toolchain/diagnostics/diagnostic_emitter.h"
 #include "toolchain/sem_ir/ids.h"
@@ -24,12 +25,29 @@ auto ImportCpp(Context& context,
                llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
                std::shared_ptr<clang::CompilerInvocation> invocation) -> void;
 
+// Imports a declaration from Clang to Carbon. If successful, returns the new
+// Carbon declaration `InstId`. If the declaration was already imported, returns
+// the mapped instruction. All unimported dependencies are imported first.
+auto ImportCppDecl(Context& context, SemIR::LocId loc_id,
+                   SemIR::ClangDeclKey key) -> SemIR::InstId;
+
 // Imports a function declaration from Clang to Carbon. If successful, returns
 // the new Carbon function declaration `InstId`. If the declaration was already
 // imported, returns the mapped instruction.
-auto ImportCppFunctionDecl(Context& context, SemIR::LocId loc_id,
-                           clang::FunctionDecl* clang_decl, int num_params)
-    -> SemIR::InstId;
+inline auto ImportCppFunctionDecl(Context& context, SemIR::LocId loc_id,
+                                  clang::FunctionDecl* clang_decl,
+                                  int num_params) -> SemIR::InstId {
+  return ImportCppDecl(
+      context, loc_id,
+      SemIR::ClangDeclKey::ForFunctionDecl(clang_decl, num_params));
+}
+
+// Imports a function declaration from Clang to Carbon. If successful, returns
+// the new Carbon function declaration `InstId`. If the declaration was already
+// imported, returns the mapped instruction. All unimported dependencies are
+// imported first.
+auto ImportCppType(Context& context, SemIR::LocId loc_id, clang::QualType type)
+    -> TypeExpr;
 
 // Imports an overloaded function set from Clang to Carbon.
 auto ImportCppOverloadSet(

+ 2 - 5
toolchain/check/cpp/type_mapping.cpp

@@ -284,11 +284,8 @@ static auto TryMapType(Context& context, SemIR::TypeId type_id)
   return clang::QualType();
 }
 
-// Maps a Carbon type to a C++ type. Returns `clang::QualType` if the mapping
-// succeeds, or `clang::QualType::isNull()` if the type is not supported.
-// TODO: unify this with the C++ to Carbon type mapping function.
-static auto MapToCppType(Context& context, SemIR::TypeId type_id)
-    -> clang::QualType {
+auto MapToCppType(Context& context, SemIR::TypeId type_id) -> clang::QualType {
+  // TODO: unify this with the C++ to Carbon type mapping function.
   llvm::SmallVector<WrapFn> wrap_fns;
   while (true) {
     CARBON_KIND_SWITCH(TryMapType(context, type_id)) {

+ 7 - 0
toolchain/check/cpp/type_mapping.h

@@ -7,10 +7,17 @@
 
 #include "clang/AST/Type.h"
 #include "toolchain/check/context.h"
+#include "toolchain/check/convert.h"
 #include "toolchain/sem_ir/ids.h"
 
 namespace Carbon::Check {
 
+// Converts a Carbon type to a corresponding C++ type. This uses the default
+// type mapping, which is suitable for template arguments, typedefs, etc. but
+// may not be the right mapping to use in a function signature. Returns a null
+// type if there is no mapping.
+auto MapToCppType(Context& context, SemIR::TypeId type_id) -> clang::QualType;
+
 // Invents a Clang argument expression to use in overload resolution to
 // represent the given Carbon argument instruction.
 auto InventClangArg(Context& context, SemIR::InstId arg_id) -> clang::Expr*;

+ 4 - 8
toolchain/check/handle_literal.cpp

@@ -20,19 +20,15 @@ namespace Carbon::Check {
 
 auto HandleParseNode(Context& context, Parse::BoolLiteralFalseId node_id)
     -> bool {
-  AddInstAndPush<SemIR::BoolLiteral>(
-      context, node_id,
-      {.type_id = GetSingletonType(context, SemIR::BoolType::TypeInstId),
-       .value = SemIR::BoolValue::False});
+  context.node_stack().Push(
+      node_id, MakeBoolLiteral(context, node_id, SemIR::BoolValue::False));
   return true;
 }
 
 auto HandleParseNode(Context& context, Parse::BoolLiteralTrueId node_id)
     -> bool {
-  AddInstAndPush<SemIR::BoolLiteral>(
-      context, node_id,
-      {.type_id = GetSingletonType(context, SemIR::BoolType::TypeInstId),
-       .value = SemIR::BoolValue::True});
+  context.node_stack().Push(
+      node_id, MakeBoolLiteral(context, node_id, SemIR::BoolValue::True));
   return true;
 }
 

+ 8 - 0
toolchain/check/literal.cpp

@@ -17,6 +17,14 @@
 
 namespace Carbon::Check {
 
+auto MakeBoolLiteral(Context& context, SemIR::LocId loc_id,
+                     SemIR::BoolValue value) -> SemIR::InstId {
+  return AddInst<SemIR::BoolLiteral>(
+      context, loc_id,
+      {.type_id = GetSingletonType(context, SemIR::BoolType::TypeInstId),
+       .value = value});
+}
+
 auto MakeIntLiteral(Context& context, Parse::NodeId node_id, IntId int_id)
     -> SemIR::InstId {
   return AddInst<SemIR::IntValue>(

+ 4 - 0
toolchain/check/literal.h

@@ -13,6 +13,10 @@
 
 namespace Carbon::Check {
 
+// Forms a BoolLiteral instruction with the given value and returns it.
+auto MakeBoolLiteral(Context& context, SemIR::LocId loc_id,
+                     SemIR::BoolValue value) -> SemIR::InstId;
+
 // Forms an IntValue instruction with type `IntLiteral` for a given literal
 // integer value, which is assumed to be unsigned.
 auto MakeIntLiteral(Context& context, Parse::NodeId node_id, IntId int_id)

+ 27 - 21
toolchain/check/testdata/interop/cpp/class/class.carbon

@@ -209,25 +209,19 @@ fn MyF() {
 
 // --- template.h
 
+struct X {};
+
 template<typename T>
 class Bar {};
 
-// --- fail_todo_import_template.carbon
+// --- import_template.carbon
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_todo_import_template.carbon:[[@LINE+4]]:10: in file included here [InCppInclude]
-// CHECK:STDERR: ./template.h:3:7: error: semantics TODO: `Unsupported: Declaration type ClassTemplate` [SemanticsTodo]
-// CHECK:STDERR: class Bar {};
-// CHECK:STDERR:       ^
 import Cpp library "template.h";
 
 //@dump-sem-ir-begin
-// CHECK:STDERR: fail_todo_import_template.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*);
+fn MyF(bar: Cpp.Bar(Cpp.X)*);
 //@dump-sem-ir-end
 
 // CHECK:STDOUT: --- import_declaration.carbon
@@ -564,34 +558,46 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_template.carbon
+// CHECK:STDOUT: --- import_template.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Bar.type: type = cpp_type_template_type Bar [concrete]
+// CHECK:STDOUT:   %Bar.template: %Bar.type = struct_value () [concrete]
+// CHECK:STDOUT:   %X: type = class_type @X [concrete]
+// 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.template
+// CHECK:STDOUT:     .X = %X.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Bar.template: %Bar.type = struct_value () [concrete = constants.%Bar.template]
+// CHECK:STDOUT:   %X.decl: type = class_decl @X [concrete = constants.%X] {} {}
+// 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> = value_binding_pattern bar [concrete]
-// CHECK:STDOUT:     %bar.param_patt: <error> = value_param_pattern %bar.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %bar.patt: %pattern_type = value_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:     %.loc15: type = splice_block %ptr [concrete = <error>] {
-// 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.param: %ptr = value_param call_param0
+// CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr] {
+// CHECK:STDOUT:       %Cpp.ref.loc7_13: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:       %Bar.ref: %Bar.type = name_ref Bar, imports.%Bar.template [concrete = constants.%Bar.template]
+// CHECK:STDOUT:       %Cpp.ref.loc7_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:       %X.ref: type = name_ref X, imports.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:       %ptr: type = ptr_type imports.%Bar.decl [concrete = constants.%ptr]
 // CHECK:STDOUT:     }
-// CHECK:STDOUT:     %bar: <error> = value_binding bar, %bar.param
+// CHECK:STDOUT:     %bar: %ptr = value_binding 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:

+ 27 - 21
toolchain/check/testdata/interop/cpp/class/union.carbon

@@ -158,25 +158,19 @@ fn MyF(bar : Cpp.Bar*) {
 
 // --- template.h
 
+struct X {};
+
 template<typename T>
 union Bar {};
 
-// --- fail_todo_import_template.carbon
+// --- import_template.carbon
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_todo_import_template.carbon:[[@LINE+4]]:10: in file included here [InCppInclude]
-// CHECK:STDERR: ./template.h:3:7: error: semantics TODO: `Unsupported: Declaration type ClassTemplate` [SemanticsTodo]
-// CHECK:STDERR: union Bar {};
-// CHECK:STDERR:       ^
 import Cpp library "template.h";
 
 //@dump-sem-ir-begin
-// CHECK:STDERR: fail_todo_import_template.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*);
+fn MyF(bar: Cpp.Bar(Cpp.X)*);
 //@dump-sem-ir-end
 
 // CHECK:STDOUT: --- import_declaration.carbon
@@ -402,34 +396,46 @@ fn MyF(bar: Cpp.Bar*);
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_todo_import_template.carbon
+// CHECK:STDOUT: --- import_template.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %Bar.type: type = cpp_type_template_type Bar [concrete]
+// CHECK:STDOUT:   %Bar.template: %Bar.type = struct_value () [concrete]
+// CHECK:STDOUT:   %X: type = class_type @X [concrete]
+// 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.template
+// CHECK:STDOUT:     .X = %X.decl
 // CHECK:STDOUT:     import Cpp//...
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Bar.template: %Bar.type = struct_value () [concrete = constants.%Bar.template]
+// CHECK:STDOUT:   %X.decl: type = class_decl @X [concrete = constants.%X] {} {}
+// 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> = value_binding_pattern bar [concrete]
-// CHECK:STDOUT:     %bar.param_patt: <error> = value_param_pattern %bar.patt, call_param0 [concrete]
+// CHECK:STDOUT:     %bar.patt: %pattern_type = value_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:     %.loc15: type = splice_block %ptr [concrete = <error>] {
-// 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.param: %ptr = value_param call_param0
+// CHECK:STDOUT:     %.loc7: type = splice_block %ptr [concrete = constants.%ptr] {
+// CHECK:STDOUT:       %Cpp.ref.loc7_13: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:       %Bar.ref: %Bar.type = name_ref Bar, imports.%Bar.template [concrete = constants.%Bar.template]
+// CHECK:STDOUT:       %Cpp.ref.loc7_21: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:       %X.ref: type = name_ref X, imports.%X.decl [concrete = constants.%X]
+// CHECK:STDOUT:       %ptr: type = ptr_type imports.%Bar.decl [concrete = constants.%ptr]
 // CHECK:STDOUT:     }
-// CHECK:STDOUT:     %bar: <error> = value_binding bar, %bar.param
+// CHECK:STDOUT:     %bar: %ptr = value_binding 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:

+ 147 - 0
toolchain/check/testdata/interop/cpp/template/alias_template.carbon

@@ -0,0 +1,147 @@
+// 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/none.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/template/alias_template.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/template/alias_template.carbon
+
+// --- alias_template.h
+
+struct A {};
+struct B {};
+
+template<typename T, typename U> using First = T;
+template<typename T, typename U> using Second = U;
+
+// --- use_alias_template.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "alias_template.h";
+
+//@dump-sem-ir-begin
+var a1: Cpp.First(Cpp.A, Cpp.B) = Cpp.A.A();
+var b1: Cpp.First(Cpp.B, Cpp.A) = Cpp.B.B();
+var b2: Cpp.Second(Cpp.A, Cpp.B) = Cpp.B.B();
+var a2: Cpp.Second(Cpp.B, Cpp.A) = Cpp.A.A();
+//@dump-sem-ir-end
+
+// CHECK:STDOUT: --- use_alias_template.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %First.type: type = cpp_type_template_type First [concrete]
+// CHECK:STDOUT:   %First.template: %First.type = struct_value () [concrete]
+// CHECK:STDOUT:   %A: type = class_type @A [concrete]
+// CHECK:STDOUT:   %B: type = class_type @B [concrete]
+// CHECK:STDOUT:   %pattern_type.9de: type = pattern_type %A [concrete]
+// CHECK:STDOUT:   %A.A.cpp_overload_set.type: type = cpp_overload_set_type @A.A.cpp_overload_set [concrete]
+// CHECK:STDOUT:   %A.A.cpp_overload_set.value: %A.A.cpp_overload_set.type = cpp_overload_set_value @A.A.cpp_overload_set [concrete]
+// CHECK:STDOUT:   %ptr.270: type = ptr_type %A [concrete]
+// CHECK:STDOUT:   %A__carbon_thunk.type: type = fn_type @A__carbon_thunk [concrete]
+// CHECK:STDOUT:   %A__carbon_thunk: %A__carbon_thunk.type = struct_value () [concrete]
+// CHECK:STDOUT:   %pattern_type.d0f: type = pattern_type %B [concrete]
+// CHECK:STDOUT:   %B.B.cpp_overload_set.type: type = cpp_overload_set_type @B.B.cpp_overload_set [concrete]
+// CHECK:STDOUT:   %B.B.cpp_overload_set.value: %B.B.cpp_overload_set.type = cpp_overload_set_value @B.B.cpp_overload_set [concrete]
+// CHECK:STDOUT:   %ptr.a04: type = ptr_type %B [concrete]
+// CHECK:STDOUT:   %B__carbon_thunk.type: type = fn_type @B__carbon_thunk [concrete]
+// CHECK:STDOUT:   %B__carbon_thunk: %B__carbon_thunk.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Second.type: type = cpp_type_template_type Second [concrete]
+// CHECK:STDOUT:   %Second.template: %Second.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .First = %First.template
+// CHECK:STDOUT:     .A = %A.decl
+// CHECK:STDOUT:     .B = %B.decl
+// CHECK:STDOUT:     .Second = %Second.template
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %First.template: %First.type = struct_value () [concrete = constants.%First.template]
+// CHECK:STDOUT:   %A.decl: type = class_decl @A [concrete = constants.%A] {} {}
+// CHECK:STDOUT:   %B.decl: type = class_decl @B [concrete = constants.%B] {} {}
+// CHECK:STDOUT:   %A.A.cpp_overload_set.value: %A.A.cpp_overload_set.type = cpp_overload_set_value @A.A.cpp_overload_set [concrete = constants.%A.A.cpp_overload_set.value]
+// CHECK:STDOUT:   %A__carbon_thunk.decl: %A__carbon_thunk.type = fn_decl @A__carbon_thunk [concrete = constants.%A__carbon_thunk] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %B.B.cpp_overload_set.value: %B.B.cpp_overload_set.type = cpp_overload_set_value @B.B.cpp_overload_set [concrete = constants.%B.B.cpp_overload_set.value]
+// CHECK:STDOUT:   %B__carbon_thunk.decl: %B__carbon_thunk.type = fn_decl @B__carbon_thunk [concrete = constants.%B__carbon_thunk] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Second.template: %Second.type = struct_value () [concrete = constants.%Second.template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a1.patt: %pattern_type.9de = ref_binding_pattern a1 [concrete]
+// CHECK:STDOUT:     %a1.var_patt: %pattern_type.9de = var_pattern %a1.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a1.var: ref %A = var %a1.var_patt [concrete]
+// CHECK:STDOUT:   %a1: ref %A = ref_binding a1, %a1.var [concrete = %a1.var]
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b1.patt: %pattern_type.d0f = ref_binding_pattern b1 [concrete]
+// CHECK:STDOUT:     %b1.var_patt: %pattern_type.d0f = var_pattern %b1.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b1.var: ref %B = var %b1.var_patt [concrete]
+// CHECK:STDOUT:   %b1: ref %B = ref_binding b1, %b1.var [concrete = %b1.var]
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b2.patt: %pattern_type.d0f = ref_binding_pattern b2 [concrete]
+// CHECK:STDOUT:     %b2.var_patt: %pattern_type.d0f = var_pattern %b2.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b2.var: ref %B = var %b2.var_patt [concrete]
+// CHECK:STDOUT:   %b2: ref %B = ref_binding b2, %b2.var [concrete = %b2.var]
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a2.patt: %pattern_type.9de = ref_binding_pattern a2 [concrete]
+// CHECK:STDOUT:     %a2.var_patt: %pattern_type.9de = var_pattern %a2.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a2.var: ref %A = var %a2.var_patt [concrete]
+// CHECK:STDOUT:   %a2: ref %A = ref_binding a2, %a2.var [concrete = %a2.var]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref.loc7: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %A.ref.loc7_38: type = name_ref A, imports.%A.decl [concrete = constants.%A]
+// CHECK:STDOUT:   %A.ref.loc7_40: %A.A.cpp_overload_set.type = name_ref A, imports.%A.A.cpp_overload_set.value [concrete = constants.%A.A.cpp_overload_set.value]
+// CHECK:STDOUT:   %.loc7_1: ref %A = splice_block file.%a1.var [concrete = file.%a1.var] {}
+// CHECK:STDOUT:   %addr.loc7: %ptr.270 = addr_of %.loc7_1
+// CHECK:STDOUT:   %A__carbon_thunk.call.loc7: init %empty_tuple.type = call imports.%A__carbon_thunk.decl(%addr.loc7)
+// CHECK:STDOUT:   %.loc7_43: init %A = in_place_init %A__carbon_thunk.call.loc7, %.loc7_1
+// CHECK:STDOUT:   assign file.%a1.var, %.loc7_43
+// CHECK:STDOUT:   %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %B.ref.loc8_38: type = name_ref B, imports.%B.decl [concrete = constants.%B]
+// CHECK:STDOUT:   %B.ref.loc8_40: %B.B.cpp_overload_set.type = name_ref B, imports.%B.B.cpp_overload_set.value [concrete = constants.%B.B.cpp_overload_set.value]
+// CHECK:STDOUT:   %.loc8_1: ref %B = splice_block file.%b1.var [concrete = file.%b1.var] {}
+// CHECK:STDOUT:   %addr.loc8: %ptr.a04 = addr_of %.loc8_1
+// CHECK:STDOUT:   %B__carbon_thunk.call.loc8: init %empty_tuple.type = call imports.%B__carbon_thunk.decl(%addr.loc8)
+// CHECK:STDOUT:   %.loc8_43: init %B = in_place_init %B__carbon_thunk.call.loc8, %.loc8_1
+// CHECK:STDOUT:   assign file.%b1.var, %.loc8_43
+// CHECK:STDOUT:   %Cpp.ref.loc9: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %B.ref.loc9_39: type = name_ref B, imports.%B.decl [concrete = constants.%B]
+// CHECK:STDOUT:   %B.ref.loc9_41: %B.B.cpp_overload_set.type = name_ref B, imports.%B.B.cpp_overload_set.value [concrete = constants.%B.B.cpp_overload_set.value]
+// CHECK:STDOUT:   %.loc9_1: ref %B = splice_block file.%b2.var [concrete = file.%b2.var] {}
+// CHECK:STDOUT:   %addr.loc9: %ptr.a04 = addr_of %.loc9_1
+// CHECK:STDOUT:   %B__carbon_thunk.call.loc9: init %empty_tuple.type = call imports.%B__carbon_thunk.decl(%addr.loc9)
+// CHECK:STDOUT:   %.loc9_44: init %B = in_place_init %B__carbon_thunk.call.loc9, %.loc9_1
+// CHECK:STDOUT:   assign file.%b2.var, %.loc9_44
+// CHECK:STDOUT:   %Cpp.ref.loc10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %A.ref.loc10_39: type = name_ref A, imports.%A.decl [concrete = constants.%A]
+// CHECK:STDOUT:   %A.ref.loc10_41: %A.A.cpp_overload_set.type = name_ref A, imports.%A.A.cpp_overload_set.value [concrete = constants.%A.A.cpp_overload_set.value]
+// CHECK:STDOUT:   %.loc10_1: ref %A = splice_block file.%a2.var [concrete = file.%a2.var] {}
+// CHECK:STDOUT:   %addr.loc10: %ptr.270 = addr_of %.loc10_1
+// CHECK:STDOUT:   %A__carbon_thunk.call.loc10: init %empty_tuple.type = call imports.%A__carbon_thunk.decl(%addr.loc10)
+// CHECK:STDOUT:   %.loc10_44: init %A = in_place_init %A__carbon_thunk.call.loc10, %.loc10_1
+// CHECK:STDOUT:   assign file.%a2.var, %.loc10_44
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 182 - 0
toolchain/check/testdata/interop/cpp/template/argument_count.carbon

@@ -0,0 +1,182 @@
+// 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
+// EXTRA-ARGS: --clang-arg=-std=c++20
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/template/argument_count.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/template/argument_count.carbon
+
+// --- templates.h
+
+struct A {};
+struct B {};
+
+template<typename T, typename U> struct TwoTypes {};
+
+template<typename ...T> struct TypePack {};
+
+template<typename, typename> concept True = true;
+
+// FixedSizePack<T1, T2, ..., Tn>::Inner has a template parameter pack of fixed
+// size `n`, because it's expanded to an arity of exactly `n` when instantiating
+// the enclosing template, because the inner pack mentions the outer one.
+template<typename ...T> struct FixedSizePack {
+  template<True<T> ...U> struct Inner {};
+};
+
+// --- valid.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+//@dump-sem-ir-begin
+var x: Cpp.TwoTypes(Cpp.A, Cpp.B);
+//@dump-sem-ir-end
+
+// --- fail_too_few_arguments.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+// CHECK:STDERR: fail_too_few_arguments.carbon:[[@LINE+8]]:26: error: too few template arguments for class template 'TwoTypes' [CppInteropParseError]
+// CHECK:STDERR:    14 | var x: Cpp.TwoTypes(Cpp.A);
+// CHECK:STDERR:       |                          ^
+// CHECK:STDERR: fail_too_few_arguments.carbon:[[@LINE-5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./templates.h:5:41: note: template is declared here [CppInteropParseNote]
+// CHECK:STDERR:     5 | template<typename T, typename U> struct TwoTypes {};
+// CHECK:STDERR:       | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        ^
+// CHECK:STDERR:
+var x: Cpp.TwoTypes(Cpp.A);
+
+// --- fail_too_many_arguments.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+// CHECK:STDERR: fail_too_many_arguments.carbon:[[@LINE+8]]:40: error: too many template arguments for class template 'TwoTypes' [CppInteropParseError]
+// CHECK:STDERR:    14 | var x: Cpp.TwoTypes(Cpp.A, Cpp.A, Cpp.A);
+// CHECK:STDERR:       |                                      ~~^
+// CHECK:STDERR: fail_too_many_arguments.carbon:[[@LINE-5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./templates.h:5:41: note: template is declared here [CppInteropParseNote]
+// CHECK:STDERR:     5 | template<typename T, typename U> struct TwoTypes {};
+// CHECK:STDERR:       | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        ^
+// CHECK:STDERR:
+var x: Cpp.TwoTypes(Cpp.A, Cpp.A, Cpp.A);
+
+// --- pack.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+//@dump-sem-ir-begin
+var a0: Cpp.TypePack();
+var a1: Cpp.TypePack(Cpp.A);
+var a2: Cpp.TypePack(Cpp.A, Cpp.A);
+var a3: Cpp.TypePack(Cpp.A, Cpp.A, Cpp.A);
+//@dump-sem-ir-end
+
+// --- fixed_size_pack.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+var x: Cpp.FixedSizePack(Cpp.A, Cpp.A).Inner(Cpp.B, Cpp.B);
+
+// --- fail_fixed_size_pack_wrong_size.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+// CHECK:STDERR: fail_fixed_size_pack_wrong_size.carbon:[[@LINE+8]]:57: error: too few template arguments for class template 'Inner' [CppInteropParseError]
+// CHECK:STDERR:    14 | var too_few: Cpp.FixedSizePack(Cpp.A, Cpp.A).Inner(Cpp.B);
+// CHECK:STDERR:       |                                                         ^
+// CHECK:STDERR: fail_fixed_size_pack_wrong_size.carbon:[[@LINE-5]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./templates.h:15:33: note: template is declared here [CppInteropParseNote]
+// CHECK:STDERR:    15 |   template<True<T> ...U> struct Inner {};
+// CHECK:STDERR:       |   ~~~~~~~~~~~~~~~~~~~~~~        ^
+// CHECK:STDERR:
+var too_few: Cpp.FixedSizePack(Cpp.A, Cpp.A).Inner(Cpp.B);
+
+// CHECK:STDERR: fail_fixed_size_pack_wrong_size.carbon:[[@LINE+8]]:72: error: too many template arguments for class template 'Inner' [CppInteropParseError]
+// CHECK:STDERR:    24 | var too_many: Cpp.FixedSizePack(Cpp.A, Cpp.A).Inner(Cpp.B, Cpp.B, Cpp.B);
+// CHECK:STDERR:       |                                                                      ~~^
+// CHECK:STDERR: fail_fixed_size_pack_wrong_size.carbon:[[@LINE-15]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./templates.h:15:33: note: template is declared here [CppInteropParseNote]
+// CHECK:STDERR:    15 |   template<True<T> ...U> struct Inner {};
+// CHECK:STDERR:       |   ~~~~~~~~~~~~~~~~~~~~~~        ^
+// CHECK:STDERR:
+var too_many: Cpp.FixedSizePack(Cpp.A, Cpp.A).Inner(Cpp.B, Cpp.B, Cpp.B);
+
+// CHECK:STDOUT: --- valid.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %TwoTypes: type = class_type @TwoTypes [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %TwoTypes [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %x.patt: %pattern_type = ref_binding_pattern x [concrete]
+// CHECK:STDOUT:     %x.var_patt: %pattern_type = var_pattern %x.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %x.var: ref %TwoTypes = var %x.var_patt [concrete]
+// CHECK:STDOUT:   %x: ref %TwoTypes = ref_binding x, %x.var [concrete = %x.var]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- pack.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %TypePack.49edc4.1: type = class_type @TypePack.1 [concrete]
+// CHECK:STDOUT:   %pattern_type.9db866.1: type = pattern_type %TypePack.49edc4.1 [concrete]
+// CHECK:STDOUT:   %TypePack.49edc4.2: type = class_type @TypePack.2 [concrete]
+// CHECK:STDOUT:   %pattern_type.9db866.2: type = pattern_type %TypePack.49edc4.2 [concrete]
+// CHECK:STDOUT:   %TypePack.49edc4.3: type = class_type @TypePack.3 [concrete]
+// CHECK:STDOUT:   %pattern_type.9db866.3: type = pattern_type %TypePack.49edc4.3 [concrete]
+// CHECK:STDOUT:   %TypePack.49edc4.4: type = class_type @TypePack.4 [concrete]
+// CHECK:STDOUT:   %pattern_type.9db866.4: type = pattern_type %TypePack.49edc4.4 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a0.patt: %pattern_type.9db866.1 = ref_binding_pattern a0 [concrete]
+// CHECK:STDOUT:     %a0.var_patt: %pattern_type.9db866.1 = var_pattern %a0.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a0.var: ref %TypePack.49edc4.1 = var %a0.var_patt [concrete]
+// CHECK:STDOUT:   %a0: ref %TypePack.49edc4.1 = ref_binding a0, %a0.var [concrete = %a0.var]
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a1.patt: %pattern_type.9db866.2 = ref_binding_pattern a1 [concrete]
+// CHECK:STDOUT:     %a1.var_patt: %pattern_type.9db866.2 = var_pattern %a1.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a1.var: ref %TypePack.49edc4.2 = var %a1.var_patt [concrete]
+// CHECK:STDOUT:   %a1: ref %TypePack.49edc4.2 = ref_binding a1, %a1.var [concrete = %a1.var]
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a2.patt: %pattern_type.9db866.3 = ref_binding_pattern a2 [concrete]
+// CHECK:STDOUT:     %a2.var_patt: %pattern_type.9db866.3 = var_pattern %a2.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a2.var: ref %TypePack.49edc4.3 = var %a2.var_patt [concrete]
+// CHECK:STDOUT:   %a2: ref %TypePack.49edc4.3 = ref_binding a2, %a2.var [concrete = %a2.var]
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a3.patt: %pattern_type.9db866.4 = ref_binding_pattern a3 [concrete]
+// CHECK:STDOUT:     %a3.var_patt: %pattern_type.9db866.4 = var_pattern %a3.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a3.var: ref %TypePack.49edc4.4 = var %a3.var_patt [concrete]
+// CHECK:STDOUT:   %a3: ref %TypePack.49edc4.4 = ref_binding a3, %a3.var [concrete = %a3.var]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 110 - 0
toolchain/check/testdata/interop/cpp/template/class_template.carbon

@@ -0,0 +1,110 @@
+// 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/none.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/template/class_template.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/template/class_template.carbon
+
+// --- class_template.h
+
+struct A {};
+struct B {};
+
+template<typename T, typename U>
+struct X {
+  static T f(U);
+};
+
+// --- use_class_template.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "class_template.h";
+
+//@dump-sem-ir-begin
+var a: Cpp.A = Cpp.X(Cpp.A, Cpp.B).f({} as Cpp.B);
+//@dump-sem-ir-end
+
+// CHECK:STDOUT: --- use_class_template.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %A: type = class_type @A [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %pattern_type.9de: type = pattern_type %A [concrete]
+// CHECK:STDOUT:   %X.type: type = cpp_type_template_type X [concrete]
+// CHECK:STDOUT:   %X.template: %X.type = struct_value () [concrete]
+// CHECK:STDOUT:   %B: type = class_type @B [concrete]
+// CHECK:STDOUT:   %X.f.cpp_overload_set.type: type = cpp_overload_set_type @X.f.cpp_overload_set [concrete]
+// CHECK:STDOUT:   %X.f.cpp_overload_set.value: %X.f.cpp_overload_set.type = cpp_overload_set_value @X.f.cpp_overload_set [concrete]
+// CHECK:STDOUT:   %empty_struct: %empty_struct_type = struct_value () [concrete]
+// CHECK:STDOUT:   %B.val: %B = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.a04: type = ptr_type %B [concrete]
+// CHECK:STDOUT:   %ptr.270: type = ptr_type %A [concrete]
+// CHECK:STDOUT:   %f__carbon_thunk.type: type = fn_type @f__carbon_thunk [concrete]
+// CHECK:STDOUT:   %f__carbon_thunk: %f__carbon_thunk.type = struct_value () [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:     .X = %X.template
+// CHECK:STDOUT:     .B = %B.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %A.decl: type = class_decl @A [concrete = constants.%A] {} {}
+// CHECK:STDOUT:   %X.template: %X.type = struct_value () [concrete = constants.%X.template]
+// CHECK:STDOUT:   %B.decl: type = class_decl @B [concrete = constants.%B] {} {}
+// CHECK:STDOUT:   %X.f.cpp_overload_set.value: %X.f.cpp_overload_set.type = cpp_overload_set_value @X.f.cpp_overload_set [concrete = constants.%X.f.cpp_overload_set.value]
+// CHECK:STDOUT:   %f__carbon_thunk.decl: %f__carbon_thunk.type = fn_decl @f__carbon_thunk [concrete = constants.%f__carbon_thunk] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.9de = ref_binding_pattern a [concrete]
+// CHECK:STDOUT:     %a.var_patt: %pattern_type.9de = var_pattern %a.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a.var: ref %A = var %a.var_patt [concrete]
+// CHECK:STDOUT:   %.loc7: type = splice_block %A.ref [concrete = constants.%A] {
+// CHECK:STDOUT:     %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %A.ref: type = name_ref A, imports.%A.decl [concrete = constants.%A]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: ref %A = ref_binding a, %a.var [concrete = %a.var]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref.loc7_16: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %X.ref: %X.type = name_ref X, imports.%X.template [concrete = constants.%X.template]
+// CHECK:STDOUT:   %Cpp.ref.loc7_22: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %A.ref: type = name_ref A, imports.%A.decl [concrete = constants.%A]
+// CHECK:STDOUT:   %Cpp.ref.loc7_29: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %B.ref.loc7_32: type = name_ref B, imports.%B.decl [concrete = constants.%B]
+// CHECK:STDOUT:   %f.ref: %X.f.cpp_overload_set.type = name_ref f, imports.%X.f.cpp_overload_set.value [concrete = constants.%X.f.cpp_overload_set.value]
+// CHECK:STDOUT:   %.loc7_39.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
+// CHECK:STDOUT:   %Cpp.ref.loc7_44: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %B.ref.loc7_47: type = name_ref B, imports.%B.decl [concrete = constants.%B]
+// CHECK:STDOUT:   %.loc7_39.2: ref %B = temporary_storage
+// CHECK:STDOUT:   %.loc7_39.3: init %B = class_init (), %.loc7_39.2 [concrete = constants.%B.val]
+// CHECK:STDOUT:   %.loc7_39.4: ref %B = temporary %.loc7_39.2, %.loc7_39.3
+// CHECK:STDOUT:   %.loc7_41.1: ref %B = converted %.loc7_39.1, %.loc7_39.4
+// CHECK:STDOUT:   %.loc7_1: ref %A = splice_block file.%a.var [concrete = file.%a.var] {}
+// CHECK:STDOUT:   %.loc7_41.2: %B = acquire_value %.loc7_41.1
+// CHECK:STDOUT:   %.loc7_41.3: ref %B = value_as_ref %.loc7_41.2
+// CHECK:STDOUT:   %addr.loc7_49.1: %ptr.a04 = addr_of %.loc7_41.3
+// CHECK:STDOUT:   %addr.loc7_49.2: %ptr.270 = addr_of %.loc7_1
+// CHECK:STDOUT:   %f__carbon_thunk.call: init %empty_tuple.type = call imports.%f__carbon_thunk.decl(%addr.loc7_49.1, %addr.loc7_49.2)
+// CHECK:STDOUT:   %.loc7_49: init %A = in_place_init %f__carbon_thunk.call, %.loc7_1
+// CHECK:STDOUT:   assign file.%a.var, %.loc7_49
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 126 - 0
toolchain/check/testdata/interop/cpp/template/concept.carbon

@@ -0,0 +1,126 @@
+// 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/bool.carbon
+// EXTRA-ARGS: --clang-arg=--std=c++20
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/template/concept.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/template/concept.carbon
+
+// --- concept.h
+
+struct Small {};
+struct Large { int n; };
+
+template<typename T> concept IsLarge = sizeof(T) > 1;
+
+// --- use_concept.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "concept.h";
+
+class X(B:! bool) {
+  fn Make() -> Self { return {}; }
+}
+
+//@dump-sem-ir-begin
+var x1: X(Cpp.IsLarge(Cpp.Small)) = X(false).Make();
+var x2: X(Cpp.IsLarge(Cpp.Large)) = X(true).Make();
+//@dump-sem-ir-end
+
+// CHECK:STDOUT: --- use_concept.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %X.type: type = generic_class_type @X [concrete]
+// CHECK:STDOUT:   %X.generic: %X.type = struct_value () [concrete]
+// CHECK:STDOUT:   %IsLarge.type: type = cpp_type_template_type IsLarge [concrete]
+// CHECK:STDOUT:   %IsLarge.template: %IsLarge.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Small: type = class_type @Small [concrete]
+// CHECK:STDOUT:   %false: bool = bool_literal false [concrete]
+// CHECK:STDOUT:   %X.363: type = class_type @X, @X(%false) [concrete]
+// CHECK:STDOUT:   %X.Make.type.58a: type = fn_type @X.Make, @X(%false) [concrete]
+// CHECK:STDOUT:   %X.Make.cb1: %X.Make.type.58a = struct_value () [concrete]
+// CHECK:STDOUT:   %pattern_type.a08: type = pattern_type %X.363 [concrete]
+// CHECK:STDOUT:   %X.Make.specific_fn.f4f: <specific function> = specific_function %X.Make.cb1, @X.Make(%false) [concrete]
+// CHECK:STDOUT:   %Large: type = class_type @Large [concrete]
+// CHECK:STDOUT:   %true: bool = bool_literal true [concrete]
+// CHECK:STDOUT:   %X.57c: type = class_type @X, @X(%true) [concrete]
+// CHECK:STDOUT:   %X.Make.type.d3c: type = fn_type @X.Make, @X(%true) [concrete]
+// CHECK:STDOUT:   %X.Make.c40: %X.Make.type.d3c = struct_value () [concrete]
+// CHECK:STDOUT:   %pattern_type.ea2: type = pattern_type %X.57c [concrete]
+// CHECK:STDOUT:   %X.Make.specific_fn.70c: <specific function> = specific_function %X.Make.c40, @X.Make(%true) [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .IsLarge = %IsLarge.template
+// CHECK:STDOUT:     .Small = %Small.decl
+// CHECK:STDOUT:     .Large = %Large.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %IsLarge.template: %IsLarge.type = struct_value () [concrete = constants.%IsLarge.template]
+// CHECK:STDOUT:   %Small.decl: type = class_decl @Small [concrete = constants.%Small] {} {}
+// CHECK:STDOUT:   %Large.decl: type = class_decl @Large [concrete = constants.%Large] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %x1.patt: %pattern_type.a08 = ref_binding_pattern x1 [concrete]
+// CHECK:STDOUT:     %x1.var_patt: %pattern_type.a08 = var_pattern %x1.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %x1.var: ref %X.363 = var %x1.var_patt [concrete]
+// CHECK:STDOUT:   %.loc11: type = splice_block %X.loc11 [concrete = constants.%X.363] {
+// CHECK:STDOUT:     %X.ref.loc11: %X.type = name_ref X, %X.decl [concrete = constants.%X.generic]
+// CHECK:STDOUT:     %Cpp.ref.loc11_11: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %IsLarge.ref.loc11: %IsLarge.type = name_ref IsLarge, imports.%IsLarge.template [concrete = constants.%IsLarge.template]
+// CHECK:STDOUT:     %Cpp.ref.loc11_23: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Small.ref: type = name_ref Small, imports.%Small.decl [concrete = constants.%Small]
+// CHECK:STDOUT:     %false: bool = bool_literal false [concrete = constants.%false]
+// CHECK:STDOUT:     %X.loc11: type = class_type @X, @X(constants.%false) [concrete = constants.%X.363]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %x1: ref %X.363 = ref_binding x1, %x1.var [concrete = %x1.var]
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %x2.patt: %pattern_type.ea2 = ref_binding_pattern x2 [concrete]
+// CHECK:STDOUT:     %x2.var_patt: %pattern_type.ea2 = var_pattern %x2.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %x2.var: ref %X.57c = var %x2.var_patt [concrete]
+// CHECK:STDOUT:   %.loc12: type = splice_block %X.loc12 [concrete = constants.%X.57c] {
+// CHECK:STDOUT:     %X.ref.loc12: %X.type = name_ref X, %X.decl [concrete = constants.%X.generic]
+// CHECK:STDOUT:     %Cpp.ref.loc12_11: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %IsLarge.ref.loc12: %IsLarge.type = name_ref IsLarge, imports.%IsLarge.template [concrete = constants.%IsLarge.template]
+// CHECK:STDOUT:     %Cpp.ref.loc12_23: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %Large.ref: type = name_ref Large, imports.%Large.decl [concrete = constants.%Large]
+// CHECK:STDOUT:     %true: bool = bool_literal true [concrete = constants.%true]
+// CHECK:STDOUT:     %X.loc12: type = class_type @X, @X(constants.%true) [concrete = constants.%X.57c]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %x2: ref %X.57c = ref_binding x2, %x2.var [concrete = %x2.var]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %X.ref.loc11: %X.type = name_ref X, file.%X.decl [concrete = constants.%X.generic]
+// CHECK:STDOUT:   %false: bool = bool_literal false [concrete = constants.%false]
+// CHECK:STDOUT:   %X.loc11: type = class_type @X, @X(constants.%false) [concrete = constants.%X.363]
+// CHECK:STDOUT:   %.loc11_45: %X.Make.type.58a = specific_constant @X.%X.Make.decl, @X(constants.%false) [concrete = constants.%X.Make.cb1]
+// CHECK:STDOUT:   %Make.ref.loc11: %X.Make.type.58a = name_ref Make, %.loc11_45 [concrete = constants.%X.Make.cb1]
+// CHECK:STDOUT:   %X.Make.specific_fn.loc11: <specific function> = specific_function %Make.ref.loc11, @X.Make(constants.%false) [concrete = constants.%X.Make.specific_fn.f4f]
+// CHECK:STDOUT:   %.loc11_1: ref %X.363 = splice_block file.%x1.var [concrete = file.%x1.var] {}
+// CHECK:STDOUT:   %X.Make.call.loc11: init %X.363 = call %X.Make.specific_fn.loc11() to %.loc11_1
+// CHECK:STDOUT:   assign file.%x1.var, %X.Make.call.loc11
+// CHECK:STDOUT:   %X.ref.loc12: %X.type = name_ref X, file.%X.decl [concrete = constants.%X.generic]
+// CHECK:STDOUT:   %true: bool = bool_literal true [concrete = constants.%true]
+// CHECK:STDOUT:   %X.loc12: type = class_type @X, @X(constants.%true) [concrete = constants.%X.57c]
+// CHECK:STDOUT:   %.loc12_44: %X.Make.type.d3c = specific_constant @X.%X.Make.decl, @X(constants.%true) [concrete = constants.%X.Make.c40]
+// CHECK:STDOUT:   %Make.ref.loc12: %X.Make.type.d3c = name_ref Make, %.loc12_44 [concrete = constants.%X.Make.c40]
+// CHECK:STDOUT:   %X.Make.specific_fn.loc12: <specific function> = specific_function %Make.ref.loc12, @X.Make(constants.%true) [concrete = constants.%X.Make.specific_fn.70c]
+// CHECK:STDOUT:   %.loc12_1: ref %X.57c = splice_block file.%x2.var [concrete = file.%x2.var] {}
+// CHECK:STDOUT:   %X.Make.call.loc12: init %X.57c = call %X.Make.specific_fn.loc12() to %.loc12_1
+// CHECK:STDOUT:   assign file.%x2.var, %X.Make.call.loc12
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 38 - 0
toolchain/check/testdata/interop/cpp/template/generic_call.carbon

@@ -0,0 +1,38 @@
+// 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=--std=c++20
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/template/generic_call.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/template/generic_call.carbon
+
+// --- header.h
+
+struct X {};
+
+template<typename T> struct S {};
+
+// --- fail_todo_generic_call.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "header.h";
+
+fn F[T:! type](x: T) {
+  // TODO: This should be treated as being template-dependent on T,
+  // and we should perform the call during monomorphization.
+  // CHECK:STDERR: fail_todo_generic_call.carbon:[[@LINE+4]]:16: error: semantics TODO: `unsupported type used as template argument` [SemanticsTodo]
+  // CHECK:STDERR:   var v: Cpp.S(T);
+  // CHECK:STDERR:                ^
+  // CHECK:STDERR:
+  var v: Cpp.S(T);
+}
+
+fn G() {
+  F({} as Cpp.X);
+}

+ 121 - 0
toolchain/check/testdata/interop/cpp/template/non_type_param.carbon

@@ -0,0 +1,121 @@
+// 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/template/non_type_param.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/template/non_type_param.carbon
+
+// --- templates.h
+
+struct A {};
+
+template<int M, int N> struct TwoNonType {};
+template<typename T, T N> struct DependentNonType {};
+
+// --- fail_todo_valid.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+//@dump-sem-ir-begin
+// CHECK:STDERR: fail_todo_valid.carbon:[[@LINE+4]]:23: error: semantics TODO: `argument for non-type template parameter` [SemanticsTodo]
+// CHECK:STDERR: var x: Cpp.TwoNonType(1, 2);
+// CHECK:STDERR:                       ^
+// CHECK:STDERR:
+var x: Cpp.TwoNonType(1, 2);
+//@dump-sem-ir-end
+
+// --- fail_todo_dependent.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+var n: i32;
+
+//@dump-sem-ir-begin
+// CHECK:STDERR: fail_todo_dependent.carbon:[[@LINE+4]]:34: error: semantics TODO: `argument for non-type template parameter` [SemanticsTodo]
+// CHECK:STDERR: var x: Cpp.DependentNonType(i32, 1);
+// CHECK:STDERR:                                  ^
+// CHECK:STDERR:
+var x: Cpp.DependentNonType(i32, 1);
+
+// CHECK:STDERR: fail_todo_dependent.carbon:[[@LINE+4]]:35: error: semantics TODO: `argument for non-type template parameter` [SemanticsTodo]
+// CHECK:STDERR: var y: Cpp.DependentNonType(i32*, &n);
+// CHECK:STDERR:                                   ^~
+// CHECK:STDERR:
+var y: Cpp.DependentNonType(i32*, &n);
+//@dump-sem-ir-end
+
+// --- fail_type_argument.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+// CHECK:STDERR: fail_type_argument.carbon:[[@LINE+4]]:23: error: semantics TODO: `argument for non-type template parameter` [SemanticsTodo]
+// CHECK:STDERR: var x: Cpp.TwoNonType(1, Cpp.A);
+// CHECK:STDERR:                       ^
+// CHECK:STDERR:
+var x: Cpp.TwoNonType(1, Cpp.A);
+
+// --- fail_no_conversion.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+var n: i32;
+
+// CHECK:STDERR: fail_no_conversion.carbon:[[@LINE+4]]:23: error: semantics TODO: `argument for non-type template parameter` [SemanticsTodo]
+// CHECK:STDERR: var x: Cpp.TwoNonType(1, &n);
+// CHECK:STDERR:                       ^
+// CHECK:STDERR:
+var x: Cpp.TwoNonType(1, &n);
+
+// CHECK:STDOUT: --- fail_todo_valid.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %x.patt: <error> = ref_binding_pattern x [concrete]
+// CHECK:STDOUT:     %x.var_patt: <error> = var_pattern %x.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %x.var: ref <error> = var %x.var_patt [concrete = <error>]
+// CHECK:STDOUT:   %x: ref <error> = ref_binding x, <error> [concrete = <error>]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_dependent.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %x.patt: <error> = ref_binding_pattern x [concrete]
+// CHECK:STDOUT:     %x.var_patt: <error> = var_pattern %x.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %x.var: ref <error> = var %x.var_patt [concrete = <error>]
+// CHECK:STDOUT:   %x: ref <error> = ref_binding x, <error> [concrete = <error>]
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %y.patt: <error> = ref_binding_pattern y [concrete]
+// CHECK:STDOUT:     %y.var_patt: <error> = var_pattern %y.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %y.var: ref <error> = var %y.var_patt [concrete = <error>]
+// CHECK:STDOUT:   %y: ref <error> = ref_binding y, <error> [concrete = <error>]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 63 - 0
toolchain/check/testdata/interop/cpp/template/template_template_param.carbon

@@ -0,0 +1,63 @@
+// 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/template/template_template_param.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/template/template_template_param.carbon
+
+// --- templates.h
+
+template<typename> struct A {};
+template<typename> struct B {};
+
+template<template<typename> typename, template<typename> typename>
+struct TwoTemplates {};
+
+// --- fail_todo_valid.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+//@dump-sem-ir-begin
+// CHECK:STDERR: fail_todo_valid.carbon:[[@LINE+4]]:25: error: semantics TODO: `argument for template template parameter` [SemanticsTodo]
+// CHECK:STDERR: var x: Cpp.TwoTemplates(Cpp.A, Cpp.B);
+// CHECK:STDERR:                         ^~~~~
+// CHECK:STDERR:
+var x: Cpp.TwoTemplates(Cpp.A, Cpp.B);
+//@dump-sem-ir-end
+
+// --- fail_non_type_argument.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+// CHECK:STDERR: fail_non_type_argument.carbon:[[@LINE+4]]:25: error: semantics TODO: `argument for template template parameter` [SemanticsTodo]
+// CHECK:STDERR: var x: Cpp.TwoTemplates(Cpp.A, true);
+// CHECK:STDERR:                         ^~~~~
+// CHECK:STDERR:
+var x: Cpp.TwoTemplates(Cpp.A, true);
+
+// CHECK:STDOUT: --- fail_todo_valid.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %x.patt: <error> = ref_binding_pattern x [concrete]
+// CHECK:STDOUT:     %x.var_patt: <error> = var_pattern %x.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %x.var: ref <error> = var %x.var_patt [concrete = <error>]
+// CHECK:STDOUT:   %x: ref <error> = ref_binding x, <error> [concrete = <error>]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 75 - 0
toolchain/check/testdata/interop/cpp/template/type_param.carbon

@@ -0,0 +1,75 @@
+// 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/template/type_param.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/template/type_param.carbon
+
+// --- templates.h
+
+struct A {};
+struct B {};
+
+template<typename T, typename U> struct TwoTypes {};
+
+// --- valid.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+//@dump-sem-ir-begin
+var x: Cpp.TwoTypes(Cpp.A, Cpp.B);
+//@dump-sem-ir-end
+
+// --- fail_non_type_argument.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+// CHECK:STDERR: fail_non_type_argument.carbon:[[@LINE+7]]:28: error: cannot implicitly convert non-type value of type `bool` to `type` [ConversionFailureNonTypeToFacet]
+// CHECK:STDERR: var x: Cpp.TwoTypes(Cpp.A, true);
+// CHECK:STDERR:                            ^~~~
+// CHECK:STDERR: fail_non_type_argument.carbon:[[@LINE+4]]:28: note: type `bool` does not implement interface `Core.ImplicitAs(type)` [MissingImplInMemberAccessNote]
+// CHECK:STDERR: var x: Cpp.TwoTypes(Cpp.A, true);
+// CHECK:STDERR:                            ^~~~
+// CHECK:STDERR:
+var x: Cpp.TwoTypes(Cpp.A, true);
+
+// --- fail_unsupported_type.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "templates.h";
+
+// CHECK:STDERR: fail_unsupported_type.carbon:[[@LINE+4]]:28: error: semantics TODO: `unsupported type used as template argument` [SemanticsTodo]
+// CHECK:STDERR: var x: Cpp.TwoTypes(Cpp.A, {});
+// CHECK:STDERR:                            ^~
+// CHECK:STDERR:
+var x: Cpp.TwoTypes(Cpp.A, {});
+
+// CHECK:STDOUT: --- valid.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %TwoTypes: type = class_type @TwoTypes [concrete]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %TwoTypes [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %x.patt: %pattern_type = ref_binding_pattern x [concrete]
+// CHECK:STDOUT:     %x.var_patt: %pattern_type = var_pattern %x.patt [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %x.var: ref %TwoTypes = var %x.var_patt [concrete]
+// CHECK:STDOUT:   %x: ref %TwoTypes = ref_binding x, %x.var [concrete = %x.var]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 139 - 0
toolchain/check/testdata/interop/cpp/template/var_template.carbon

@@ -0,0 +1,139 @@
+// 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/none.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/template/var_template.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/template/var_template.carbon
+
+// --- var_template.h
+
+struct A {};
+struct B {};
+
+template<typename T> T var = {};
+
+struct Wrap {
+  template<typename T> static T var = {};
+};
+
+// --- use_var_template.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "var_template.h";
+
+//@dump-sem-ir-begin
+let pa: Cpp.A* = &Cpp.r#var(Cpp.A);
+let pb: Cpp.B* = &Cpp.r#var(Cpp.B);
+let pwa: Cpp.A* = &Cpp.Wrap.r#var(Cpp.A);
+let pwb: Cpp.B* = &Cpp.Wrap.r#var(Cpp.B);
+//@dump-sem-ir-end
+
+// CHECK:STDOUT: --- use_var_template.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %A: type = class_type @A [concrete]
+// CHECK:STDOUT:   %ptr.270: type = ptr_type %A [concrete]
+// CHECK:STDOUT:   %pattern_type.bcf: type = pattern_type %ptr.270 [concrete]
+// CHECK:STDOUT:   %var.type.230: type = cpp_type_template_type r#var [concrete]
+// CHECK:STDOUT:   %var.template.326: %var.type.230 = struct_value () [concrete]
+// CHECK:STDOUT:   %addr.0b0: %ptr.270 = addr_of imports.%var.var.2f7 [concrete]
+// CHECK:STDOUT:   %B: type = class_type @B [concrete]
+// CHECK:STDOUT:   %ptr.a04: type = ptr_type %B [concrete]
+// CHECK:STDOUT:   %pattern_type.837: type = pattern_type %ptr.a04 [concrete]
+// CHECK:STDOUT:   %addr.324: %ptr.a04 = addr_of imports.%var.var.a15 [concrete]
+// CHECK:STDOUT:   %Wrap: type = class_type @Wrap [concrete]
+// CHECK:STDOUT:   %var.type.17b: type = cpp_type_template_type r#var [concrete]
+// CHECK:STDOUT:   %var.template.df3: %var.type.17b = struct_value () [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:     .r#var = %var.template.326
+// CHECK:STDOUT:     .B = %B.decl
+// CHECK:STDOUT:     .Wrap = %Wrap.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %A.decl: type = class_decl @A [concrete = constants.%A] {} {}
+// CHECK:STDOUT:   %var.template.326: %var.type.230 = struct_value () [concrete = constants.%var.template.326]
+// CHECK:STDOUT:   %var.var.2f7: ref %A = var %var.var_patt.093 [concrete]
+// CHECK:STDOUT:   %B.decl: type = class_decl @B [concrete = constants.%B] {} {}
+// CHECK:STDOUT:   %var.var.a15: ref %B = var %var.var_patt.56c [concrete]
+// CHECK:STDOUT:   %Wrap.decl: type = class_decl @Wrap [concrete = constants.%Wrap] {} {}
+// CHECK:STDOUT:   %var.template.df3: %var.type.17b = struct_value () [concrete = constants.%var.template.df3]
+// CHECK:STDOUT:   %var.var.2e6: ref %A = var %var.var_patt.0cb
+// CHECK:STDOUT:   %var.var.bab: ref %B = var %var.var_patt.efb
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %pa.patt: %pattern_type.bcf = value_binding_pattern pa [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc7: type = splice_block %ptr.loc7 [concrete = constants.%ptr.270] {
+// CHECK:STDOUT:     %Cpp.ref.loc7: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %A.ref.loc7: type = name_ref A, imports.%A.decl [concrete = constants.%A]
+// CHECK:STDOUT:     %ptr.loc7: type = ptr_type %A.ref.loc7 [concrete = constants.%ptr.270]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %pa: %ptr.270 = value_binding pa, @__global_init.%addr.loc7
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %pb.patt: %pattern_type.837 = value_binding_pattern pb [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc8: type = splice_block %ptr.loc8 [concrete = constants.%ptr.a04] {
+// CHECK:STDOUT:     %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %B.ref.loc8: type = name_ref B, imports.%B.decl [concrete = constants.%B]
+// CHECK:STDOUT:     %ptr.loc8: type = ptr_type %B.ref.loc8 [concrete = constants.%ptr.a04]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %pb: %ptr.a04 = value_binding pb, @__global_init.%addr.loc8
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %pwa.patt: %pattern_type.bcf = value_binding_pattern pwa [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc9: type = splice_block %ptr.loc9 [concrete = constants.%ptr.270] {
+// CHECK:STDOUT:     %Cpp.ref.loc9: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %A.ref.loc9: type = name_ref A, imports.%A.decl [concrete = constants.%A]
+// CHECK:STDOUT:     %ptr.loc9: type = ptr_type %A.ref.loc9 [concrete = constants.%ptr.270]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %pwa: %ptr.270 = value_binding pwa, @__global_init.%addr.loc9
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %pwb.patt: %pattern_type.837 = value_binding_pattern pwb [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc10: type = splice_block %ptr.loc10 [concrete = constants.%ptr.a04] {
+// CHECK:STDOUT:     %Cpp.ref.loc10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:     %B.ref.loc10: type = name_ref B, imports.%B.decl [concrete = constants.%B]
+// CHECK:STDOUT:     %ptr.loc10: type = ptr_type %B.ref.loc10 [concrete = constants.%ptr.a04]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %pwb: %ptr.a04 = value_binding pwb, @__global_init.%addr.loc10
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref.loc7_19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %var.ref.loc7: %var.type.230 = name_ref r#var, imports.%var.template.326 [concrete = constants.%var.template.326]
+// CHECK:STDOUT:   %Cpp.ref.loc7_29: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %A.ref.loc7: type = name_ref A, imports.%A.decl [concrete = constants.%A]
+// CHECK:STDOUT:   %addr.loc7: %ptr.270 = addr_of imports.%var.var.2f7 [concrete = constants.%addr.0b0]
+// CHECK:STDOUT:   %Cpp.ref.loc8_19: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %var.ref.loc8: %var.type.230 = name_ref r#var, imports.%var.template.326 [concrete = constants.%var.template.326]
+// CHECK:STDOUT:   %Cpp.ref.loc8_29: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %B.ref.loc8: type = name_ref B, imports.%B.decl [concrete = constants.%B]
+// CHECK:STDOUT:   %addr.loc8: %ptr.a04 = addr_of imports.%var.var.a15 [concrete = constants.%addr.324]
+// CHECK:STDOUT:   %Cpp.ref.loc9_20: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Wrap.ref.loc9: type = name_ref Wrap, imports.%Wrap.decl [concrete = constants.%Wrap]
+// CHECK:STDOUT:   %var.ref.loc9: %var.type.17b = name_ref r#var, imports.%var.template.df3 [concrete = constants.%var.template.df3]
+// CHECK:STDOUT:   %Cpp.ref.loc9_35: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %A.ref.loc9: type = name_ref A, imports.%A.decl [concrete = constants.%A]
+// CHECK:STDOUT:   %addr.loc9: %ptr.270 = addr_of imports.%var.var.2e6
+// CHECK:STDOUT:   %Cpp.ref.loc10_20: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Wrap.ref.loc10: type = name_ref Wrap, imports.%Wrap.decl [concrete = constants.%Wrap]
+// CHECK:STDOUT:   %var.ref.loc10: %var.type.17b = name_ref r#var, imports.%var.template.df3 [concrete = constants.%var.template.df3]
+// CHECK:STDOUT:   %Cpp.ref.loc10_35: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %B.ref.loc10: type = name_ref B, imports.%B.decl [concrete = constants.%B]
+// CHECK:STDOUT:   %addr.loc10: %ptr.a04 = addr_of imports.%var.var.bab
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 25 - 23
toolchain/check/testdata/interop/cpp/unsupported_decl_type.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/int.carbon
 //
 // AUTOUPDATE
 // TIP: To test this file alone, run:
@@ -10,47 +10,49 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/unsupported_decl_type.carbon
 
-// --- template.h
+// --- structured_binding.h
 
-template<typename T> class C {};
+struct X { int a, b; };
+auto [a, b] = X{};
 
-// --- fail_todo_use_template.carbon
+// --- fail_todo_use_structured_binding.carbon
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_todo_use_template.carbon:[[@LINE+4]]:10: in file included here [InCppInclude]
-// CHECK:STDERR: ./template.h:2:28: error: semantics TODO: `Unsupported: Declaration type ClassTemplate` [SemanticsTodo]
-// CHECK:STDERR: template<typename T> class C {};
-// CHECK:STDERR:                            ^
-import Cpp library "template.h";
+// CHECK:STDERR: fail_todo_use_structured_binding.carbon:[[@LINE+4]]:10: in file included here [InCppInclude]
+// CHECK:STDERR: ./structured_binding.h:3:7: error: semantics TODO: `Unsupported: Declaration type Binding` [SemanticsTodo]
+// CHECK:STDERR: auto [a, b] = X{};
+// CHECK:STDERR:       ^
+import Cpp library "structured_binding.h";
 
-fn F() {
+fn F() -> i32 {
   //@dump-sem-ir-begin
-  // CHECK:STDERR: fail_todo_use_template.carbon:[[@LINE+4]]:10: note: in `Cpp` name lookup for `C` [InCppNameLookup]
-  // CHECK:STDERR:   var c: Cpp.C({});
+  // CHECK:STDERR: fail_todo_use_structured_binding.carbon:[[@LINE+4]]:10: note: in `Cpp` name lookup for `a` [InCppNameLookup]
+  // CHECK:STDERR:   return Cpp.a;
   // CHECK:STDERR:          ^~~~~
   // CHECK:STDERR:
-  var c: Cpp.C({});
+  return Cpp.a;
   //@dump-sem-ir-end
 }
 
-// CHECK:STDOUT: --- fail_todo_use_template.carbon
+// CHECK:STDOUT: --- fail_todo_use_structured_binding.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
+// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .a = <error>
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: fn @F() -> %i32 {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   name_binding_decl {
-// CHECK:STDOUT:     %c.patt: <error> = ref_binding_pattern c [concrete]
-// CHECK:STDOUT:     %c.var_patt: <error> = var_pattern %c.patt [concrete]
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %c.var: ref <error> = var %c.var_patt [concrete = <error>]
-// CHECK:STDOUT:   <elided>
-// CHECK:STDOUT:   %c: ref <error> = ref_binding c, <error> [concrete = <error>]
-// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %a.ref: <error> = name_ref a, <error> [concrete = <error>]
+// CHECK:STDOUT:   return <error> to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 6 - 0
toolchain/check/type.cpp

@@ -178,6 +178,12 @@ auto GetCppOverloadSetType(Context& context,
       context, overload_set_id, specific_id);
 }
 
+auto GetCppTemplateNameType(Context& context, SemIR::EntityNameId name_id,
+                            SemIR::ClangDeclId decl_id) -> SemIR::TypeId {
+  return GetCompleteTypeImpl<SemIR::CppTemplateNameType>(context, name_id,
+                                                         decl_id);
+}
+
 auto GetFunctionType(Context& context, SemIR::FunctionId fn_id,
                      SemIR::SpecificId specific_id) -> SemIR::TypeId {
   return GetCompleteTypeImpl<SemIR::FunctionType>(context, fn_id, specific_id);

+ 4 - 0
toolchain/check/type.h

@@ -52,6 +52,10 @@ auto GetCppOverloadSetType(Context& context,
                            SemIR::CppOverloadSetId overload_set_id,
                            SemIR::SpecificId specific_id) -> SemIR::TypeId;
 
+// Gets a C++ template name type. The returned type will be complete.
+auto GetCppTemplateNameType(Context& context, SemIR::EntityNameId name_id,
+                            SemIR::ClangDeclId decl_id) -> SemIR::TypeId;
+
 // Gets a function type. The returned type will be complete.
 auto GetFunctionType(Context& context, SemIR::FunctionId fn_id,
                      SemIR::SpecificId specific_id) -> SemIR::TypeId;

+ 4 - 4
toolchain/check/type_completion.cpp

@@ -223,10 +223,10 @@ class TypeCompleter {
   template <typename InstT>
     requires(InstT::Kind.template IsAnyOf<
              SemIR::AssociatedEntityType, SemIR::CppOverloadSetType,
-             SemIR::FunctionType, SemIR::FunctionTypeWithSelfType,
-             SemIR::GenericClassType, SemIR::GenericInterfaceType,
-             SemIR::GenericNamedConstraintType, SemIR::InstType,
-             SemIR::UnboundElementType, SemIR::WhereExpr>())
+             SemIR::CppTemplateNameType, SemIR::FunctionType,
+             SemIR::FunctionTypeWithSelfType, SemIR::GenericClassType,
+             SemIR::GenericInterfaceType, SemIR::GenericNamedConstraintType,
+             SemIR::InstType, SemIR::UnboundElementType, SemIR::WhereExpr>())
   auto BuildInfoForInst(SemIR::TypeId /*type_id*/, InstT /*inst*/) const
       -> SemIR::CompleteTypeInfo {
     // These types have no runtime operations, so we use an empty value

+ 2 - 1
toolchain/lower/file_context.cpp

@@ -1010,7 +1010,8 @@ static auto BuildTypeForInst(FileContext& context,
 template <typename InstT>
   requires(InstT::Kind.template IsAnyOf<
            SemIR::AssociatedEntityType, SemIR::AutoType, SemIR::BoundMethodType,
-           SemIR::CharLiteralType, SemIR::CppOverloadSetType, SemIR::FacetType,
+           SemIR::CharLiteralType, SemIR::CppOverloadSetType,
+           SemIR::CppTemplateNameType, SemIR::FacetType,
            SemIR::FloatLiteralType, SemIR::FunctionType,
            SemIR::FunctionTypeWithSelfType, SemIR::GenericClassType,
            SemIR::GenericInterfaceType, SemIR::GenericNamedConstraintType,

+ 0 - 14
toolchain/sem_ir/clang_decl.h

@@ -94,20 +94,6 @@ struct ClangDecl : public Printable<ClangDecl> {
   auto GetAsKey() const -> ClangDeclKey { return key; }
 };
 
-// The ID of a `ClangDecl`.
-//
-// These IDs are importantly distinct from the `inst_id` associated with each
-// declaration. These form a dense range of IDs that is used to reference the
-// AST node pointers without storing those pointers directly into SemIR and
-// needing space to hold a full pointer. We can't avoid having these IDs without
-// embedding pointers directly into the storage of SemIR as part of an
-// instruction.
-struct ClangDeclId : public IdBase<ClangDeclId> {
-  static constexpr llvm::StringLiteral Label = "clang_decl_id";
-
-  using IdBase::IdBase;
-};
-
 // Use the AST node pointer directly when doing `Lookup` to find an ID.
 using ClangDeclStore =
     CanonicalValueStore<ClangDeclId, ClangDeclKey, ClangDecl>;

+ 7 - 0
toolchain/sem_ir/formatter.cpp

@@ -1086,6 +1086,13 @@ auto Formatter::FormatInstRhs(Inst inst) -> void {
       return;
     }
 
+    case CARBON_KIND(CppTemplateNameType type): {
+      // Omit the Clang declaration. We don't have a good way to format it, and
+      // the entity name should suffice to identify the template.
+      FormatArgs(type.name_id);
+      return;
+    }
+
     case CARBON_KIND(CustomLayoutType type): {
       out_ << " {";
       auto layout = sem_ir_->custom_layouts().Get(type.layout_id);

+ 1 - 0
toolchain/sem_ir/id_kind.h

@@ -33,6 +33,7 @@ using IdKind = TypeEnum<
     BoolValue,
     CallParamIndex,
     CharId,
+    ClangDeclId,
     ClassId,
     CompileTimeBindIndex,
     ConstantId,

+ 14 - 0
toolchain/sem_ir/ids.h

@@ -472,6 +472,20 @@ struct ImportIRId : public IdBase<ImportIRId> {
 inline constexpr ImportIRId ImportIRId::ApiForImpl = ImportIRId(0);
 inline constexpr ImportIRId ImportIRId::Cpp = ImportIRId(ApiForImpl.index + 1);
 
+// The ID of a `ClangDecl`.
+//
+// These IDs are importantly distinct from the `inst_id` associated with each
+// declaration. These form a dense range of IDs that is used to reference the
+// AST node pointers without storing those pointers directly into SemIR and
+// needing space to hold a full pointer. We can't avoid having these IDs without
+// embedding pointers directly into the storage of SemIR as part of an
+// instruction.
+struct ClangDeclId : public IdBase<ClangDeclId> {
+  static constexpr llvm::StringLiteral Label = "clang_decl_id";
+
+  using IdBase::IdBase;
+};
+
 // A boolean value.
 struct BoolValue : public IdBase<BoolValue> {
   // Not used by `Print`, but for `IdKind`.

+ 7 - 0
toolchain/sem_ir/inst_fingerprinter.cpp

@@ -229,6 +229,13 @@ struct Worklist {
     }
   }
 
+  auto Add(ClangDeclId /*decl_id*/) -> void {
+    // TODO: For `CppTemplateNameType` we don't need to fingerprint the
+    // `decl_id`, because fingerprinting the `NameId` is sufficient to identify
+    // the template, but this won't necessarily be true for other
+    // `ClangDeclId`s.
+  }
+
   auto Add(ClassId class_id) -> void {
     AddEntity(sem_ir->classes().Get(class_id));
   }

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -55,6 +55,7 @@ CARBON_SEM_IR_INST_KIND(ConvertToValueAction)
 CARBON_SEM_IR_INST_KIND(Converted)
 CARBON_SEM_IR_INST_KIND(CppOverloadSetType)
 CARBON_SEM_IR_INST_KIND(CppOverloadSetValue)
+CARBON_SEM_IR_INST_KIND(CppTemplateNameType)
 CARBON_SEM_IR_INST_KIND(CppWitness)
 CARBON_SEM_IR_INST_KIND(CustomLayoutType)
 CARBON_SEM_IR_INST_KIND(Deref)

+ 10 - 0
toolchain/sem_ir/inst_namer.cpp

@@ -907,6 +907,10 @@ auto InstNamer::NamingContext::NameInst() -> void {
       AddInstName("const");
       return;
     }
+    case CARBON_KIND(CppTemplateNameType inst): {
+      AddInstNameId(sem_ir().entity_names().Get(inst.name_id).name_id, ".type");
+      return;
+    }
     case CppWitness::Kind: {
       AddInstName("cpp_witness");
       return;
@@ -1264,6 +1268,12 @@ auto InstNamer::NamingContext::NameInst() -> void {
                           .Get(generic_interface_ty->interface_id)
                           .name_id,
                       ".generic");
+      } else if (auto template_name_ty =
+                     sem_ir().types().TryGetAs<CppTemplateNameType>(
+                         inst.type_id)) {
+        AddInstNameId(
+            sem_ir().entity_names().Get(template_name_ty->name_id).name_id,
+            ".template");
       } else {
         if (sem_ir().inst_blocks().Get(inst.elements_id).empty()) {
           AddInstName("empty_struct");

+ 5 - 0
toolchain/sem_ir/stringify.cpp

@@ -327,6 +327,11 @@ class Stringifier {
     step_stack_->PushInstId(inst.inner_id);
   }
 
+  auto StringifyInst(InstId /*inst_id*/, CppTemplateNameType inst) -> void {
+    *out_ << "<type of ";
+    step_stack_->Push(inst.name_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 "

+ 1 - 0
toolchain/sem_ir/type_iterator.cpp

@@ -90,6 +90,7 @@ auto TypeIterator::ProcessTypeId(TypeId type_id) -> std::optional<Step> {
     case BoolType::Kind:
     case CharLiteralType::Kind:
     case CppOverloadSetType::Kind:
+    case CppTemplateNameType::Kind:
     case FacetType::Kind:
     case FloatLiteralType::Kind:
     case FloatType::Kind:

+ 16 - 0
toolchain/sem_ir/typed_insts.h

@@ -754,6 +754,22 @@ struct CppOverloadSetValue {
   CppOverloadSetId overload_set_id;
 };
 
+// The type of the name of a C++ template. The corresponding value is an empty
+// `StructValue`. This does not handle function templates, which are instead
+// represented as a `CppOverloadSetValue` of type `CppOverloadSetType`.
+struct CppTemplateNameType {
+  // This is only ever created as a constant, so doesn't have a location.
+  static constexpr auto Kind =
+      InstKind::CppTemplateNameType.Define<Parse::NoneNodeId>(
+          {.ir_name = "cpp_type_template_type",
+           .is_type = InstIsType::Always,
+           .constant_kind = InstConstantKind::Always});
+
+  TypeId type_id;
+  EntityNameId name_id;
+  ClangDeclId decl_id;
+};
+
 // A witness synthesized for a C++ construct such as a constructor, conversion
 // function, or overloaded operator.
 struct CppWitness {