Skip to content

Commit 372f632

Browse files
zygoloiddanakjCarbonInfraBot
authored
Implement support for copying C++ classes. (#6434)
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 `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. --------- Co-authored-by: Dana Jansens <[email protected]> Co-authored-by: Carbon Infra Bot <[email protected]>
1 parent 19660cc commit 372f632

File tree

16 files changed

+923
-127
lines changed

16 files changed

+923
-127
lines changed

toolchain/check/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ cc_library(
2424
"cpp/access.cpp",
2525
"cpp/call.cpp",
2626
"cpp/custom_type_mapping.cpp",
27+
"cpp/impl_lookup.cpp",
2728
"cpp/import.cpp",
2829
"cpp/location.cpp",
2930
"cpp/macros.cpp",
@@ -77,6 +78,7 @@ cc_library(
7778
"cpp/access.h",
7879
"cpp/call.h",
7980
"cpp/custom_type_mapping.h",
81+
"cpp/impl_lookup.h",
8082
"cpp/import.h",
8183
"cpp/location.h",
8284
"cpp/macros.h",
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
5+
#include "toolchain/check/cpp/impl_lookup.h"
6+
7+
#include "clang/Sema/Sema.h"
8+
#include "toolchain/base/kind_switch.h"
9+
#include "toolchain/check/cpp/import.h"
10+
#include "toolchain/check/cpp/location.h"
11+
#include "toolchain/check/cpp/overload_resolution.h"
12+
#include "toolchain/check/impl.h"
13+
#include "toolchain/check/impl_lookup.h"
14+
#include "toolchain/check/import_ref.h"
15+
#include "toolchain/check/inst.h"
16+
#include "toolchain/check/type.h"
17+
#include "toolchain/sem_ir/ids.h"
18+
#include "toolchain/sem_ir/typed_insts.h"
19+
20+
namespace Carbon::Check {
21+
22+
// If the given type is a C++ class type, returns the corresponding class
23+
// declaration. Otherwise returns nullptr.
24+
// TODO: Handle qualified types.
25+
static auto TypeAsClassDecl(Context& context, SemIR::TypeId type_id)
26+
-> clang::CXXRecordDecl* {
27+
auto class_type = context.types().TryGetAs<SemIR::ClassType>(type_id);
28+
if (!class_type) {
29+
// Not a class.
30+
return nullptr;
31+
}
32+
33+
SemIR::NameScopeId class_scope_id =
34+
context.classes().Get(class_type->class_id).scope_id;
35+
if (!class_scope_id.has_value()) {
36+
return nullptr;
37+
}
38+
39+
const auto& scope = context.name_scopes().Get(class_scope_id);
40+
auto decl_id = scope.clang_decl_context_id();
41+
if (!decl_id.has_value()) {
42+
return nullptr;
43+
}
44+
45+
return dyn_cast<clang::CXXRecordDecl>(
46+
context.clang_decls().Get(decl_id).key.decl);
47+
}
48+
49+
// Builds a witness that the given type implements the given interface,
50+
// populating it with the specified set of values. Returns a corresponding
51+
// lookup result. Produces a diagnostic and returns `None` if the specified
52+
// values aren't suitable for the interface.
53+
static auto BuildWitness(Context& context, SemIR::LocId loc_id,
54+
SemIR::TypeId self_type_id,
55+
SemIR::SpecificInterface specific_interface,
56+
llvm::ArrayRef<SemIR::InstId> values)
57+
-> SemIR::InstId {
58+
const auto& interface =
59+
context.interfaces().Get(specific_interface.interface_id);
60+
auto assoc_entities =
61+
context.inst_blocks().GetOrEmpty(interface.associated_entities_id);
62+
if (assoc_entities.size() != values.size()) {
63+
context.TODO(loc_id, ("Unsupported definition of interface " +
64+
context.names().GetFormatted(interface.name_id))
65+
.str());
66+
return SemIR::ErrorInst::InstId;
67+
}
68+
69+
// Prepare an empty witness table.
70+
auto witness_table_id =
71+
context.inst_blocks().AddUninitialized(assoc_entities.size());
72+
auto witness_table = context.inst_blocks().GetMutable(witness_table_id);
73+
for (auto& witness_value_id : witness_table) {
74+
witness_value_id = SemIR::InstId::ImplWitnessTablePlaceholder;
75+
}
76+
77+
// Build a witness. We use an `ImplWitness` with an `impl_id` of `None` to
78+
// represent a synthesized witness.
79+
// TODO: Stop using `ImplWitnessTable` here and add a distinct instruction
80+
// that doesn't contain an `InstId` and supports deduplication.
81+
auto witness_table_inst_id = AddInst<SemIR::ImplWitnessTable>(
82+
context, loc_id,
83+
{.elements_id = witness_table_id, .impl_id = SemIR::ImplId::None});
84+
auto witness_id = AddInst<SemIR::ImplWitness>(
85+
context, loc_id,
86+
{.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId),
87+
.witness_table_id = witness_table_inst_id,
88+
.specific_id = SemIR::SpecificId::None});
89+
90+
// Fill in the witness table.
91+
for (const auto& [assoc_entity_id, value_id, witness_value_id] :
92+
llvm::zip_equal(assoc_entities, values, witness_table)) {
93+
LoadImportRef(context, assoc_entity_id);
94+
auto decl_id =
95+
context.constant_values().GetInstId(SemIR::GetConstantValueInSpecific(
96+
context.sem_ir(), specific_interface.specific_id, assoc_entity_id));
97+
CARBON_CHECK(decl_id.has_value(), "Non-constant associated entity");
98+
auto decl = context.insts().Get(decl_id);
99+
CARBON_KIND_SWITCH(decl) {
100+
case CARBON_KIND(SemIR::StructValue struct_value): {
101+
if (struct_value.type_id == SemIR::ErrorInst::TypeId) {
102+
return SemIR::ErrorInst::InstId;
103+
}
104+
witness_value_id = CheckAssociatedFunctionImplementation(
105+
context,
106+
context.types().GetAs<SemIR::FunctionType>(struct_value.type_id),
107+
value_id, self_type_id, witness_id,
108+
/*defer_thunk_definition=*/false);
109+
break;
110+
}
111+
case SemIR::AssociatedConstantDecl::Kind: {
112+
context.TODO(loc_id,
113+
"Associated constant in interface with synthesized impl");
114+
return SemIR::ErrorInst::InstId;
115+
}
116+
default:
117+
CARBON_CHECK(decl_id == SemIR::ErrorInst::InstId,
118+
"Unexpected kind of associated entity {0}", decl);
119+
return SemIR::ErrorInst::InstId;
120+
}
121+
}
122+
123+
return witness_id;
124+
}
125+
126+
static auto LookupCopyImpl(Context& context, SemIR::LocId loc_id,
127+
SemIR::TypeId self_type_id,
128+
SemIR::SpecificInterface specific_interface)
129+
-> SemIR::InstId {
130+
auto* class_decl = TypeAsClassDecl(context, self_type_id);
131+
if (!class_decl) {
132+
// TODO: Should we also provide a `Copy` implementation for enumerations?
133+
return SemIR::InstId::None;
134+
}
135+
136+
auto* ctor = context.clang_sema().LookupCopyingConstructor(
137+
class_decl, clang::Qualifiers::Const);
138+
if (!ctor) {
139+
// TODO: If the impl lookup failure is an error, we should produce a
140+
// diagnostic explaining why the class is not copyable.
141+
return SemIR::InstId::None;
142+
}
143+
144+
auto ctor_id =
145+
context.clang_sema().DiagnoseUseOfOverloadedDecl(
146+
ctor, GetCppLocation(context, loc_id))
147+
? SemIR::ErrorInst::InstId
148+
: ImportCppFunctionDecl(context, loc_id, ctor, /*num_params=*/1);
149+
if (auto ctor_decl =
150+
context.insts().TryGetAsWithId<SemIR::FunctionDecl>(ctor_id)) {
151+
CheckCppOverloadAccess(context, loc_id,
152+
clang::DeclAccessPair::make(ctor, ctor->getAccess()),
153+
ctor_decl->inst_id);
154+
} else {
155+
CARBON_CHECK(ctor_id == SemIR::ErrorInst::InstId);
156+
return SemIR::ErrorInst::InstId;
157+
}
158+
return BuildWitness(context, loc_id, self_type_id, specific_interface,
159+
{ctor_id});
160+
}
161+
162+
auto LookupCppImpl(Context& context, SemIR::LocId loc_id,
163+
SemIR::TypeId self_type_id,
164+
SemIR::SpecificInterface specific_interface,
165+
const TypeStructure* best_impl_type_structure,
166+
SemIR::LocId best_impl_loc_id) -> SemIR::InstId {
167+
// Determine whether this is an interface that we have special knowledge of.
168+
auto& interface = context.interfaces().Get(specific_interface.interface_id);
169+
if (!context.name_scopes().IsCorePackage(interface.parent_scope_id)) {
170+
return SemIR::InstId::None;
171+
}
172+
if (!interface.name_id.AsIdentifierId().has_value()) {
173+
return SemIR::InstId::None;
174+
}
175+
176+
if (context.identifiers().Get(interface.name_id.AsIdentifierId()) == "Copy") {
177+
return LookupCopyImpl(context, loc_id, self_type_id, specific_interface);
178+
}
179+
180+
// TODO: Handle other interfaces.
181+
182+
// TODO: Infer a C++ type structure and check whether it's less strict than
183+
// the best Carbon type structure.
184+
static_cast<void>(best_impl_type_structure);
185+
static_cast<void>(best_impl_loc_id);
186+
187+
return SemIR::InstId::None;
188+
}
189+
190+
} // namespace Carbon::Check

toolchain/check/cpp/impl_lookup.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
5+
#ifndef CARBON_TOOLCHAIN_CHECK_CPP_IMPL_LOOKUP_H_
6+
#define CARBON_TOOLCHAIN_CHECK_CPP_IMPL_LOOKUP_H_
7+
8+
#include "toolchain/check/context.h"
9+
#include "toolchain/check/impl_lookup.h"
10+
#include "toolchain/check/type_structure.h"
11+
#include "toolchain/sem_ir/ids.h"
12+
#include "toolchain/sem_ir/specific_interface.h"
13+
14+
namespace Carbon::Check {
15+
16+
// Performs lookup for an impl witness for a query involving C++ types. Returns
17+
// a witness value, or `None` if a synthesized C++ witness should not be used.
18+
//
19+
// If `interface` is an interface for which we can synthesize a witness based on
20+
// C++ operator overloads or special member functions, performs the suitable C++
21+
// lookup to determine if this interface should be considered implemented for
22+
// the specified type, and if so, synthesizes and returns a suitable witness.
23+
//
24+
// `best_impl_type_structure` provides the type structure of the best-matching
25+
// impl declaration. If this is better than every viable C++ candidate, a "none"
26+
// result will be returned. If this is worse than the best viable C++ candidate
27+
// according to C++ rules, a witness for the C++ candidate will be returned.
28+
// Otherwise, it is at least as good as the best viable C++ candidate, but there
29+
// is some C++ candidate that has a better type structure, in which case the
30+
// result is ambiguous and we diagnose an error. This parameter can be null if
31+
// there is no usable impl for this query.
32+
//
33+
// `best_impl_loc_id` gives the location of the impl corresponding to the best
34+
// type structure, and can be `None` if `best_impl_type_structure` is null. This
35+
// parameter is used only for ambiguity diagnostics.
36+
auto LookupCppImpl(Context& context, SemIR::LocId loc_id,
37+
SemIR::TypeId self_type_id,
38+
SemIR::SpecificInterface specific_interface,
39+
const TypeStructure* best_impl_type_structure,
40+
SemIR::LocId best_impl_loc_id) -> SemIR::InstId;
41+
42+
} // namespace Carbon::Check
43+
44+
#endif // CARBON_TOOLCHAIN_CHECK_CPP_IMPL_LOOKUP_H_

toolchain/check/cpp/import.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "toolchain/check/convert.h"
4141
#include "toolchain/check/cpp/access.h"
4242
#include "toolchain/check/cpp/custom_type_mapping.h"
43+
#include "toolchain/check/cpp/location.h"
4344
#include "toolchain/check/cpp/macros.h"
4445
#include "toolchain/check/cpp/thunk.h"
4546
#include "toolchain/check/diagnostic_helpers.h"
@@ -1809,6 +1810,11 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
18091810
function_info.SetHasCppThunk(thunk_function_decl_id);
18101811
}
18111812
}
1813+
} else {
1814+
// Inform Clang that the function has been referenced. This will trigger
1815+
// instantiation if needed.
1816+
context.clang_sema().MarkFunctionReferenced(GetCppLocation(context, loc_id),
1817+
clang_decl);
18121818
}
18131819

18141820
return function_info.first_owning_decl_id;

toolchain/check/cpp/operators.cpp

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ static auto GetClangOperatorKind(Context& context, SemIR::LocId loc_id,
2525
-> std::optional<clang::OverloadedOperatorKind> {
2626
// Unary operators.
2727
if (interface_name == "Destroy" || interface_name == "As" ||
28-
interface_name == "ImplicitAs") {
28+
interface_name == "ImplicitAs" || interface_name == "Copy") {
2929
// TODO: Support destructors and conversions.
3030
return std::nullopt;
3131
}
@@ -280,17 +280,37 @@ auto IsCppOperatorMethodDecl(clang::Decl* decl) -> bool {
280280
return clang_method_decl && clang_method_decl->isOverloadedOperator();
281281
}
282282

283-
auto IsCppOperatorMethod(Context& context, SemIR::InstId inst_id) -> bool {
283+
static auto GetAsCppFunctionDecl(Context& context, SemIR::InstId inst_id)
284+
-> clang::FunctionDecl* {
284285
auto function_type = context.types().TryGetAs<SemIR::FunctionType>(
285286
context.insts().Get(inst_id).type_id());
286287
if (!function_type) {
287-
return false;
288+
return nullptr;
288289
}
289290
SemIR::ClangDeclId clang_decl_id =
290291
context.functions().Get(function_type->function_id).clang_decl_id;
291-
return clang_decl_id.has_value() &&
292-
IsCppOperatorMethodDecl(
293-
context.clang_decls().Get(clang_decl_id).key.decl);
292+
return clang_decl_id.has_value()
293+
? dyn_cast<clang::FunctionDecl>(
294+
context.clang_decls().Get(clang_decl_id).key.decl)
295+
: nullptr;
296+
}
297+
298+
auto IsCppOperatorMethod(Context& context, SemIR::InstId inst_id) -> bool {
299+
auto* function_decl = GetAsCppFunctionDecl(context, inst_id);
300+
return function_decl && IsCppOperatorMethodDecl(function_decl);
301+
}
302+
303+
auto IsCppConstructorOrNonMethodOperator(Context& context,
304+
SemIR::InstId inst_id) -> bool {
305+
auto* function_decl = GetAsCppFunctionDecl(context, inst_id);
306+
if (!function_decl) {
307+
return false;
308+
}
309+
if (isa<clang::CXXConstructorDecl>(function_decl)) {
310+
return true;
311+
}
312+
return !isa<clang::CXXMethodDecl>(function_decl) &&
313+
function_decl->isOverloadedOperator();
294314
}
295315

296316
} // namespace Carbon::Check

toolchain/check/cpp/operators.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ auto IsCppOperatorMethodDecl(clang::Decl* decl) -> bool;
2525
// than as the first argument.
2626
auto IsCppOperatorMethod(Context& context, SemIR::InstId inst_id) -> bool;
2727

28+
// Returns whether the specified instruction refers to a C++ constructor or
29+
// non-operator method. If so, when mapping from a Carbon interface to a C++
30+
// call, we pass a `self` parameter as the first argument instead.
31+
auto IsCppConstructorOrNonMethodOperator(Context& context,
32+
SemIR::InstId inst_id) -> bool;
33+
2834
} // namespace Carbon::Check
2935

3036
#endif // CARBON_TOOLCHAIN_CHECK_CPP_OPERATORS_H_

toolchain/check/cpp/overload_resolution.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
162162
case clang::OverloadingResult::OR_Success: {
163163
CARBON_CHECK(best_viable_fn->Function);
164164
CARBON_CHECK(!best_viable_fn->RewriteKind);
165-
sema.MarkFunctionReferenced(loc, best_viable_fn->Function);
166165
SemIR::InstId result_id = ImportCppFunctionDecl(
167166
context, loc_id, best_viable_fn->Function, arg_exprs.size());
168167
if (auto fn_decl =

0 commit comments

Comments
 (0)