-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathscope_stack.h
More file actions
353 lines (282 loc) · 13.5 KB
/
scope_stack.h
File metadata and controls
353 lines (282 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
// 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
#ifndef CARBON_TOOLCHAIN_CHECK_SCOPE_STACK_H_
#define CARBON_TOOLCHAIN_CHECK_SCOPE_STACK_H_
#include "common/array_stack.h"
#include "common/move_only.h"
#include "common/set.h"
#include "llvm/ADT/SmallVector.h"
#include "toolchain/check/full_pattern_stack.h"
#include "toolchain/check/lexical_lookup.h"
#include "toolchain/check/scope_index.h"
#include "toolchain/sem_ir/file.h"
#include "toolchain/sem_ir/ids.h"
namespace Carbon::Check {
// A stack of lexical and semantic scopes that we are currently performing
// checking within.
class ScopeStack {
public:
explicit ScopeStack(const SemIR::File* sem_ir)
: sem_ir_(sem_ir),
lexical_lookup_(sem_ir->identifiers()),
full_pattern_stack_(&lexical_lookup_) {}
// A scope in which `break` and `continue` can be used.
struct BreakContinueScope {
SemIR::InstBlockId break_target;
SemIR::InstBlockId continue_target;
};
// A non-lexical scope in which unqualified lookup may be required.
struct NonLexicalScope {
// The index of the scope in the scope stack.
ScopeIndex scope_index;
// The corresponding name scope.
SemIR::NameScopeId name_scope_id;
// The corresponding specific.
SemIR::SpecificId specific_id;
};
// Information about a scope that has been temporarily removed from the stack.
// This type is large, so moves of this type should be avoided.
struct SuspendedScope;
// Pushes a scope for a declaration name's parameters.
auto PushForDeclName() -> void;
// Pushes a non-function entity scope. Functions must use
// `PushForFunctionBody` instead.
auto PushForEntity(SemIR::InstId scope_inst_id, SemIR::NameScopeId scope_id,
SemIR::SpecificId specific_id,
bool lexical_lookup_has_load_error = false) -> void;
// Pushes a scope which should be in the same region as the current scope.
// These can be in a function without breaking `return` scoping. For example,
// this is used by struct literals and code blocks.
auto PushForSameRegion() -> void;
// Pushes a function scope.
auto PushForFunctionBody(SemIR::InstId scope_inst_id) -> void;
// Pops the top scope from scope_stack_. Removes names from lexical_lookup_.
auto Pop() -> void;
// Pops the top scope from scope_stack_ if it contains no names.
auto PopIfEmpty() -> void {
if (scope_stack_.back().num_names == 0) {
Pop();
}
}
// Pops scopes until we return to the specified scope index.
auto PopTo(ScopeIndex index) -> void;
// Returns the scope index associated with the current scope.
auto PeekIndex() const -> ScopeIndex { return Peek().index; }
// Returns the name scope associated with the current lexical scope, if any.
auto PeekNameScopeId() const -> SemIR::NameScopeId { return Peek().scope_id; }
// Returns the instruction associated with the current scope, or `None` if
// there is no such instruction, such as for a block scope.
auto PeekInstId() const -> SemIR::InstId { return Peek().scope_inst_id; }
// Returns the specific associated with the innermost enclosing scope that is
// associated with a specific. This will generally be the self specific of the
// innermost enclosing generic, as there is no way to enter any other specific
// scope.
auto PeekSpecificId() const -> SemIR::SpecificId {
return Peek().specific_id;
}
// Returns true if the current scope is inside a function scope (either the
// scope itself, or a lexical scope), without an intervening entity scope.
auto IsInFunctionScope() const -> bool {
return !return_scope_stack_.empty() &&
!return_scope_stack_.back().nested_scope_index.has_value();
}
// Returns the current scope, if it is of the specified kind. Otherwise,
// returns nullopt.
//
// TODO: Consider adding TryAs*Scope functions in name_scope.h to replace use
// of this, as they could handle any NameScopeId instead of just the current
// one.
template <typename InstT>
auto GetCurrentScopeAs() -> std::optional<InstT> {
auto inst_id = PeekInstId();
if (!inst_id.has_value()) {
return std::nullopt;
}
return sem_ir_->insts().TryGetAs<InstT>(inst_id);
}
// If there is no `returned var` in scope, sets the given instruction to be
// the current `returned var` and returns an `None`. If there
// is already a `returned var`, returns it instead.
auto SetReturnedVarOrGetExisting(SemIR::InstId inst_id) -> SemIR::InstId;
// Returns the `returned var` instruction that's currently in scope, or `None`
// if there isn't one.
auto GetReturnedVar() -> SemIR::InstId {
CARBON_CHECK(IsInFunctionScope(), "Handling return but not in a function");
return return_scope_stack_.back().returned_var;
}
// Returns the decl ID for the current return scope.
auto GetReturnScopeDeclId() -> SemIR::InstId {
CARBON_CHECK(IsInFunctionScope(), "Handling return but not in a function");
return return_scope_stack_.back().decl_id;
}
// Looks up the name `name_id` in the current scope and enclosing scopes, but
// do not look past `scope_index`. Returns the existing lookup result, if any.
auto LookupInLexicalScopesWithin(SemIR::NameId name_id,
ScopeIndex scope_index) -> SemIR::InstId;
// Looks up the name `name_id` in the current scope and related lexical
// scopes. Returns the innermost lexical lookup result, if any, along with a
// list of non-lexical scopes in which lookup should also be performed,
// ordered from outermost to innermost.
auto LookupInLexicalScopes(SemIR::NameId name_id)
-> std::pair<SemIR::InstId, llvm::ArrayRef<NonLexicalScope>>;
// Looks up the name `name_id` in the current scope, or in `scope_index` if
// specified. Returns the existing instruction if the name is already declared
// in that scope or any unfinished scope within it, and otherwise adds the
// name with the value `target_id` and returns `None`.
auto LookupOrAddName(SemIR::NameId name_id, SemIR::InstId target_id,
ScopeIndex scope_index = ScopeIndex::None)
-> SemIR::InstId;
// Prepares to add a compile-time binding in the current scope, and returns
// its index. The added binding must then be pushed using
// `PushCompileTimeBinding`.
auto AddCompileTimeBinding() -> SemIR::CompileTimeBindIndex {
auto index = scope_stack_.back().next_compile_time_bind_index;
++scope_stack_.back().next_compile_time_bind_index.index;
return index;
}
// Pushes a compile-time binding into the current scope.
auto PushCompileTimeBinding(SemIR::InstId bind_id) -> void {
compile_time_binding_stack_.AppendToTop(bind_id);
}
// Temporarily removes the top of the stack and its lexical lookup results.
auto Suspend() -> SuspendedScope;
// Restores a suspended scope stack entry.
auto Restore(SuspendedScope&& scope) -> void;
// Runs verification that the processing cleanly finished.
auto VerifyOnFinish() const -> void;
auto break_continue_stack() -> llvm::SmallVector<BreakContinueScope>& {
return break_continue_stack_;
}
auto destroy_id_stack() -> ArrayStack<SemIR::InstId>& {
return destroy_id_stack_;
}
auto compile_time_bindings_stack() -> ArrayStack<SemIR::InstId>& {
return compile_time_binding_stack_;
}
auto full_pattern_stack() -> FullPatternStack& { return full_pattern_stack_; }
private:
// An entry in scope_stack_.
struct ScopeStackEntry : public MoveOnly<ScopeStackEntry> {
auto is_lexical_scope() const -> bool { return !scope_id.has_value(); }
// The sequential index of this scope entry within the file.
ScopeIndex index;
// The instruction associated with this entry, if any. This can be one of:
//
// - A `ClassDecl`, for a class definition scope.
// - A `FunctionDecl`, for the outermost scope in a function
// definition.
// - Invalid, for any other scope.
SemIR::InstId scope_inst_id;
// The name scope associated with this entry, if any.
SemIR::NameScopeId scope_id;
// The specific associated with this entry, if any.
SemIR::SpecificId specific_id;
// The next compile-time binding index to allocate in this scope.
SemIR::CompileTimeBindIndex next_compile_time_bind_index;
// Whether lexical_lookup_ has load errors from this scope or an ancestor
// scope.
bool lexical_lookup_has_load_error;
// Whether a `returned var` was introduced in this scope, and needs to be
// unregistered when the scope ends.
bool has_returned_var = false;
// Whether there are any ids in the `names` set.
int num_names = 0;
// Names which are registered with lexical_lookup_, and will need to be
// unregistered when the scope ends.
Set<SemIR::NameId> names = {};
};
// A scope in which `return` can be used.
struct ReturnScope {
// The `FunctionDecl`.
SemIR::InstId decl_id;
// The value corresponding to the current `returned var`, if any. Will be
// set and unset as `returned var`s are declared and go out of scope.
SemIR::InstId returned_var = SemIR::InstId::None;
// When a nested scope interrupts a return scope, this is the index of the
// outermost interrupting scope (the one closest to the function scope).
// This can then be used to determine whether we're actually inside the most
// recent `ReturnScope`, or inside a different entity scope.
//
// This won't be set for functions directly inside functions, because they
// will have their own `ReturnScope`.
// For example, when a `class` is inside a `fn`, it interrupts the function
// body by setting this on `PushEntity`; `Pop` will set it back to `None`.
ScopeIndex nested_scope_index = ScopeIndex::None;
};
// Pushes a scope onto scope_stack_. NameScopeId::None is used for new scopes.
// lexical_lookup_has_load_error is used to limit diagnostics when a given
// namespace may contain a mix of both successful and failed name imports.
auto Push(SemIR::InstId scope_inst_id, SemIR::NameScopeId scope_id,
SemIR::SpecificId specific_id, bool lexical_lookup_has_load_error)
-> void;
auto Peek() const -> const ScopeStackEntry& { return scope_stack_.back(); }
// Returns whether lexical lookup currently has any load errors.
auto LexicalLookupHasLoadError() const -> bool {
return !scope_stack_.empty() &&
scope_stack_.back().lexical_lookup_has_load_error;
}
// If inside a return scope, marks a nested scope (see `nested_scope_index`).
// Called after pushing the new scope.
auto MarkNestingIfInReturnScope() -> void {
if (!return_scope_stack_.empty() &&
!return_scope_stack_.back().nested_scope_index.has_value()) {
return_scope_stack_.back().nested_scope_index = scope_stack_.back().index;
}
}
// Checks that the provided scope's `next_compile_time_bind_index` matches the
// full size of the current `compile_time_binding_stack_`. The values should
// always match, and this is used to validate the correspondence during
// significant changes.
auto VerifyNextCompileTimeBindIndex(llvm::StringLiteral label,
const ScopeStackEntry& scope) -> void;
// The current file.
const SemIR::File* sem_ir_;
// A stack of scopes from which we can `return`.
llvm::SmallVector<ReturnScope> return_scope_stack_;
// A stack of `break` and `continue` targets.
llvm::SmallVector<BreakContinueScope> break_continue_stack_;
// A stack for scope context.
llvm::SmallVector<ScopeStackEntry> scope_stack_;
// A stack of instances to destroy. This only has entries inside of function
// bodies, where destruction on scope exit is required.
ArrayStack<SemIR::InstId> destroy_id_stack_;
// Information about non-lexical scopes. This is a subset of the entries and
// the information in scope_stack_.
llvm::SmallVector<NonLexicalScope> non_lexical_scope_stack_;
// A stack of the current compile time bindings.
ArrayStack<SemIR::InstId> compile_time_binding_stack_;
// The index of the next scope that will be pushed onto scope_stack_. The
// first is always the package scope.
ScopeIndex next_scope_index_ = ScopeIndex::Package;
// Tracks lexical lookup results.
LexicalLookup lexical_lookup_;
// Stack of full-patterns currently being checked.
FullPatternStack full_pattern_stack_;
};
struct ScopeStack::SuspendedScope : public MoveOnly<SuspendedScope> {
// An item that was suspended within this scope. This represents either a
// lexical lookup entry in this scope, or a compile time binding entry in this
// scope.
//
// TODO: For compile-time bindings, the common case is that they will both
// have a suspended lexical lookup entry and a suspended compile time binding
// entry. We should be able to store that as a single ScopeItem rather than
// two.
struct ScopeItem {
static constexpr uint32_t IndexForCompileTimeBinding = -1;
// The scope index for a LexicalLookup::SuspendedResult, or
// CompileTimeBindingIndex for a suspended compile time binding.
uint32_t index;
// The instruction within the scope.
SemIR::InstId inst_id;
};
// The suspended scope stack entry.
ScopeStackEntry entry;
// The list of items that were within this scope when it was suspended. The
// inline size is an attempt to keep the size of a `SuspendedFunction`
// reasonable while avoiding heap allocations most of the time.
llvm::SmallVector<ScopeItem, 8> suspended_items;
};
} // namespace Carbon::Check
#endif // CARBON_TOOLCHAIN_CHECK_SCOPE_STACK_H_