// 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_DECL_NAME_STACK_H_ #define CARBON_TOOLCHAIN_CHECK_DECL_NAME_STACK_H_ #include "llvm/ADT/SmallVector.h" #include "toolchain/check/name_component.h" #include "toolchain/check/scope_index.h" #include "toolchain/check/scope_stack.h" #include "toolchain/sem_ir/ids.h" namespace Carbon::Check { class Context; // Provides support and stacking for qualified declaration name handling. // // A qualified declaration name will consist of entries, which are `Name`s // optionally followed by generic parameter lists, such as `Vector(T:! type)` // in `fn Vector(T:! type).Clear();`, but parameter lists aren't supported yet. // Identifiers such as `Clear` will be resolved to a name if possible, for // example when declaring things that are in a non-generic type or namespace, // and are otherwise marked as an unresolved identifier. // // Unresolved identifiers are valid if and only if they are the last step of a // qualified name; all resolved qualifiers must resolve to an entity with // members, such as a namespace. Resolved identifiers in the last step will // occur for both out-of-line definitions and new declarations, depending on // context. // // For each name component that is processed and denotes a scope, the // corresponding scope is also entered. This is important for unqualified name // lookup both in the definition of the entity being declared, and for names // appearing later in the declaration name itself. For example, in: // // ``` // fn ClassA.ClassB(T:! U).Fn() { var x: V; } // ``` // // the lookup for `U` looks in `ClassA`; the lookup for `V` looks first in // `ClassA.ClassB`, then its parent scope `ClassA`. Scopes entered as part of // processing the name are exited when the name is popped from the stack. // // Example state transitions: // // ``` // // Empty -> Unresolved, because `MyNamespace` is newly declared. // namespace MyNamespace; // // // Empty -> Resolved -> Unresolved, because `MyType` is newly declared. // class MyNamespace.MyType; // // // Empty -> Resolved -> Resolved, because `MyType` was forward declared. // class MyNamespace.MyType { // // Empty -> Unresolved, because `DoSomething` is newly declared. // fn DoSomething(); // } // // // Empty -> Resolved -> Resolved -> ResolvedNonScope, because `DoSomething` // // is forward declared in `MyType`, but is not a scope itself. // fn MyNamespace.MyType.DoSomething() { ... } // ``` class DeclNameStack { public: // Context for declaration name construction. // TODO: Add a helper for class, function, and interface to turn a NameContext // into an EntityWithParamsBase. struct NameContext { enum class State : int8_t { // A context that has not processed any parts of the qualifier. Empty, // The name has been resolved to an instruction ID. Resolved, // An identifier didn't resolve. Unresolved, // An identifier was poisoned in this scope. Poisoned, // The name has already been finished. This is not set in the name // returned by `FinishName`, but is used internally to track that // `FinishName` has already been called. Finished, // An error has occurred, such as an additional qualifier past an // unresolved name. No new diagnostics should be emitted. Error, }; // Combines name information to produce a base struct for entity // construction. auto MakeEntityWithParamsBase(const NameComponent& name, SemIR::InstId decl_id, bool is_extern, SemIR::LibraryNameId extern_library) -> SemIR::EntityWithParamsBase { return { .name_id = name_id_for_new_inst(), .parent_scope_id = parent_scope_id, .generic_id = SemIR::GenericId::None, .first_param_node_id = name.first_param_node_id, .last_param_node_id = name.last_param_node_id, .pattern_block_id = name.pattern_block_id, .implicit_param_patterns_id = name.implicit_param_patterns_id, .param_patterns_id = name.param_patterns_id, .call_params_id = name.call_params_id, .is_extern = is_extern, .extern_library_id = extern_library, .non_owning_decl_id = extern_library.has_value() ? decl_id : SemIR::InstId::None, .first_owning_decl_id = extern_library.has_value() ? SemIR::InstId::None : decl_id, }; } // Returns any name collision found, or `None`. auto prev_inst_id() -> SemIR::InstId; // Returns the name_id for a new instruction. This is `None` when the name // resolved. auto name_id_for_new_inst() -> SemIR::NameId { switch (state) { case State::Unresolved: case State::Poisoned: return unresolved_name_id; default: return SemIR::NameId::None; } } // The current scope when this name began. This is the scope that we will // return to at the end of the declaration. ScopeIndex initial_scope_index; State state = State::Empty; // Whether there have been qualifiers in the name. bool has_qualifiers = false; // The scope which qualified names are added to. For unqualified names in // an unnamed scope, this will be `None` to indicate the current scope // should be used. SemIR::NameScopeId parent_scope_id; // The last location ID used. SemIR::LocId loc_id = SemIR::LocId::None; union { // The ID of a resolved qualifier, including both identifiers and // expressions. `None` indicates resolution failed. SemIR::InstId resolved_inst_id; // The ID of an unresolved identifier. SemIR::NameId unresolved_name_id = SemIR::NameId::None; }; // When `state` is `Poisoned` (name is unresolved due to name poisoning), // the poisoning location. SemIR::LocId poisoning_loc_id = SemIR::LocId::None; }; // Information about a declaration name that has been temporarily removed from // the stack and will later be restored. Names can only be suspended once they // are finished. struct SuspendedName { // The declaration name information. NameContext name_context; // Suspended scopes. We only preallocate space for two of these, because // suspended names are usually used for classes and functions with // unqualified names, which only need at most two scopes -- one scope for // the parameter and one scope for the entity itself, and we can store quite // a few of these when processing a large class definition. llvm::SmallVector scopes; }; explicit DeclNameStack(Context* context) : context_(context) {} // Pushes processing of a new declaration name, which will be used // contextually, and prepares to enter scopes for that name. To pop this // state, `FinishName` and `PopScope` must be called, in that order. auto PushScopeAndStartName() -> void; // Creates and returns a name context corresponding to declaring an // unqualified name in the current context. This is suitable for adding to // name lookup in situations where a qualified name is not permitted, such as // a pattern binding. auto MakeUnqualifiedName(SemIR::LocId loc_id, SemIR::NameId name_id) -> NameContext; // Applies a name component as a qualifier for the current name. This will // enter the scope corresponding to the name if the name describes an existing // scope, such as a namespace or a defined class. auto ApplyNameQualifier(const NameComponent& name) -> void; // Finishes the current declaration name processing, returning the final // context for adding the name to lookup. The final name component should be // popped and passed to this function, and will be added to the declaration // name. auto FinishName(const NameComponent& name) -> NameContext; // Finishes the current declaration name processing for an `impl`, returning // the final context for adding the name to lookup. // // `impl`s don't actually have names, but want the rest of the name processing // logic such as building parameter scopes, so are a special case. auto FinishImplName() -> NameContext; // Pops the declaration name from the declaration name stack, and pops all // scopes that were entered as part of handling the declaration name. These // are the scopes corresponding to name qualifiers in the name, for example // the `A.B` in `fn A.B.F()`. // // This should be called at the end of the declaration. auto PopScope() -> void; // Peeks the current parent scope of the name on top of the stack. Note // that if we're still processing the name qualifiers, this can change before // the name is completed. Also, if the name up to this point was already // declared and is a scope, this will be that scope, rather than the scope // containing it. auto PeekParentScopeId() const -> SemIR::NameScopeId { return decl_name_stack_.back().parent_scope_id; } // Peeks the resolution scope index of the name on top of the stack. auto PeekInitialScopeIndex() const -> ScopeIndex { return decl_name_stack_.back().initial_scope_index; } // Temporarily remove the current declaration name and its associated scopes // from the stack. Can only be called once the name is finished. auto Suspend() -> SuspendedName; // Restore a previously suspended name. auto Restore(SuspendedName sus) -> void; // Adds a name to name lookup. Assumes duplicates are already handled. auto AddName(NameContext name_context, SemIR::InstId target_id, SemIR::AccessKind access_kind) -> void; // Adds a name to name lookup. Prints a diagnostic for name conflicts. auto AddNameOrDiagnose(NameContext name_context, SemIR::InstId target_id, SemIR::AccessKind access_kind) -> void; // Adds a name to name lookup if neither already declared nor poisoned in this // scope. auto LookupOrAddName(NameContext name_context, SemIR::InstId target_id, SemIR::AccessKind access_kind) -> SemIR::ScopeLookupResult; private: // Returns a name context corresponding to an empty name. auto MakeEmptyNameContext() -> NameContext; // Appends a name to the given name context, and performs a lookup to find // what, if anything, the name refers to. auto ApplyAndLookupName(NameContext& name_context, SemIR::LocId loc_id, SemIR::NameId name_id) -> void; // Attempts to resolve the given name context as a scope, and returns the // corresponding scope. Issues a suitable diagnostic and returns `None` if // the name doesn't resolve to a scope. auto ResolveAsScope(const NameContext& name_context, const NameComponent& name) const -> std::pair; // The linked context. Context* context_; // Provides nesting for construction. llvm::SmallVector decl_name_stack_; }; } // namespace Carbon::Check #endif // CARBON_TOOLCHAIN_CHECK_DECL_NAME_STACK_H_