-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Implement support for copying C++ classes. #6434
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Implement support for copying C++ classes. #6434
Conversation
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.
| SemIR::SpecificInterface specific_interface, | ||
| llvm::ArrayRef<SemIR::InstId> values) | ||
| -> EvalImplLookupResult { | ||
| auto& interface = context.interfaces().Get(specific_interface.interface_id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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}); |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
| 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); | ||
| } |
There was a problem hiding this comment.
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
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); |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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(); |
There was a problem hiding this comment.
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?
When performing impl lookup for
Core.Copyfor 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.