diff --git a/toolchain/check/cpp/export.cpp b/toolchain/check/cpp/export.cpp index e92d221a1615..4b2be4615b0d 100644 --- a/toolchain/check/cpp/export.cpp +++ b/toolchain/check/cpp/export.cpp @@ -343,13 +343,13 @@ struct FunctionInfo { Param(Context& context, SemIR::InstId param_inst_id) : type_id(ExtractScrutineeType( context.sem_ir(), context.insts().Get(param_inst_id).type_id())), - is_ref(context.insts().Is(param_inst_id)) {} + kind(GetParamPatternKind(context, param_inst_id)) {} // Type of the parameter's scrutinee. SemIR::TypeId type_id; - // Whether this is a `ref` param. - bool is_ref; + // Kind of the parameter pattern. + ParamPatternKind kind; }; explicit FunctionInfo(Context& context, SemIR::FunctionId function_id, @@ -416,6 +416,22 @@ struct FunctionInfo { }; } // namespace +// Converts a Carbon parameter type to the parameter type that should be used +// for the C++ declaration of the Carbon -> Carbon thunk. This is always a +// reference type. +static auto MapToCppThunkParamType(Context& context, SemIR::TypeId type_id) + -> clang::QualType { + auto cpp_type = MapToCppType(context, type_id); + if (cpp_type.isNull()) { + return clang::QualType(); + } + // The function exposed to C++ may have a `const&` parameter type for a value + // parameter. Always use a const reference here so we accept the argument, + // even though we might not need the `const`. + return context.ast_context().getLValueReferenceType( + context.ast_context().getConstType(cpp_type)); +} + // Create a `clang::FunctionDecl` for the given Carbon function. This // can be used to call the Carbon function from C++. The Carbon // function's ABI must be compatible with C++. @@ -434,22 +450,20 @@ static auto BuildCppFunctionDeclForCarbonFn(Context& context, // Get parameters types. llvm::SmallVector cpp_param_types; if (callee.self_param) { - auto cpp_type = MapToCppType(context, callee.self_param->type_id); + auto cpp_type = MapToCppThunkParamType(context, callee.self_param->type_id); if (cpp_type.isNull()) { context.TODO(loc_id, "failed to map Carbon self type to C++"); return nullptr; } - cpp_type = context.ast_context().getLValueReferenceType(cpp_type); cpp_param_types.push_back(cpp_type); } for (auto param : callee.explicit_params) { - auto cpp_type = MapToCppType(context, param.type_id); + auto cpp_type = MapToCppThunkParamType(context, param.type_id); if (cpp_type.isNull()) { context.TODO(loc_id, "failed to map Carbon type to C++"); return nullptr; } - auto ref_type = context.ast_context().getLValueReferenceType(cpp_type); - cpp_param_types.push_back(ref_type); + cpp_param_types.push_back(cpp_type); } CARBON_CHECK(function.return_type_inst_id == SemIR::TypeInstId::None); @@ -508,12 +522,17 @@ static auto BuildCppToCarbonThunkDecl( context.TODO(loc_id, "failed to map Carbon return type to C++ type"); return nullptr; } + if (cpp_return_type->isArrayType()) { + // C++ doesn't support returning arrays by value. + context.TODO(loc_id, "array return type"); + return nullptr; + } } clang::DeclarationNameInfo name_info(thunk_name, clang_loc); auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo(); - if (target.self_param && target.self_param->is_ref) { + if (target.self_param && target.self_param->kind == ParamPatternKind::Ref) { ext_proto_info.RefQualifier = clang::RQ_LValue; } clang::QualType thunk_function_type = ast_context.getFunctionType( @@ -657,6 +676,39 @@ static auto BuildCppToCarbonThunkBody(clang::Sema& sema, clang_loc); } +// Returns whether the given Carbon parameter should be passed as a C++ const +// reference. +static auto PassAsConstRef(Context& /*context*/, + const FunctionInfo::Param& param, + clang::QualType cpp_type) -> bool { + // Use pass-by-const-ref for value parameters of array type. + // TODO: Should we do this for value parameters of any type that uses a + // pointer value representation? + return param.kind == ParamPatternKind::Value && cpp_type->isArrayType(); +} + +// Converts a Carbon parameter type to the parameter type that should be exposed +// to C++ callers. +static auto MapToCppParamType(Context& context, SemIR::LocId loc_id, + const FunctionInfo::Param& param) + -> clang::QualType { + auto cpp_type = MapToCppType(context, param.type_id); + if (cpp_type.isNull()) { + return clang::QualType(); + } + if (param.kind == Check::ParamPatternKind::Ref) { + cpp_type = context.ast_context().getLValueReferenceType(cpp_type); + } else if (PassAsConstRef(context, param, cpp_type)) { + cpp_type = context.ast_context().getLValueReferenceType( + context.ast_context().getConstType(cpp_type)); + } else if (cpp_type->isArrayType()) { + // C++ doesn't support passing arrays by value. + context.TODO(loc_id, "by-var array parameter"); + return clang::QualType(); + } + return cpp_type; +} + // Create a C++ thunk that calls the Carbon thunk. The C++ thunk's // parameter types are mapped from the parameters of the target function // with `MapToCppType`. (Note that the target function here is the @@ -670,19 +722,19 @@ static auto BuildCppToCarbonThunk(Context& context, SemIR::LocId loc_id, llvm::SmallVector param_types; for (auto param : target.explicit_params) { - auto cpp_type = MapToCppType(context, param.type_id); + auto cpp_type = MapToCppParamType(context, loc_id, param); if (cpp_type.isNull()) { context.TODO(loc_id, "failed to map C++ type to Carbon"); return nullptr; } - if (param.is_ref) { - cpp_type = context.ast_context().getLValueReferenceType(cpp_type); - } param_types.push_back(cpp_type); } auto* thunk_function_decl = BuildCppToCarbonThunkDecl( context, loc_id, target, &thunk_ident, param_types); + if (!thunk_function_decl) { + return nullptr; + } // Build the thunk function body. clang::Sema& sema = context.clang_sema(); @@ -863,6 +915,9 @@ auto ExportDestructorToCpp(Context& context, const SemIR::Class& class_info, auto thunk_function_id = BuildDestroyThunk(context, loc_id, class_info); auto* cpp_function_decl = BuildCppFunctionDeclForCarbonFn(context, loc_id, thunk_function_id); + if (!cpp_function_decl) { + return nullptr; + } // Build the destructor body. clang::Sema::ContextRAII context_raii(sema, cpp_destructor_decl); diff --git a/toolchain/check/cpp/import.cpp b/toolchain/check/cpp/import.cpp index dc56c35d92b7..aeb4b7390985 100644 --- a/toolchain/check/cpp/import.cpp +++ b/toolchain/check/cpp/import.cpp @@ -1334,6 +1334,26 @@ static auto MapReferenceType(Context& context, clang::QualType type, return TypeExpr::ForUnsugared(context, pointer_type_id); } +// Maps a C++ array type to a Carbon array type. +static auto MapArrayType(Context& context, const clang::ArrayType* array_type, + TypeExpr element_type_expr) -> TypeExpr { + if (const auto* constant_array_type = + llvm::dyn_cast(array_type)) { + auto bound_const_id = TryEvalInst( + context, + SemIR::IntValue{.type_id = GetSingletonType( + context, SemIR::IntLiteralType::TypeInstId), + .int_id = context.ints().AddUnsigned( + constant_array_type->getSize())}); + auto bound_inst_id = context.constant_values().GetInstId(bound_const_id); + auto array_type_id = + GetArrayType(context, bound_inst_id, element_type_expr.inst_id); + return TypeExpr::ForUnsugared(context, array_type_id); + } + + return TypeExpr::None; +} + // Maps a C++ type to a Carbon type. `type` should not be canonicalized because // we check for pointer nullability and nullability will be lost by // canonicalization. @@ -1349,6 +1369,8 @@ static auto MapType(Context& context, SemIR::LocId loc_id, clang::QualType type) type = type->getPointeeType(); } else if (type->isReferenceType()) { type = type.getNonReferenceType(); + } else if (const auto* array_type = type->getAsArrayTypeUnsafe()) { + type = array_type->getElementType(); } else { break; } @@ -1369,6 +1391,8 @@ static auto MapType(Context& context, SemIR::LocId loc_id, clang::QualType type) mapped = MapPointerType(context, loc_id, wrapper, mapped); } else if (wrapper->isReferenceType()) { mapped = MapReferenceType(context, wrapper, mapped); + } else if (const auto* array_type = wrapper->getAsArrayTypeUnsafe()) { + mapped = MapArrayType(context, array_type, mapped); } else { CARBON_FATAL("Unexpected wrapper type {0}", wrapper.getAsString()); } diff --git a/toolchain/check/cpp/type_mapping.cpp b/toolchain/check/cpp/type_mapping.cpp index 2b3dfbf33d5c..86d0d8ae34d3 100644 --- a/toolchain/check/cpp/type_mapping.cpp +++ b/toolchain/check/cpp/type_mapping.cpp @@ -5,6 +5,7 @@ #include "toolchain/check/cpp/type_mapping.h" #include +#include #include #include @@ -33,10 +34,9 @@ namespace Carbon::Check { // A function that wraps a C++ type to form another C++ type. Note that this is -// a raw function pointer; we don't currently use any lambda captures here. This -// can be replaced by a `std::function` if captures are found to be needed. -using WrapFn = auto (*)(Context& context, clang::QualType inner_type) - -> clang::QualType; +// a std::function to allow lambda captures. +using WrapFn = std::function; // Represents a type that requires a subtype to be mapped into a Clang type // before it can be mapped. @@ -276,6 +276,27 @@ static auto TryMapType(Context& context, SemIR::TypeId type_id) clang::attr::TypeNonNull, pointer_type, pointer_type); }}; } + case CARBON_KIND(SemIR::ArrayType array_type): { + auto bound_const_id = context.constant_values().Get(array_type.bound_id); + if (!bound_const_id.is_constant()) { + return clang::QualType(); + } + auto bound_val_inst = + context.constant_values().TryGetInstAs( + bound_const_id); + if (!bound_val_inst) { + return clang::QualType(); + } + return WrappedType{ + .inner_type_id = context.types().GetTypeIdForTypeInstId( + array_type.element_type_inst_id), + .wrap_fn = [int_id = bound_val_inst->int_id]( + Context& context, clang::QualType inner_type) { + return context.ast_context().getConstantArrayType( + inner_type, context.ints().Get(int_id), /*SizeExpr=*/nullptr, + clang::ArraySizeModifier::Normal, /*IndexTypeQuals=*/0); + }}; + } default: { return clang::QualType(); @@ -291,7 +312,7 @@ auto MapToCppType(Context& context, SemIR::TypeId type_id) -> clang::QualType { while (true) { CARBON_KIND_SWITCH(TryMapType(context, type_id)) { case CARBON_KIND(clang::QualType type): { - for (auto wrap_fn : llvm::reverse(wrap_fns)) { + for (const auto& wrap_fn : llvm::reverse(wrap_fns)) { if (type.isNull()) { break; } diff --git a/toolchain/check/pattern.cpp b/toolchain/check/pattern.cpp index 54dd8f0c6986..884ed9f25474 100644 --- a/toolchain/check/pattern.cpp +++ b/toolchain/check/pattern.cpp @@ -13,6 +13,7 @@ #include "toolchain/check/type.h" #include "toolchain/diagnostics/emitter.h" #include "toolchain/sem_ir/inst.h" +#include "toolchain/sem_ir/inst_categories.h" namespace Carbon::Check { @@ -224,6 +225,19 @@ auto AddPatternVarStorage(Context& context, SemIR::InstBlockId pattern_block_id, } } +auto GetParamPatternKind(Context& context, SemIR::InstId param_inst_id) + -> ParamPatternKind { + auto param = context.insts().Get(param_inst_id); + CARBON_CHECK(param.Is()); + if (param.Is()) { + return ParamPatternKind::Ref; + } else if (param.Is()) { + return ParamPatternKind::Var; + } else { + return ParamPatternKind::Value; + } +} + auto AddParamPattern(Context& context, SemIR::LocId loc_id, SemIR::NameId name_id, SemIR::ExprRegionId type_expr_region_id, diff --git a/toolchain/check/pattern.h b/toolchain/check/pattern.h index c3ff406dd264..8ca01d6a4b58 100644 --- a/toolchain/check/pattern.h +++ b/toolchain/check/pattern.h @@ -74,6 +74,10 @@ enum class ParamPatternKind { Var, }; +// Returns the `ParamPatternKind` of the parameter instruction `param_inst_id`. +auto GetParamPatternKind(Context& context, SemIR::InstId param_inst_id) + -> ParamPatternKind; + // Adds a parameter pattern with the specified name and type information. The // pattern emulates `x: T`, `ref x: T`, or `var x: T` depending on the value of // `kind`. This only sets up the parameter pattern, binding pattern and type; diff --git a/toolchain/check/testdata/interop/cpp/function/export/array.carbon b/toolchain/check/testdata/interop/cpp/function/export/array.carbon new file mode 100644 index 000000000000..a68c5c4948cb --- /dev/null +++ b/toolchain/check/testdata/interop/cpp/function/export/array.carbon @@ -0,0 +1,104 @@ +// 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/function/export/array.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/function/export/array.carbon + +// --- array_value_param.carbon +library "[[@TEST_NAME]]"; + +import Cpp; + +fn F(a: array(i32, 5)) -> i32 { return a[2]; } + +inline Cpp ''' +int G() { + int arr[5]; + //@dump-sem-ir-begin + return Carbon::F(arr); + //@dump-sem-ir-end +} + +int H() { + //@dump-sem-ir-begin + return Carbon::F({1, 2, 3, 4, 5}); + //@dump-sem-ir-end +} +'''; + +// --- fail_todo_array_var_param.carbon +library "[[@TEST_NAME]]"; + +import Cpp; + +// We do not currently support C++ calls to functions with by-var array +// parameters, as C++ does not have such parameters. +// TODO: Support this, perhaps using Clang's `ArrayParameterType`, which is an +// HLSL extension, or by mapping to a `std::array` parameter. + +// TODO: These diagnostics are very bad. +// CHECK:STDERR: fail_todo_array_var_param.carbon:[[@LINE+18]]:1: error: cannot copy value of type `array(i32, 5)` [CopyOfUncopyableType] +// CHECK:STDERR: fn F(var a: array(i32, 5)) -> i32 { return a[2]; } +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: fail_todo_array_var_param.carbon:[[@LINE+15]]:1: note: type `array(i32, 5)` does not implement interface `Core.Copy` [MissingImplInMemberAccessInContext] +// CHECK:STDERR: fn F(var a: array(i32, 5)) -> i32 { return a[2]; } +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: fail_todo_array_var_param.carbon:[[@LINE+12]]:6: note: initializing function parameter [InCallToFunctionParam] +// CHECK:STDERR: fn F(var a: array(i32, 5)) -> i32 { return a[2]; } +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +// CHECK:STDERR: fail_todo_array_var_param.carbon:[[@LINE+8]]:1: error: semantics TODO: `by-var array parameter` [SemanticsTodo] +// CHECK:STDERR: fn F(var a: array(i32, 5)) -> i32 { return a[2]; } +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +// CHECK:STDERR: fail_todo_array_var_param.carbon:[[@LINE+4]]:1: error: semantics TODO: `failed to map C++ type to Carbon` [SemanticsTodo] +// CHECK:STDERR: fn F(var a: array(i32, 5)) -> i32 { return a[2]; } +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +fn F(var a: array(i32, 5)) -> i32 { return a[2]; } + +inline Cpp ''' +int G() { + //@dump-sem-ir-begin + // CHECK:STDERR: fail_todo_array_var_param.carbon:[[@LINE+4]]:18: error: no member named 'F' in namespace 'Carbon' [CppInteropParseError] + // CHECK:STDERR: 38 | return Carbon::F({1, 2, 3, 4, 5}); + // CHECK:STDERR: | ^ + // CHECK:STDERR: + return Carbon::F({1, 2, 3, 4, 5}); + //@dump-sem-ir-end +} +'''; + +// --- fail_todo_array_return.carbon +library "[[@TEST_NAME]]"; + +import Cpp; + +// We do not currently support C++ calls to functions with array return +// types, as C++ does not have such return types. +// TODO: Support this, perhaps by returning a `std::array`. + +// CHECK:STDERR: fail_todo_array_return.carbon:[[@LINE+4]]:1: error: semantics TODO: `array return type` [SemanticsTodo] +// CHECK:STDERR: fn F() -> array(i32, 5) { +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +fn F() -> array(i32, 5) { + return (1, 2, 3, 4, 5); +} + +inline Cpp ''' +int G() { + // CHECK:STDERR: fail_todo_array_return.carbon:[[@LINE+4]]:24: error: no member named 'F' in namespace 'Carbon' [CppInteropParseError] + // CHECK:STDERR: 23 | auto &&arr = Carbon::F(); + // CHECK:STDERR: | ^ + // CHECK:STDERR: + auto &&arr = Carbon::F(); + return arr[0]; +} +'''; diff --git a/toolchain/check/testdata/interop/cpp/function/export/thunk_ast.carbon b/toolchain/check/testdata/interop/cpp/function/export/thunk_ast.carbon index 55346eda4256..fc2cac2a5026 100644 --- a/toolchain/check/testdata/interop/cpp/function/export/thunk_ast.carbon +++ b/toolchain/check/testdata/interop/cpp/function/export/thunk_ast.carbon @@ -27,18 +27,20 @@ fn F(i: i32) -> i32 { // CHECK:STDOUT: | | |-DeclStmt {{0x[a-f0-9]+}} // CHECK:STDOUT: | | | `-VarDecl {{0x[a-f0-9]+}} col:21 used return_storage 'int' nrvo // CHECK:STDOUT: | | |-CallExpr {{0x[a-f0-9]+}} 'void' -// CHECK:STDOUT: | | | |-ImplicitCastExpr {{0x[a-f0-9]+}} 'void (*)(int &, int &)' -// CHECK:STDOUT: | | | | `-DeclRefExpr {{0x[a-f0-9]+}} 'void (int &, int &)' Function {{0x[a-f0-9]+}} 'F__carbon_thunk' 'void (int &, int &)' -// CHECK:STDOUT: | | | |-DeclRefExpr {{0x[a-f0-9]+}} 'int' lvalue ParmVar {{0x[a-f0-9]+}} depth 0 index 0 'int' -// CHECK:STDOUT: | | | `-DeclRefExpr {{0x[a-f0-9]+}} 'int' lvalue Var {{0x[a-f0-9]+}} 'return_storage' 'int' +// CHECK:STDOUT: | | | |-ImplicitCastExpr {{0x[a-f0-9]+}} 'void (*)(const int &, const int &)' +// CHECK:STDOUT: | | | | `-DeclRefExpr {{0x[a-f0-9]+}} 'void (const int &, const int &)' Function {{0x[a-f0-9]+}} 'F__carbon_thunk' 'void (const int &, const int &)' +// CHECK:STDOUT: | | | |-ImplicitCastExpr {{0x[a-f0-9]+}} 'const int' lvalue +// CHECK:STDOUT: | | | | `-DeclRefExpr {{0x[a-f0-9]+}} 'int' lvalue ParmVar {{0x[a-f0-9]+}} depth 0 index 0 'int' +// CHECK:STDOUT: | | | `-ImplicitCastExpr {{0x[a-f0-9]+}} 'const int' lvalue +// CHECK:STDOUT: | | | `-DeclRefExpr {{0x[a-f0-9]+}} 'int' lvalue Var {{0x[a-f0-9]+}} 'return_storage' 'int' // CHECK:STDOUT: | | `-ReturnStmt {{0x[a-f0-9]+}} nrvo_candidate(Var {{0x[a-f0-9]+}} 'return_storage' 'int') // CHECK:STDOUT: | | `-DeclRefExpr {{0x[a-f0-9]+}} 'int' lvalue Var {{0x[a-f0-9]+}} 'return_storage' 'int' // CHECK:STDOUT: | |-AlwaysInlineAttr {{0x[a-f0-9]+}} <> Implicit always_inline // CHECK:STDOUT: | `-InternalLinkageAttr {{0x[a-f0-9]+}} <> Implicit -// CHECK:STDOUT: `-FunctionDecl {{0x[a-f0-9]+}} line:35:5 G 'int (int)' external-linkage +// CHECK:STDOUT: `-FunctionDecl {{0x[a-f0-9]+}} line:37:5 G 'int (int)' external-linkage // CHECK:STDOUT: |-ParmVarDecl {{0x[a-f0-9]+}} col:11 used i 'int' -// CHECK:STDOUT: `-CompoundStmt {{0x[a-f0-9]+}} -// CHECK:STDOUT: `-ReturnStmt {{0x[a-f0-9]+}} +// CHECK:STDOUT: `-CompoundStmt {{0x[a-f0-9]+}} +// CHECK:STDOUT: `-ReturnStmt {{0x[a-f0-9]+}} // CHECK:STDOUT: `-CallExpr {{0x[a-f0-9]+}} 'int' // CHECK:STDOUT: |-ImplicitCastExpr {{0x[a-f0-9]+}} 'int (*)(int)' // CHECK:STDOUT: | `-DeclRefExpr {{0x[a-f0-9]+}} 'int (int)' lvalue Function {{0x[a-f0-9]+}} 'F' 'int (int)' diff --git a/toolchain/check/testdata/interop/cpp/macros/macros.carbon b/toolchain/check/testdata/interop/cpp/macros/macros.carbon index a140cc4577e0..39c064ed5aa6 100644 --- a/toolchain/check/testdata/interop/cpp/macros/macros.carbon +++ b/toolchain/check/testdata/interop/cpp/macros/macros.carbon @@ -920,9 +920,6 @@ library "[[@TEST_NAME]]"; import Cpp inline ''' struct Struct { - // CHECK:STDERR: fail_todo_assign_array.carbon:[[@LINE+3]]:7: error: semantics TODO: `Unsupported: field declaration has unhandled type or kind` [SemanticsTodo] - // CHECK:STDERR: int arr[3]; - // CHECK:STDERR: ^ int arr[3]; }; Struct s; @@ -930,20 +927,19 @@ Struct s; '''; fn F() { - // CHECK:STDERR: fail_todo_assign_array.carbon:[[@LINE+15]]:3: note: in `Cpp` name lookup for `m` [InCppNameLookup] + // CHECK:STDERR: fail_todo_assign_array.carbon:[[@LINE+14]]:3: error: semantics TODO: `lvalue path contains an array type` [SemanticsTodo] + // CHECK:STDERR: Cpp.m = 2; + // CHECK:STDERR: ^~~~~ + // CHECK:STDERR: fail_todo_assign_array.carbon:[[@LINE+11]]:3: note: in `Cpp` name lookup for `m` [InCppNameLookup] // CHECK:STDERR: Cpp.m = 2; // CHECK:STDERR: ^~~~~ // CHECK:STDERR: - // CHECK:STDERR: fail_todo_assign_array.carbon:[[@LINE+11]]:3: error: semantics TODO: `unsupported field in lvalue path: &s.arr[1]` [SemanticsTodo] + // CHECK:STDERR: fail_todo_assign_array.carbon:[[@LINE+7]]:3: error: cannot implicitly convert expression of type `Core.IntLiteral` to `array(i32, 3)` [ConversionFailure] // CHECK:STDERR: Cpp.m = 2; - // CHECK:STDERR: ^~~~~ - // CHECK:STDERR: fail_todo_assign_array.carbon:[[@LINE+8]]:3: note: in `Cpp` name lookup for `m` [InCppNameLookup] + // CHECK:STDERR: ^~~~~~~~~ + // CHECK:STDERR: fail_todo_assign_array.carbon:[[@LINE+4]]:3: note: type `Core.IntLiteral` does not implement interface `Core.ImplicitAs(array(i32, 3))` [MissingImplInMemberAccessInContext] // CHECK:STDERR: Cpp.m = 2; - // CHECK:STDERR: ^~~~~ - // CHECK:STDERR: - // CHECK:STDERR: fail_todo_assign_array.carbon:[[@LINE+4]]:3: error: member name `m` not found in `Cpp` [MemberNameNotFoundInInstScope] - // CHECK:STDERR: Cpp.m = 2; - // CHECK:STDERR: ^~~~~ + // CHECK:STDERR: ^~~~~~~~~ // CHECK:STDERR: Cpp.m = 2; } diff --git a/toolchain/check/testdata/interop/cpp/primitive_types/array.carbon b/toolchain/check/testdata/interop/cpp/primitive_types/array.carbon new file mode 100644 index 000000000000..400de55acaef --- /dev/null +++ b/toolchain/check/testdata/interop/cpp/primitive_types/array.carbon @@ -0,0 +1,86 @@ +// 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/primitive_types/array.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/primitive_types/array.carbon + +// --- import.carbon +library "[[@TEST_NAME]]"; + +import Cpp; +inline Cpp ''' +int arr[5]; + +struct X { + int arr[5]; +} x; +'''; + +fn F(n: i32) -> i32 { + return Cpp.arr[n]; +} + +fn G(x: Cpp.X, n: i32) -> i32 { + return x.arr[n]; +} + +// --- fail_todo_export_variable.carbon +library "[[@TEST_NAME]]"; + +import Cpp; + +var arr: array(i32, 5); + +inline Cpp ''' +int F(int n) { + // CHECK:STDERR: fail_todo_export_variable.carbon:[[@LINE+4]]:18: error: no member named 'arr' in namespace 'Carbon' [CppInteropParseError] + // CHECK:STDERR: 13 | return Carbon::arr[n]; + // CHECK:STDERR: | ^~~ + // CHECK:STDERR: + return Carbon::arr[n]; +} +'''; + +// --- export.carbon +library "[[@TEST_NAME]]"; + +import Cpp; + +class X { + var arr: array(i32, 5); +} + +inline Cpp ''' +int G(const Carbon::X& x, int n) { + return x.arr[n]; +} +'''; + +// --- fail_incomplete.carbon +library "[[@TEST_NAME]]"; + +import Cpp; + +// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+4]]:1: error: class was forward declared here [ClassForwardDeclaredHere] +// CHECK:STDERR: class C; +// CHECK:STDERR: ^~~~~~~~ +// CHECK:STDERR: +class C; + +fn F() -> array(C, 5)*; + +inline Cpp ''' +void G() { + // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+4]]:33: error: subscript of pointer to incomplete type 'Carbon::C' [CppInteropParseError] + // CHECK:STDERR: 19 | Carbon::C *p = &(*Carbon::F())[0]; + // CHECK:STDERR: | ~~~~~~~~~~~~~~^ + // CHECK:STDERR: + Carbon::C *p = &(*Carbon::F())[0]; +} +'''; diff --git a/toolchain/check/type.cpp b/toolchain/check/type.cpp index a2615e19f94c..c3253f814794 100644 --- a/toolchain/check/type.cpp +++ b/toolchain/check/type.cpp @@ -6,6 +6,7 @@ #include "toolchain/check/eval.h" #include "toolchain/check/facet_type.h" +#include "toolchain/check/inst.h" #include "toolchain/check/type_completion.h" #include "toolchain/sem_ir/facet_type_info.h" #include "toolchain/sem_ir/ids.h" @@ -255,6 +256,15 @@ auto GetPointerType(Context& context, SemIR::TypeInstId pointee_type_id) return GetCompleteTypeImpl(context, pointee_type_id); } +auto GetArrayType(Context& context, SemIR::InstId bound_id, + SemIR::TypeInstId element_type_inst_id) -> SemIR::TypeId { + SemIR::ArrayType inst = {.type_id = SemIR::TypeType::TypeId, + .bound_id = bound_id, + .element_type_inst_id = element_type_inst_id}; + return context.types().GetTypeIdForTypeConstantId( + EvalOrAddInst(context, SemIR::LocIdAndInst::NoLoc(inst))); +} + auto GetPatternType(Context& context, SemIR::TypeId scrutinee_type_id) -> SemIR::TypeId { CARBON_CHECK(!context.types().Is(scrutinee_type_id), diff --git a/toolchain/check/type.h b/toolchain/check/type.h index f337aa2749ac..ea9bd426a924 100644 --- a/toolchain/check/type.h +++ b/toolchain/check/type.h @@ -113,6 +113,10 @@ auto GetFacetAccessType(Context& context, SemIR::InstId facet_value_inst_id) auto GetPointerType(Context& context, SemIR::TypeInstId pointee_type_id) -> SemIR::TypeId; +// Returns an array type with the given `bound_id` and `element_type_inst_id`. +auto GetArrayType(Context& context, SemIR::InstId bound_id, + SemIR::TypeInstId element_type_inst_id) -> SemIR::TypeId; + // Returns a struct type with the given fields. auto GetStructType(Context& context, SemIR::StructTypeFieldsId fields_id) -> SemIR::TypeId; diff --git a/toolchain/lower/testdata/interop/cpp/array.carbon b/toolchain/lower/testdata/interop/cpp/array.carbon new file mode 100644 index 000000000000..23fd046d3fce --- /dev/null +++ b/toolchain/lower/testdata/interop/cpp/array.carbon @@ -0,0 +1,138 @@ +// 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/lower/testdata/interop/cpp/array.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/array.carbon + +// --- array_value_param.carbon +library "[[@TEST_NAME]]"; + +import Cpp; + +fn F(a: array(i32, 5)) -> i32 { return a[2]; } + +inline Cpp ''' +int G() { + int arr[5]; + return Carbon::F(arr); +} + +int H() { + return Carbon::F({1, 2, 3, 4, 5}); +} +'''; + +// CHECK:STDOUT: ; ModuleID = 'array_value_param.carbon' +// CHECK:STDOUT: source_filename = "array_value_param.carbon" +// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" +// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu" +// CHECK:STDOUT: +// CHECK:STDOUT: @constinit = private constant [5 x i32] [i32 1, i32 2, i32 3, i32 4, i32 5], align 4 +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: mustprogress uwtable +// CHECK:STDOUT: define dso_local noundef i32 @_Z1Gv() #0 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %arr = alloca [5 x i32], align 16 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %arr) #3 +// CHECK:STDOUT: %call = call noundef i32 @_ZN6CarbonL1FERA5_Ki(ptr noundef nonnull align 4 dereferenceable(20) %arr) +// CHECK:STDOUT: call void @llvm.lifetime.end.p0(ptr %arr) #3 +// CHECK:STDOUT: ret i32 %call +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #1 +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable +// CHECK:STDOUT: define internal noundef i32 @_ZN6CarbonL1FERA5_Ki(ptr noundef nonnull align 4 dereferenceable(20) %0) #2 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %retval = alloca i32, align 4 +// CHECK:STDOUT: %.addr = alloca ptr, align 8 +// CHECK:STDOUT: store ptr %0, ptr %.addr, align 8, !tbaa !11 +// CHECK:STDOUT: %1 = load ptr, ptr %.addr, align 8, !tbaa !11, !nonnull !14, !align !15 +// CHECK:STDOUT: call void @_CF__carbon_thunk.Main(ptr noundef nonnull align 4 dereferenceable(20) %1, ptr noundef nonnull align 4 dereferenceable(4) %retval) +// CHECK:STDOUT: %2 = load i32, ptr %retval, align 4 +// CHECK:STDOUT: ret i32 %2 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +// CHECK:STDOUT: declare void @llvm.lifetime.end.p0(ptr captures(none)) #1 +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: mustprogress uwtable +// CHECK:STDOUT: define dso_local noundef i32 @_Z1Hv() #0 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %ref.tmp = alloca [5 x i32], align 4 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %ref.tmp) #3 +// CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %ref.tmp, ptr align 4 @constinit, i64 20, i1 false), !tbaa.struct !16 +// CHECK:STDOUT: %call = call noundef i32 @_ZN6CarbonL1FERA5_Ki(ptr noundef nonnull align 4 dereferenceable(20) %ref.tmp) +// CHECK:STDOUT: call void @llvm.lifetime.end.p0(ptr %ref.tmp) #3 +// CHECK:STDOUT: ret i32 %call +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #1 +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define i32 @_CF.Main(ptr %a) #3 !dbg !18 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.loc5_43.2.array.index = getelementptr inbounds [5 x i32], ptr %a, i32 0, i32 2, !dbg !25 +// CHECK:STDOUT: %.loc5_43.3 = load i32, ptr %.loc5_43.2.array.index, align 4, !dbg !25 +// CHECK:STDOUT: ret i32 %.loc5_43.3, !dbg !26 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CF__carbon_thunk.Main(ptr %_, ptr %_1) #3 !dbg !27 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %F.call = call i32 @_CF.Main(ptr %_), !dbg !33 +// CHECK:STDOUT: store i32 %F.call, ptr %_1, align 4, !dbg !33 +// CHECK:STDOUT: ret void, !dbg !33 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: attributes #0 = { mustprogress uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +// CHECK:STDOUT: attributes #2 = { alwaysinline mustprogress nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: attributes #3 = { nounwind } +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4} +// CHECK:STDOUT: !llvm.dbg.cu = !{!5} +// CHECK:STDOUT: !llvm.errno.tbaa = !{!7} +// CHECK:STDOUT: +// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !2 = !{i32 8, !"PIC Level", i32 2} +// CHECK:STDOUT: !3 = !{i32 7, !"PIE Level", i32 2} +// CHECK:STDOUT: !4 = !{i32 7, !"uwtable", i32 2} +// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !6 = !DIFile(filename: "array_value_param.carbon", directory: "") +// CHECK:STDOUT: !7 = !{!8, !8, i64 0} +// CHECK:STDOUT: !8 = !{!"int", !9, i64 0} +// CHECK:STDOUT: !9 = !{!"omnipotent char", !10, i64 0} +// CHECK:STDOUT: !10 = !{!"Simple C++ TBAA"} +// CHECK:STDOUT: !11 = !{!12, !12, i64 0} +// CHECK:STDOUT: !12 = !{!"p1 int", !13, i64 0} +// CHECK:STDOUT: !13 = !{!"any pointer", !9, i64 0} +// CHECK:STDOUT: !14 = !{} +// CHECK:STDOUT: !15 = !{i64 4} +// CHECK:STDOUT: !16 = !{i64 0, i64 20, !17} +// CHECK:STDOUT: !17 = !{!9, !9, i64 0} +// CHECK:STDOUT: !18 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main", scope: null, file: !6, line: 5, type: !19, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !23) +// CHECK:STDOUT: !19 = !DISubroutineType(types: !20) +// CHECK:STDOUT: !20 = !{!21, !22} +// CHECK:STDOUT: !21 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +// CHECK:STDOUT: !22 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64) +// CHECK:STDOUT: !23 = !{!24} +// CHECK:STDOUT: !24 = !DILocalVariable(arg: 1, scope: !18, type: !22) +// CHECK:STDOUT: !25 = !DILocation(line: 5, column: 40, scope: !18) +// CHECK:STDOUT: !26 = !DILocation(line: 5, column: 33, scope: !18) +// CHECK:STDOUT: !27 = distinct !DISubprogram(name: "F__carbon_thunk", linkageName: "_CF__carbon_thunk.Main", scope: null, file: !6, line: 5, type: !28, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !30) +// CHECK:STDOUT: !28 = !DISubroutineType(types: !29) +// CHECK:STDOUT: !29 = !{null, !22, !21} +// CHECK:STDOUT: !30 = !{!31, !32} +// CHECK:STDOUT: !31 = !DILocalVariable(arg: 1, scope: !27, type: !22) +// CHECK:STDOUT: !32 = !DILocalVariable(arg: 2, scope: !27, type: !21) +// CHECK:STDOUT: !33 = !DILocation(line: 5, column: 1, scope: !27)