From 730935691ae63d9619bf2f16cfe906346663ef92 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 16 Sep 2025 16:40:33 -0700 Subject: [PATCH] Support for mapping str to std::string_view in interop. (#6079) We already did the opposite direction; this enables use of `str` in overload resolution. Fixes #6062 --------- Co-authored-by: Dana Jansens Co-authored-by: Carbon Infra Bot --- toolchain/check/cpp_import.cpp | 4 +- toolchain/check/cpp_type_mapping.cpp | 163 +++++++++++------- .../interop/cpp/stdlib/string_view.carbon | 41 ++--- toolchain/sem_ir/name_scope.h | 2 +- toolchain/sem_ir/type_info.cpp | 5 +- 5 files changed, 127 insertions(+), 88 deletions(-) diff --git a/toolchain/check/cpp_import.cpp b/toolchain/check/cpp_import.cpp index e939ea3b5da1..ed025f14cdf5 100644 --- a/toolchain/check/cpp_import.cpp +++ b/toolchain/check/cpp_import.cpp @@ -1983,7 +1983,7 @@ static auto IsTopCppScope(Context& context, SemIR::NameScopeId scope_id) } // For builtin names like `Cpp.long`, return the associated types. -static auto LookupBuiltInTypes(Context& context, SemIR::LocId loc_id, +static auto LookupBuiltinTypes(Context& context, SemIR::LocId loc_id, SemIR::NameScopeId scope_id, SemIR::NameId name_id) -> SemIR::InstId { if (!IsTopCppScope(context, scope_id)) { @@ -2118,7 +2118,7 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id, auto lookup = ClangLookupName(context, scope_id, name_id); if (!lookup) { SemIR::InstId builtin_inst_id = - LookupBuiltInTypes(context, loc_id, scope_id, name_id); + LookupBuiltinTypes(context, loc_id, scope_id, name_id); if (builtin_inst_id.has_value()) { AddNameToScope(context, scope_id, name_id, SemIR::AccessKind::Public, builtin_inst_id); diff --git a/toolchain/check/cpp_type_mapping.cpp b/toolchain/check/cpp_type_mapping.cpp index 407b26bf517d..7c5669003dd0 100644 --- a/toolchain/check/cpp_type_mapping.cpp +++ b/toolchain/check/cpp_type_mapping.cpp @@ -10,6 +10,7 @@ #include "clang/AST/Type.h" #include "clang/Basic/TargetInfo.h" +#include "clang/Sema/Lookup.h" #include "toolchain/base/int.h" #include "toolchain/base/kind_switch.h" #include "toolchain/base/value_ids.h" @@ -20,6 +21,7 @@ #include "toolchain/sem_ir/ids.h" #include "toolchain/sem_ir/inst.h" #include "toolchain/sem_ir/type.h" +#include "toolchain/sem_ir/type_info.h" #include "toolchain/sem_ir/typed_insts.h" namespace Carbon::Check { @@ -47,20 +49,106 @@ static auto FindIntLiteralBitWidth(Context& context, SemIR::InstId arg_id) return (arg_non_sign_bits <= 32) ? IntId::MakeRaw(32) : IntId::MakeRaw(64); } -// Maps a Carbon builtin type to a Cpp type. Returns an empty QualType if the +// Attempts to look up a type by name, and returns the corresponding `QualType`, +// or a null type if lookup fails. `name_components` is the full path of the +// type, including any namespaces or nested types, separated into separate +// strings. +static auto LookupCppType( + Context& context, std::initializer_list name_components) + -> clang::QualType { + clang::ASTUnit* ast = context.sem_ir().clang_ast_unit(); + CARBON_CHECK(ast); + clang::Sema& sema = ast->getSema(); + + clang::Decl* decl = sema.getASTContext().getTranslationUnitDecl(); + for (auto name_component : name_components) { + auto* scope = dyn_cast(decl); + if (!scope) { + return clang::QualType(); + } + + // TODO: Map the LocId of the lookup to a clang SourceLocation and provide + // it here so that clang's diagnostics can point into the carbon code that + // uses the name. + auto* identifier = sema.getPreprocessor().getIdentifierInfo(name_component); + clang::LookupResult lookup( + sema, clang::DeclarationNameInfo(identifier, clang::SourceLocation()), + clang::Sema::LookupNameKind::LookupOrdinaryName); + if (!sema.LookupQualifiedName(lookup, scope) || !lookup.isSingleResult()) { + return clang::QualType(); + } + decl = lookup.getFoundDecl(); + } + + auto* type_decl = dyn_cast(decl); + return type_decl ? sema.getASTContext().getTypeDeclType(type_decl) + : clang::QualType(); +} + +// Maps a Carbon class type to a C++ type. Returns a null `QualType` if the // type is not supported. +static auto TryMapClassType(Context& context, SemIR::ClassType class_type) + -> clang::QualType { + // If the class was imported from C++, return the original C++ type. + auto clang_decl_id = + context.name_scopes() + .Get(context.sem_ir().classes().Get(class_type.class_id).scope_id) + .clang_decl_context_id(); + if (clang_decl_id.has_value()) { + clang::Decl* clang_decl = + context.sem_ir().clang_decls().Get(clang_decl_id).decl; + auto* tag_type_decl = clang::cast(clang_decl); + return context.ast_context().getCanonicalTagType(tag_type_decl); + } + + // If the class represents a Carbon type literal, map it to the corresponding + // C++ builtin type. + auto literal = SemIR::TypeLiteralInfo::ForType(context.sem_ir(), class_type); + switch (literal.kind) { + case SemIR::TypeLiteralInfo::None: { + break; + } + case SemIR::TypeLiteralInfo::Numeric: { + switch (literal.numeric.kind) { + case SemIR::NumericTypeLiteralInfo::None: { + CARBON_FATAL("Unexpected invalid numeric type literal"); + } + case SemIR::NumericTypeLiteralInfo::Float: { + return context.ast_context().getRealTypeForBitwidth( + literal.numeric.bit_width_id.AsValue(), + clang::FloatModeKind::NoFloat); + } + case SemIR::NumericTypeLiteralInfo::Int: { + return context.ast_context().getIntTypeForBitwidth( + literal.numeric.bit_width_id.AsValue(), true); + } + case SemIR::NumericTypeLiteralInfo::UInt: { + return context.ast_context().getIntTypeForBitwidth( + literal.numeric.bit_width_id.AsValue(), false); + } + } + } + case SemIR::TypeLiteralInfo::Char: { + return context.ast_context().CharTy; + } + case SemIR::TypeLiteralInfo::Str: { + return LookupCppType(context, {"std", "string_view"}); + } + } + + // Otherwise we don't have a mapping for this Carbon class type. + // TODO: If the class type wasn't imported from C++, create a corresponding + // C++ class type. + return clang::QualType(); +} + +// Maps a non-wrapper (no const or pointer) Carbon type to a C++ type. Returns a +// null QualType if the type is not supported. // TODO: Have both Carbon -> C++ and C++ -> Carbon mappings in a single place // to keep them in sync. -static auto TryMapBuiltinType(Context& context, SemIR::InstId inst_id, +static auto MapNonWrapperType(Context& context, SemIR::InstId inst_id, SemIR::TypeId type_id) -> clang::QualType { - // TODO: Object representation should not be used here. Instead, use - // NumericTypeLiteralInfo to decompose a ClassType into a uN / iN / fN type. - auto object_repr_id = context.sem_ir().types().GetObjectRepr(type_id); - if (!object_repr_id.has_value()) { - return clang::QualType(); - } - auto type_inst = - context.insts().Get(context.sem_ir().types().GetInstId(object_repr_id)); + auto type_inst = context.sem_ir().types().GetAsInst(type_id); CARBON_KIND_SWITCH(type_inst) { case SemIR::BoolType::Kind: { @@ -69,6 +157,9 @@ static auto TryMapBuiltinType(Context& context, SemIR::InstId inst_id, case Carbon::SemIR::CharLiteralType::Kind: { return context.ast_context().CharTy; } + case CARBON_KIND(SemIR::ClassType class_type): { + return TryMapClassType(context, class_type); + } case SemIR::IntLiteralType::Kind: { IntId bit_width_id = FindIntLiteralBitWidth(context, inst_id); if (bit_width_id == IntId::None) { @@ -77,68 +168,16 @@ static auto TryMapBuiltinType(Context& context, SemIR::InstId inst_id, return context.ast_context().getIntTypeForBitwidth(bit_width_id.AsValue(), true); } - case CARBON_KIND(SemIR::IntType int_type): { - auto bit_width_inst = context.sem_ir().insts().TryGetAs( - int_type.bit_width_id); - return context.ast_context().getIntTypeForBitwidth( - bit_width_inst->int_id.AsValue(), int_type.int_kind.is_signed()); - } // TODO: What if the value doesn't fit to f64? case SemIR::FloatLiteralType::Kind: { return context.ast_context().DoubleTy; } - case CARBON_KIND(SemIR::FloatType float_type): { - auto bit_width_inst = context.sem_ir().insts().TryGetAs( - float_type.bit_width_id); - return context.ast_context().getRealTypeForBitwidth( - bit_width_inst->int_id.AsValue(), clang::FloatModeKind::NoFloat); - } default: { return clang::QualType(); } } } -// Maps a Carbon class type to a C++ type. Returns a null `QualType` if the -// Carbon type is not a `ClassType` or was not imported from C++. -// TODO: If the class type wasn't imported from C++, create a corresponding C++ -// class type. -static auto TryMapClassType(Context& context, SemIR::TypeId type_id) - -> clang::QualType { - auto class_type = - context.sem_ir().types().TryGetAs(type_id); - if (!class_type) { - return clang::QualType(); - } - - auto clang_decl_id = - context.name_scopes() - .Get(context.sem_ir().classes().Get(class_type->class_id).scope_id) - .clang_decl_context_id(); - if (!clang_decl_id.has_value()) { - return clang::QualType(); - } - clang::Decl* clang_decl = - context.sem_ir().clang_decls().Get(clang_decl_id).decl; - auto* tag_type_decl = clang::cast(clang_decl); - return context.ast_context().getCanonicalTagType(tag_type_decl); -} - -// Maps a non-wrapper (no const or pointer) Carbon type to a C++ type. -static auto MapNonWrapperType(Context& context, SemIR::InstId inst_id, - SemIR::TypeId type_id) -> clang::QualType { - // It's important to check for a class type first, because an enum imported - // from C++ is both a Carbon class type and has an object representation that - // is a builtin type, and we want to map back to the enum. - // TODO: The order won't matter once TryMapBuiltinType stops looking through - // adapters. - clang::QualType mapped_type = TryMapClassType(context, type_id); - if (mapped_type.isNull()) { - mapped_type = TryMapBuiltinType(context, inst_id, type_id); - } - return mapped_type; -} - // TODO: unify this with the C++ to Carbon type mapping function. auto MapToCppType(Context& context, SemIR::InstId inst_id) -> clang::QualType { auto type_id = context.insts().Get(inst_id).type_id(); diff --git a/toolchain/check/testdata/interop/cpp/stdlib/string_view.carbon b/toolchain/check/testdata/interop/cpp/stdlib/string_view.carbon index 58e3a71f9278..27643c12dbfe 100644 --- a/toolchain/check/testdata/interop/cpp/stdlib/string_view.carbon +++ b/toolchain/check/testdata/interop/cpp/stdlib/string_view.carbon @@ -10,8 +10,6 @@ // 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 -// TODO: Tests marked as `fail_todo_5891_` to fixed as a follow-up of https://github.com/carbon-language/carbon-lang/pull/5891. - // --- string_view.h namespace std { @@ -38,7 +36,7 @@ namespace std { auto Consume(std::string_view sv) -> void; auto Produce() -> std::string_view; -// --- fail_todo_5891_import_multiple.carbon +// --- import_multiple.carbon library "[[@TEST_NAME]]"; @@ -46,13 +44,6 @@ import Cpp library "string_view.h"; //@dump-sem-ir-begin fn F() { - // CHECK:STDERR: fail_todo_5891_import_multiple.carbon:[[@LINE+7]]:15: error: call argument of type `str` is not supported [CppCallArgTypeNotSupported] - // CHECK:STDERR: Cpp.Consume("hello"); - // CHECK:STDERR: ^~~~~~~ - // CHECK:STDERR: fail_todo_5891_import_multiple.carbon:[[@LINE+4]]:3: note: in call to Cpp function here [InCallToCppFunction] - // CHECK:STDERR: Cpp.Consume("hello"); - // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~ - // CHECK:STDERR: Cpp.Consume("hello"); } //@dump-sem-ir-end @@ -63,7 +54,7 @@ fn G() -> str { } //@dump-sem-ir-end -// CHECK:STDOUT: --- fail_todo_5891_import_multiple.carbon +// CHECK:STDOUT: --- import_multiple.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { // CHECK:STDOUT: %F.type: type = fn_type @F [concrete] @@ -75,17 +66,19 @@ fn G() -> str { // 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: %.fd2: type = cpp_overload_set_type @Produce [concrete] +// CHECK:STDOUT: %.fd2: type = cpp_overload_set_type @Consume__carbon_thunk [concrete] // CHECK:STDOUT: %empty_struct.c28: %.fd2 = struct_value () [concrete] // CHECK:STDOUT: %str.3b1: %ptr.3e8 = string_literal "hello" [concrete] // CHECK:STDOUT: %int_5: %u64 = int_value 5 [concrete] // CHECK:STDOUT: %String.val: %str.ee0 = struct_value (%str.3b1, %int_5) [concrete] // CHECK:STDOUT: %pattern_type.461: type = pattern_type %str.ee0 [concrete] +// CHECK:STDOUT: %ptr.85f: type = ptr_type %str.ee0 [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: %G.type: type = fn_type @G [concrete] // CHECK:STDOUT: %G: %G.type = struct_value () [concrete] -// CHECK:STDOUT: %.a47: type = cpp_overload_set_type @Produce__carbon_thunk [concrete] +// CHECK:STDOUT: %.a47: type = cpp_overload_set_type @G [concrete] // CHECK:STDOUT: %empty_struct.ab9: %.a47 = struct_value () [concrete] -// CHECK:STDOUT: %ptr.85f: type = ptr_type %str.ee0 [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: } @@ -96,8 +89,13 @@ fn G() -> str { // CHECK:STDOUT: .Produce = %.5d1 // CHECK:STDOUT: import Cpp//... // CHECK:STDOUT: } -// CHECK:STDOUT: %.f17: %.fd2 = cpp_overload_set_value @Produce [concrete = constants.%empty_struct.c28] -// CHECK:STDOUT: %.5d1: %.a47 = cpp_overload_set_value @Produce__carbon_thunk [concrete = constants.%empty_struct.ab9] +// CHECK:STDOUT: %.f17: %.fd2 = cpp_overload_set_value @Consume__carbon_thunk [concrete = constants.%empty_struct.c28] +// 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: %.5d1: %.a47 = cpp_overload_set_value @G [concrete = constants.%empty_struct.ab9] // CHECK:STDOUT: %Produce__carbon_thunk.decl: %Produce__carbon_thunk.type = fn_decl @Produce__carbon_thunk [concrete = constants.%Produce__carbon_thunk] { // CHECK:STDOUT: // CHECK:STDOUT: } { @@ -123,6 +121,9 @@ fn G() -> str { // CHECK:STDOUT: %str: %ptr.3e8 = string_literal "hello" [concrete = constants.%str.3b1] // CHECK:STDOUT: %int_5: %u64 = int_value 5 [concrete = constants.%int_5] // CHECK:STDOUT: %String.val: %str.ee0 = struct_value (%str, %int_5) [concrete = constants.%String.val] +// CHECK:STDOUT: %.loc8: ref %str.ee0 = 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: @@ -130,10 +131,10 @@ fn G() -> str { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %Cpp.ref: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] // CHECK:STDOUT: %Produce.ref: %.a47 = name_ref Produce, imports.%.5d1 [concrete = constants.%empty_struct.ab9] -// CHECK:STDOUT: %.loc20: ref %str.ee0 = splice_block %return {} -// CHECK:STDOUT: %addr: %ptr.85f = addr_of %.loc20 +// CHECK:STDOUT: %.loc13: ref %str.ee0 = splice_block %return {} +// CHECK:STDOUT: %addr: %ptr.85f = addr_of %.loc13 // CHECK:STDOUT: %Produce__carbon_thunk.call: init %empty_tuple.type = call imports.%Produce__carbon_thunk.decl(%addr) -// CHECK:STDOUT: %.loc21: init %str.ee0 = in_place_init %Produce__carbon_thunk.call, %.loc20 -// CHECK:STDOUT: return %.loc21 to %return +// CHECK:STDOUT: %.loc14: init %str.ee0 = in_place_init %Produce__carbon_thunk.call, %.loc13 +// CHECK:STDOUT: return %.loc14 to %return // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/sem_ir/name_scope.h b/toolchain/sem_ir/name_scope.h index ad7507f4c534..3e464752b41d 100644 --- a/toolchain/sem_ir/name_scope.h +++ b/toolchain/sem_ir/name_scope.h @@ -357,7 +357,7 @@ class NameScopeStore { // Returns whether the provided scope ID is valid and is directly contained // within the Core package. - auto IsInCorePackage(NameScopeId scope_id) const -> bool { + auto IsInCorePackageRoot(NameScopeId scope_id) const -> bool { return scope_id.has_value() && IsCorePackage(Get(scope_id).parent_scope_id()); } diff --git a/toolchain/sem_ir/type_info.cpp b/toolchain/sem_ir/type_info.cpp index 6b2cb82f00a2..002c178764e8 100644 --- a/toolchain/sem_ir/type_info.cpp +++ b/toolchain/sem_ir/type_info.cpp @@ -85,7 +85,7 @@ auto NumericTypeLiteralInfo::ForType(const File& file, ClassType class_type) // The class must be declared in the `Core` package. const auto& class_info = file.classes().Get(class_type.class_id); - if (!file.name_scopes().IsInCorePackage(class_info.scope_id)) { + if (!file.name_scopes().IsInCorePackageRoot(class_info.scope_id)) { return NumericTypeLiteralInfo::Invalid; } @@ -145,7 +145,7 @@ auto TypeLiteralInfo::ForType(const File& file, ClassType class_type) // The class must be declared in the `Core` package. const auto& class_info = file.classes().Get(class_type.class_id); - if (!file.name_scopes().IsInCorePackage(class_info.scope_id)) { + if (!file.name_scopes().IsInCorePackageRoot(class_info.scope_id)) { return {.kind = None}; } @@ -164,7 +164,6 @@ auto TypeLiteralInfo::ForType(const File& file, ClassType class_type) auto TypeLiteralInfo::PrintLiteral(const File& file, llvm::raw_ostream& out) const -> void { - CARBON_CHECK(is_valid()); switch (kind) { case None: CARBON_FATAL("Printing invalid type literal");