diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index 04566875ed1e..ea2ce791d281 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -24,6 +24,7 @@ cc_library( "cpp/access.cpp", "cpp/call.cpp", "cpp/custom_type_mapping.cpp", + "cpp/impl_lookup.cpp", "cpp/import.cpp", "cpp/location.cpp", "cpp/macros.cpp", @@ -77,6 +78,7 @@ cc_library( "cpp/access.h", "cpp/call.h", "cpp/custom_type_mapping.h", + "cpp/impl_lookup.h", "cpp/import.h", "cpp/location.h", "cpp/macros.h", diff --git a/toolchain/check/cpp/impl_lookup.cpp b/toolchain/check/cpp/impl_lookup.cpp new file mode 100644 index 000000000000..29361d1c16fc --- /dev/null +++ b/toolchain/check/cpp/impl_lookup.cpp @@ -0,0 +1,190 @@ +// 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/impl_lookup.h" + +#include "clang/Sema/Sema.h" +#include "toolchain/base/kind_switch.h" +#include "toolchain/check/cpp/import.h" +#include "toolchain/check/cpp/location.h" +#include "toolchain/check/cpp/overload_resolution.h" +#include "toolchain/check/impl.h" +#include "toolchain/check/impl_lookup.h" +#include "toolchain/check/import_ref.h" +#include "toolchain/check/inst.h" +#include "toolchain/check/type.h" +#include "toolchain/sem_ir/ids.h" +#include "toolchain/sem_ir/typed_insts.h" + +namespace Carbon::Check { + +// If the given type is a C++ class type, returns the corresponding class +// declaration. Otherwise returns nullptr. +// TODO: Handle qualified types. +static auto TypeAsClassDecl(Context& context, SemIR::TypeId type_id) + -> clang::CXXRecordDecl* { + auto class_type = context.types().TryGetAs(type_id); + if (!class_type) { + // Not a class. + return nullptr; + } + + SemIR::NameScopeId class_scope_id = + context.classes().Get(class_type->class_id).scope_id; + if (!class_scope_id.has_value()) { + return nullptr; + } + + const auto& scope = context.name_scopes().Get(class_scope_id); + auto decl_id = scope.clang_decl_context_id(); + if (!decl_id.has_value()) { + return nullptr; + } + + return dyn_cast( + context.clang_decls().Get(decl_id).key.decl); +} + +// Builds a witness that the given type implements the given interface, +// populating it with the specified set of values. Returns a corresponding +// lookup result. Produces a diagnostic and returns `None` if the specified +// values aren't suitable for the interface. +static auto BuildWitness(Context& context, SemIR::LocId loc_id, + SemIR::TypeId self_type_id, + SemIR::SpecificInterface specific_interface, + llvm::ArrayRef values) + -> SemIR::InstId { + const auto& interface = + context.interfaces().Get(specific_interface.interface_id); + auto assoc_entities = + context.inst_blocks().GetOrEmpty(interface.associated_entities_id); + if (assoc_entities.size() != values.size()) { + context.TODO(loc_id, ("Unsupported definition of interface " + + context.names().GetFormatted(interface.name_id)) + .str()); + return SemIR::ErrorInst::InstId; + } + + // Prepare an empty witness table. + auto witness_table_id = + context.inst_blocks().AddUninitialized(assoc_entities.size()); + auto witness_table = context.inst_blocks().GetMutable(witness_table_id); + for (auto& witness_value_id : witness_table) { + witness_value_id = SemIR::InstId::ImplWitnessTablePlaceholder; + } + + // Build a witness. We use an `ImplWitness` with an `impl_id` of `None` to + // represent a synthesized witness. + // TODO: Stop using `ImplWitnessTable` here and add a distinct instruction + // that doesn't contain an `InstId` and supports deduplication. + auto witness_table_inst_id = AddInst( + context, loc_id, + {.elements_id = witness_table_id, .impl_id = SemIR::ImplId::None}); + auto witness_id = AddInst( + context, loc_id, + {.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId), + .witness_table_id = witness_table_inst_id, + .specific_id = SemIR::SpecificId::None}); + + // Fill in the witness table. + for (const auto& [assoc_entity_id, value_id, witness_value_id] : + llvm::zip_equal(assoc_entities, values, witness_table)) { + LoadImportRef(context, assoc_entity_id); + auto decl_id = + context.constant_values().GetInstId(SemIR::GetConstantValueInSpecific( + context.sem_ir(), specific_interface.specific_id, assoc_entity_id)); + CARBON_CHECK(decl_id.has_value(), "Non-constant associated entity"); + auto decl = context.insts().Get(decl_id); + CARBON_KIND_SWITCH(decl) { + case CARBON_KIND(SemIR::StructValue struct_value): { + if (struct_value.type_id == SemIR::ErrorInst::TypeId) { + return SemIR::ErrorInst::InstId; + } + witness_value_id = CheckAssociatedFunctionImplementation( + context, + context.types().GetAs(struct_value.type_id), + value_id, self_type_id, witness_id, + /*defer_thunk_definition=*/false); + break; + } + case SemIR::AssociatedConstantDecl::Kind: { + context.TODO(loc_id, + "Associated constant in interface with synthesized impl"); + return SemIR::ErrorInst::InstId; + } + default: + CARBON_CHECK(decl_id == SemIR::ErrorInst::InstId, + "Unexpected kind of associated entity {0}", decl); + return SemIR::ErrorInst::InstId; + } + } + + return witness_id; +} + +static auto LookupCopyImpl(Context& context, SemIR::LocId loc_id, + SemIR::TypeId self_type_id, + SemIR::SpecificInterface specific_interface) + -> SemIR::InstId { + auto* class_decl = TypeAsClassDecl(context, self_type_id); + if (!class_decl) { + // TODO: Should we also provide a `Copy` implementation for enumerations? + return SemIR::InstId::None; + } + + auto* ctor = context.clang_sema().LookupCopyingConstructor( + class_decl, clang::Qualifiers::Const); + if (!ctor) { + // TODO: If the impl lookup failure is an error, we should produce a + // diagnostic explaining why the class is not copyable. + return SemIR::InstId::None; + } + + auto ctor_id = + context.clang_sema().DiagnoseUseOfOverloadedDecl( + ctor, GetCppLocation(context, loc_id)) + ? SemIR::ErrorInst::InstId + : ImportCppFunctionDecl(context, loc_id, ctor, /*num_params=*/1); + if (auto ctor_decl = + context.insts().TryGetAsWithId(ctor_id)) { + CheckCppOverloadAccess(context, loc_id, + clang::DeclAccessPair::make(ctor, ctor->getAccess()), + ctor_decl->inst_id); + } else { + CARBON_CHECK(ctor_id == SemIR::ErrorInst::InstId); + return SemIR::ErrorInst::InstId; + } + return BuildWitness(context, loc_id, self_type_id, specific_interface, + {ctor_id}); +} + +auto LookupCppImpl(Context& context, SemIR::LocId loc_id, + SemIR::TypeId self_type_id, + SemIR::SpecificInterface specific_interface, + const TypeStructure* best_impl_type_structure, + SemIR::LocId best_impl_loc_id) -> SemIR::InstId { + // Determine whether this is an interface that we have special knowledge of. + auto& interface = context.interfaces().Get(specific_interface.interface_id); + if (!context.name_scopes().IsCorePackage(interface.parent_scope_id)) { + return SemIR::InstId::None; + } + if (!interface.name_id.AsIdentifierId().has_value()) { + return SemIR::InstId::None; + } + + if (context.identifiers().Get(interface.name_id.AsIdentifierId()) == "Copy") { + return LookupCopyImpl(context, loc_id, self_type_id, specific_interface); + } + + // TODO: Handle other interfaces. + + // TODO: Infer a C++ type structure and check whether it's less strict than + // the best Carbon type structure. + static_cast(best_impl_type_structure); + static_cast(best_impl_loc_id); + + return SemIR::InstId::None; +} + +} // namespace Carbon::Check diff --git a/toolchain/check/cpp/impl_lookup.h b/toolchain/check/cpp/impl_lookup.h new file mode 100644 index 000000000000..dfd6db45fb42 --- /dev/null +++ b/toolchain/check/cpp/impl_lookup.h @@ -0,0 +1,44 @@ +// 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_IMPL_LOOKUP_H_ +#define CARBON_TOOLCHAIN_CHECK_CPP_IMPL_LOOKUP_H_ + +#include "toolchain/check/context.h" +#include "toolchain/check/impl_lookup.h" +#include "toolchain/check/type_structure.h" +#include "toolchain/sem_ir/ids.h" +#include "toolchain/sem_ir/specific_interface.h" + +namespace Carbon::Check { + +// Performs lookup for an impl witness for a query involving C++ types. Returns +// a witness value, or `None` if a synthesized C++ witness should not be used. +// +// If `interface` is an interface for which we can synthesize a witness based on +// C++ operator overloads or special member functions, performs the suitable C++ +// lookup to determine if this interface should be considered implemented for +// the specified type, and if so, synthesizes and returns a suitable witness. +// +// `best_impl_type_structure` provides the type structure of the best-matching +// impl declaration. If this is better than every viable C++ candidate, a "none" +// result will be returned. If this is worse than the best viable C++ candidate +// according to C++ rules, a witness for the C++ candidate will be returned. +// Otherwise, it is at least as good as the best viable C++ candidate, but there +// is some C++ candidate that has a better type structure, in which case the +// result is ambiguous and we diagnose an error. This parameter can be null if +// there is no usable impl for this query. +// +// `best_impl_loc_id` gives the location of the impl corresponding to the best +// type structure, and can be `None` if `best_impl_type_structure` is null. This +// parameter is used only for ambiguity diagnostics. +auto LookupCppImpl(Context& context, SemIR::LocId loc_id, + SemIR::TypeId self_type_id, + SemIR::SpecificInterface specific_interface, + const TypeStructure* best_impl_type_structure, + SemIR::LocId best_impl_loc_id) -> SemIR::InstId; + +} // namespace Carbon::Check + +#endif // CARBON_TOOLCHAIN_CHECK_CPP_IMPL_LOOKUP_H_ diff --git a/toolchain/check/cpp/import.cpp b/toolchain/check/cpp/import.cpp index 8a2211275084..2f64be4f591c 100644 --- a/toolchain/check/cpp/import.cpp +++ b/toolchain/check/cpp/import.cpp @@ -40,6 +40,7 @@ #include "toolchain/check/convert.h" #include "toolchain/check/cpp/access.h" #include "toolchain/check/cpp/custom_type_mapping.h" +#include "toolchain/check/cpp/location.h" #include "toolchain/check/cpp/macros.h" #include "toolchain/check/cpp/thunk.h" #include "toolchain/check/diagnostic_helpers.h" @@ -1809,6 +1810,11 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id, function_info.SetHasCppThunk(thunk_function_decl_id); } } + } else { + // Inform Clang that the function has been referenced. This will trigger + // instantiation if needed. + context.clang_sema().MarkFunctionReferenced(GetCppLocation(context, loc_id), + clang_decl); } return function_info.first_owning_decl_id; diff --git a/toolchain/check/cpp/operators.cpp b/toolchain/check/cpp/operators.cpp index 822e6404e8b3..97db0a7b2257 100644 --- a/toolchain/check/cpp/operators.cpp +++ b/toolchain/check/cpp/operators.cpp @@ -25,7 +25,7 @@ static auto GetClangOperatorKind(Context& context, SemIR::LocId loc_id, -> std::optional { // Unary operators. if (interface_name == "Destroy" || interface_name == "As" || - interface_name == "ImplicitAs") { + interface_name == "ImplicitAs" || interface_name == "Copy") { // TODO: Support destructors and conversions. return std::nullopt; } @@ -280,17 +280,37 @@ auto IsCppOperatorMethodDecl(clang::Decl* decl) -> bool { return clang_method_decl && clang_method_decl->isOverloadedOperator(); } -auto IsCppOperatorMethod(Context& context, SemIR::InstId inst_id) -> bool { +static auto GetAsCppFunctionDecl(Context& context, SemIR::InstId inst_id) + -> clang::FunctionDecl* { auto function_type = context.types().TryGetAs( context.insts().Get(inst_id).type_id()); if (!function_type) { - return false; + return nullptr; } SemIR::ClangDeclId clang_decl_id = context.functions().Get(function_type->function_id).clang_decl_id; - return clang_decl_id.has_value() && - IsCppOperatorMethodDecl( - context.clang_decls().Get(clang_decl_id).key.decl); + return clang_decl_id.has_value() + ? dyn_cast( + context.clang_decls().Get(clang_decl_id).key.decl) + : nullptr; +} + +auto IsCppOperatorMethod(Context& context, SemIR::InstId inst_id) -> bool { + auto* function_decl = GetAsCppFunctionDecl(context, inst_id); + return function_decl && IsCppOperatorMethodDecl(function_decl); +} + +auto IsCppConstructorOrNonMethodOperator(Context& context, + SemIR::InstId inst_id) -> bool { + auto* function_decl = GetAsCppFunctionDecl(context, inst_id); + if (!function_decl) { + return false; + } + if (isa(function_decl)) { + return true; + } + return !isa(function_decl) && + function_decl->isOverloadedOperator(); } } // namespace Carbon::Check diff --git a/toolchain/check/cpp/operators.h b/toolchain/check/cpp/operators.h index 3dab1140be15..285ce2881f12 100644 --- a/toolchain/check/cpp/operators.h +++ b/toolchain/check/cpp/operators.h @@ -25,6 +25,12 @@ auto IsCppOperatorMethodDecl(clang::Decl* decl) -> bool; // than as the first argument. auto IsCppOperatorMethod(Context& context, SemIR::InstId inst_id) -> bool; +// Returns whether the specified instruction refers to a C++ constructor or +// non-operator method. If so, when mapping from a Carbon interface to a C++ +// call, we pass a `self` parameter as the first argument instead. +auto IsCppConstructorOrNonMethodOperator(Context& context, + SemIR::InstId inst_id) -> bool; + } // namespace Carbon::Check #endif // CARBON_TOOLCHAIN_CHECK_CPP_OPERATORS_H_ diff --git a/toolchain/check/cpp/overload_resolution.cpp b/toolchain/check/cpp/overload_resolution.cpp index 6cb71e1c1584..5ef6bab5627f 100644 --- a/toolchain/check/cpp/overload_resolution.cpp +++ b/toolchain/check/cpp/overload_resolution.cpp @@ -162,7 +162,6 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id, case clang::OverloadingResult::OR_Success: { CARBON_CHECK(best_viable_fn->Function); CARBON_CHECK(!best_viable_fn->RewriteKind); - sema.MarkFunctionReferenced(loc, best_viable_fn->Function); SemIR::InstId result_id = ImportCppFunctionDecl( context, loc_id, best_viable_fn->Function, arg_exprs.size()); if (auto fn_decl = diff --git a/toolchain/check/impl.cpp b/toolchain/check/impl.cpp index 76132ac95a3b..78f17c64021a 100644 --- a/toolchain/check/impl.cpp +++ b/toolchain/check/impl.cpp @@ -38,26 +38,25 @@ static auto NoteAssociatedFunction(Context& context, DiagnosticBuilder& builder, function.name_id); } -// Checks that `impl_function_id` is a valid implementation of the function -// described in the interface as `interface_function_id`. Returns the value to -// put into the corresponding slot in the witness table, which can be -// `BuiltinErrorInst` if the function is not usable. -static auto CheckAssociatedFunctionImplementation( +auto CheckAssociatedFunctionImplementation( Context& context, SemIR::FunctionType interface_function_type, SemIR::InstId impl_decl_id, SemIR::TypeId self_type_id, - SemIR::InstId witness_inst_id) -> SemIR::InstId { + SemIR::InstId witness_inst_id, bool defer_thunk_definition) + -> SemIR::InstId { auto impl_function_decl = context.insts().TryGetAs(impl_decl_id); if (!impl_function_decl) { - CARBON_DIAGNOSTIC(ImplFunctionWithNonFunction, Error, - "associated function {0} implemented by non-function", - SemIR::NameId); - auto builder = context.emitter().Build( - impl_decl_id, ImplFunctionWithNonFunction, - context.functions().Get(interface_function_type.function_id).name_id); - NoteAssociatedFunction(context, builder, - interface_function_type.function_id); - builder.Emit(); + if (impl_decl_id != SemIR::ErrorInst::InstId) { + CARBON_DIAGNOSTIC(ImplFunctionWithNonFunction, Error, + "associated function {0} implemented by non-function", + SemIR::NameId); + auto builder = context.emitter().Build( + impl_decl_id, ImplFunctionWithNonFunction, + context.functions().Get(interface_function_type.function_id).name_id); + NoteAssociatedFunction(context, builder, + interface_function_type.function_id); + builder.Emit(); + } return SemIR::ErrorInst::InstId; } @@ -80,7 +79,8 @@ static auto CheckAssociatedFunctionImplementation( impl_enclosing_specific_id, self_type_id, witness_inst_id); return BuildThunk(context, interface_function_type.function_id, - interface_function_specific_id, impl_decl_id); + interface_function_specific_id, impl_decl_id, + defer_thunk_definition); } // Builds an initial witness from the rewrites in the facet type, if any. @@ -212,7 +212,7 @@ auto FinishImplWitness(Context& context, SemIR::ImplId impl_id) -> void { used_decl_ids.push_back(lookup_result.target_inst_id()); witness_value = CheckAssociatedFunctionImplementation( context, *fn_type, lookup_result.target_inst_id(), self_type_id, - impl.witness_id); + impl.witness_id, /*defer_thunk_definition=*/true); } else { CARBON_DIAGNOSTIC( ImplMissingFunction, Error, diff --git a/toolchain/check/impl.h b/toolchain/check/impl.h index 4d50ffa8b61a..efcc3b6a1853 100644 --- a/toolchain/check/impl.h +++ b/toolchain/check/impl.h @@ -35,6 +35,16 @@ auto AssignImplIdInWitness(Context& context, SemIR::ImplId impl_id, // being concrete. auto IsImplEffectivelyFinal(Context& context, const SemIR::Impl& impl) -> bool; +// Checks that `impl_function_id` is a valid implementation of the function +// described in the interface as `interface_function_id`. Returns the value to +// put into the corresponding slot in the witness table, which can be +// `ErrorInst::InstId` if the function is not usable. +auto CheckAssociatedFunctionImplementation( + Context& context, SemIR::FunctionType interface_function_type, + SemIR::InstId impl_decl_id, SemIR::TypeId self_type_id, + SemIR::InstId witness_inst_id, bool defer_thunk_definition) + -> SemIR::InstId; + // Checks that the constraint specified for the impl is valid and identified. // Returns the interface that the impl implements. On error, issues a diagnostic // and returns `None`. diff --git a/toolchain/check/impl_lookup.cpp b/toolchain/check/impl_lookup.cpp index 8b327b6be0e0..9d15407001a0 100644 --- a/toolchain/check/impl_lookup.cpp +++ b/toolchain/check/impl_lookup.cpp @@ -10,6 +10,7 @@ #include #include "toolchain/base/kind_switch.h" +#include "toolchain/check/cpp/impl_lookup.h" #include "toolchain/check/deduce.h" #include "toolchain/check/diagnostic_helpers.h" #include "toolchain/check/eval.h" @@ -46,9 +47,23 @@ static auto FindAssociatedImportIRs( if (!decl_id.has_value()) { return; } - if (auto ir_id = GetCanonicalImportIRInst(context, decl_id).ir_id(); - ir_id.has_value()) { - result.push_back(ir_id); + + auto import_ir_inst = GetCanonicalImportIRInst(context, decl_id); + const auto* sem_ir = &context.sem_ir(); + if (import_ir_inst.ir_id().has_value()) { + sem_ir = context.import_irs().Get(import_ir_inst.ir_id()).sem_ir; + } + + // For an instruction imported from C++, `GetCanonicalImportIRInst` returns + // the final Carbon import instruction, so go one extra step to check for a + // C++ import. + if (auto import_ir_inst_id = + sem_ir->insts().GetImportSource(import_ir_inst.inst_id()); + import_ir_inst_id.has_value()) { + result.push_back( + sem_ir->import_ir_insts().Get(import_ir_inst_id).ir_id()); + } else if (import_ir_inst.ir_id().has_value()) { + result.push_back(import_ir_inst.ir_id()); } }; @@ -788,16 +803,29 @@ struct CandidateImpl { TypeStructure type_structure; }; +struct CandidateImpls { + llvm::SmallVector impls; + bool consider_cpp_candidates = false; +}; + // Returns the list of candidates impls for lookup to select from. static auto CollectCandidateImplsForQuery( Context& context, bool final_only, SemIR::ConstantId query_self_const_id, const TypeStructure& query_type_structure, - SemIR::SpecificInterface& query_specific_interface) - -> llvm::SmallVector { + SemIR::SpecificInterface& query_specific_interface) -> CandidateImpls { + CandidateImpls candidates; + auto import_irs = FindAssociatedImportIRs(context, query_self_const_id, query_specific_interface); for (auto import_ir_id : import_irs) { + // If `Cpp` is an associated package, then we'll instead look for C++ + // operator overloads for certain well-known interfaces. + if (import_ir_id == SemIR::ImportIRId::Cpp) { + candidates.consider_cpp_candidates = true; + continue; + } + // Instead of importing all impls, only import ones that are in some way // connected to this query. ImportImplFilter filter(context, import_ir_id, query_specific_interface); @@ -811,7 +839,6 @@ static auto CollectCandidateImplsForQuery( } } - llvm::SmallVector candidate_impls; for (auto [id, impl] : context.impls().enumerate()) { CARBON_CHECK(impl.witness_id.has_value()); @@ -854,7 +881,7 @@ static auto CollectCandidateImplsForQuery( continue; } - candidate_impls.push_back( + candidates.impls.push_back( {id, impl.definition_id, std::move(*type_structure)}); } @@ -866,9 +893,29 @@ static auto CollectCandidateImplsForQuery( // TODO: Allow Carbon code to provide a priority ordering explicitly. For // now they have all the same priority, so the priority is the order in // which they are found in code. - llvm::stable_sort(candidate_impls, compare); + llvm::stable_sort(candidates.impls, compare); - return candidate_impls; + return candidates; +} + +// Given a value whose type `IsFacetTypeOrError`, returns the corresponding +// type. +static auto GetFacetAsType(Context& context, SemIR::LocId loc_id, + SemIR::ConstantId facet_or_type_const_id) + -> SemIR::TypeId { + auto facet_or_type_id = + context.constant_values().GetInstId(facet_or_type_const_id); + auto type_type_id = context.insts().Get(facet_or_type_id).type_id(); + CARBON_CHECK(context.types().IsFacetTypeOrError(type_type_id)); + + if (context.types().Is(type_type_id)) { + // It's a facet; access its type. + facet_or_type_id = GetOrAddInst( + context, loc_id, + {.type_id = SemIR::TypeType::TypeId, + .facet_value_inst_id = facet_or_type_id}); + } + return context.types().GetTypeIdForTypeInstId(facet_or_type_id); } auto EvalLookupSingleImplWitness(Context& context, SemIR::LocId loc_id, @@ -885,6 +932,35 @@ auto EvalLookupSingleImplWitness(Context& context, SemIR::LocId loc_id, return facet_lookup_result; } + // Ensure specifics don't substitute in weird things for the query self. + CARBON_CHECK(context.types().IsFacetType( + context.insts().Get(eval_query.query_self_inst_id).type_id())); + SemIR::ConstantId query_self_const_id = + context.constant_values().Get(eval_query.query_self_inst_id); + + // The kind of lookup we're performing, which determines what kind of result + // we provide. + enum LookupKind { + // This is a concrete query, which should either provide a concrete witness + // or fail. + Concrete, + // This query refers to an interface that can be found symbolically within + // the facet type of the self value. The lookup will always succeed, but we + // are still checking in case a more precise final impl supplies values of + // associated constants. + FoundInFacet, + // This is an impl lookup with a symbolic query. + Symbolic, + }; + + LookupKind kind = + QueryIsConcrete(context, query_self_const_id, query_specific_interface) + ? Concrete + : facet_lookup_result.has_value() ? FoundInFacet + : Symbolic; + CARBON_CHECK(kind != Concrete || !facet_lookup_result.has_value(), + "Non-concrete facet lookup value for concrete query"); + // If the self type is a facet that provides a witness, then we are in an // `interface` or an `impl`. In both cases, we don't want to do any impl // lookups. The query will eventually resolve to a concrete witness when it @@ -897,8 +973,7 @@ auto EvalLookupSingleImplWitness(Context& context, SemIR::LocId loc_id, // when the eval block is run, it finds the same `impl`, tries to build a // specific from it, which runs the eval block, creating a recursive loop that // crashes. - bool self_facet_provides_witness = facet_lookup_result.has_value(); - if (self_facet_provides_witness) { + if (kind == FoundInFacet) { if (auto bind = context.insts().TryGetAs( eval_query.query_self_inst_id)) { const auto& entity = context.entity_names().Get(bind->entity_name_id); @@ -909,24 +984,12 @@ auto EvalLookupSingleImplWitness(Context& context, SemIR::LocId loc_id, } } - // Ensure specifics don't substitute in weird things for the query self. - CARBON_CHECK(context.types().IsFacetType( - context.insts().Get(eval_query.query_self_inst_id).type_id())); - SemIR::ConstantId query_self_const_id = - context.constant_values().Get(eval_query.query_self_inst_id); - auto query_type_structure = BuildTypeStructure( context, context.constant_values().GetInstId(query_self_const_id), query_specific_interface); if (!query_type_structure) { return EvalImplLookupResult::MakeNone(); } - bool query_is_concrete = - QueryIsConcrete(context, query_self_const_id, query_specific_interface); - - // If we have a symbolic witness in the self query, then the query can not be - // concrete: the query includes a symbolic self value. - CARBON_CHECK(!self_facet_provides_witness || !query_is_concrete); // If the self value is a (symbolic) facet value that has a symbolic witness, // then we don't need to do impl lookup, except that we want to find any final @@ -934,11 +997,11 @@ auto EvalLookupSingleImplWitness(Context& context, SemIR::LocId loc_id, // final impls only in that case. Note as in the CHECK above, the query can // not be concrete in this case, so only final impls can produce a concrete // witness for this query. - auto candidate_impls = CollectCandidateImplsForQuery( - context, self_facet_provides_witness, query_self_const_id, - *query_type_structure, query_specific_interface); + auto candidates = CollectCandidateImplsForQuery( + context, kind == FoundInFacet, query_self_const_id, *query_type_structure, + query_specific_interface); - for (const auto& candidate : candidate_impls) { + for (const auto& candidate : candidates.impls) { // In deferred lookup for a symbolic impl witness, while building a // specific, there may be no stack yet as this may be the first lookup. If // further lookups are started as a result in deduce, they will build the @@ -951,7 +1014,7 @@ auto EvalLookupSingleImplWitness(Context& context, SemIR::LocId loc_id, } auto result = GetWitnessIdForImpl( - context, loc_id, query_is_concrete, query_self_const_id, + context, loc_id, kind == Concrete, query_self_const_id, query_specific_interface, candidate.impl_id); if (result.has_value()) { // Record the query which found a final impl witness. It's illegal to @@ -968,18 +1031,50 @@ auto EvalLookupSingleImplWitness(Context& context, SemIR::LocId loc_id, .query = eval_query, .impl_witness = result.final_witness()}); } + + if (kind == Concrete && candidates.consider_cpp_candidates) { + // We found a Carbon impl. Also check for a C++ candidate that is a + // better match than that impl. + auto cpp_witness_id = LookupCppImpl( + context, loc_id, + GetFacetAsType(context, loc_id, query_self_const_id), + query_specific_interface, &candidate.type_structure, + SemIR::LocId( + context.impls().Get(candidate.impl_id).first_owning_decl_id)); + if (cpp_witness_id.has_value()) { + return EvalImplLookupResult::MakeFinal(cpp_witness_id); + } + } + return result; } } - if (self_facet_provides_witness) { - // If we did not find a final impl, but the self value is a facet that - // provides a symbolic witness, when we record that an impl will exist for - // the specific, but is yet unknown. - return EvalImplLookupResult::MakeNonFinal(); - } + // We didn't find a matching impl. Produce a suitable result. + switch (kind) { + case Concrete: + if (candidates.consider_cpp_candidates) { + // Look for a matching C++ result, with no Carbon candidate to compare + // against. + auto cpp_witness_id = LookupCppImpl( + context, loc_id, + GetFacetAsType(context, loc_id, query_self_const_id), + query_specific_interface, nullptr, SemIR::LocId::None); + if (cpp_witness_id.has_value()) { + return EvalImplLookupResult::MakeFinal(cpp_witness_id); + } + } + return EvalImplLookupResult::MakeNone(); - return EvalImplLookupResult::MakeNone(); + case FoundInFacet: + // We did not find a final impl, but the self value is a facet that + // provides a symbolic witness. Record that an impl will exist for the + // specific, but is yet unknown. + return EvalImplLookupResult::MakeNonFinal(); + + case Symbolic: + return EvalImplLookupResult::MakeNone(); + } } auto LookupMatchesImpl(Context& context, SemIR::LocId loc_id, diff --git a/toolchain/check/testdata/interop/cpp/impls/copy.carbon b/toolchain/check/testdata/interop/cpp/impls/copy.carbon new file mode 100644 index 000000000000..cd713d948456 --- /dev/null +++ b/toolchain/check/testdata/interop/cpp/impls/copy.carbon @@ -0,0 +1,302 @@ +// 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/convert.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/impls/copy.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/impls/copy.carbon + +// --- types.h + +class Copyable { + public: + Copyable(const Copyable&); +}; + +class ExplicitCopy { + public: + explicit ExplicitCopy(const ExplicitCopy&); +}; + +class DeletedCopy { + public: + DeletedCopy(const DeletedCopy&) = delete; +}; + +class MoveOnly { + public: + MoveOnly(MoveOnly&&); +}; + +class DeletedMove { + public: + DeletedMove(DeletedMove&&) = delete; +}; + +class NonConstCopy { + public: + NonConstCopy(NonConstCopy&); +}; + +class AmbiguousCopy { + public: + AmbiguousCopy(const AmbiguousCopy&, int = 0); + AmbiguousCopy(const AmbiguousCopy&, char = 0); +}; + +class PrivateCopy { + private: + PrivateCopy(const PrivateCopy&); +}; + +class ProtectedCopy { + protected: + ProtectedCopy(const ProtectedCopy&); +}; + +// --- copy_copyable.carbon + +library "[[@TEST_NAME]]"; + +import Cpp library "types.h"; + +fn CopyCopyable(c: Cpp.Copyable) -> Cpp.Copyable { + //@dump-sem-ir-begin + return c; + //@dump-sem-ir-end +} + +fn CopyExplicitCopy(c: Cpp.ExplicitCopy) -> Cpp.ExplicitCopy { + //@dump-sem-ir-begin + return c; + //@dump-sem-ir-end +} + +// --- fail_copy_noncopyable.carbon + +library "[[@TEST_NAME]]"; + +import Cpp library "types.h"; + +fn CopyDeletedCopy(c: Cpp.DeletedCopy) -> Cpp.DeletedCopy { + // CHECK:STDERR: fail_copy_noncopyable.carbon:[[@LINE+8]]:10: error: attempt to use a deleted function [CppInteropParseError] + // CHECK:STDERR: 15 | return c; + // CHECK:STDERR: | ^ + // CHECK:STDERR: fail_copy_noncopyable.carbon:[[@LINE-6]]:10: in file included here [InCppInclude] + // CHECK:STDERR: ./types.h:14:3: note: 'DeletedCopy' has been explicitly marked deleted here [CppInteropParseNote] + // CHECK:STDERR: 14 | DeletedCopy(const DeletedCopy&) = delete; + // CHECK:STDERR: | ^ + // CHECK:STDERR: + return c; +} + +fn CopyMoveOnly(c: Cpp.MoveOnly) -> Cpp.MoveOnly { + // CHECK:STDERR: fail_copy_noncopyable.carbon:[[@LINE+8]]:10: error: attempt to use a deleted function [CppInteropParseError] + // CHECK:STDERR: 27 | return c; + // CHECK:STDERR: | ^ + // CHECK:STDERR: fail_copy_noncopyable.carbon:[[@LINE-18]]:10: in file included here [InCppInclude] + // CHECK:STDERR: ./types.h:19:3: note: copy constructor is implicitly deleted because 'MoveOnly' has a user-declared move constructor [CppInteropParseNote] + // CHECK:STDERR: 19 | MoveOnly(MoveOnly&&); + // CHECK:STDERR: | ^ + // CHECK:STDERR: + return c; +} + +fn CopyDeletedMove(c: Cpp.DeletedMove) -> Cpp.DeletedMove { + // CHECK:STDERR: fail_copy_noncopyable.carbon:[[@LINE+8]]:10: error: attempt to use a deleted function [CppInteropParseError] + // CHECK:STDERR: 39 | return c; + // CHECK:STDERR: | ^ + // CHECK:STDERR: fail_copy_noncopyable.carbon:[[@LINE-30]]:10: in file included here [InCppInclude] + // CHECK:STDERR: ./types.h:24:3: note: copy constructor is implicitly deleted because 'DeletedMove' has a user-declared move constructor [CppInteropParseNote] + // CHECK:STDERR: 24 | DeletedMove(DeletedMove&&) = delete; + // CHECK:STDERR: | ^ + // CHECK:STDERR: + return c; +} + +fn CopyNonConstCopy(c: Cpp.NonConstCopy) -> Cpp.NonConstCopy { + // TODO: List candidates here. + // CHECK:STDERR: fail_copy_noncopyable.carbon:[[@LINE+7]]:10: error: cannot copy value of type `Cpp.NonConstCopy` [CopyOfUncopyableType] + // CHECK:STDERR: return c; + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_copy_noncopyable.carbon:[[@LINE+4]]:10: note: type `Cpp.NonConstCopy` does not implement interface `Core.Copy` [MissingImplInMemberAccessNote] + // CHECK:STDERR: return c; + // CHECK:STDERR: ^ + // CHECK:STDERR: + return c; +} + +fn CopyAmbiguousCopy(c: Cpp.AmbiguousCopy) -> Cpp.AmbiguousCopy { + // TODO: List candidates here. + // CHECK:STDERR: fail_copy_noncopyable.carbon:[[@LINE+7]]:10: error: cannot copy value of type `Cpp.AmbiguousCopy` [CopyOfUncopyableType] + // CHECK:STDERR: return c; + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_copy_noncopyable.carbon:[[@LINE+4]]:10: note: type `Cpp.AmbiguousCopy` does not implement interface `Core.Copy` [MissingImplInMemberAccessNote] + // CHECK:STDERR: return c; + // CHECK:STDERR: ^ + // CHECK:STDERR: + return c; +} + +fn CopyPrivateCopy(c: Cpp.PrivateCopy) -> Cpp.PrivateCopy { + // TODO: Note is missing location. + // CHECK:STDERR: fail_copy_noncopyable.carbon:[[@LINE+5]]:10: error: cannot access private member `PrivateCopy` of type `Cpp.PrivateCopy` [ClassInvalidMemberAccess] + // CHECK:STDERR: return c; + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_copy_noncopyable.carbon: note: declared here [ClassMemberDeclaration] + // CHECK:STDERR: + return c; +} + +fn CopyProtectedCopy(c: Cpp.ProtectedCopy) -> Cpp.ProtectedCopy { + // TODO: Note is missing location. + // CHECK:STDERR: fail_copy_noncopyable.carbon:[[@LINE+5]]:10: error: cannot access protected member `ProtectedCopy` of type `Cpp.ProtectedCopy` [ClassInvalidMemberAccess] + // CHECK:STDERR: return c; + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_copy_noncopyable.carbon: note: declared here [ClassMemberDeclaration] + // CHECK:STDERR: + return c; +} + +// --- fail_todo_copy_generically.carbon + +library "[[@TEST_NAME]]"; + +import Cpp library "types.h"; + +fn Copy[T:! Core.Copy](c: T) -> T { + return c; +} + +fn DoCopy(c: Cpp.Copyable) -> Cpp.Copyable { + // TODO: We perform multiple impl lookups for `Cpp.Copyable as Core.Copy` + // here, and each one produces a different impl witness, resulting in a + // deduction failure. + // CHECK:STDERR: fail_todo_copy_generically.carbon:[[@LINE+7]]:10: error: inconsistent deductions for value of generic parameter `T` [DeductionInconsistent] + // CHECK:STDERR: return Copy(c); + // CHECK:STDERR: ^~~~~~~ + // CHECK:STDERR: fail_todo_copy_generically.carbon:[[@LINE-11]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere] + // CHECK:STDERR: fn Copy[T:! Core.Copy](c: T) -> T { + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + return Copy(c); +} + +class Wrap(T:! Core.Copy) {} + +// TODO: This should type-check: each conversion of `Cpp.Copyable` to +// `Core.Copy` should produce the same value. +fn EqualWitnesses(p: Wrap(Cpp.Copyable)*) -> Wrap(Cpp.Copyable)* { + // CHECK:STDERR: fail_todo_copy_generically.carbon:[[@LINE+7]]:3: error: cannot implicitly convert expression of type `Wrap(Cpp.Copyable as Core.Copy)*` to `Wrap(Cpp.Copyable as Core.Copy)*` [ConversionFailure] + // CHECK:STDERR: return p; + // CHECK:STDERR: ^~~~~~~~~ + // CHECK:STDERR: fail_todo_copy_generically.carbon:[[@LINE+4]]:3: note: type `Wrap(Cpp.Copyable as Core.Copy)*` does not implement interface `Core.ImplicitAs(Wrap(Cpp.Copyable as Core.Copy)*)` [MissingImplInMemberAccessNote] + // CHECK:STDERR: return p; + // CHECK:STDERR: ^~~~~~~~~ + // CHECK:STDERR: + return p; +} + +// CHECK:STDOUT: --- copy_copyable.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %Copyable: type = class_type @Copyable [concrete] +// CHECK:STDOUT: %Copy.type: type = facet_type <@Copy> [concrete] +// CHECK:STDOUT: %Copy.Op.type: type = fn_type @Copy.Op [concrete] +// CHECK:STDOUT: %Copyable.Copyable.type: type = fn_type @Copyable.Copyable [concrete] +// CHECK:STDOUT: %Copyable.Copyable: %Copyable.Copyable.type = struct_value () [concrete] +// CHECK:STDOUT: %const.622: type = const_type %Copyable [concrete] +// CHECK:STDOUT: %ptr.29d: type = ptr_type %const.622 [concrete] +// CHECK:STDOUT: %ptr.e47: type = ptr_type %Copyable [concrete] +// CHECK:STDOUT: %Copyable__carbon_thunk.type: type = fn_type @Copyable__carbon_thunk [concrete] +// CHECK:STDOUT: %Copyable__carbon_thunk: %Copyable__carbon_thunk.type = struct_value () [concrete] +// CHECK:STDOUT: %impl_witness.65e: = impl_witness @CopyCopyable.%impl_witness_table [concrete] +// CHECK:STDOUT: %Copy.facet.26f: %Copy.type = facet_value %Copyable, (%impl_witness.65e) [concrete] +// CHECK:STDOUT: %Copyable.Op.type: type = fn_type @Copyable.Op [concrete] +// CHECK:STDOUT: %Copyable.Op: %Copyable.Op.type = struct_value () [concrete] +// CHECK:STDOUT: %.667: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.26f [concrete] +// CHECK:STDOUT: %ExplicitCopy: type = class_type @ExplicitCopy [concrete] +// CHECK:STDOUT: %ExplicitCopy.ExplicitCopy.type: type = fn_type @ExplicitCopy.ExplicitCopy [concrete] +// CHECK:STDOUT: %ExplicitCopy.ExplicitCopy: %ExplicitCopy.ExplicitCopy.type = struct_value () [concrete] +// CHECK:STDOUT: %const.d8d: type = const_type %ExplicitCopy [concrete] +// CHECK:STDOUT: %ptr.093: type = ptr_type %const.d8d [concrete] +// CHECK:STDOUT: %ptr.84c: type = ptr_type %ExplicitCopy [concrete] +// CHECK:STDOUT: %ExplicitCopy__carbon_thunk.type: type = fn_type @ExplicitCopy__carbon_thunk [concrete] +// CHECK:STDOUT: %ExplicitCopy__carbon_thunk: %ExplicitCopy__carbon_thunk.type = struct_value () [concrete] +// CHECK:STDOUT: %impl_witness.215: = impl_witness @CopyExplicitCopy.%impl_witness_table [concrete] +// CHECK:STDOUT: %Copy.facet.cfa: %Copy.type = facet_value %ExplicitCopy, (%impl_witness.215) [concrete] +// CHECK:STDOUT: %ExplicitCopy.Op.type: type = fn_type @ExplicitCopy.Op [concrete] +// CHECK:STDOUT: %ExplicitCopy.Op: %ExplicitCopy.Op.type = struct_value () [concrete] +// CHECK:STDOUT: %.a87: type = fn_type_with_self_type %Copy.Op.type, %Copy.facet.cfa [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Copyable.Copyable.decl: %Copyable.Copyable.type = fn_decl @Copyable.Copyable [concrete = constants.%Copyable.Copyable] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: %Copyable__carbon_thunk.decl: %Copyable__carbon_thunk.type = fn_decl @Copyable__carbon_thunk [concrete = constants.%Copyable__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: %ExplicitCopy.ExplicitCopy.decl: %ExplicitCopy.ExplicitCopy.type = fn_decl @ExplicitCopy.ExplicitCopy [concrete = constants.%ExplicitCopy.ExplicitCopy] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: %ExplicitCopy__carbon_thunk.decl: %ExplicitCopy__carbon_thunk.type = fn_decl @ExplicitCopy__carbon_thunk [concrete = constants.%ExplicitCopy__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @CopyCopyable(%c.param: %Copyable) -> %return.param: %Copyable { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %c.ref: %Copyable = name_ref c, %c +// CHECK:STDOUT: %impl_witness_table = impl_witness_table (%Copyable.Op.decl), invalid [concrete] +// CHECK:STDOUT: %impl_witness: = impl_witness %impl_witness_table [concrete = constants.%impl_witness.65e] +// CHECK:STDOUT: +// CHECK:STDOUT: %impl.elem0: %.667 = impl_witness_access constants.%impl_witness.65e, element0 [concrete = constants.%Copyable.Op] +// CHECK:STDOUT: %bound_method: = bound_method %c.ref, %impl.elem0 +// CHECK:STDOUT: %.loc8_10.1: ref %Copyable = temporary_storage +// CHECK:STDOUT: %Op.ref: %Copyable.Copyable.type = name_ref Op, imports.%Copyable.Copyable.decl [concrete = constants.%Copyable.Copyable] +// CHECK:STDOUT: +// CHECK:STDOUT: %.loc8_10.2: ref %Copyable = value_as_ref %c.ref +// CHECK:STDOUT: %addr.loc8_10.1: %ptr.e47 = addr_of %.loc8_10.2 +// CHECK:STDOUT: %.loc8_10.3: %ptr.29d = as_compatible %addr.loc8_10.1 +// CHECK:STDOUT: %.loc8_10.4: %ptr.29d = converted %addr.loc8_10.1, %.loc8_10.3 +// CHECK:STDOUT: %addr.loc8_10.2: %ptr.e47 = addr_of %.loc6_34 +// CHECK:STDOUT: %Copyable__carbon_thunk.call: init %empty_tuple.type = call imports.%Copyable__carbon_thunk.decl(%.loc8_10.4, %addr.loc8_10.2) +// CHECK:STDOUT: %.loc8_10.5: init %Copyable = in_place_init %Copyable__carbon_thunk.call, %.loc6_34 +// CHECK:STDOUT: return %.loc8_10.5 to %return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @CopyExplicitCopy(%c.param: %ExplicitCopy) -> %return.param: %ExplicitCopy { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %c.ref: %ExplicitCopy = name_ref c, %c +// CHECK:STDOUT: %impl_witness_table = impl_witness_table (%ExplicitCopy.Op.decl), invalid [concrete] +// CHECK:STDOUT: %impl_witness: = impl_witness %impl_witness_table [concrete = constants.%impl_witness.215] +// CHECK:STDOUT: +// CHECK:STDOUT: %impl.elem0: %.a87 = impl_witness_access constants.%impl_witness.215, element0 [concrete = constants.%ExplicitCopy.Op] +// CHECK:STDOUT: %bound_method: = bound_method %c.ref, %impl.elem0 +// CHECK:STDOUT: %.loc14_10.1: ref %ExplicitCopy = temporary_storage +// CHECK:STDOUT: %Op.ref: %ExplicitCopy.ExplicitCopy.type = name_ref Op, imports.%ExplicitCopy.ExplicitCopy.decl [concrete = constants.%ExplicitCopy.ExplicitCopy] +// CHECK:STDOUT: +// CHECK:STDOUT: %.loc14_10.2: ref %ExplicitCopy = value_as_ref %c.ref +// CHECK:STDOUT: %addr.loc14_10.1: %ptr.84c = addr_of %.loc14_10.2 +// CHECK:STDOUT: %.loc14_10.3: %ptr.093 = as_compatible %addr.loc14_10.1 +// CHECK:STDOUT: %.loc14_10.4: %ptr.093 = converted %addr.loc14_10.1, %.loc14_10.3 +// CHECK:STDOUT: %addr.loc14_10.2: %ptr.84c = addr_of %.loc12_42 +// CHECK:STDOUT: %ExplicitCopy__carbon_thunk.call: init %empty_tuple.type = call imports.%ExplicitCopy__carbon_thunk.decl(%.loc14_10.4, %addr.loc14_10.2) +// CHECK:STDOUT: %.loc14_10.5: init %ExplicitCopy = in_place_init %ExplicitCopy__carbon_thunk.call, %.loc12_42 +// CHECK:STDOUT: return %.loc14_10.5 to %return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/thunk.cpp b/toolchain/check/thunk.cpp index 1c34b1586d08..37dd1c824298 100644 --- a/toolchain/check/thunk.cpp +++ b/toolchain/check/thunk.cpp @@ -9,6 +9,7 @@ #include "toolchain/base/kind_switch.h" #include "toolchain/check/call.h" #include "toolchain/check/convert.h" +#include "toolchain/check/cpp/operators.h" #include "toolchain/check/deferred_definition_worklist.h" #include "toolchain/check/diagnostic_helpers.h" #include "toolchain/check/function.h" @@ -213,67 +214,6 @@ static auto HasDeclaredReturnType(Context& context, .return_slot_pattern_id.has_value(); } -auto BuildThunk(Context& context, SemIR::FunctionId signature_id, - SemIR::SpecificId signature_specific_id, - SemIR::InstId callee_id) -> SemIR::InstId { - auto callee = SemIR::GetCalleeAsFunction(context.sem_ir(), callee_id); - - // Check whether we can use the given function without a thunk. - // TODO: For virtual functions, we want different rules for checking `self`. - // TODO: This is too strict; for example, we should not compare parameter - // names here. - if (CheckFunctionTypeMatches( - context, context.functions().Get(callee.function_id), - context.functions().Get(signature_id), signature_specific_id, - /*check_syntax=*/false, /*check_self=*/true, /*diagnose=*/false)) { - return callee_id; - } - - // From P3763: - // If the function in the interface does not have a return type, the - // program is invalid if the function in the impl specifies a return type. - // - // Call into the redeclaration checking logic to produce a suitable error. - // - // TODO: Consider a different rule: always use an explicit return type for the - // thunk, and always convert the result of the wrapped call to the return type - // of the thunk. - if (!HasDeclaredReturnType(context, signature_id) && - HasDeclaredReturnType(context, callee.function_id)) { - bool success = CheckFunctionReturnTypeMatches( - context, context.functions().Get(callee.function_id), - context.functions().Get(signature_id), signature_specific_id); - CARBON_CHECK(!success, "Return type unexpectedly matches"); - return SemIR::ErrorInst::InstId; - } - - // Create a scope for the function's parameters and generic parameters. - context.scope_stack().PushForDeclName(); - - // We can't use the function directly. Build a thunk. - // TODO: Check for and diagnose obvious reasons why this will fail, such as - // arity mismatch, before trying to build the thunk. - auto [function_id, thunk_id] = - CloneFunctionDecl(context, SemIR::LocId(callee_id), signature_id, - signature_specific_id, callee.function_id); - - // Track that this function is a thunk. - context.functions().Get(function_id).SetThunk(callee_id); - - // Register the thunk to be defined when we reach the end of the enclosing - // deferred definition scope, for example an `impl` or `class` definition, as - // if the thunk's body were written inline in this location. - context.deferred_definition_worklist().SuspendThunkAndPush( - context, { - .signature_id = signature_id, - .function_id = function_id, - .decl_id = thunk_id, - .callee_id = callee_id, - }); - - return thunk_id; -} - // Build an expression that names the value matched by a pattern. static auto BuildPatternRef(Context& context, llvm::ArrayRef arg_ids, @@ -303,16 +243,25 @@ auto PerformThunkCall(Context& context, SemIR::LocId loc_id, SemIR::InstId callee_id) -> SemIR::InstId { auto& function = context.functions().Get(function_id); + llvm::SmallVector args; + // If we have a self parameter, form `self.`. if (function.self_param_id.has_value()) { - callee_id = PerformCompoundMemberAccess( - context, loc_id, - BuildPatternRef(context, call_arg_ids, function.self_param_id), - callee_id); + auto self_arg_id = + BuildPatternRef(context, call_arg_ids, function.self_param_id); + if (IsCppConstructorOrNonMethodOperator(context, callee_id)) { + // When calling a C++ constructor to implement `Copy`, or calling a C++ + // non-method operator to implement a Carbon operator, the interface has a + // `self` parameter but C++ models that parameter as an explicit argument + // instead, so add the `self` to the argument list instead in that case. + args.push_back(self_arg_id); + } else { + callee_id = + PerformCompoundMemberAccess(context, loc_id, self_arg_id, callee_id); + } } // Form an argument list. - llvm::SmallVector args; for (auto pattern_id : context.inst_blocks().Get(function.param_patterns_id)) { args.push_back(BuildPatternRef(context, call_arg_ids, pattern_id)); @@ -423,4 +372,72 @@ auto BuildThunkDefinition(Context& context, context.scope_stack().Pop(); } +auto BuildThunk(Context& context, SemIR::FunctionId signature_id, + SemIR::SpecificId signature_specific_id, + SemIR::InstId callee_id, bool defer_definition) + -> SemIR::InstId { + auto callee = SemIR::GetCalleeAsFunction(context.sem_ir(), callee_id); + + // Check whether we can use the given function without a thunk. + // TODO: For virtual functions, we want different rules for checking `self`. + // TODO: This is too strict; for example, we should not compare parameter + // names here. + if (CheckFunctionTypeMatches( + context, context.functions().Get(callee.function_id), + context.functions().Get(signature_id), signature_specific_id, + /*check_syntax=*/false, /*check_self=*/true, /*diagnose=*/false)) { + return callee_id; + } + + // From P3763: + // If the function in the interface does not have a return type, the + // program is invalid if the function in the impl specifies a return type. + // + // Call into the redeclaration checking logic to produce a suitable error. + // + // TODO: Consider a different rule: always use an explicit return type for the + // thunk, and always convert the result of the wrapped call to the return type + // of the thunk. + if (!HasDeclaredReturnType(context, signature_id) && + HasDeclaredReturnType(context, callee.function_id)) { + bool success = CheckFunctionReturnTypeMatches( + context, context.functions().Get(callee.function_id), + context.functions().Get(signature_id), signature_specific_id); + CARBON_CHECK(!success, "Return type unexpectedly matches"); + return SemIR::ErrorInst::InstId; + } + + // Create a scope for the function's parameters and generic parameters. + context.scope_stack().PushForDeclName(); + + // We can't use the function directly. Build a thunk. + // TODO: Check for and diagnose obvious reasons why this will fail, such as + // arity mismatch, before trying to build the thunk. + auto [function_id, thunk_id] = + CloneFunctionDecl(context, SemIR::LocId(callee_id), signature_id, + signature_specific_id, callee.function_id); + + // Track that this function is a thunk. + context.functions().Get(function_id).SetThunk(callee_id); + + if (defer_definition) { + // Register the thunk to be defined when we reach the end of the enclosing + // deferred definition scope, for example an `impl` or `class` definition, + // as if the thunk's body were written inline in this location. + context.deferred_definition_worklist().SuspendThunkAndPush( + context, { + .signature_id = signature_id, + .function_id = function_id, + .decl_id = thunk_id, + .callee_id = callee_id, + }); + } else { + BuildThunkDefinition(context, signature_id, function_id, thunk_id, + callee_id); + context.scope_stack().Pop(); + } + + return thunk_id; +} + } // namespace Carbon::Check diff --git a/toolchain/check/thunk.h b/toolchain/check/thunk.h index 84628e4bc430..40d621dcc8ee 100644 --- a/toolchain/check/thunk.h +++ b/toolchain/check/thunk.h @@ -16,7 +16,8 @@ namespace Carbon::Check { // unchanged if it can be used directly. auto BuildThunk(Context& context, SemIR::FunctionId signature_id, SemIR::SpecificId signature_specific_id, - SemIR::InstId callee_id) -> SemIR::InstId; + SemIR::InstId callee_id, bool defer_definition) + -> SemIR::InstId; // Builds a call to a function that forwards a call argument list built for // `function_id` to a call to `callee_id`, for use when building a call from a diff --git a/toolchain/lower/testdata/interop/cpp/constructor.carbon b/toolchain/lower/testdata/interop/cpp/constructor.carbon index b7069cd48e76..f02ed4109f3e 100644 --- a/toolchain/lower/testdata/interop/cpp/constructor.carbon +++ b/toolchain/lower/testdata/interop/cpp/constructor.carbon @@ -35,6 +35,31 @@ fn F() { let c: Cpp.C = Cpp.C.C(); } +// ============================================================================ +// Copy constructor +// ============================================================================ + +// --- copy.h + +class Copy { + public: + Copy(); + Copy(const Copy&); + + private: + int n; +}; + +// --- call_copy.carbon + +library "[[@TEST_NAME]]"; + +import Cpp library "copy.h"; + +fn Copy(c: Cpp.Copy) -> Cpp.Copy { + return c; +} + // CHECK:STDOUT: ; ModuleID = 'import_default.carbon' // CHECK:STDOUT: source_filename = "import_default.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" @@ -112,3 +137,70 @@ fn F() { // CHECK:STDOUT: !9 = !{null} // CHECK:STDOUT: !10 = !DILocation(line: 7, column: 18, scope: !7) // CHECK:STDOUT: !11 = !DILocation(line: 6, column: 1, scope: !7) +// CHECK:STDOUT: ; ModuleID = 'call_copy.carbon' +// CHECK:STDOUT: source_filename = "call_copy.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: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CCopy.Main(ptr sret([4 x i8]) %return, ptr %c) #0 !dbg !7 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.loc7_10.1.temp = alloca [4 x i8], align 1, !dbg !13 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc7_10.1.temp), !dbg !13 +// CHECK:STDOUT: call void @_ZN4CopyC1ERKS_.carbon_thunk(ptr %c, ptr %return), !dbg !13 +// CHECK:STDOUT: ret void, !dbg !14 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline nounwind +// CHECK:STDOUT: define void @"_COp:thunk.Copy.Cpp"(ptr sret([4 x i8]) %return, ptr %self) #1 !dbg !15 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: call void @_ZN4CopyC1ERKS_.carbon_thunk(ptr %self, ptr %return), !dbg !18 +// CHECK:STDOUT: ret void, !dbg !18 +// 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)) #2 +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress +// CHECK:STDOUT: define dso_local void @_ZN4CopyC1ERKS_.carbon_thunk(ptr %0, ptr %return) #3 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.addr = alloca ptr, align 8 +// CHECK:STDOUT: %return.addr = alloca ptr, align 8 +// CHECK:STDOUT: store ptr %0, ptr %.addr, align 8 +// CHECK:STDOUT: store ptr %return, ptr %return.addr, align 8 +// CHECK:STDOUT: %1 = load ptr, ptr %return.addr, align 8 +// CHECK:STDOUT: %2 = load ptr, ptr %.addr, align 8 +// CHECK:STDOUT: call void @_ZN4CopyC1ERKS_(ptr nonnull align 4 dereferenceable(4) %1, ptr nonnull align 4 dereferenceable(4) %2) +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_ZN4CopyC1ERKS_(ptr nonnull align 4 dereferenceable(4), ptr nonnull align 4 dereferenceable(4)) unnamed_addr #4 +// CHECK:STDOUT: +// CHECK:STDOUT: attributes #0 = { nounwind } +// CHECK:STDOUT: attributes #1 = { alwaysinline nounwind } +// CHECK:STDOUT: attributes #2 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +// CHECK:STDOUT: attributes #3 = { alwaysinline mustprogress "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: attributes #4 = { "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4} +// CHECK:STDOUT: !llvm.dbg.cu = !{!5} +// 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 1, !"wchar_size", i32 4} +// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0} +// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", 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: "call_copy.carbon", directory: "") +// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "Copy", linkageName: "_CCopy.Main", scope: null, file: !6, line: 6, type: !8, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !11) +// CHECK:STDOUT: !8 = !DISubroutineType(types: !9) +// CHECK:STDOUT: !9 = !{!10, !10} +// CHECK:STDOUT: !10 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 8) +// CHECK:STDOUT: !11 = !{!12} +// CHECK:STDOUT: !12 = !DILocalVariable(arg: 1, scope: !7, type: !10) +// CHECK:STDOUT: !13 = !DILocation(line: 7, column: 10, scope: !7) +// CHECK:STDOUT: !14 = !DILocation(line: 7, column: 3, scope: !7) +// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "Op", linkageName: "_COp:thunk.Copy.Cpp", scope: null, file: !6, type: !8, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !16) +// CHECK:STDOUT: !16 = !{!17} +// CHECK:STDOUT: !17 = !DILocalVariable(arg: 1, scope: !15, type: !10) +// CHECK:STDOUT: !18 = !DILocation(line: 0, scope: !15) diff --git a/toolchain/sem_ir/inst_fingerprinter.cpp b/toolchain/sem_ir/inst_fingerprinter.cpp index bb41d3e4fde3..d0917c4f5415 100644 --- a/toolchain/sem_ir/inst_fingerprinter.cpp +++ b/toolchain/sem_ir/inst_fingerprinter.cpp @@ -264,6 +264,11 @@ struct Worklist { } auto Add(ImplId impl_id) -> void { + if (!impl_id.has_value()) { + AddInvalid(); + return; + } + const auto& impl = sem_ir->impls().Get(impl_id); Add(sem_ir->constant_values().Get(impl.self_id)); Add(sem_ir->constant_values().Get(impl.constraint_id)); diff --git a/toolchain/sem_ir/type.h b/toolchain/sem_ir/type.h index 6ee5bc4cb201..f1cee6ecbe82 100644 --- a/toolchain/sem_ir/type.h +++ b/toolchain/sem_ir/type.h @@ -212,11 +212,18 @@ class TypeStore : public Yaml::Printable { auto TryGetIntTypeInfo(TypeId int_type_id) const -> std::optional; - // Returns whether `type_id` represents a facet type. + // Returns whether `type_id` represents a valid facet type. auto IsFacetType(TypeId type_id) const -> bool { return type_id == TypeType::TypeId || Is(type_id); } + // Returns whether `type_id` represents any kind of facet type, including the + // error instruction, which can be used as a type and so should be treated as + // a facet type in some contexts. + auto IsFacetTypeOrError(TypeId type_id) const -> bool { + return IsFacetType(type_id) || type_id == ErrorInst::TypeId; + } + // Returns a list of types that were completed in this file, in the order in // which they were completed. Earlier types in this list cannot contain // instances of later types.