From bd90fe1d9bb9e4da9f5d5dcabbc1559467156900 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 26 Aug 2025 18:02:39 -0700 Subject: [PATCH] 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 --- toolchain/check/BUILD | 2 + toolchain/check/convert.h | 5 + toolchain/check/cpp_custom_type_mapping.cpp | 111 +++++++++++++ toolchain/check/cpp_custom_type_mapping.h | 29 ++++ toolchain/check/import_cpp.cpp | 45 ++++-- toolchain/check/literal.cpp | 32 ++-- toolchain/check/literal.h | 5 +- .../interop/cpp/stdlib/string_view.carbon | 153 ++++++++++++++++++ 8 files changed, 356 insertions(+), 26 deletions(-) create mode 100644 toolchain/check/cpp_custom_type_mapping.cpp create mode 100644 toolchain/check/cpp_custom_type_mapping.h create mode 100644 toolchain/check/testdata/interop/cpp/stdlib/string_view.carbon diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index 2f548bec0b15..4b036566a070 100644 --- a/toolchain/check/BUILD +++ b/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", diff --git a/toolchain/check/convert.h b/toolchain/check/convert.h index 5486ab197377..33e807bf15d4 100644 --- a/toolchain/check/convert.h +++ b/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 diff --git a/toolchain/check/cpp_custom_type_mapping.cpp b/toolchain/check/cpp_custom_type_mapping.cpp new file mode 100644 index 000000000000..832b7ad4710d --- /dev/null +++ b/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 +using Matcher = llvm::function_refbool>; + +// 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 args_matcher + [[clang::lifetimebound]]) -> auto { + return [=](const clang::CXXRecordDecl* class_decl) -> bool { + const auto* specialization = + dyn_cast(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 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 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> 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`. +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 diff --git a/toolchain/check/cpp_custom_type_mapping.h b/toolchain/check/cpp_custom_type_mapping.h new file mode 100644 index 000000000000..f5cc9e76659a --- /dev/null +++ b/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_ diff --git a/toolchain/check/import_cpp.cpp b/toolchain/check/import_cpp.cpp index 49c5f45f231c..f5d43f09123b 100644 --- a/toolchain/check/import_cpp.cpp +++ b/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(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_decl)) { // void. - return {.inst_id = SemIR::TypeInstId::None, .type_id = SemIR::TypeId::None}; + return TypeExpr::None; } // TODO: Make this a `PartialType`. diff --git a/toolchain/check/literal.cpp b/toolchain/check/literal.cpp index 625706520ae8..dfe923d8f4fc 100644 --- a/toolchain/check/literal.cpp +++ b/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( - 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 diff --git a/toolchain/check/literal.h b/toolchain/check/literal.h index 1fd550c1eaf0..be09e074595b 100644 --- a/toolchain/check/literal.h +++ b/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 diff --git a/toolchain/check/testdata/interop/cpp/stdlib/string_view.carbon b/toolchain/check/testdata/interop/cpp/stdlib/string_view.carbon new file mode 100644 index 000000000000..8b2cfe8fd71f --- /dev/null +++ b/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 struct char_traits {}; + + template> + 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; + } +} + +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 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: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: %Consume__carbon_thunk.decl: %Consume__carbon_thunk.type = fn_decl @Consume__carbon_thunk [concrete = constants.%Consume__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: %Produce.decl: %Produce.type = fn_decl @Produce [concrete = constants.%Produce] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: %Produce__carbon_thunk.decl: %Produce__carbon_thunk.type = fn_decl @Produce__carbon_thunk [concrete = constants.%Produce__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// 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: = 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: = 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 %.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: