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>
This commit is contained in:
Richard Smith
2025-08-27 01:02:39 +00:00
committed by GitHub
co-authored by Chandler Carruth
parent 82ba1a43a1
commit bd90fe1d9b
8 changed files with 356 additions and 26 deletions
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
@@ -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: