Skip to content

Commit 7209ad7

Browse files
jonmeowgeoffromer
andauthored
Generate Destroy impls for classes (#5873)
Although this focused on `Destroy` support, some choices here around `implicit_type_impls` are because copy/move will likely follow a similar approach. I'm trying not to predict too much about how we'll structure those, but I'm putting `Destroy` impl logic in a file that could perhaps be shared with those. They'd likely be interested in similar things, e.g. traversing members of types (particularly class, struct literal, tuple literal). At present this sets the destroy function as `no_op` which is consistent with current logic, but has a TODO to correctly define. Constant importing for functions changes slightly due to some issues I was having with `GetFunctionType`. zygoloid suggested this approach to avoid `EvalInst` logic. Adds a flag for controlling whether to generating these impls. While this does generation for `class`, as noted above this'll also need to be done for tuples and struct literals, which would leave the `none.carbon` min_prelude unable to use any types. Note if destruction *would* occur, it'll still look up `Core.Destroy` for that and fail, but that's already true of any test using `none.carbon`. I'm trying to use the flag to see if we can keep `none.carbon` working mostly-consistently. I'd tried separating out the flag to #5852, but that got a lot of pushback over whether the behavior was appropriate. I'm hoping that the interactions here make it clearer why the particular approach -- the goal is not to enable advanced testing, or create some new end-user behavior that we really support, it's just to keep no-prelude tests functional. The main question raised there was why not just keep generating `impl T as Core.Destroy` if `fn destroy` is present -- but I think here it should be apparent that would require additional complexity, as the generation of `impl T as Core.Destroy` is not currently conditioned based on the implementation of `fn destroy`. I'd rather add complexity to this flag only if it's enabling interesting test functionality. --------- Co-authored-by: Geoff Romer <[email protected]>
1 parent a5a5e38 commit 7209ad7

File tree

215 files changed

+22249
-2732
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

215 files changed

+22249
-2732
lines changed

toolchain/check/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ cc_library(
3333
"impl.cpp",
3434
"impl_lookup.cpp",
3535
"impl_validation.cpp",
36+
"implicit_type_impls.cpp",
3637
"import.cpp",
3738
"import_cpp.cpp",
3839
"import_ref.cpp",
@@ -78,6 +79,7 @@ cc_library(
7879
"impl.h",
7980
"impl_lookup.h",
8081
"impl_validation.h",
82+
"implicit_type_impls.h",
8183
"import.h",
8284
"import_cpp.h",
8385
"import_ref.h",

toolchain/check/check.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ auto CheckParseTrees(
448448
check_index < static_cast<int>(ready_to_check.size()); ++check_index) {
449449
auto* unit_info = ready_to_check[check_index];
450450
CheckUnit(unit_info, &tree_and_subtrees_getters, fs, clang_invocation,
451-
options.vlog_stream)
451+
options.gen_implicit_type_impls, options.vlog_stream)
452452
.Run();
453453
for (auto* incoming_import : unit_info->incoming_imports) {
454454
--incoming_import->imports_remaining;
@@ -497,7 +497,7 @@ auto CheckParseTrees(
497497
for (auto& unit_info : unit_infos) {
498498
if (unit_info.imports_remaining > 0) {
499499
CheckUnit(&unit_info, &tree_and_subtrees_getters, fs, clang_invocation,
500-
options.vlog_stream)
500+
options.gen_implicit_type_impls, options.vlog_stream)
501501
.Run();
502502
}
503503
}

toolchain/check/check.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ struct CheckParseTreesOptions {
4040
// Whether to import the prelude.
4141
bool prelude_import = false;
4242

43+
// Whether to generate standard `impl`s for types, such as `Core.Destroy`.
44+
// This only controls generation of the `impl`; code which expects the `impl`
45+
// is expected to fail.
46+
bool gen_implicit_type_impls = true;
47+
4348
// If set, enables verbose output.
4449
llvm::raw_ostream* vlog_stream = nullptr;
4550

toolchain/check/check_unit.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ CheckUnit::CheckUnit(
5959
const Parse::GetTreeAndSubtreesStore* tree_and_subtrees_getters,
6060
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
6161
std::shared_ptr<clang::CompilerInvocation> clang_invocation,
62-
llvm::raw_ostream* vlog_stream)
62+
bool gen_implicit_type_impls, llvm::raw_ostream* vlog_stream)
6363
: unit_and_imports_(unit_and_imports),
6464
tree_and_subtrees_getter_(tree_and_subtrees_getters->Get(
6565
unit_and_imports->unit->sem_ir->check_ir_id())),
@@ -68,9 +68,10 @@ CheckUnit::CheckUnit(
6868
clang_invocation_(std::move(clang_invocation)),
6969
emitter_(&unit_and_imports_->err_tracker, tree_and_subtrees_getters,
7070
unit_and_imports_->unit->sem_ir),
71-
context_(
72-
&emitter_, tree_and_subtrees_getter_, unit_and_imports_->unit->sem_ir,
73-
GetImportedIRCount(unit_and_imports), total_ir_count_, vlog_stream) {}
71+
context_(&emitter_, tree_and_subtrees_getter_,
72+
unit_and_imports_->unit->sem_ir,
73+
GetImportedIRCount(unit_and_imports), total_ir_count_,
74+
gen_implicit_type_impls, vlog_stream) {}
7475

7576
auto CheckUnit::Run() -> void {
7677
Timings::ScopedTiming timing(unit_and_imports_->unit->timings, "check");

toolchain/check/check_unit.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ class CheckUnit {
128128
const Parse::GetTreeAndSubtreesStore* tree_and_subtrees_getters,
129129
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
130130
std::shared_ptr<clang::CompilerInvocation> clang_invocation,
131-
llvm::raw_ostream* vlog_stream);
131+
bool gen_implicit_type_impls, llvm::raw_ostream* vlog_stream);
132132

133133
// Produces and checks the IR for the provided unit.
134134
auto Run() -> void;

toolchain/check/class.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,23 @@
55
#include "toolchain/check/class.h"
66

77
#include "toolchain/check/context.h"
8+
#include "toolchain/check/convert.h"
89
#include "toolchain/check/eval.h"
910
#include "toolchain/check/function.h"
11+
#include "toolchain/check/generic.h"
12+
#include "toolchain/check/impl.h"
1013
#include "toolchain/check/import_ref.h"
1114
#include "toolchain/check/inst.h"
15+
#include "toolchain/check/name_lookup.h"
16+
#include "toolchain/check/name_ref.h"
17+
#include "toolchain/check/pattern.h"
18+
#include "toolchain/check/pattern_match.h"
1219
#include "toolchain/check/type.h"
1320
#include "toolchain/parse/node_ids.h"
21+
#include "toolchain/sem_ir/builtin_function_kind.h"
22+
#include "toolchain/sem_ir/function.h"
23+
#include "toolchain/sem_ir/ids.h"
24+
#include "toolchain/sem_ir/typed_insts.h"
1425

1526
namespace Carbon::Check {
1627

toolchain/check/context.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ namespace Carbon::Check {
1616
Context::Context(DiagnosticEmitterBase* emitter,
1717
Parse::GetTreeAndSubtreesFn tree_and_subtrees_getter,
1818
SemIR::File* sem_ir, int imported_ir_count, int total_ir_count,
19-
llvm::raw_ostream* vlog_stream)
19+
bool gen_implicit_type_impls, llvm::raw_ostream* vlog_stream)
2020
: emitter_(emitter),
2121
tree_and_subtrees_getter_(tree_and_subtrees_getter),
2222
sem_ir_(sem_ir),
23+
gen_implicit_type_impls_(gen_implicit_type_impls),
2324
vlog_stream_(vlog_stream),
2425
node_stack_(sem_ir->parse_tree(), vlog_stream),
2526
inst_block_stack_("inst_block_stack_", *sem_ir, vlog_stream),

toolchain/check/context.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ class Context {
5858
explicit Context(DiagnosticEmitterBase* emitter,
5959
Parse::GetTreeAndSubtreesFn tree_and_subtrees_getter,
6060
SemIR::File* sem_ir, int imported_ir_count,
61-
int total_ir_count, llvm::raw_ostream* vlog_stream);
61+
int total_ir_count, bool gen_implicit_type_impls,
62+
llvm::raw_ostream* vlog_stream);
6263

6364
// Marks an implementation TODO. Always returns false.
6465
auto TODO(SemIR::LocId loc_id, std::string label) -> bool;
@@ -92,6 +93,8 @@ class Context {
9293
return parse_tree().tokens();
9394
}
9495

96+
auto gen_implicit_type_impls() -> bool { return gen_implicit_type_impls_; }
97+
9598
auto vlog_stream() -> llvm::raw_ostream* { return vlog_stream_; }
9699

97100
auto node_stack() -> NodeStack& { return node_stack_; }
@@ -304,6 +307,10 @@ class Context {
304307
// The SemIR::File being added to.
305308
SemIR::File* sem_ir_;
306309

310+
// Whether to generate standard `impl`s for types, such as `Core.Destroy`; see
311+
// `CheckParseTreesOptions`.
312+
bool gen_implicit_type_impls_;
313+
307314
// Whether to print verbose output.
308315
llvm::raw_ostream* vlog_stream_;
309316

toolchain/check/handle_class.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include "toolchain/check/eval.h"
1515
#include "toolchain/check/generic.h"
1616
#include "toolchain/check/handle.h"
17+
#include "toolchain/check/impl.h"
18+
#include "toolchain/check/implicit_type_impls.h"
1719
#include "toolchain/check/import.h"
1820
#include "toolchain/check/import_ref.h"
1921
#include "toolchain/check/inst.h"
@@ -565,6 +567,8 @@ auto HandleParseNode(Context& context, Parse::ClassDefinitionId node_id)
565567
auto class_id =
566568
context.node_stack().Pop<Parse::NodeKind::ClassDefinitionStart>();
567569

570+
MakeClassDestroyImpl(context, class_id);
571+
568572
// The class type is now fully defined. Compute its object representation.
569573
ComputeClassObjectRepr(context, node_id, class_id,
570574
context.field_decls_stack().PeekArray(),
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
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/implicit_type_impls.h"
6+
7+
#include "toolchain/check/convert.h"
8+
#include "toolchain/check/generic.h"
9+
#include "toolchain/check/impl.h"
10+
#include "toolchain/check/inst.h"
11+
#include "toolchain/check/name_lookup.h"
12+
#include "toolchain/check/name_ref.h"
13+
#include "toolchain/check/pattern.h"
14+
#include "toolchain/check/pattern_match.h"
15+
#include "toolchain/check/type.h"
16+
#include "toolchain/sem_ir/ids.h"
17+
18+
namespace Carbon::Check {
19+
20+
// Produces an `impl <self_type_id> as <interface_id>` declaration. The caller
21+
// should inspect the resulting `impl` to ensure it's incomplete before
22+
// proceeding to define it.
23+
static auto TryDeclareImpl(Context& context, SemIR::LocId loc_id,
24+
SemIR::NameScopeId parent_scope_id,
25+
SemIR::TypeId self_type_id,
26+
SemIR::InstId interface_id)
27+
-> std::pair<SemIR::ImplId, SemIR::InstId> {
28+
auto impl_decl_id = AddPlaceholderInst(
29+
context,
30+
SemIR::LocIdAndInst::UncheckedLoc(
31+
loc_id, SemIR::ImplDecl{.impl_id = SemIR::ImplId::None,
32+
.decl_block_id = SemIR::InstBlockId::Empty}));
33+
34+
auto self_id = context.types().GetInstId(self_type_id);
35+
auto constraint_id = ExprAsType(context, loc_id, interface_id).inst_id;
36+
37+
SemIR::Impl impl = {
38+
{
39+
.name_id = SemIR::NameId::None,
40+
.parent_scope_id = parent_scope_id,
41+
.generic_id = SemIR::GenericId::None,
42+
.first_param_node_id = Parse::NodeId::None,
43+
.last_param_node_id = Parse::NodeId::None,
44+
.pattern_block_id = SemIR::InstBlockId::None,
45+
.implicit_param_patterns_id = SemIR::InstBlockId::None,
46+
.param_patterns_id = SemIR::InstBlockId::None,
47+
.is_extern = false,
48+
.extern_library_id = SemIR::LibraryNameId::None,
49+
.non_owning_decl_id = SemIR::InstId::None,
50+
.first_owning_decl_id = impl_decl_id,
51+
},
52+
{
53+
.self_id = self_id,
54+
.constraint_id = constraint_id,
55+
.interface =
56+
CheckConstraintIsInterface(context, impl_decl_id, constraint_id),
57+
.is_final = true,
58+
}};
59+
60+
StartGenericDecl(context);
61+
return StartImplDecl(context, loc_id,
62+
/*implicit_params_loc_id=*/SemIR::LocId::None, impl,
63+
/*is_definition=*/true, /*extend_impl=*/std::nullopt);
64+
}
65+
66+
// Constructs the implicit params for the `Op` function. Returns the block and
67+
// the `self` pattern.
68+
static auto MakeImplicitParams(Context& context, SemIR::LocId loc_id)
69+
-> std::pair<SemIR::InstBlockId, SemIR::InstId> {
70+
BeginSubpattern(context);
71+
72+
auto result = LookupUnqualifiedName(context, loc_id, SemIR::NameId::SelfType);
73+
auto self_id =
74+
BuildNameRef(context, loc_id, SemIR::NameId::SelfType,
75+
result.scope_result.target_inst_id(), result.specific_id);
76+
auto self_type_expr = ExprAsType(context, loc_id, self_id);
77+
78+
SemIR::ExprRegionId type_expr_region_id =
79+
EndSubpatternAsExpr(context, self_type_expr.inst_id);
80+
81+
auto self_pattern_id = AddAddrSelfParamPattern(
82+
context, loc_id, type_expr_region_id, self_type_expr.inst_id);
83+
84+
auto implicit_param_patterns_id =
85+
context.inst_blocks().Add({self_pattern_id});
86+
return {implicit_param_patterns_id, self_pattern_id};
87+
}
88+
89+
// Defines the `Op` function for the `impl`.
90+
static auto DeclareImplOpFunction(Context& context, SemIR::LocId loc_id,
91+
const SemIR::Impl& impl)
92+
-> std::pair<SemIR::FunctionId, SemIR::InstId> {
93+
StartGenericDecl(context);
94+
95+
auto name_id = SemIR::NameId::ForIdentifier(context.identifiers().Add("Op"));
96+
97+
context.inst_block_stack().Push();
98+
99+
context.pattern_block_stack().Push();
100+
auto [implicit_param_patterns_id, self_pattern_id] =
101+
MakeImplicitParams(context, loc_id);
102+
constexpr auto NoRegularParams = SemIR::InstBlockId::Empty;
103+
constexpr auto NoReturnSlot = SemIR::InstId::None;
104+
auto pattern_block_id = context.pattern_block_stack().Pop();
105+
106+
// Perform callee-side pattern matching to rebuild the parameter list.
107+
auto call_params_id = CalleePatternMatch(context, implicit_param_patterns_id,
108+
NoRegularParams, NoReturnSlot);
109+
auto decl_block_id = context.inst_block_stack().Pop();
110+
111+
// Create the `FunctionDecl` instruction.
112+
SemIR::FunctionDecl function_decl = {SemIR::TypeId::None,
113+
SemIR::FunctionId::None, decl_block_id};
114+
auto decl_id = AddPlaceholderInst(
115+
context, SemIR::LocIdAndInst::UncheckedLoc(loc_id, function_decl));
116+
auto generic_id = BuildGenericDecl(context, decl_id);
117+
118+
// Create the `Function` object.
119+
function_decl.function_id = context.functions().Add(SemIR::Function{
120+
{
121+
.name_id = name_id,
122+
.parent_scope_id = impl.scope_id,
123+
.generic_id = generic_id,
124+
.first_param_node_id = Parse::NodeId::None,
125+
.last_param_node_id = Parse::NodeId::None,
126+
.pattern_block_id = pattern_block_id,
127+
.implicit_param_patterns_id = implicit_param_patterns_id,
128+
.param_patterns_id = NoRegularParams,
129+
.is_extern = false,
130+
.extern_library_id = SemIR::LibraryNameId::None,
131+
.non_owning_decl_id = SemIR::InstId::None,
132+
.first_owning_decl_id = decl_id,
133+
},
134+
{
135+
.call_params_id = call_params_id,
136+
.return_slot_pattern_id = NoReturnSlot,
137+
.virtual_modifier = SemIR::FunctionFields::VirtualModifier::None,
138+
.virtual_index = -1,
139+
.self_param_id = self_pattern_id,
140+
}});
141+
function_decl.type_id =
142+
GetFunctionType(context, function_decl.function_id,
143+
context.scope_stack().PeekSpecificId());
144+
ReplaceInstBeforeConstantUse(context, decl_id, function_decl);
145+
context.name_scopes().AddRequiredName(impl.scope_id, name_id, decl_id);
146+
147+
return {function_decl.function_id, decl_id};
148+
}
149+
150+
auto MakeClassDestroyImpl(Context& context, SemIR::ClassId class_id) -> void {
151+
if (!context.gen_implicit_type_impls()) {
152+
return;
153+
}
154+
155+
// Identify the type and interface for implementation.
156+
auto& class_info = context.classes().Get(class_id);
157+
auto loc_id = context.insts().GetLocIdForDesugaring(
158+
SemIR::LocId(class_info.latest_decl_id()));
159+
160+
auto destroy_id = LookupNameInCore(context, loc_id, "Destroy");
161+
if (destroy_id == SemIR::ErrorInst::InstId) {
162+
return;
163+
}
164+
165+
// Declare the `impl`.
166+
auto [impl_id, impl_decl_id] =
167+
TryDeclareImpl(context, loc_id, class_info.scope_id,
168+
class_info.self_type_id, destroy_id);
169+
auto& impl = context.impls().Get(impl_id);
170+
if (impl.is_complete()) {
171+
return;
172+
}
173+
174+
// Define the `impl`.
175+
impl.definition_id = impl_decl_id;
176+
impl.scope_id = context.name_scopes().Add(impl_decl_id, SemIR::NameId::None,
177+
class_info.scope_id);
178+
179+
context.scope_stack().PushForEntity(
180+
impl_decl_id, impl.scope_id,
181+
context.generics().GetSelfSpecific(impl.generic_id));
182+
StartGenericDefinition(context, impl.generic_id);
183+
context.inst_block_stack().Push();
184+
185+
// Declare the `Op` function.
186+
auto [fn_id, fn_decl_id] = DeclareImplOpFunction(context, loc_id, impl);
187+
188+
// Define the `Op` function.
189+
// TODO: Add an actual definition.
190+
context.scope_stack().PushForFunctionBody(fn_decl_id);
191+
auto& function = context.functions().Get(fn_id);
192+
function.SetBuiltinFunction(SemIR::BuiltinFunctionKind::NoOp);
193+
StartGenericDefinition(context, function.generic_id);
194+
FinishGenericDefinition(context, function.generic_id);
195+
context.scope_stack().Pop();
196+
197+
// Close the `impl` definition.
198+
FinishImplWitness(context, impl_id);
199+
impl.defined = true;
200+
FinishGenericDefinition(context, impl.generic_id);
201+
context.scope_stack().Pop();
202+
impl.body_block_id = context.inst_block_stack().Pop();
203+
}
204+
205+
} // namespace Carbon::Check

0 commit comments

Comments
 (0)