mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 22:02:23 +01:00
Factor the scope stack and lexical lookups out of Check::Context. (#3688)
This commit is contained in:
+20
-2
@@ -28,6 +28,25 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "scope_stack",
|
||||
srcs = ["scope_stack.cpp"],
|
||||
hdrs = [
|
||||
"lexical_lookup.h",
|
||||
"scope_index.h",
|
||||
"scope_stack.h",
|
||||
],
|
||||
deps = [
|
||||
"//common:check",
|
||||
"//common:ostream",
|
||||
"//common:vlog",
|
||||
"//toolchain/base:index_base",
|
||||
"//toolchain/sem_ir:file",
|
||||
"//toolchain/sem_ir:ids",
|
||||
"@llvm-project//llvm:Support",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "context",
|
||||
srcs = [
|
||||
@@ -46,17 +65,16 @@ cc_library(
|
||||
"decl_state.h",
|
||||
"eval.h",
|
||||
"inst_block_stack.h",
|
||||
"lexical_lookup.h",
|
||||
"modifiers.h",
|
||||
"pending_block.h",
|
||||
"return.h",
|
||||
"scope_index.h",
|
||||
],
|
||||
deps = [
|
||||
":node_stack",
|
||||
"//common:check",
|
||||
"//common:vlog",
|
||||
"//toolchain/base:index_base",
|
||||
"//toolchain/check:scope_stack",
|
||||
"//toolchain/lex:tokenized_buffer",
|
||||
"//toolchain/parse:node_kind",
|
||||
"//toolchain/parse:tree",
|
||||
|
||||
@@ -175,13 +175,13 @@ static auto InitPackageScopeAndImports(Context& context, UnitInfo& unit_info)
|
||||
if (error_in_import) {
|
||||
context.name_scopes().Get(SemIR::NameScopeId::Package).has_error = true;
|
||||
}
|
||||
context.PushScope(package_inst_id, SemIR::NameScopeId::Package,
|
||||
error_in_import);
|
||||
context.scope_stack().Push(package_inst_id, SemIR::NameScopeId::Package,
|
||||
error_in_import);
|
||||
} else {
|
||||
// Push the scope; there are no names to add.
|
||||
context.PushScope(package_inst_id, SemIR::NameScopeId::Package);
|
||||
context.scope_stack().Push(package_inst_id, SemIR::NameScopeId::Package);
|
||||
}
|
||||
CARBON_CHECK(context.current_scope_index() == ScopeIndex::Package);
|
||||
CARBON_CHECK(context.scope_stack().PeekIndex() == ScopeIndex::Package);
|
||||
|
||||
for (auto& [package_id, package_imports] : unit_info.package_imports_map) {
|
||||
if (!package_id.is_valid()) {
|
||||
@@ -255,7 +255,7 @@ static auto CheckParseTree(
|
||||
|
||||
// Pop information for the file-level scope.
|
||||
sem_ir.set_top_inst_block_id(context.inst_block_stack().Pop());
|
||||
context.PopScope();
|
||||
context.scope_stack().Pop();
|
||||
context.FinalizeExports();
|
||||
|
||||
context.VerifyOnFinish();
|
||||
|
||||
+16
-111
@@ -38,7 +38,7 @@ Context::Context(const Lex::TokenizedBuffer& tokens, DiagnosticEmitter& emitter,
|
||||
params_or_args_stack_("params_or_args_stack_", sem_ir, vlog_stream),
|
||||
args_type_info_stack_("args_type_info_stack_", sem_ir, vlog_stream),
|
||||
decl_name_stack_(this),
|
||||
lexical_lookup_(sem_ir_->identifiers()) {
|
||||
scope_stack_(sem_ir_->identifiers()) {
|
||||
// Map the builtin `<error>` and `type` type constants to their corresponding
|
||||
// special `TypeId` values.
|
||||
type_ids_for_type_constants_.insert(
|
||||
@@ -61,7 +61,7 @@ auto Context::VerifyOnFinish() -> void {
|
||||
// various pieces of context go out of scope. At this point, nothing should
|
||||
// remain.
|
||||
// node_stack_ will still contain top-level entities.
|
||||
CARBON_CHECK(scope_stack_.empty()) << scope_stack_.size();
|
||||
scope_stack_.VerifyOnFinish();
|
||||
CARBON_CHECK(inst_block_stack_.empty()) << inst_block_stack_.size();
|
||||
CARBON_CHECK(params_or_args_stack_.empty()) << params_or_args_stack_.size();
|
||||
}
|
||||
@@ -206,18 +206,9 @@ auto Context::AddPackageImports(Parse::NodeId import_node,
|
||||
|
||||
auto Context::AddNameToLookup(SemIR::NameId name_id, SemIR::InstId target_id)
|
||||
-> void {
|
||||
if (current_scope().names.insert(name_id).second) {
|
||||
// TODO: Reject if we previously performed a failed lookup for this name in
|
||||
// this scope or a scope nested within it.
|
||||
auto& lexical_results = lexical_lookup_.Get(name_id);
|
||||
CARBON_CHECK(lexical_results.empty() ||
|
||||
lexical_results.back().scope_index < current_scope_index())
|
||||
<< "Failed to clean up after scope nested within the current scope";
|
||||
lexical_results.push_back(
|
||||
{.inst_id = target_id, .scope_index = current_scope_index()});
|
||||
} else {
|
||||
DiagnoseDuplicateName(target_id,
|
||||
lexical_lookup_.Get(name_id).back().inst_id);
|
||||
if (auto existing = scope_stack().LookupOrAddName(name_id, target_id);
|
||||
existing.is_valid()) {
|
||||
DiagnoseDuplicateName(target_id, existing);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,15 +278,11 @@ auto Context::LookupNameInDecl(Parse::NodeId /*parse_node*/,
|
||||
// In this case, we're not in the correct scope to define a member of
|
||||
// class A, so we should reject, and we achieve this by not finding the
|
||||
// name A from the outer scope.
|
||||
auto& lexical_results = lexical_lookup_.Get(name_id);
|
||||
if (!lexical_results.empty()) {
|
||||
auto result = lexical_results.back();
|
||||
if (result.scope_index == current_scope_index()) {
|
||||
ResolveIfImportRefUnused(result.inst_id);
|
||||
return result.inst_id;
|
||||
}
|
||||
auto result = scope_stack().LookupInCurrentScope(name_id);
|
||||
if (result.is_valid()) {
|
||||
ResolveIfImportRefUnused(result);
|
||||
}
|
||||
return SemIR::InstId::Invalid;
|
||||
return result;
|
||||
} else {
|
||||
// We do not look into `extend`ed scopes here. A qualified name in a
|
||||
// declaration must specify the exact scope in which the name was originally
|
||||
@@ -316,20 +303,11 @@ auto Context::LookupUnqualifiedName(Parse::NodeId parse_node,
|
||||
|
||||
// Find the results from enclosing lexical scopes. These will be combined with
|
||||
// results from non-lexical scopes such as namespaces and classes.
|
||||
llvm::ArrayRef<LexicalLookup::Result> lexical_results =
|
||||
lexical_lookup_.Get(name_id);
|
||||
auto [lexical_result, non_lexical_scopes] =
|
||||
scope_stack().LookupInEnclosingScopes(name_id);
|
||||
|
||||
// Walk the non-lexical scopes and perform lookups into each of them.
|
||||
for (auto [index, name_scope_id] : llvm::reverse(non_lexical_scope_stack_)) {
|
||||
// If the innermost lexical result is within this non-lexical scope, then
|
||||
// it shadows all further non-lexical results and we're done.
|
||||
if (!lexical_results.empty() &&
|
||||
lexical_results.back().scope_index > index) {
|
||||
auto inst_id = lexical_results.back().inst_id;
|
||||
ResolveIfImportRefUnused(inst_id);
|
||||
return inst_id;
|
||||
}
|
||||
|
||||
for (auto [index, name_scope_id] : llvm::reverse(non_lexical_scopes)) {
|
||||
if (auto non_lexical_result =
|
||||
LookupQualifiedName(parse_node, name_id, name_scope_id,
|
||||
/*required=*/false);
|
||||
@@ -338,16 +316,13 @@ auto Context::LookupUnqualifiedName(Parse::NodeId parse_node,
|
||||
}
|
||||
}
|
||||
|
||||
if (!lexical_results.empty()) {
|
||||
auto inst_id = lexical_results.back().inst_id;
|
||||
ResolveIfImportRefUnused(inst_id);
|
||||
return inst_id;
|
||||
if (lexical_result.is_valid()) {
|
||||
ResolveIfImportRefUnused(lexical_result);
|
||||
return lexical_result;
|
||||
}
|
||||
|
||||
// We didn't find anything at all.
|
||||
if (!lexical_lookup_has_load_error_) {
|
||||
DiagnoseNameNotFound(parse_node, name_id);
|
||||
}
|
||||
DiagnoseNameNotFound(parse_node, name_id);
|
||||
return SemIR::InstId::BuiltinError;
|
||||
}
|
||||
|
||||
@@ -410,76 +385,6 @@ auto Context::LookupQualifiedName(Parse::NodeId parse_node,
|
||||
return result_id;
|
||||
}
|
||||
|
||||
auto Context::PushScope(SemIR::InstId scope_inst_id,
|
||||
SemIR::NameScopeId scope_id,
|
||||
bool lexical_lookup_has_load_error) -> void {
|
||||
scope_stack_.push_back(
|
||||
{.index = next_scope_index_,
|
||||
.scope_inst_id = scope_inst_id,
|
||||
.scope_id = scope_id,
|
||||
.prev_lexical_lookup_has_load_error = lexical_lookup_has_load_error_});
|
||||
if (scope_id.is_valid()) {
|
||||
non_lexical_scope_stack_.push_back({next_scope_index_, scope_id});
|
||||
}
|
||||
|
||||
lexical_lookup_has_load_error_ |= lexical_lookup_has_load_error;
|
||||
|
||||
// TODO: Handle this case more gracefully.
|
||||
CARBON_CHECK(next_scope_index_.index != std::numeric_limits<int32_t>::max())
|
||||
<< "Ran out of scopes";
|
||||
++next_scope_index_.index;
|
||||
}
|
||||
|
||||
auto Context::PopScope() -> void {
|
||||
auto scope = scope_stack_.pop_back_val();
|
||||
|
||||
lexical_lookup_has_load_error_ = scope.prev_lexical_lookup_has_load_error;
|
||||
|
||||
for (const auto& str_id : scope.names) {
|
||||
auto& lexical_results = lexical_lookup_.Get(str_id);
|
||||
CARBON_CHECK(lexical_results.back().scope_index == scope.index)
|
||||
<< "Inconsistent scope index for name " << names().GetFormatted(str_id);
|
||||
lexical_results.pop_back();
|
||||
}
|
||||
|
||||
if (scope.scope_id.is_valid()) {
|
||||
CARBON_CHECK(non_lexical_scope_stack_.back().first == scope.index);
|
||||
non_lexical_scope_stack_.pop_back();
|
||||
}
|
||||
|
||||
if (scope.has_returned_var) {
|
||||
CARBON_CHECK(!return_scope_stack_.empty());
|
||||
CARBON_CHECK(return_scope_stack_.back().returned_var.is_valid());
|
||||
return_scope_stack_.back().returned_var = SemIR::InstId::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
auto Context::PopToScope(ScopeIndex index) -> void {
|
||||
while (current_scope_index() > index) {
|
||||
PopScope();
|
||||
}
|
||||
CARBON_CHECK(current_scope_index() == index)
|
||||
<< "Scope index " << index << " does not enclose the current scope "
|
||||
<< current_scope_index();
|
||||
}
|
||||
|
||||
auto Context::SetReturnedVarOrGetExisting(SemIR::InstId inst_id)
|
||||
-> SemIR::InstId {
|
||||
CARBON_CHECK(!return_scope_stack_.empty()) << "`returned var` in no function";
|
||||
auto& returned_var = return_scope_stack_.back().returned_var;
|
||||
if (returned_var.is_valid()) {
|
||||
return returned_var;
|
||||
}
|
||||
|
||||
returned_var = inst_id;
|
||||
CARBON_CHECK(!current_scope().has_returned_var)
|
||||
<< "Scope has returned var but none is set";
|
||||
if (inst_id.is_valid()) {
|
||||
current_scope().has_returned_var = true;
|
||||
}
|
||||
return SemIR::InstId::Invalid;
|
||||
}
|
||||
|
||||
template <typename BranchNode, typename... Args>
|
||||
static auto AddDominatedBlockAndBranchImpl(Context& context,
|
||||
Parse::NodeId parse_node,
|
||||
|
||||
+14
-143
@@ -6,14 +6,13 @@
|
||||
#define CARBON_TOOLCHAIN_CHECK_CONTEXT_H_
|
||||
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/ADT/FoldingSet.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "toolchain/check/decl_name_stack.h"
|
||||
#include "toolchain/check/decl_state.h"
|
||||
#include "toolchain/check/inst_block_stack.h"
|
||||
#include "toolchain/check/lexical_lookup.h"
|
||||
#include "toolchain/check/node_stack.h"
|
||||
#include "toolchain/check/scope_stack.h"
|
||||
#include "toolchain/parse/node_ids.h"
|
||||
#include "toolchain/parse/tree.h"
|
||||
#include "toolchain/parse/tree_node_location_translator.h"
|
||||
@@ -51,23 +50,6 @@ class Context {
|
||||
using DiagnosticEmitter = Carbon::DiagnosticEmitter<SemIRLocation>;
|
||||
using DiagnosticBuilder = DiagnosticEmitter::DiagnosticBuilder;
|
||||
|
||||
// A scope in which `break` and `continue` can be used.
|
||||
struct BreakContinueScope {
|
||||
SemIR::InstBlockId break_target;
|
||||
SemIR::InstBlockId continue_target;
|
||||
};
|
||||
|
||||
// A scope in which `return` can be used.
|
||||
struct ReturnScope {
|
||||
// The declaration from which we can return. Inside a function, this will
|
||||
// be a `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::Invalid;
|
||||
};
|
||||
|
||||
// Stores references for work.
|
||||
explicit Context(const Lex::TokenizedBuffer& tokens,
|
||||
DiagnosticEmitter& emitter, const Parse::Tree& parse_tree,
|
||||
@@ -168,68 +150,13 @@ class Context {
|
||||
auto NoteIncompleteClass(SemIR::ClassId class_id, DiagnosticBuilder& builder)
|
||||
-> void;
|
||||
|
||||
// Pushes a scope onto scope_stack_. NameScopeId::Invalid 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 PushScope(SemIR::InstId scope_inst_id = SemIR::InstId::Invalid,
|
||||
SemIR::NameScopeId scope_id = SemIR::NameScopeId::Invalid,
|
||||
bool lexical_lookup_has_load_error = false) -> void;
|
||||
|
||||
// Pops the top scope from scope_stack_, cleaning up names from
|
||||
// lexical_lookup_.
|
||||
auto PopScope() -> void;
|
||||
|
||||
// Pops the top scope from scope_stack_ if it contains no names.
|
||||
auto PopScopeIfEmpty() -> void {
|
||||
if (scope_stack_.back().names.empty()) {
|
||||
PopScope();
|
||||
}
|
||||
}
|
||||
|
||||
// Pops scopes until we return to the specified scope index.
|
||||
auto PopToScope(ScopeIndex index) -> void;
|
||||
|
||||
// Returns the scope index associated with the current scope.
|
||||
auto current_scope_index() const -> ScopeIndex {
|
||||
return current_scope().index;
|
||||
}
|
||||
|
||||
// Returns the name scope associated with the current lexical scope, if any.
|
||||
auto current_scope_id() const -> SemIR::NameScopeId {
|
||||
return current_scope().scope_id;
|
||||
}
|
||||
|
||||
// Returns the instruction associated with the current scope, or Invalid if
|
||||
// there is no such instruction, such as for a block scope.
|
||||
auto current_scope_inst_id() const -> SemIR::InstId {
|
||||
return current_scope().scope_inst_id;
|
||||
}
|
||||
|
||||
auto GetCurrentScopeParseNode() const -> Parse::NodeId {
|
||||
auto inst_id = current_scope_inst_id();
|
||||
if (!inst_id.is_valid()) {
|
||||
return Parse::NodeId::Invalid;
|
||||
}
|
||||
return sem_ir_->insts().GetParseNode(inst_id);
|
||||
}
|
||||
|
||||
// Returns the current scope, if it is of the specified kind. Otherwise,
|
||||
// returns nullopt.
|
||||
template <typename InstT>
|
||||
auto GetCurrentScopeAs() -> std::optional<InstT> {
|
||||
auto inst_id = current_scope_inst_id();
|
||||
if (!inst_id.is_valid()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return insts().TryGetAs<InstT>(inst_id);
|
||||
return scope_stack().GetCurrentScopeAs<InstT>(sem_ir());
|
||||
}
|
||||
|
||||
// If there is no `returned var` in scope, sets the given instruction to be
|
||||
// the current `returned var` and returns an invalid instruction ID. If there
|
||||
// is already a `returned var`, returns it instead.
|
||||
auto SetReturnedVarOrGetExisting(SemIR::InstId inst_id) -> SemIR::InstId;
|
||||
|
||||
// Adds a `Branch` instruction branching to a new instruction block, and
|
||||
// returns the ID of the new block. All paths to the branch target must go
|
||||
// through the current block, though not necessarily through this branch.
|
||||
@@ -391,19 +318,20 @@ class Context {
|
||||
return args_type_info_stack_;
|
||||
}
|
||||
|
||||
auto return_scope_stack() -> llvm::SmallVector<ReturnScope>& {
|
||||
return return_scope_stack_;
|
||||
}
|
||||
|
||||
auto break_continue_stack() -> llvm::SmallVector<BreakContinueScope>& {
|
||||
return break_continue_stack_;
|
||||
}
|
||||
|
||||
auto decl_name_stack() -> DeclNameStack& { return decl_name_stack_; }
|
||||
|
||||
auto decl_state_stack() -> DeclStateStack& { return decl_state_stack_; }
|
||||
|
||||
auto lexical_lookup() -> LexicalLookup& { return lexical_lookup_; }
|
||||
auto scope_stack() -> ScopeStack& { return scope_stack_; }
|
||||
|
||||
auto return_scope_stack() -> llvm::SmallVector<ScopeStack::ReturnScope>& {
|
||||
return scope_stack().return_scope_stack();
|
||||
}
|
||||
|
||||
auto break_continue_stack()
|
||||
-> llvm::SmallVector<ScopeStack::BreakContinueScope>& {
|
||||
return scope_stack().break_continue_stack();
|
||||
}
|
||||
|
||||
// Directly expose SemIR::File data accessors for brevity in calls.
|
||||
|
||||
@@ -461,45 +389,10 @@ class Context {
|
||||
SemIR::TypeId type_id_;
|
||||
};
|
||||
|
||||
// An entry in scope_stack_.
|
||||
struct ScopeStackEntry {
|
||||
// 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 previous state of lexical_lookup_has_load_error_, restored on pop.
|
||||
bool prev_lexical_lookup_has_load_error;
|
||||
|
||||
// Names which are registered with lexical_lookup_, and will need to be
|
||||
// unregistered when the scope ends.
|
||||
llvm::DenseSet<SemIR::NameId> names;
|
||||
|
||||
// Whether a `returned var` was introduced in this scope, and needs to be
|
||||
// unregistered when the scope ends.
|
||||
bool has_returned_var = false;
|
||||
|
||||
// TODO: This likely needs to track things which need to be destructed.
|
||||
};
|
||||
|
||||
// If the passed in instruction ID is a ImportRefUnused, resolves it for use.
|
||||
// Called when name lookup intends to return an inst_id.
|
||||
auto ResolveIfImportRefUnused(SemIR::InstId inst_id) -> void;
|
||||
|
||||
auto current_scope() -> ScopeStackEntry& { return scope_stack_.back(); }
|
||||
auto current_scope() const -> const ScopeStackEntry& {
|
||||
return scope_stack_.back();
|
||||
}
|
||||
|
||||
// Tokens for getting data on literals.
|
||||
const Lex::TokenizedBuffer* tokens_;
|
||||
|
||||
@@ -533,36 +426,14 @@ class Context {
|
||||
// for a type separate from the literal arguments.
|
||||
InstBlockStack args_type_info_stack_;
|
||||
|
||||
// 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_;
|
||||
|
||||
// Information about non-lexical scopes. This is a subset of the entries and
|
||||
// the information in scope_stack_.
|
||||
llvm::SmallVector<std::pair<ScopeIndex, SemIR::NameScopeId>>
|
||||
non_lexical_scope_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;
|
||||
|
||||
// The stack used for qualified declaration name construction.
|
||||
DeclNameStack decl_name_stack_;
|
||||
|
||||
// The stack of declarations that could have modifiers.
|
||||
DeclStateStack decl_state_stack_;
|
||||
|
||||
// Tracks lexical lookup results.
|
||||
LexicalLookup lexical_lookup_;
|
||||
|
||||
// Whether lexical_lookup_ has load errors, updated whenever scope_stack_ is
|
||||
// pushed or popped.
|
||||
bool lexical_lookup_has_load_error_ = false;
|
||||
// The stack of scopes we are currently within.
|
||||
ScopeStack scope_stack_;
|
||||
|
||||
// Cache of reverse mapping from type constants to types.
|
||||
//
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
namespace Carbon::Check {
|
||||
|
||||
auto DeclNameStack::MakeEmptyNameContext() -> NameContext {
|
||||
return NameContext{.enclosing_scope = context_->current_scope_index(),
|
||||
.target_scope_id = context_->current_scope_id()};
|
||||
return NameContext{
|
||||
.enclosing_scope = context_->scope_stack().PeekIndex(),
|
||||
.target_scope_id = context_->scope_stack().PeekNameScopeId()};
|
||||
}
|
||||
|
||||
auto DeclNameStack::MakeUnqualifiedName(Parse::NodeId parse_node,
|
||||
@@ -25,7 +26,7 @@ auto DeclNameStack::PushScopeAndStartName() -> void {
|
||||
decl_name_stack_.push_back(MakeEmptyNameContext());
|
||||
|
||||
// Create a scope for any parameters introduced in this name.
|
||||
context_->PushScope();
|
||||
context_->scope_stack().Push();
|
||||
}
|
||||
|
||||
auto DeclNameStack::FinishName() -> NameContext {
|
||||
@@ -49,7 +50,7 @@ auto DeclNameStack::FinishName() -> NameContext {
|
||||
auto DeclNameStack::PopScope() -> void {
|
||||
CARBON_CHECK(decl_name_stack_.back().state == NameContext::State::Finished)
|
||||
<< "Missing call to FinishName before PopScope";
|
||||
context_->PopToScope(decl_name_stack_.back().enclosing_scope);
|
||||
context_->scope_stack().PopTo(decl_name_stack_.back().enclosing_scope);
|
||||
decl_name_stack_.pop_back();
|
||||
}
|
||||
|
||||
@@ -154,12 +155,12 @@ static auto PushNameQualifierScope(Context& context,
|
||||
bool has_error = false) -> void {
|
||||
// If the qualifier has no parameters, we don't need to keep around a
|
||||
// parameter scope.
|
||||
context.PopScopeIfEmpty();
|
||||
context.scope_stack().PopIfEmpty();
|
||||
|
||||
context.PushScope(scope_inst_id, scope_id, has_error);
|
||||
context.scope_stack().Push(scope_inst_id, scope_id, has_error);
|
||||
|
||||
// Enter a parameter scope in case the qualified name itself has parameters.
|
||||
context.PushScope();
|
||||
context.scope_stack().Push();
|
||||
}
|
||||
|
||||
auto DeclNameStack::UpdateScopeIfNeeded(NameContext& name_context) -> void {
|
||||
|
||||
@@ -26,7 +26,8 @@ auto HandleAnyBindingPattern(Context& context, Parse::NodeId parse_node,
|
||||
// TODO: Eventually the name will need to support associations with other
|
||||
// scopes, but right now we don't support qualified names here.
|
||||
auto bind_name_id = context.bind_names().Add(
|
||||
{.name_id = name_id, .enclosing_scope_id = context.current_scope_id()});
|
||||
{.name_id = name_id,
|
||||
.enclosing_scope_id = context.scope_stack().PeekNameScopeId()});
|
||||
if (is_generic) {
|
||||
// TODO: Create a `BindTemplateName` instead inside a `template` pattern.
|
||||
return {name_node,
|
||||
|
||||
@@ -144,7 +144,7 @@ auto HandleClassDefinitionStart(Context& context,
|
||||
}
|
||||
|
||||
// Enter the class scope.
|
||||
context.PushScope(class_decl_id, class_info.scope_id);
|
||||
context.scope_stack().Push(class_decl_id, class_info.scope_id);
|
||||
|
||||
// Introduce `Self`.
|
||||
context.AddNameToLookup(SemIR::NameId::SelfType,
|
||||
@@ -329,7 +329,7 @@ auto HandleClassDefinition(Context& context,
|
||||
auto class_id =
|
||||
context.node_stack().Pop<Parse::NodeKind::ClassDefinitionStart>();
|
||||
context.inst_block_stack().Pop();
|
||||
context.PopScope();
|
||||
context.scope_stack().Pop();
|
||||
context.decl_name_stack().PopScope();
|
||||
|
||||
// The class type is now fully defined.
|
||||
|
||||
@@ -9,13 +9,13 @@ namespace Carbon::Check {
|
||||
auto HandleCodeBlockStart(Context& context, Parse::CodeBlockStartId parse_node)
|
||||
-> bool {
|
||||
context.node_stack().Push(parse_node);
|
||||
context.PushScope();
|
||||
context.scope_stack().Push();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto HandleCodeBlock(Context& context, Parse::CodeBlockId /*parse_node*/)
|
||||
-> bool {
|
||||
context.PopScope();
|
||||
context.scope_stack().Pop();
|
||||
context.node_stack()
|
||||
.PopAndDiscardSoloParseNode<Parse::NodeKind::CodeBlockStart>();
|
||||
return true;
|
||||
|
||||
@@ -220,7 +220,7 @@ auto HandleFunctionDefinitionStart(Context& context,
|
||||
// Create the function scope and the entry block.
|
||||
context.return_scope_stack().push_back({.decl_id = decl_id});
|
||||
context.inst_block_stack().Push();
|
||||
context.PushScope(decl_id);
|
||||
context.scope_stack().Push(decl_id);
|
||||
context.AddCurrentCodeBlockToFunction();
|
||||
|
||||
// Bring the implicit and explicit parameters into scope.
|
||||
@@ -270,7 +270,7 @@ auto HandleFunctionDefinition(Context& context,
|
||||
}
|
||||
}
|
||||
|
||||
context.PopScope();
|
||||
context.scope_stack().Pop();
|
||||
context.inst_block_stack().Pop();
|
||||
context.return_scope_stack().pop_back();
|
||||
context.decl_name_stack().PopScope();
|
||||
|
||||
@@ -20,7 +20,7 @@ auto HandleImplIntroducer(Context& context, Parse::ImplIntroducerId parse_node)
|
||||
|
||||
// Create a scope for implicit parameters. We may not use it, but it's simpler
|
||||
// to create it unconditionally than to track whether it exists.
|
||||
context.PushScope();
|
||||
context.scope_stack().Push();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId /*parse_node*/)
|
||||
|
||||
auto HandleImplDecl(Context& context, Parse::ImplDeclId parse_node) -> bool {
|
||||
BuildImplDecl(context, parse_node);
|
||||
context.PopScope();
|
||||
context.scope_stack().Pop();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -77,14 +77,14 @@ auto HandleImplDefinitionStart(Context& context,
|
||||
auto enclosing_scope_id = SemIR::NameScopeId::Invalid;
|
||||
auto scope_id = context.name_scopes().Add(
|
||||
impl_decl_id, SemIR::NameId::Invalid, enclosing_scope_id);
|
||||
context.PushScope(impl_decl_id, scope_id);
|
||||
context.scope_stack().Push(impl_decl_id, scope_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto HandleImplDefinition(Context& context,
|
||||
Parse::ImplDefinitionId /*parse_node*/) -> bool {
|
||||
context.PopScope();
|
||||
context.PopScope();
|
||||
context.scope_stack().Pop();
|
||||
context.scope_stack().Pop();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ auto HandleInterfaceDefinitionStart(
|
||||
}
|
||||
|
||||
// Enter the interface scope.
|
||||
context.PushScope(interface_decl_id, interface_info.scope_id);
|
||||
context.scope_stack().Push(interface_decl_id, interface_info.scope_id);
|
||||
|
||||
// TODO: Introduce `Self`.
|
||||
|
||||
@@ -152,7 +152,7 @@ auto HandleInterfaceDefinition(Context& context,
|
||||
auto interface_id =
|
||||
context.node_stack().Pop<Parse::NodeKind::InterfaceDefinitionStart>();
|
||||
context.inst_block_stack().Pop();
|
||||
context.PopScope();
|
||||
context.scope_stack().Pop();
|
||||
context.decl_name_stack().PopScope();
|
||||
|
||||
// The interface type is now fully defined.
|
||||
|
||||
@@ -34,9 +34,9 @@ auto HandleLetDecl(Context& context, Parse::LetDeclId parse_node) -> bool {
|
||||
// TODO: For a qualified `let` declaration, this should use the target scope
|
||||
// of the name introduced in the declaration. See #2590.
|
||||
CheckAccessModifiersOnDecl(context, Lex::TokenKind::Let,
|
||||
context.current_scope_id());
|
||||
context.scope_stack().PeekNameScopeId());
|
||||
RequireDefaultFinalOnlyInInterfaces(context, Lex::TokenKind::Let,
|
||||
context.current_scope_id());
|
||||
context.scope_stack().PeekNameScopeId());
|
||||
LimitModifiersOnDecl(
|
||||
context, KeywordModifierSet::Access | KeywordModifierSet::Interface,
|
||||
Lex::TokenKind::Let);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Carbon::Check {
|
||||
auto HandleStructLiteralOrStructTypeLiteralStart(
|
||||
Context& context, Parse::StructLiteralOrStructTypeLiteralStartId parse_node)
|
||||
-> bool {
|
||||
context.PushScope();
|
||||
context.scope_stack().Push();
|
||||
context.node_stack().Push(parse_node);
|
||||
// At this point we aren't sure whether this will be a value or type literal,
|
||||
// so we push onto args irrespective. It just won't be used for a type
|
||||
@@ -94,7 +94,7 @@ auto HandleStructLiteral(Context& context, Parse::StructLiteralId parse_node)
|
||||
auto refs_id = context.ParamOrArgEnd(
|
||||
Parse::NodeKind::StructLiteralOrStructTypeLiteralStart);
|
||||
|
||||
context.PopScope();
|
||||
context.scope_stack().Pop();
|
||||
context.node_stack()
|
||||
.PopAndDiscardSoloParseNode<
|
||||
Parse::NodeKind::StructLiteralOrStructTypeLiteralStart>();
|
||||
@@ -117,7 +117,7 @@ auto HandleStructTypeLiteral(Context& context,
|
||||
auto refs_id = context.ParamOrArgEnd(
|
||||
Parse::NodeKind::StructLiteralOrStructTypeLiteralStart);
|
||||
|
||||
context.PopScope();
|
||||
context.scope_stack().Pop();
|
||||
context.node_stack()
|
||||
.PopAndDiscardSoloParseNode<
|
||||
Parse::NodeKind::StructLiteralOrStructTypeLiteralStart>();
|
||||
|
||||
@@ -85,7 +85,7 @@ auto HandleVariableDecl(Context& context, Parse::VariableDeclId parse_node)
|
||||
// TODO: For a qualified `var` declaration, this should use the target scope
|
||||
// of the name introduced in the declaration. See #2590.
|
||||
CheckAccessModifiersOnDecl(context, Lex::TokenKind::Var,
|
||||
context.current_scope_id());
|
||||
context.scope_stack().PeekNameScopeId());
|
||||
LimitModifiersOnDecl(context, KeywordModifierSet::Access,
|
||||
Lex::TokenKind::Var);
|
||||
auto modifiers = context.decl_state_stack().innermost().modifier_set;
|
||||
|
||||
@@ -91,7 +91,7 @@ auto CheckReturnedVar(Context& context, Parse::NodeId returned_node,
|
||||
}
|
||||
|
||||
auto RegisterReturnedVar(Context& context, SemIR::InstId bind_id) -> void {
|
||||
auto existing_id = context.SetReturnedVarOrGetExisting(bind_id);
|
||||
auto existing_id = context.scope_stack().SetReturnedVarOrGetExisting(bind_id);
|
||||
if (existing_id.is_valid()) {
|
||||
CARBON_DIAGNOSTIC(ReturnedVarShadowed, Error,
|
||||
"Cannot declare a `returned var` in the scope of "
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
// 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
|
||||
|
||||
#include "toolchain/check/scope_stack.h"
|
||||
|
||||
#include "common/check.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
auto ScopeStack::VerifyOnFinish() -> void {
|
||||
CARBON_CHECK(scope_stack_.empty()) << scope_stack_.size();
|
||||
}
|
||||
|
||||
auto ScopeStack::Push(SemIR::InstId scope_inst_id, SemIR::NameScopeId scope_id,
|
||||
bool lexical_lookup_has_load_error) -> void {
|
||||
scope_stack_.push_back(
|
||||
{.index = next_scope_index_,
|
||||
.scope_inst_id = scope_inst_id,
|
||||
.scope_id = scope_id,
|
||||
.prev_lexical_lookup_has_load_error = lexical_lookup_has_load_error_});
|
||||
if (scope_id.is_valid()) {
|
||||
non_lexical_scope_stack_.push_back({next_scope_index_, scope_id});
|
||||
}
|
||||
|
||||
lexical_lookup_has_load_error_ |= lexical_lookup_has_load_error;
|
||||
|
||||
// TODO: Handle this case more gracefully.
|
||||
CARBON_CHECK(next_scope_index_.index != std::numeric_limits<int32_t>::max())
|
||||
<< "Ran out of scopes";
|
||||
++next_scope_index_.index;
|
||||
}
|
||||
|
||||
auto ScopeStack::Pop() -> void {
|
||||
auto scope = scope_stack_.pop_back_val();
|
||||
|
||||
lexical_lookup_has_load_error_ = scope.prev_lexical_lookup_has_load_error;
|
||||
|
||||
for (const auto& str_id : scope.names) {
|
||||
auto& lexical_results = lexical_lookup_.Get(str_id);
|
||||
CARBON_CHECK(lexical_results.back().scope_index == scope.index)
|
||||
<< "Inconsistent scope index for name " << str_id;
|
||||
lexical_results.pop_back();
|
||||
}
|
||||
|
||||
if (scope.scope_id.is_valid()) {
|
||||
CARBON_CHECK(non_lexical_scope_stack_.back().scope_index == scope.index);
|
||||
non_lexical_scope_stack_.pop_back();
|
||||
}
|
||||
|
||||
if (scope.has_returned_var) {
|
||||
CARBON_CHECK(!return_scope_stack_.empty());
|
||||
CARBON_CHECK(return_scope_stack_.back().returned_var.is_valid());
|
||||
return_scope_stack_.back().returned_var = SemIR::InstId::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
auto ScopeStack::PopTo(ScopeIndex index) -> void {
|
||||
while (PeekIndex() > index) {
|
||||
Pop();
|
||||
}
|
||||
CARBON_CHECK(PeekIndex() == index)
|
||||
<< "Scope index " << index << " does not enclose the current scope "
|
||||
<< PeekIndex();
|
||||
}
|
||||
|
||||
auto ScopeStack::LookupInCurrentScope(SemIR::NameId name_id) -> SemIR::InstId {
|
||||
auto& lexical_results = lexical_lookup_.Get(name_id);
|
||||
if (lexical_results.empty()) {
|
||||
return SemIR::InstId::Invalid;
|
||||
}
|
||||
|
||||
auto result = lexical_results.back();
|
||||
if (result.scope_index != PeekIndex()) {
|
||||
return SemIR::InstId::Invalid;
|
||||
}
|
||||
|
||||
return result.inst_id;
|
||||
}
|
||||
|
||||
auto ScopeStack::LookupInEnclosingScopes(SemIR::NameId name_id)
|
||||
-> std::pair<SemIR::InstId, llvm::ArrayRef<NonLexicalScope>> {
|
||||
// Find the results from enclosing lexical scopes. These will be combined with
|
||||
// results from non-lexical scopes such as namespaces and classes.
|
||||
llvm::ArrayRef<LexicalLookup::Result> lexical_results =
|
||||
lexical_lookup_.Get(name_id);
|
||||
|
||||
// If we have no lexical results, check all non-lexical scopes.
|
||||
if (lexical_results.empty()) {
|
||||
return {lexical_lookup_has_load_error_ ? SemIR::InstId::BuiltinError
|
||||
: SemIR::InstId::Invalid,
|
||||
non_lexical_scope_stack_};
|
||||
}
|
||||
|
||||
// Find the first non-lexical scope that is within the scope of the lexical
|
||||
// lookup result.
|
||||
auto* first_non_lexical_scope = std::lower_bound(
|
||||
non_lexical_scope_stack_.begin(), non_lexical_scope_stack_.end(),
|
||||
lexical_results.back().scope_index,
|
||||
[](const NonLexicalScope& scope, ScopeIndex index) {
|
||||
return scope.scope_index < index;
|
||||
});
|
||||
return {
|
||||
lexical_results.back().inst_id,
|
||||
llvm::ArrayRef(first_non_lexical_scope, non_lexical_scope_stack_.end())};
|
||||
}
|
||||
|
||||
auto ScopeStack::LookupOrAddName(SemIR::NameId name_id, SemIR::InstId target_id)
|
||||
-> SemIR::InstId {
|
||||
if (!scope_stack_.back().names.insert(name_id).second) {
|
||||
auto existing = lexical_lookup_.Get(name_id).back().inst_id;
|
||||
CARBON_CHECK(existing.is_valid())
|
||||
<< "Name in scope but not in lexical lookups";
|
||||
return existing;
|
||||
}
|
||||
|
||||
// TODO: Reject if we previously performed a failed lookup for this name
|
||||
// in this scope or a scope nested within it.
|
||||
auto& lexical_results = lexical_lookup_.Get(name_id);
|
||||
CARBON_CHECK(lexical_results.empty() ||
|
||||
lexical_results.back().scope_index < PeekIndex())
|
||||
<< "Failed to clean up after scope nested within the current scope";
|
||||
lexical_results.push_back({.inst_id = target_id, .scope_index = PeekIndex()});
|
||||
return SemIR::InstId::Invalid;
|
||||
}
|
||||
|
||||
auto ScopeStack::SetReturnedVarOrGetExisting(SemIR::InstId inst_id)
|
||||
-> SemIR::InstId {
|
||||
CARBON_CHECK(!return_scope_stack_.empty()) << "`returned var` in no function";
|
||||
auto& returned_var = return_scope_stack_.back().returned_var;
|
||||
if (returned_var.is_valid()) {
|
||||
return returned_var;
|
||||
}
|
||||
|
||||
returned_var = inst_id;
|
||||
CARBON_CHECK(!scope_stack_.back().has_returned_var)
|
||||
<< "Scope has returned var but none is set";
|
||||
if (inst_id.is_valid()) {
|
||||
scope_stack_.back().has_returned_var = true;
|
||||
}
|
||||
return SemIR::InstId::Invalid;
|
||||
}
|
||||
|
||||
} // namespace Carbon::Check
|
||||
@@ -0,0 +1,186 @@
|
||||
// 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 "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/ADT/SmallVector.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 StringStoreWrapper<IdentifierId>& identifiers)
|
||||
: lexical_lookup_(identifiers) {}
|
||||
|
||||
// A scope in which `break` and `continue` can be used.
|
||||
struct BreakContinueScope {
|
||||
SemIR::InstBlockId break_target;
|
||||
SemIR::InstBlockId continue_target;
|
||||
};
|
||||
|
||||
// A scope in which `return` can be used.
|
||||
struct ReturnScope {
|
||||
// The declaration from which we can return. Inside a function, this will
|
||||
// be a `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::Invalid;
|
||||
};
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
// Pushes a scope onto scope_stack_. NameScopeId::Invalid 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::InstId::Invalid,
|
||||
SemIR::NameScopeId scope_id = SemIR::NameScopeId::Invalid,
|
||||
bool lexical_lookup_has_load_error = false) -> void;
|
||||
|
||||
// Pops the top scope from scope_stack_, cleaning up 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().names.empty()) {
|
||||
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 Invalid if
|
||||
// there is no such instruction, such as for a block scope.
|
||||
auto PeekInstId() const -> SemIR::InstId { return Peek().scope_inst_id; }
|
||||
|
||||
// Returns the current scope, if it is of the specified kind. Otherwise,
|
||||
// returns nullopt.
|
||||
template <typename InstT>
|
||||
auto GetCurrentScopeAs(const SemIR::File& sem_ir) -> std::optional<InstT> {
|
||||
auto inst_id = PeekInstId();
|
||||
if (!inst_id.is_valid()) {
|
||||
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 invalid instruction ID. If there
|
||||
// is already a `returned var`, returns it instead.
|
||||
auto SetReturnedVarOrGetExisting(SemIR::InstId inst_id) -> SemIR::InstId;
|
||||
|
||||
// Looks up the name `name_id` in the current scope. Returns the existing
|
||||
// lookup result, if any.
|
||||
auto LookupInCurrentScope(SemIR::NameId name_id) -> SemIR::InstId;
|
||||
|
||||
// Looks up the name `name_id` in the current scope and its enclosing 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 LookupInEnclosingScopes(SemIR::NameId name_id)
|
||||
-> std::pair<SemIR::InstId, llvm::ArrayRef<NonLexicalScope>>;
|
||||
|
||||
// Looks up the name `name_id` in the current scope. Returns the existing
|
||||
// instruction if any, and otherwise adds the name with the value `target_id`
|
||||
// and returns Invalid.
|
||||
auto LookupOrAddName(SemIR::NameId name_id, SemIR::InstId target_id)
|
||||
-> SemIR::InstId;
|
||||
|
||||
// Runs verification that the processing cleanly finished.
|
||||
auto VerifyOnFinish() -> void;
|
||||
|
||||
auto return_scope_stack() -> llvm::SmallVector<ReturnScope>& {
|
||||
return return_scope_stack_;
|
||||
}
|
||||
|
||||
auto break_continue_stack() -> llvm::SmallVector<BreakContinueScope>& {
|
||||
return break_continue_stack_;
|
||||
}
|
||||
|
||||
private:
|
||||
// An entry in scope_stack_.
|
||||
struct ScopeStackEntry {
|
||||
// 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 previous state of lexical_lookup_has_load_error_, restored on pop.
|
||||
bool prev_lexical_lookup_has_load_error;
|
||||
|
||||
// Names which are registered with lexical_lookup_, and will need to be
|
||||
// unregistered when the scope ends.
|
||||
llvm::DenseSet<SemIR::NameId> names;
|
||||
|
||||
// Whether a `returned var` was introduced in this scope, and needs to be
|
||||
// unregistered when the scope ends.
|
||||
bool has_returned_var = false;
|
||||
|
||||
// TODO: This likely needs to track things which need to be destructed.
|
||||
};
|
||||
|
||||
auto Peek() const -> const ScopeStackEntry& { return scope_stack_.back(); }
|
||||
|
||||
// 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_;
|
||||
|
||||
// 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_;
|
||||
|
||||
// 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_;
|
||||
|
||||
// Whether lexical_lookup_ has load errors, updated whenever scope_stack_ is
|
||||
// pushed or popped.
|
||||
bool lexical_lookup_has_load_error_ = false;
|
||||
};
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
#endif // CARBON_TOOLCHAIN_CHECK_SCOPE_STACK_H_
|
||||
Reference in New Issue
Block a user