Files
carbon-lang/toolchain/check/custom_witness.cpp
T
Christopher Di BellaandGeoff Romer 4d0003765d refactors LookupCppImpl to handle multiple associated functions (#6816)
`LookupCppImpl` is used to find associated functions for a witness. As
some witnesses contain multiple associated functions, we need robust
mechanims for looking up C++ components.

The logic in `LookupCppImpl` is primarily concerned with finding exactly
one C++ declaration at a time. In order to handle witnesses with more
than one associated function, we move the bulk of `LookupCppImpl` to a
new function called `FindCppAssociatedFunction`. This frees up
`CppLookupImpl` to delegate to `FindCppAssociatedFunction` when a
witness has only one associated function, and to functions that are able
to compose multiple associated functions.

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-03-04 20:56:25 +00:00

387 lines
15 KiB
C++

// 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/custom_witness.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/facet_type.h"
#include "toolchain/check/function.h"
#include "toolchain/check/generic.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/name_lookup.h"
#include "toolchain/check/type.h"
#include "toolchain/sem_ir/builtin_function_kind.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
// Given a value whose type `IsFacetTypeOrError`, returns the corresponding
// type.
static auto GetFacetAsType(Context& context,
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<SemIR::FacetType>(type_type_id)) {
// It's a facet; access its type.
facet_or_type_id = context.types().GetTypeInstId(
GetFacetAccessType(context, facet_or_type_id));
}
return context.types().GetTypeIdForTypeInstId(facet_or_type_id);
}
// Returns the body for `Destroy.Op`. This will return `None` if using the
// builtin `NoOp` is appropriate.
//
// TODO: This is a placeholder still not actually destroying things, intended to
// maintain mostly-consistent behavior with current logic while working. That
// also means using `self`.
// TODO: This mirrors `TypeCanDestroy` below, think about ways to share what's
// handled.
static auto MakeDestroyOpBody(Context& context, SemIR::LocId loc_id,
SemIR::TypeId self_type_id)
-> SemIR::InstBlockId {
context.inst_block_stack().Push();
auto inst = context.types().GetAsInst(self_type_id);
while (auto class_type = inst.TryAs<SemIR::ClassType>()) {
// Switch to looking at the object representation.
auto class_info = context.classes().Get(class_type->class_id);
CARBON_CHECK(class_info.is_complete());
inst = context.types().GetAsInst(
class_info.GetObjectRepr(context.sem_ir(), class_type->specific_id));
}
CARBON_KIND_SWITCH(inst) {
case SemIR::ArrayType::Kind:
case SemIR::ConstType::Kind:
case SemIR::MaybeUnformedType::Kind:
case SemIR::PartialType::Kind:
case SemIR::StructType::Kind:
case SemIR::TupleType::Kind:
// TODO: Implement iterative destruction of types.
break;
case SemIR::BoolType::Kind:
case SemIR::FloatType::Kind:
case SemIR::IntType::Kind:
case SemIR::PointerType::Kind:
// For trivially destructible types, we don't generate anything, so that
// this can collapse to a noop implementation when possible.
break;
case SemIR::ErrorInst::Kind:
// Errors can't be destroyed, but we'll still try to generate calls for
// other members.
break;
default:
CARBON_FATAL("Unexpected type for destroy: {0}", inst);
}
if (context.inst_block_stack().PeekCurrentBlockContents().empty()) {
context.inst_block_stack().PopAndDiscard();
return SemIR::InstBlockId::None;
}
AddInst(context, loc_id, SemIR::Return{});
return context.inst_block_stack().Pop();
}
// Returns a manufactured `Destroy.Op` function with the `self` parameter typed
// to `self_type_id`.
static auto MakeDestroyOpFunction(Context& context, SemIR::LocId loc_id,
SemIR::TypeId self_type_id,
SemIR::NameScopeId parent_scope_id)
-> SemIR::InstId {
auto name_id = context.core_identifiers().AddNameId(CoreIdentifier::Op);
auto [decl_id, function_id] =
MakeGeneratedFunctionDecl(context, loc_id,
{.parent_scope_id = parent_scope_id,
.name_id = name_id,
.self_type_id = self_type_id});
auto& function = context.functions().Get(function_id);
auto body_id = MakeDestroyOpBody(context, loc_id, self_type_id);
if (body_id.has_value()) {
function.SetCoreWitness();
function.body_block_ids.push_back(body_id);
} else {
function.SetCoreWitness(SemIR::BuiltinFunctionKind::NoOp);
}
return decl_id;
}
static auto MakeCustomWitnessConstantInst(
Context& context, SemIR::LocId loc_id,
SemIR::SpecificInterfaceId query_specific_interface_id,
SemIR::InstBlockId associated_entities_block_id) -> SemIR::InstId {
// The witness is a CustomWitness of the query interface with a table that
// contains each associated entity.
auto const_id = EvalOrAddInst<SemIR::CustomWitness>(
context, loc_id,
{.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId),
.elements_id = associated_entities_block_id,
.query_specific_interface_id = query_specific_interface_id});
return context.constant_values().GetInstId(const_id);
}
struct TypesForSelfFacet {
// A FacetType that contains only the query interface.
SemIR::TypeId facet_type_for_query_specific_interface;
// The query self as a type, which involves a conversion if it was a facet.
SemIR::TypeId query_self_as_type_id;
};
static auto GetTypesForSelfFacet(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id)
-> TypesForSelfFacet {
const auto query_specific_interface =
context.specific_interfaces().Get(query_specific_interface_id);
// The Self facet will have type FacetType, for the query interface.
auto facet_type_for_query_specific_interface =
context.types().GetTypeIdForTypeConstantId(
EvalOrAddInst<SemIR::FacetType>(
context, loc_id,
FacetTypeFromInterface(context,
query_specific_interface.interface_id,
query_specific_interface.specific_id)));
// The Self facet needs to point to a type value. If it's not one already,
// convert to type.
auto query_self_as_type_id = GetFacetAsType(context, query_self_const_id);
return {facet_type_for_query_specific_interface, query_self_as_type_id};
}
// Build a new facet from the query self, using a CustomWitness for the query
// interface with an entry for each associated entity so far.
static auto MakeSelfFacetWithCustomWitness(
Context& context, SemIR::LocId loc_id, TypesForSelfFacet query_types,
SemIR::SpecificInterfaceId query_specific_interface_id,
SemIR::InstBlockId associated_entities_block_id) -> SemIR::ConstantId {
// We are building a facet value for a single interface, so the witness block
// is a single witness for that interface.
auto witnesses_block_id =
context.inst_blocks().Add({MakeCustomWitnessConstantInst(
context, loc_id, query_specific_interface_id,
associated_entities_block_id)});
return EvalOrAddInst<SemIR::FacetValue>(
context, loc_id,
{.type_id = query_types.facet_type_for_query_specific_interface,
.type_inst_id =
context.types().GetTypeInstId(query_types.query_self_as_type_id),
.witnesses_block_id = witnesses_block_id});
}
auto BuildCustomWitness(Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id,
llvm::ArrayRef<SemIR::InstId> values) -> SemIR::InstId {
const auto query_specific_interface =
context.specific_interfaces().Get(query_specific_interface_id);
const auto& interface =
context.interfaces().Get(query_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;
}
auto query_types_for_self_facet = GetTypesForSelfFacet(
context, loc_id, query_self_const_id, query_specific_interface_id);
// The values that will go in the witness table.
llvm::SmallVector<SemIR::InstId> entries;
// Fill in the witness table.
for (const auto& [assoc_entity_id, value_id] :
llvm::zip_equal(assoc_entities, values)) {
CARBON_DCHECK(value_id != SemIR::InstId::None);
CARBON_DCHECK(value_id != SemIR::ErrorInst::InstId);
LoadImportRef(context, assoc_entity_id);
// Build a witness with the current contents of the witness table. This will
// grow as we progress through the impl. In theory this will build O(n^2)
// table entries, but in practice n <= 2, so that's OK.
//
// This is necessary because later associated entities may refer to earlier
// associated entities in their signatures. In particular, an associated
// result type may be used as the return type of an associated function.
auto self_facet = MakeSelfFacetWithCustomWitness(
context, loc_id, query_types_for_self_facet,
query_specific_interface_id, context.inst_blocks().Add(entries));
auto interface_with_self_specific_id = MakeSpecificWithInnerSelf(
context, loc_id, interface.generic_id, interface.generic_with_self_id,
query_specific_interface.specific_id, self_facet);
auto decl_id =
context.constant_values().GetInstId(SemIR::GetConstantValueInSpecific(
context.sem_ir(), interface_with_self_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;
}
// TODO: If a thunk is needed, this will build a different value each
// time it's called, so we won't properly deduplicate repeated
// witnesses.
entries.push_back(CheckAssociatedFunctionImplementation(
context,
context.types().GetAs<SemIR::FunctionType>(struct_value.type_id),
query_specific_interface.specific_id, value_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;
}
}
// TODO: Consider building one witness after all associated constants, and
// then a second after all associated functions, rather than building one in
// each `StructValue`. Right now the code is written assuming at most one
// function, though this CHECK can be removed as a temporary workaround.
CARBON_CHECK(entries.size() <= 1,
"TODO: Support multiple associated functions");
return MakeCustomWitnessConstantInst(context, loc_id,
query_specific_interface_id,
context.inst_blocks().Add(entries));
}
auto GetCoreInterface(Context& context, SemIR::InterfaceId interface_id)
-> CoreInterface {
const auto& interface = context.interfaces().Get(interface_id);
if (!context.name_scopes().IsCorePackage(interface.parent_scope_id) ||
!interface.name_id.AsIdentifierId().has_value()) {
return CoreInterface::Unknown;
}
for (auto [core_identifier, core_interface] :
{std::pair{CoreIdentifier::Copy, CoreInterface::Copy},
std::pair{CoreIdentifier::Destroy, CoreInterface::Destroy}}) {
if (interface.name_id ==
context.core_identifiers().AddNameId(core_identifier)) {
return core_interface;
}
}
return CoreInterface::Unknown;
}
// Returns true if the `Self` should impl `Destroy`.
static auto TypeCanDestroy(Context& context,
SemIR::ConstantId query_self_const_id,
SemIR::InterfaceId destroy_interface_id) -> bool {
auto inst = context.insts().Get(context.constant_values().GetInstId(
GetCanonicalFacetOrTypeValue(context, query_self_const_id)));
// For facet values, look if the FacetType provides the same.
if (auto facet_type =
context.types().TryGetAs<SemIR::FacetType>(inst.type_id())) {
const auto& info = context.facet_types().Get(facet_type->facet_type_id);
for (auto interface : info.extend_constraints) {
if (interface.interface_id == destroy_interface_id) {
return true;
}
}
}
CARBON_KIND_SWITCH(inst) {
case CARBON_KIND(SemIR::ClassType class_type): {
auto class_info = context.classes().Get(class_type.class_id);
// Incomplete and abstract classes can't be destroyed.
if (!class_info.is_complete() ||
class_info.inheritance_kind ==
SemIR::Class::InheritanceKind::Abstract) {
return false;
}
// `LookupCppImpl` handles C++ types.
if (context.name_scopes().Get(class_info.scope_id).is_cpp_scope()) {
return false;
}
// TODO: Return false if the object repr doesn't impl `Destroy`.
return true;
}
case SemIR::ArrayType::Kind:
case SemIR::ConstType::Kind:
case SemIR::MaybeUnformedType::Kind:
case SemIR::PartialType::Kind:
case SemIR::StructType::Kind:
case SemIR::TupleType::Kind:
// TODO: Return false for types that indirectly reference a type that
// doesn't impl `Destroy`.
return true;
case SemIR::BoolType::Kind:
case SemIR::FloatType::Kind:
case SemIR::IntType::Kind:
case SemIR::PointerType::Kind:
// Trivially destructible.
return true;
default:
return false;
}
}
auto LookupCustomWitness(Context& context, SemIR::LocId loc_id,
CoreInterface core_interface,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id)
-> std::optional<SemIR::InstId> {
// TODO: Handle more interfaces, particularly copy, move, and conversion.
if (core_interface != CoreInterface::Destroy) {
return std::nullopt;
}
auto query_specific_interface =
context.specific_interfaces().Get(query_specific_interface_id);
if (!TypeCanDestroy(context, query_self_const_id,
query_specific_interface.interface_id)) {
return std::nullopt;
}
if (query_self_const_id.is_symbolic()) {
return SemIR::InstId::None;
}
// Mark functions with the interface's scope as a hint to mangling. This does
// not add them to the scope.
auto parent_scope_id = context.interfaces()
.Get(query_specific_interface.interface_id)
.scope_without_self_id;
auto self_type_id = GetFacetAsType(context, query_self_const_id);
auto op_id =
MakeDestroyOpFunction(context, loc_id, self_type_id, parent_scope_id);
return BuildCustomWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, {op_id});
}
} // namespace Carbon::Check