Skip to content

Conversation

@zygoloid
Copy link
Contributor

When performing impl lookup for Core.Copy for a C++ class type, look for a copy constructor. If we find one, synthesize an impl witness that calls the constructor.

This adds initial support for impl lookup to delegate to the C++ interop logic for queries involving C++ types. For now, we don't implement the rules from #6166 that compare a synthesized type structure for the C++ impl against the best Carbon type structure, but the framework for building that support is established here.

Currently there is no caching of the lookup here, and we build unique ImplWitnessTables for each lookup, which leads to each impl lookup producing a distinct facet value. This results in some errors in generic contexts; this will be addressed in follow-up changes. This PR aims only to support the non-generic case.

When performing impl lookup for `Core.Copy` for a C++ class type, look
for a copy constructor. If we find one, synthesize an impl witness that
calls the constructor.

This adds initial support for impl lookup to delegate to the C++ interop
logic for queries involving C++ types. For now, we don't implement the
rules from carbon-language#6166 that compare a synthesized type structure for the C++
impl against the best Carbon type structure, but the framework for
building that support is established here.

Currently there is no caching of the lookup here, and we build unique
`ImplWitnessTable`s for each lookup, which leads to each impl lookup
producing a distinct facet value. This results in some errors in generic
contexts; this will be addressed in follow-up changes. This PR aims only
to support the non-generic case.
@zygoloid zygoloid requested a review from a team as a code owner November 25, 2025 23:16
@zygoloid zygoloid requested review from danakj and removed request for a team November 25, 2025 23:16
SemIR::SpecificInterface specific_interface,
llvm::ArrayRef<SemIR::InstId> values)
-> EvalImplLookupResult {
auto& interface = context.interfaces().Get(specific_interface.interface_id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto& interface = context.interfaces().Get(specific_interface.interface_id);
const auto& interface = context.interfaces().Get(specific_interface.interface_id);

return nullptr;
}

auto& scope = context.name_scopes().Get(class_scope_id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto& scope = context.name_scopes().Get(class_scope_id);
const auto& scope = context.name_scopes().Get(class_scope_id);

// represent a synthesized witness.
auto witness_table_inst_id = AddInst<SemIR::ImplWitnessTable>(
context, loc_id,
{.elements_id = witness_table_id, .impl_id = SemIR::ImplId::None});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would really prefer we introduce a special ImplId::Cpp for this, if that seems like it would work?

Of course, then code has to check for it before looking in ImplStore (and in printing). But it would make the semir more clear I think?

SemIR::TypeId self_type_id,
SemIR::SpecificInterface specific_interface,
const TypeStructure* best_impl_type_structure,
SemIR::LocId best_impl_loc_id) -> EvalImplLookupResult {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would prefer if all of these functions returned SemIR::InstBlockIdOrError, which is the output of the more-public LookupImplWitness(). The eval stuff is a bit of an implementation detail, only in the header to make it visible to eval. Is there any reason why we couldn't do that?

return candidates;
}

// Given a value that is either a type or a non-type facet, returns the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"non-type facet" reads to me at first like a symbolic non-type, but I think this is saying a "type or a facet value", so could we write it that way? That's how I worded similar in other places.

Comment on lines +902 to 919
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().IsFacetType(type_type_id) ||
type_type_id == SemIR::ErrorInst::TypeId);

if (context.types().Is<SemIR::FacetType>(type_type_id)) {
// It's a facet; access its type.
facet_or_type_id = GetOrAddInst<SemIR::FacetAccessType>(
context, loc_id,
{.type_id = SemIR::TypeType::TypeId,
.facet_value_inst_id = facet_or_type_id});
}
return context.types().GetTypeIdForTypeInstId(facet_or_type_id);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is a cheaper version of calling ExprAsType, with CHECKs involved, right? I had thought of replacing some other places that construct FacetAccessType with ExprAsType. Like

https://github.com/carbon-language/carbon-lang/blob/a179bd461b4a07bf2defe91e68f1bb9edfa60551/toolchain/check/handle_require.cpp#L74C1-L77C1

Maybe this should go live beside ExprAsType so we can reuse it?

}

if (query_is_concrete && candidates.consider_cpp_candidates) {
CARBON_CHECK(!self_facet_provides_witness);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we leave a comment explaining why this is true?


if (query_is_concrete && candidates.consider_cpp_candidates) {
CARBON_CHECK(!self_facet_provides_witness);
return LookupCppImpl(context, loc_id,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return LookupCppImpl(context, loc_id,
// No Carbon candidates were found for the concrete query. Try find a C++ one, without citing
// a Carbon candidate to compare with.
return LookupCppImpl(context, loc_id,

}

if (query_is_concrete && candidates.consider_cpp_candidates) {
auto cpp_result = LookupCppImpl(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto cpp_result = LookupCppImpl(
// We prefer a C++ candidate if it's a better match than the Carbon candidate we have found.
auto cpp_result = LookupCppImpl(


auto Add(ImplId impl_id) -> void {
if (!impl_id.has_value()) {
AddInvalid();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this a bit surprising? I mean that a C++ witness isn't invalid, I'd expect that it fingerprints reasonably. Maybe AddInvalid isn't as problematic as I am imagining?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants