/
githubmirror
/
carbon-lang
Обзор
Документация
Войти
/
githubmirror
/
carbon-lang
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
trunk
toolchain/check/scope_stack.h
495 строк
20 KB
Richard Smith
Destroy temporaries at the end of expression statements. (#7513)
16 июл 2026, 18:12
Не верифицирован
16 июл 2026, 18:12
703529f
Код
Авторство
О чём код?
// 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 { class Context; // A stack of lexical and semantic scopes that we are currently performing // checking within. class ScopeStack { public: explicit ScopeStack(Context& context); // The kind of cleanup scope that is associated with a scope. A cleanup scope // contains the destructor calls that are necessary to perform when leaving a // scope. enum class CleanupScopeKind : uint8_t { // This scope is not used for runtime expression evaluation, so should only // contain constants. Any cleanups introduced here can be discarded. // TODO: Is this the behavior we want for such scopes? Should we check that // the cleanups themselves are constant? None, // This scope is associated with a subexpression, not a full-expression, so // its cleanup scope is inherited from the parent scope. Inherited, // This scope is associated with a statement, so it owns a cleanup scope, // and its cleanups must be run or explicitly discarded when exiting this // scope. Owned, }; // An index for the distance to the bottom of the cleanup stack. struct CleanupScopeDepth : public IndexBase<CleanupScopeDepth> { static constexpr llvm::StringLiteral Label = "cleanup_scope_depth"; using IndexBase::IndexBase; }; // A scope in which `break` and `continue` can be used. struct BreakContinueScope { SemIR::InstBlockId break_target; CleanupScopeDepth break_depth; SemIR::InstBlockId continue_target; CleanupScopeDepth continue_depth; }; // 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(CleanupScopeKind cleanup_scope_kind = CleanupScopeKind::Inherited) -> 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_. // If `check_unused` is set, checks and emits diagnostics for unused names. auto Pop(bool check_unused = false) -> void; // Pops the top scope from scope_stack_ if it contains no names. auto PopIfEmpty(bool check_unused = false) -> void { if (scope_stack_.back().num_names == 0) { Pop(check_unused); } } // Pops scopes until we return to the specified scope index. auto PopTo(ScopeIndex index, bool check_unused = false) -> 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 instruction associated with the parent scope, or `None` if // there is no such instruction, such as for a block scope. auto PeekParentInstId() const -> SemIR::InstId { return Peek(1).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(); } // Merges the innermost scope into its grandparent scope, and pops the // now-empty scope. This is used when handling a `for` statement. Given: // // for (var a: i32 in MakeTempRange()) { // // we have an outer scope containing `var a: i32` and an inner scope // containing `MakeTempRange()`, and we want them the other way around, so we // create an extra enclosing scope in advance and merge the inner scope into // it. // // Requires that no names were introduced in the innermost scope. auto MergeTopScopeIntoGrandparentAndPop() -> void; // Returns the current scope, if it is of the specified kind. Otherwise, // returns nullopt. template <typename InstT> auto TryGetCurrentScopeAs() -> std::optional<InstT> { auto inst_id = PeekInstId(); if (!inst_id.has_value()) { return std::nullopt; } return sem_ir().insts().TryGetAs<InstT>(inst_id); } // Returns the current scope, assuming it is of the specified kind. // Check-fails if there is no instruction for a current scope, or the scope is // of a different kind. template <typename InstT> auto GetCurrentScopeAs() -> InstT { auto inst_id = PeekInstId(); CARBON_CHECK(inst_id.has_value()); return sem_ir().insts().GetAs<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::NameId name_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. // If `use_loc_id` is specified, the name is marked as used at that location. auto LookupInLexicalScopesWithin(SemIR::NameId name_id, ScopeIndex scope_index, SemIR::LocId use_loc_id, bool is_reachable) -> 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. If `use_loc_id` is specified, the // name is marked as used at that location. auto LookupInLexicalScopes(SemIR::NameId name_id, SemIR::LocId use_loc_id, bool is_reachable) -> 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`. `is_decl_reachable` // indicates whether the name was declared in a reachable position. auto LookupOrAddName(SemIR::NameId name_id, SemIR::InstId target_id, ScopeIndex scope_index = ScopeIndex::None, bool is_decl_reachable = true) -> 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; // Returns whether this is a scope in which cleanups are tracked. auto IsCleanupScope() const -> bool { return Peek().cleanup_scope_kind != CleanupScopeKind::None; } // Registers a cleanup for `inst_id` within the current cleanup scope. auto PushCleanupFor(SemIR::InstId inst_id) -> void { CARBON_CHECK(IsCleanupScope()); destroy_id_stack_.push_back(inst_id); } // Returns all values on `destroy_id_stack_` added since `depth`. auto GetCleanupsSince(CleanupScopeDepth depth) const -> llvm::ArrayRef<SemIR::InstId> { return llvm::ArrayRef(destroy_id_stack_).slice(depth.index); } // Add all cleanups created so far in this scope to the ambient state of the // scope. This causes them to be deferred until the scope is exited. auto DeferCleanups() -> void { CARBON_CHECK(IsCleanupScope() || static_cast<size_t>(Peek().cleanup_scope_depth.index) == destroy_id_stack_.size()); scope_stack_.back().cleanup_scope_depth = CleanupScopeDepth(destroy_id_stack_.size()); } // Discards cleanups after the given depth, which must be within the current // scope. auto DiscardCleanupsSince(CleanupScopeDepth depth) -> void { auto enclosing = enclosing_cleanup_scope_depth(); CARBON_CHECK(depth >= enclosing); destroy_id_stack_.truncate(depth.index); if (scope_stack_.back().cleanup_scope_depth.index > depth.index) { // We have discarded ambient cleanups. Reduce the ambient cleanup depth to // match. This happens when exiting the scope. scope_stack_.back().cleanup_scope_depth = depth; } } // Returns the current depth of the cleanup stack. auto cleanup_scope_depth() const -> CleanupScopeDepth { return CleanupScopeDepth(destroy_id_stack_.size()); } // Returns the ambient depth of the cleanup stack in the current scope. auto ambient_cleanup_scope_depth() const -> CleanupScopeDepth { return Peek().cleanup_scope_depth; } // Returns the depth of the cleanup stack enclosing the current scope. auto enclosing_cleanup_scope_depth() const -> CleanupScopeDepth { return scope_stack_.size() < 2 ? CleanupScopeDepth(0) : Peek(1).cleanup_scope_depth; } // Returns the depth of the cleanup stack enclosing this function scope. auto function_cleanup_scope_depth() const -> CleanupScopeDepth { return return_scope_stack_.back().cleanup_scope_depth; } auto break_continue_stack() -> llvm::SmallVector<BreakContinueScope>& { return break_continue_stack_; } auto compile_time_binding_stack() -> ArrayStack<SemIR::InstId>& { return compile_time_binding_stack_; } auto full_pattern_stack() -> FullPatternStack& { return full_pattern_stack_; } private: auto sem_ir() const -> const SemIR::File&; auto lexical_lookup() -> LexicalLookup& { return lexical_lookup_; } // 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; // The kind of cleanup scope that is associated with this scope. CleanupScopeKind cleanup_scope_kind = CleanupScopeKind::None; // The ambient cleanup scope depth in this scope. This is the depth that we // will return to at the end of a statement in this scope. CleanupScopeDepth cleanup_scope_depth; // 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; // The cleanup stack depth when entering the function body. CleanupScopeDepth cleanup_scope_depth; // 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, CleanupScopeKind cleanup_scope_kind, bool lexical_lookup_has_load_error) -> void; auto Peek(int drop = 0) const -> const ScopeStackEntry& { CARBON_DCHECK(drop < static_cast<int>(scope_stack_.size())); return scope_stack_[scope_stack_.size() - 1 - drop]; } // 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; } } // Marks the name `name_id` as used at the given location. auto MarkUsed(SemIR::NameId name_id, SemIR::LocId loc_id, bool is_reachable) -> void; // 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; // Context, used only for checks and emitting diagnostics. Context* context_; // 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. llvm::SmallVector<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; // Whether the name was declared in a reachable position. bool is_decl_reachable; // The location of the first use of the name, if any. SemIR::LocId use_loc_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_