Pārlūkot izejas kodu

Interop: map C++ `std::string_view` into Carbon `str` when importing. (#5985)

We assume these types have the same representation. For now, that will
only be the case for libc++ on 64-bit targets, because libc++ puts the
size field first, and `Core.String` always uses a 64-bit size field even
on 32-bit targets.

---------

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
Richard Smith 8 mēneši atpakaļ
vecāks
revīzija
bd90fe1d9b

+ 2 - 0
toolchain/check/BUILD

@@ -21,6 +21,7 @@ cc_library(
         "context.cpp",
         "control_flow.cpp",
         "convert.cpp",
+        "cpp_custom_type_mapping.cpp",
         "cpp_thunk.cpp",
         "decl_name_stack.cpp",
         "deduce.cpp",
@@ -66,6 +67,7 @@ cc_library(
         "context.h",
         "control_flow.h",
         "convert.h",
+        "cpp_custom_type_mapping.h",
         "cpp_thunk.h",
         "decl_introducer_state.h",
         "decl_name_stack.h",

+ 5 - 0
toolchain/check/convert.h

@@ -124,12 +124,17 @@ auto ConvertCallArgs(Context& context, SemIR::LocId call_loc_id,
 
 // A type that has been converted for use as a type expression.
 struct TypeExpr {
+  static const TypeExpr None;
+
   // The converted expression of type `type`, or `ErrorInst::InstId`.
   SemIR::TypeInstId inst_id;
   // The corresponding type, or `ErrorInst::TypeId`.
   SemIR::TypeId type_id;
 };
 
+constexpr inline TypeExpr TypeExpr::None = {.inst_id = SemIR::TypeInstId::None,
+                                            .type_id = SemIR::TypeId::None};
+
 // Converts an expression for use as a type.
 //
 // If `diagnose` is true, errors are diagnosed to the user. Set it to false when

+ 111 - 0
toolchain/check/cpp_custom_type_mapping.cpp

@@ -0,0 +1,111 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "toolchain/check/cpp_custom_type_mapping.h"
+
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclTemplate.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringRef.h"
+
+namespace Carbon::Check {
+
+// A small, lightweight library of AST matchers. Unlike clang's ASTMatchers,
+// this avoids heap allocations and is suitable for one-off matching rather than
+// matching against a whole AST.
+namespace Matchers {
+// A matcher for a type T is just a function that takes a T and returns whether
+// it matched. Matchers should be invoked immediately, and are not expected to
+// outlive the arguments of the call that created them.
+// TODO: We could avoid the indirect calls by making the below functions be
+// templated on the inner matcher.
+template <typename T>
+using Matcher = llvm::function_ref<auto(T)->bool>;
+
+// Returns a matcher for class declarations that determines whether the given
+// class is a class template specialization in namespace std with the specified
+// name and template arguments matching the given predicate.
+static auto StdClassTemplate(
+    llvm::StringLiteral name,
+    Matcher<const clang::TemplateArgumentList&> args_matcher
+    [[clang::lifetimebound]]) -> auto {
+  return [=](const clang::CXXRecordDecl* class_decl) -> bool {
+    const auto* specialization =
+        dyn_cast<clang::ClassTemplateSpecializationDecl>(class_decl);
+    const auto* identifier = class_decl->getIdentifier();
+    return specialization && identifier && identifier->isStr(name) &&
+           specialization->isInStdNamespace() &&
+           args_matcher(specialization->getTemplateArgs());
+  };
+}
+
+// Returns a matcher that matches types if they are class types whose class
+// matches the given matcher.
+static auto Class(Matcher<const clang::CXXRecordDecl*> class_matcher
+                  [[clang::lifetimebound]]) -> auto {
+  return [=](clang::QualType type) -> bool {
+    const auto* class_decl = type->getAsCXXRecordDecl();
+    return !type.hasQualifiers() && class_decl && class_matcher(class_decl);
+  };
+}
+
+// Returns a matcher that determines whether the given template argument is a
+// type matching the given predicate.
+static auto TypeTemplateArgument(Matcher<clang::QualType> type_matcher
+                                 [[clang::lifetimebound]]) -> auto {
+  return [=](clang::TemplateArgument arg) -> bool {
+    return arg.getKind() == clang::TemplateArgument::Type &&
+           type_matcher(arg.getAsType());
+  };
+}
+
+// A matcher that determines whether the given type is `char`.
+static auto Char(clang::QualType type) -> bool {
+  return !type.hasQualifiers() && type->isCharType();
+}
+
+// Returns a matcher that determines whether the given template argument list
+// matches the given sequence of template argument matchers.
+static auto TemplateArgumentsAre(
+    std::initializer_list<Matcher<clang::TemplateArgument>> arg_matchers
+    [[clang::lifetimebound]]) -> auto {
+  return [=](const clang::TemplateArgumentList& args) -> bool {
+    if (args.size() != arg_matchers.size()) {
+      return false;
+    }
+    for (auto [arg, matcher] : llvm::zip_equal(args.asArray(), arg_matchers)) {
+      if (!matcher(arg)) {
+        return false;
+      }
+    }
+    return true;
+  };
+}
+
+// A matcher for `std::char_traits<char>`.
+static auto StdCharTraitsChar(clang::QualType type) -> bool {
+  return Class(StdClassTemplate(
+      "char_traits", TemplateArgumentsAre({TypeTemplateArgument(Char)})))(type);
+}
+
+// A matcher for `std::string_view`.
+static auto StdStringView(const clang::CXXRecordDecl* record_decl) -> bool {
+  return StdClassTemplate(
+      "basic_string_view",
+      TemplateArgumentsAre({TypeTemplateArgument(Char),
+                            TypeTemplateArgument(StdCharTraitsChar)}))(
+      record_decl);
+}
+}  // end namespace Matchers
+
+auto GetCustomCppTypeMapping(const clang::CXXRecordDecl* record_decl)
+    -> CustomCppTypeMapping {
+  if (Matchers::StdStringView(record_decl)) {
+    return CustomCppTypeMapping::Str;
+  }
+
+  return CustomCppTypeMapping::None;
+}
+
+}  // namespace Carbon::Check

+ 29 - 0
toolchain/check/cpp_custom_type_mapping.h

@@ -0,0 +1,29 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_TOOLCHAIN_CHECK_CPP_CUSTOM_TYPE_MAPPING_H_
+#define CARBON_TOOLCHAIN_CHECK_CPP_CUSTOM_TYPE_MAPPING_H_
+
+#include "clang/AST/DeclCXX.h"
+
+namespace Carbon::Check {
+
+// Carbon types that have a custom mapping from C++.
+enum class CustomCppTypeMapping : uint8_t {
+  // None.
+  None,
+
+  // The Carbon `Str` type, which maps to `std::string_view`.
+  Str,
+};
+
+// Determines whether record_decl is a C++ class that has a custom mapping into
+// Carbon, and if so, returns the corresponding Carbon type. Otherwise returns
+// None.
+auto GetCustomCppTypeMapping(const clang::CXXRecordDecl* record_decl)
+    -> CustomCppTypeMapping;
+
+}  // namespace Carbon::Check
+
+#endif  // CARBON_TOOLCHAIN_CHECK_CPP_CUSTOM_TYPE_MAPPING_H_

+ 36 - 9
toolchain/check/import_cpp.cpp

@@ -29,6 +29,7 @@
 #include "toolchain/check/class.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/convert.h"
+#include "toolchain/check/cpp_custom_type_mapping.h"
 #include "toolchain/check/cpp_thunk.h"
 #include "toolchain/check/diagnostic_helpers.h"
 #include "toolchain/check/eval.h"
@@ -1200,7 +1201,24 @@ static auto MapBuiltinType(Context& context, SemIR::LocId loc_id,
     // TODO: Handle floating-point types that map to named aliases.
   }
 
-  return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
+  return TypeExpr::None;
+}
+
+// Determines whether record_decl is a C++ class that has a custom mapping into
+// Carbon, and if so, returns the corresponding Carbon type. Otherwise returns
+// None.
+static auto LookupCustomRecordType(Context& context,
+                                   const clang::CXXRecordDecl* record_decl)
+    -> TypeExpr {
+  switch (GetCustomCppTypeMapping(record_decl)) {
+    case CustomCppTypeMapping::None:
+      return TypeExpr::None;
+
+    case CustomCppTypeMapping::Str:
+      return MakeStringType(
+          context,
+          AddImportIRInst(context.sem_ir(), record_decl->getLocation()));
+  }
 }
 
 // Maps a C++ tag type (class, struct, union, enum) to a Carbon type.
@@ -1210,12 +1228,21 @@ static auto MapTagType(Context& context, const clang::TagType& type)
   CARBON_CHECK(tag_decl);
 
   // Check if the declaration is already mapped.
-  SemIR::InstId record_inst_id = LookupClangDeclInstId(context, tag_decl);
-  if (!record_inst_id.has_value()) {
-    record_inst_id = ImportTagDecl(context, tag_decl);
+  SemIR::InstId tag_inst_id = LookupClangDeclInstId(context, tag_decl);
+  if (!tag_inst_id.has_value()) {
+    if (auto* record_decl = dyn_cast<clang::CXXRecordDecl>(tag_decl)) {
+      auto custom_type = LookupCustomRecordType(context, record_decl);
+      if (custom_type.inst_id.has_value()) {
+        context.sem_ir().clang_decls().Add(
+            {.decl = record_decl, .inst_id = custom_type.inst_id});
+        return custom_type;
+      }
+    }
+
+    tag_inst_id = ImportTagDecl(context, tag_decl);
   }
   SemIR::TypeInstId record_type_inst_id =
-      context.types().GetAsTypeInstId(record_inst_id);
+      context.types().GetAsTypeInstId(tag_inst_id);
   return {
       .inst_id = record_type_inst_id,
       .type_id = context.types().GetTypeIdForTypeInstId(record_type_inst_id)};
@@ -1237,7 +1264,7 @@ static auto MapNonWrapperType(Context& context, SemIR::LocId loc_id,
   CARBON_CHECK(!type.hasQualifiers() && !type->isPointerType(),
                "Should not see wrapper types here");
 
-  return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
+  return TypeExpr::None;
 }
 
 // Maps a qualified C++ type to a Carbon type.
@@ -1254,7 +1281,7 @@ static auto MapQualifiedType(Context& context, clang::QualType type,
 
   // TODO: Support other qualifiers.
   if (!quals.empty()) {
-    return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
+    return TypeExpr::None;
   }
 
   return type_expr;
@@ -1269,7 +1296,7 @@ static auto MapPointerType(Context& context, clang::QualType type,
       !nullability.has_value() ||
       *nullability != clang::NullabilityKind::NonNull) {
     // TODO: Support nullable pointers.
-    return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
+    return TypeExpr::None;
   }
 
   SemIR::TypeId pointer_type_id =
@@ -1467,7 +1494,7 @@ static auto GetReturnTypeExpr(Context& context, SemIR::LocId loc_id,
 
   if (!isa<clang::CXXConstructorDecl>(clang_decl)) {
     // void.
-    return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None};
+    return TypeExpr::None;
   }
 
   // TODO: Make this a `PartialType`.

+ 17 - 15
toolchain/check/literal.cpp

@@ -7,6 +7,7 @@
 #include "toolchain/check/call.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/convert.h"
+#include "toolchain/check/diagnostic_helpers.h"
 #include "toolchain/check/name_lookup.h"
 #include "toolchain/check/type.h"
 #include "toolchain/check/type_completion.h"
@@ -106,23 +107,23 @@ static auto GetStringLiteralRepr(Context& context, SemIR::LocId loc_id,
 
 auto MakeStringLiteral(Context& context, Parse::StringLiteralId node_id,
                        StringLiteralValueId value_id) -> SemIR::InstId {
-  auto str_type_id = MakeStringType(context, node_id);
-  if (!RequireCompleteType(context, str_type_id, node_id, [&] {
+  auto str_type = MakeStringType(context, node_id);
+  if (!RequireCompleteType(context, str_type.type_id, node_id, [&] {
         CARBON_DIAGNOSTIC(StringLiteralTypeIncomplete, Error,
-                          "type {0} is incomplete", SemIR::TypeId);
+                          "type {0} is incomplete", InstIdAsType);
         return context.emitter().Build(node_id, StringLiteralTypeIncomplete,
-                                       str_type_id);
+                                       str_type.inst_id);
       })) {
     return SemIR::ErrorInst::InstId;
   }
 
-  auto repr = GetStringLiteralRepr(context, node_id, str_type_id);
+  auto repr = GetStringLiteralRepr(context, node_id, str_type.type_id);
   if (!repr) {
-    if (str_type_id != SemIR::ErrorInst::TypeId) {
+    if (str_type.type_id != SemIR::ErrorInst::TypeId) {
       CARBON_DIAGNOSTIC(StringLiteralTypeUnexpected, Error,
-                        "unexpected representation for type {0}",
-                        SemIR::TypeId);
-      context.emitter().Emit(node_id, StringLiteralTypeUnexpected, str_type_id);
+                        "unexpected representation for type {0}", InstIdAsType);
+      context.emitter().Emit(node_id, StringLiteralTypeUnexpected,
+                             str_type.inst_id);
     }
     return SemIR::ErrorInst::InstId;
   }
@@ -157,17 +158,18 @@ auto MakeStringLiteral(Context& context, Parse::StringLiteralId node_id,
   // Build the representation struct.
   auto elements_id = context.inst_blocks().Add({ptr_value_id, size_value_id});
   return AddInst<SemIR::StructValue>(
-      context, node_id, {.type_id = str_type_id, .elements_id = elements_id});
+      context, node_id,
+      {.type_id = str_type.type_id, .elements_id = elements_id});
 }
 
-auto MakeStringTypeLiteral(Context& context, Parse::NodeId node_id)
+auto MakeStringTypeLiteral(Context& context, SemIR::LocId loc_id)
     -> SemIR::InstId {
-  return LookupNameInCore(context, node_id, "String");
+  return LookupNameInCore(context, loc_id, "String");
 }
 
-auto MakeStringType(Context& context, Parse::NodeId node_id) -> SemIR::TypeId {
-  auto type_inst_id = MakeStringTypeLiteral(context, node_id);
-  return ExprAsType(context, node_id, type_inst_id).type_id;
+auto MakeStringType(Context& context, SemIR::LocId loc_id) -> TypeExpr {
+  auto type_inst_id = MakeStringTypeLiteral(context, loc_id);
+  return ExprAsType(context, loc_id, type_inst_id);
 }
 
 }  // namespace Carbon::Check

+ 3 - 2
toolchain/check/literal.h

@@ -7,6 +7,7 @@
 
 #include "toolchain/base/value_ids.h"
 #include "toolchain/check/context.h"
+#include "toolchain/check/convert.h"
 #include "toolchain/lex/token_info.h"
 #include "toolchain/sem_ir/ids.h"
 
@@ -35,11 +36,11 @@ auto MakeStringLiteral(Context& context, Parse::StringLiteralId node_id,
                        StringLiteralValueId value_id) -> SemIR::InstId;
 
 // Forms a string literal type expression for a `str` literal.
-auto MakeStringTypeLiteral(Context& context, Parse::NodeId node_id)
+auto MakeStringTypeLiteral(Context& context, SemIR::LocId loc_id)
     -> SemIR::InstId;
 
 // Forms a string type.
-auto MakeStringType(Context& context, Parse::NodeId node_id) -> SemIR::TypeId;
+auto MakeStringType(Context& context, SemIR::LocId) -> TypeExpr;
 
 }  // namespace Carbon::Check
 

+ 153 - 0
toolchain/check/testdata/interop/cpp/stdlib/string_view.carbon

@@ -0,0 +1,153 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/primitives.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/stdlib/string_view.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/stdlib/string_view.carbon
+
+// --- string_view.h
+
+namespace std {
+  using size_t = __SIZE_TYPE__;
+
+  inline namespace __1 {
+    template<typename T> struct char_traits {};
+
+    template<typename CharT, typename Traits = char_traits<CharT>>
+    class basic_string_view {
+     public:
+      basic_string_view() = default;
+      size_t size() const { return size_; }
+
+     private:
+      const CharT* data_;
+      size_t size_;
+    };
+
+    using string_view = basic_string_view<char>;
+  }
+}
+
+auto Consume(std::string_view sv) -> void;
+auto Produce() -> std::string_view;
+
+// --- import_multiple.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "string_view.h";
+
+//@dump-sem-ir-begin
+fn F() {
+  Cpp.Consume("hello");
+}
+//@dump-sem-ir-end
+
+//@dump-sem-ir-begin
+fn G() -> str {
+  return Cpp.Produce();
+}
+//@dump-sem-ir-end
+
+// CHECK:STDOUT: --- import_multiple.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %String: type = class_type @String [concrete]
+// CHECK:STDOUT:   %int_64: Core.IntLiteral = int_value 64 [concrete]
+// CHECK:STDOUT:   %u64: type = class_type @UInt, @UInt(%int_64) [concrete]
+// CHECK:STDOUT:   %int_8: Core.IntLiteral = int_value 8 [concrete]
+// CHECK:STDOUT:   %u8: type = class_type @UInt, @UInt(%int_8) [concrete]
+// CHECK:STDOUT:   %ptr.3e8: type = ptr_type %u8 [concrete]
+// CHECK:STDOUT:   %pattern_type.461: type = pattern_type %String [concrete]
+// CHECK:STDOUT:   %Consume.type: type = fn_type @Consume [concrete]
+// CHECK:STDOUT:   %Consume: %Consume.type = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.85f: type = ptr_type %String [concrete]
+// CHECK:STDOUT:   %Consume__carbon_thunk.type: type = fn_type @Consume__carbon_thunk [concrete]
+// CHECK:STDOUT:   %Consume__carbon_thunk: %Consume__carbon_thunk.type = struct_value () [concrete]
+// CHECK:STDOUT:   %str: %ptr.3e8 = string_literal "hello" [concrete]
+// CHECK:STDOUT:   %int_5: %u64 = int_value 5 [concrete]
+// CHECK:STDOUT:   %String.val: %String = struct_value (%str, %int_5) [concrete]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Produce.type: type = fn_type @Produce [concrete]
+// CHECK:STDOUT:   %Produce: %Produce.type = struct_value () [concrete]
+// CHECK:STDOUT:   %Produce__carbon_thunk.type: type = fn_type @Produce__carbon_thunk [concrete]
+// CHECK:STDOUT:   %Produce__carbon_thunk: %Produce__carbon_thunk.type = struct_value () [concrete]
+// CHECK:STDOUT:   %String.as.Destroy.impl.Op.type: type = fn_type @String.as.Destroy.impl.Op [concrete]
+// CHECK:STDOUT:   %String.as.Destroy.impl.Op: %String.as.Destroy.impl.Op.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .Consume = %Consume.decl
+// CHECK:STDOUT:     .Produce = %Produce.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Consume.decl: %Consume.type = fn_decl @Consume [concrete = constants.%Consume] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Consume__carbon_thunk.decl: %Consume__carbon_thunk.type = fn_decl @Consume__carbon_thunk [concrete = constants.%Consume__carbon_thunk] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Produce.decl: %Produce.type = fn_decl @Produce [concrete = constants.%Produce] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Produce__carbon_thunk.decl: %Produce__carbon_thunk.type = fn_decl @Produce__carbon_thunk [concrete = constants.%Produce__carbon_thunk] {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     <elided>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {
+// CHECK:STDOUT:     %return.patt: %pattern_type.461 = return_slot_pattern [concrete]
+// CHECK:STDOUT:     %return.param_patt: %pattern_type.461 = out_param_pattern %return.patt, call_param0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %return.param: ref %String = out_param call_param0
+// CHECK:STDOUT:     %return: ref %String = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Consume.ref: %Consume.type = name_ref Consume, imports.%Consume.decl [concrete = constants.%Consume]
+// CHECK:STDOUT:   %str: %ptr.3e8 = string_literal "hello" [concrete = constants.%str]
+// CHECK:STDOUT:   %int_5: %u64 = int_value 5 [concrete = constants.%int_5]
+// CHECK:STDOUT:   %String.val: %String = struct_value (%str, %int_5) [concrete = constants.%String.val]
+// CHECK:STDOUT:   %.loc8: ref %String = value_as_ref %String.val
+// CHECK:STDOUT:   %addr: %ptr.85f = addr_of %.loc8
+// CHECK:STDOUT:   %Consume__carbon_thunk.call: init %empty_tuple.type = call imports.%Consume__carbon_thunk.decl(%addr)
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G() -> %return.param: %String {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %Produce.ref: %Produce.type = name_ref Produce, imports.%Produce.decl [concrete = constants.%Produce]
+// CHECK:STDOUT:   %.loc13: ref %String = splice_block %return {}
+// CHECK:STDOUT:   %addr.loc14: %ptr.85f = addr_of %.loc13
+// CHECK:STDOUT:   %Produce__carbon_thunk.call: init %empty_tuple.type = call imports.%Produce__carbon_thunk.decl(%addr.loc14)
+// CHECK:STDOUT:   %.loc14: init %String = in_place_init %Produce__carbon_thunk.call, %.loc13
+// CHECK:STDOUT:   %String.as.Destroy.impl.Op.bound: <bound method> = bound_method %.loc13, constants.%String.as.Destroy.impl.Op
+// CHECK:STDOUT:   %addr.loc13: %ptr.85f = addr_of %.loc13
+// CHECK:STDOUT:   %String.as.Destroy.impl.Op.call: init %empty_tuple.type = call %String.as.Destroy.impl.Op.bound(%addr.loc13)
+// CHECK:STDOUT:   return %.loc14 to %return
+// CHECK:STDOUT: }
+// CHECK:STDOUT: