From afef6cd940b6e59819afc04427424f4d1dbfe769 Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Wed, 12 Feb 2025 14:03:08 -0800 Subject: [PATCH] Refactor name lookup logic out of Context (#4930) This is a pretty straight move of name lookup functionality to name_lookup.* --- toolchain/check/BUILD | 2 + toolchain/check/context.cpp | 463 -------------------- toolchain/check/context.h | 87 ---- toolchain/check/decl_name_stack.cpp | 11 +- toolchain/check/handle_binding_pattern.cpp | 3 +- toolchain/check/handle_impl.cpp | 3 +- toolchain/check/handle_index.cpp | 3 +- toolchain/check/handle_literal.cpp | 5 +- toolchain/check/handle_name.cpp | 3 +- toolchain/check/impl.cpp | 7 +- toolchain/check/literal.cpp | 5 +- toolchain/check/member_access.cpp | 18 +- toolchain/check/name_lookup.cpp | 483 +++++++++++++++++++++ toolchain/check/name_lookup.h | 102 +++++ toolchain/check/operator.cpp | 3 +- 15 files changed, 622 insertions(+), 576 deletions(-) create mode 100644 toolchain/check/name_lookup.cpp create mode 100644 toolchain/check/name_lookup.h diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index a2f7f4c10227..5d0b4ba58f4a 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -36,6 +36,7 @@ cc_library( "merge.cpp", "modifiers.cpp", "name_component.cpp", + "name_lookup.cpp", "operator.cpp", "pattern_match.cpp", "return.cpp", @@ -68,6 +69,7 @@ cc_library( "merge.h", "modifiers.h", "name_component.h", + "name_lookup.h", "operator.h", "param_and_arg_refs_stack.h", "pattern_match.h", diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index a3b8b076e03a..19bc9036a410 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -242,28 +242,6 @@ auto Context::DiagnoseNameNotFound(SemIRLoc loc, SemIR::NameId name_id) emitter_->Emit(loc, NameNotFound, name_id); } -auto Context::DiagnoseMemberNameNotFound( - SemIRLoc loc, SemIR::NameId name_id, - llvm::ArrayRef lookup_scopes) -> void { - if (lookup_scopes.size() == 1 && - lookup_scopes.front().name_scope_id.has_value()) { - auto specific_id = lookup_scopes.front().specific_id; - auto scope_inst_id = - specific_id.has_value() - ? GetInstForSpecific(*this, specific_id) - : name_scopes().Get(lookup_scopes.front().name_scope_id).inst_id(); - CARBON_DIAGNOSTIC(MemberNameNotFoundInScope, Error, - "member name `{0}` not found in {1}", SemIR::NameId, - InstIdAsType); - emitter_->Emit(loc, MemberNameNotFoundInScope, name_id, scope_inst_id); - return; - } - - CARBON_DIAGNOSTIC(MemberNameNotFound, Error, "member name `{0}` not found", - SemIR::NameId); - emitter_->Emit(loc, MemberNameNotFound, name_id); -} - auto Context::NoteAbstractClass(SemIR::ClassId class_id, DiagnosticBuilder& builder) -> void { const auto& class_info = classes().Get(class_id); @@ -306,447 +284,6 @@ auto Context::NoteUndefinedInterface(SemIR::InterfaceId interface_id, } } -auto Context::AddNameToLookup(SemIR::NameId name_id, SemIR::InstId target_id, - ScopeIndex scope_index) -> void { - if (auto existing = - scope_stack().LookupOrAddName(name_id, target_id, scope_index); - existing.has_value()) { - DiagnoseDuplicateName(target_id, existing); - } -} - -auto Context::LookupNameInDecl(SemIR::LocId loc_id, SemIR::NameId name_id, - SemIR::NameScopeId scope_id, - ScopeIndex scope_index) - -> SemIR::ScopeLookupResult { - if (!scope_id.has_value()) { - // Look for a name in the specified scope or a scope nested within it only. - // There are two cases where the name would be in an outer scope: - // - // - The name is the sole component of the declared name: - // - // class A; - // fn F() { - // class A; - // } - // - // In this case, the inner A is not the same class as the outer A, so - // lookup should not find the outer A. - // - // - The name is a qualifier of some larger declared name: - // - // class A { class B; } - // fn F() { - // class A.B {} - // } - // - // 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. - // - // There is also one case where the name would be in an inner scope: - // - // - The name is redeclared by a parameter of the same entity: - // - // fn F() { - // class C(C:! type); - // } - // - // In this case, the class C is not a redeclaration of its parameter, but - // we find the parameter in order to diagnose a redeclaration error. - return SemIR::ScopeLookupResult::MakeWrappedLookupResult( - scope_stack().LookupInLexicalScopesWithin(name_id, scope_index), - SemIR::AccessKind::Public); - } 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 - // introduced: - // - // base class A { fn F(); } - // class B { extend base: A; } - // - // // Error, no `F` in `B`. - // fn B.F() {} - return LookupNameInExactScope(loc_id, name_id, scope_id, - name_scopes().Get(scope_id), - /*is_being_declared=*/true); - } -} - -auto Context::LookupUnqualifiedName(Parse::NodeId node_id, - SemIR::NameId name_id, bool required) - -> LookupResult { - // TODO: Check for shadowed lookup results. - - // Find the results from ancestor lexical scopes. These will be combined with - // results from non-lexical scopes such as namespaces and classes. - auto [lexical_result, non_lexical_scopes] = - scope_stack().LookupInLexicalScopes(name_id); - - // Walk the non-lexical scopes and perform lookups into each of them. - for (auto [index, lookup_scope_id, specific_id] : - llvm::reverse(non_lexical_scopes)) { - if (auto non_lexical_result = - LookupQualifiedName(node_id, name_id, - LookupScope{.name_scope_id = lookup_scope_id, - .specific_id = specific_id}, - /*required=*/false); - non_lexical_result.scope_result.is_found()) { - return non_lexical_result; - } - } - - if (lexical_result == SemIR::InstId::InitTombstone) { - CARBON_DIAGNOSTIC(UsedBeforeInitialization, Error, - "`{0}` used before initialization", SemIR::NameId); - emitter_->Emit(node_id, UsedBeforeInitialization, name_id); - return {.specific_id = SemIR::SpecificId::None, - .scope_result = SemIR::ScopeLookupResult::MakeError()}; - } - - if (lexical_result.has_value()) { - // A lexical scope never needs an associated specific. If there's a - // lexically enclosing generic, then it also encloses the point of use of - // the name. - return {.specific_id = SemIR::SpecificId::None, - .scope_result = SemIR::ScopeLookupResult::MakeFound( - lexical_result, SemIR::AccessKind::Public)}; - } - - // We didn't find anything at all. - if (required) { - DiagnoseNameNotFound(node_id, name_id); - } - - return {.specific_id = SemIR::SpecificId::None, - .scope_result = SemIR::ScopeLookupResult::MakeError()}; -} - -auto Context::LookupNameInExactScope(SemIR::LocId loc_id, SemIR::NameId name_id, - SemIR::NameScopeId scope_id, - SemIR::NameScope& scope, - bool is_being_declared) - -> SemIR::ScopeLookupResult { - if (auto entry_id = is_being_declared - ? scope.Lookup(name_id) - : scope.LookupOrPoison(loc_id, name_id)) { - auto lookup_result = scope.GetEntry(*entry_id).result; - if (!lookup_result.is_poisoned()) { - LoadImportRef(*this, lookup_result.target_inst_id()); - } - return lookup_result; - } - - if (!scope.import_ir_scopes().empty()) { - // TODO: Enforce other access modifiers for imports. - return SemIR::ScopeLookupResult::MakeWrappedLookupResult( - ImportNameFromOtherPackage(*this, loc_id, scope_id, - scope.import_ir_scopes(), name_id), - SemIR::AccessKind::Public); - } - return SemIR::ScopeLookupResult::MakeNotFound(); -} - -// Prints diagnostics on invalid qualified name access. -static auto DiagnoseInvalidQualifiedNameAccess(Context& context, SemIRLoc loc, - SemIR::InstId scope_result_id, - SemIR::NameId name_id, - SemIR::AccessKind access_kind, - bool is_parent_access, - AccessInfo access_info) -> void { - auto class_type = context.insts().TryGetAs( - context.constant_values().GetInstId(access_info.constant_id)); - if (!class_type) { - return; - } - - // TODO: Support scoped entities other than just classes. - const auto& class_info = context.classes().Get(class_type->class_id); - - auto parent_type_id = class_info.self_type_id; - - if (access_kind == SemIR::AccessKind::Private && is_parent_access) { - if (auto base_type_id = - class_info.GetBaseType(context.sem_ir(), class_type->specific_id); - base_type_id.has_value()) { - parent_type_id = base_type_id; - } else if (auto adapted_type_id = class_info.GetAdaptedType( - context.sem_ir(), class_type->specific_id); - adapted_type_id.has_value()) { - parent_type_id = adapted_type_id; - } else { - CARBON_FATAL("Expected parent for parent access"); - } - } - - CARBON_DIAGNOSTIC( - ClassInvalidMemberAccess, Error, - "cannot access {0:private|protected} member `{1}` of type {2}", - BoolAsSelect, SemIR::NameId, SemIR::TypeId); - CARBON_DIAGNOSTIC(ClassMemberDeclaration, Note, "declared here"); - context.emitter() - .Build(loc, ClassInvalidMemberAccess, - access_kind == SemIR::AccessKind::Private, name_id, parent_type_id) - .Note(scope_result_id, ClassMemberDeclaration) - .Emit(); -} - -// Returns whether the access is prohibited by the access modifiers. -static auto IsAccessProhibited(std::optional access_info, - SemIR::AccessKind access_kind, - bool is_parent_access) -> bool { - if (!access_info) { - return false; - } - - switch (access_kind) { - case SemIR::AccessKind::Public: - return false; - case SemIR::AccessKind::Protected: - return access_info->highest_allowed_access == SemIR::AccessKind::Public; - case SemIR::AccessKind::Private: - return access_info->highest_allowed_access != - SemIR::AccessKind::Private || - is_parent_access; - } -} - -// Information regarding a prohibited access. -struct ProhibitedAccessInfo { - // The resulting inst of the lookup. - SemIR::InstId scope_result_id; - // The access kind of the lookup. - SemIR::AccessKind access_kind; - // If the lookup is from an extended scope. For example, if this is a base - // class member access from a class that extends it. - bool is_parent_access; -}; - -auto Context::AppendLookupScopesForConstant( - SemIR::LocId loc_id, SemIR::ConstantId base_const_id, - llvm::SmallVector* scopes) -> bool { - auto base_id = constant_values().GetInstId(base_const_id); - auto base = insts().Get(base_id); - if (auto base_as_namespace = base.TryAs()) { - scopes->push_back( - LookupScope{.name_scope_id = base_as_namespace->name_scope_id, - .specific_id = SemIR::SpecificId::None}); - return true; - } - if (auto base_as_class = base.TryAs()) { - RequireDefinedType( - *this, GetTypeIdForTypeConstant(base_const_id), loc_id, [&] { - CARBON_DIAGNOSTIC(QualifiedExprInIncompleteClassScope, Error, - "member access into incomplete class {0}", - InstIdAsType); - return emitter().Build(loc_id, QualifiedExprInIncompleteClassScope, - base_id); - }); - auto& class_info = classes().Get(base_as_class->class_id); - scopes->push_back(LookupScope{.name_scope_id = class_info.scope_id, - .specific_id = base_as_class->specific_id}); - return true; - } - if (auto base_as_facet_type = base.TryAs()) { - RequireDefinedType( - *this, GetTypeIdForTypeConstant(base_const_id), loc_id, [&] { - CARBON_DIAGNOSTIC(QualifiedExprInUndefinedInterfaceScope, Error, - "member access into undefined interface {0}", - InstIdAsType); - return emitter().Build(loc_id, QualifiedExprInUndefinedInterfaceScope, - base_id); - }); - const auto& facet_type_info = - facet_types().Get(base_as_facet_type->facet_type_id); - for (auto interface : facet_type_info.impls_constraints) { - auto& interface_info = interfaces().Get(interface.interface_id); - scopes->push_back(LookupScope{.name_scope_id = interface_info.scope_id, - .specific_id = interface.specific_id}); - } - return true; - } - if (base_const_id == SemIR::ErrorInst::SingletonConstantId) { - // Lookup into this scope should fail without producing an error. - scopes->push_back(LookupScope{.name_scope_id = SemIR::NameScopeId::None, - .specific_id = SemIR::SpecificId::None}); - return true; - } - // TODO: Per the design, if `base_id` is any kind of type, then lookup should - // treat it as a name scope, even if it doesn't have members. For example, - // `(i32*).X` should fail because there's no name `X` in `i32*`, not because - // there's no name `X` in `type`. - return false; -} - -auto Context::LookupQualifiedName(SemIR::LocId loc_id, SemIR::NameId name_id, - llvm::ArrayRef lookup_scopes, - bool required, - std::optional access_info) - -> LookupResult { - llvm::SmallVector scopes(lookup_scopes); - - // TODO: Support reporting of multiple prohibited access. - llvm::SmallVector prohibited_accesses; - - LookupResult result = { - .specific_id = SemIR::SpecificId::None, - .scope_result = SemIR::ScopeLookupResult::MakeNotFound()}; - bool has_error = false; - bool is_parent_access = false; - - // Walk this scope and, if nothing is found here, the scopes it extends. - while (!scopes.empty()) { - auto [scope_id, specific_id] = scopes.pop_back_val(); - if (!scope_id.has_value()) { - has_error = true; - continue; - } - auto& name_scope = name_scopes().Get(scope_id); - has_error |= name_scope.has_error(); - - const SemIR::ScopeLookupResult scope_result = - LookupNameInExactScope(loc_id, name_id, scope_id, name_scope); - SemIR::AccessKind access_kind = scope_result.access_kind(); - - auto is_access_prohibited = - IsAccessProhibited(access_info, access_kind, is_parent_access); - - // Keep track of prohibited accesses, this will be useful for reporting - // multiple prohibited accesses if we can't find a suitable lookup. - if (is_access_prohibited) { - prohibited_accesses.push_back({ - .scope_result_id = scope_result.target_inst_id(), - .access_kind = access_kind, - .is_parent_access = is_parent_access, - }); - } - - if (!scope_result.is_found() || is_access_prohibited) { - // If nothing is found in this scope or if we encountered an invalid - // access, look in its extended scopes. - const auto& extended = name_scope.extended_scopes(); - scopes.reserve(scopes.size() + extended.size()); - for (auto extended_id : llvm::reverse(extended)) { - // Substitute into the constant describing the extended scope to - // determine its corresponding specific. - CARBON_CHECK(extended_id.has_value()); - LoadImportRef(*this, extended_id); - SemIR::ConstantId const_id = - GetConstantValueInSpecific(sem_ir(), specific_id, extended_id); - - DiagnosticAnnotationScope annotate_diagnostics( - &emitter(), [&](auto& builder) { - CARBON_DIAGNOSTIC(FromExtendHere, Note, - "declared as an extended scope here"); - builder.Note(extended_id, FromExtendHere); - }); - if (!AppendLookupScopesForConstant(loc_id, const_id, &scopes)) { - // TODO: Handle case where we have a symbolic type and instead should - // look in its type. - } - } - is_parent_access |= !extended.empty(); - continue; - } - - // If this is our second lookup result, diagnose an ambiguity. - if (result.scope_result.is_found()) { - CARBON_DIAGNOSTIC( - NameAmbiguousDueToExtend, Error, - "ambiguous use of name `{0}` found in multiple extended scopes", - SemIR::NameId); - emitter_->Emit(loc_id, NameAmbiguousDueToExtend, name_id); - // TODO: Add notes pointing to the scopes. - return {.specific_id = SemIR::SpecificId::None, - .scope_result = SemIR::ScopeLookupResult::MakeError()}; - } - - result.scope_result = scope_result; - result.specific_id = specific_id; - } - - if (required && !result.scope_result.is_found()) { - if (!has_error) { - if (prohibited_accesses.empty()) { - DiagnoseMemberNameNotFound(loc_id, name_id, lookup_scopes); - } else { - // TODO: We should report multiple prohibited accesses in case we don't - // find a valid lookup. Reporting the last one should suffice for now. - auto [scope_result_id, access_kind, is_parent_access] = - prohibited_accesses.back(); - - // Note, `access_info` is guaranteed to have a value here, since - // `prohibited_accesses` is non-empty. - DiagnoseInvalidQualifiedNameAccess(*this, loc_id, scope_result_id, - name_id, access_kind, - is_parent_access, *access_info); - } - } - - CARBON_CHECK(!result.scope_result.is_poisoned()); - return {.specific_id = SemIR::SpecificId::None, - .scope_result = SemIR::ScopeLookupResult::MakeError()}; - } - - return result; -} - -// Returns the scope of the Core package, or `None` if it's not found. -// -// TODO: Consider tracking the Core package in SemIR so we don't need to use -// name lookup to find it. -static auto GetCorePackage(Context& context, SemIR::LocId loc_id, - llvm::StringRef name) -> SemIR::NameScopeId { - auto packaging = context.parse_tree().packaging_decl(); - if (packaging && packaging->names.package_id == PackageNameId::Core) { - return SemIR::NameScopeId::Package; - } - auto core_name_id = SemIR::NameId::Core; - - // Look up `package.Core`. - auto core_scope_result = context.LookupNameInExactScope( - loc_id, core_name_id, SemIR::NameScopeId::Package, - context.name_scopes().Get(SemIR::NameScopeId::Package)); - if (core_scope_result.is_found()) { - // We expect it to be a namespace. - if (auto namespace_inst = context.insts().TryGetAs( - core_scope_result.target_inst_id())) { - // TODO: Decide whether to allow the case where `Core` is not a package. - return namespace_inst->name_scope_id; - } - } - - CARBON_DIAGNOSTIC( - CoreNotFound, Error, - "`Core.{0}` implicitly referenced here, but package `Core` not found", - std::string); - context.emitter().Emit(loc_id, CoreNotFound, name.str()); - return SemIR::NameScopeId::None; -} - -auto Context::LookupNameInCore(SemIR::LocId loc_id, llvm::StringRef name) - -> SemIR::InstId { - auto core_package_id = GetCorePackage(*this, loc_id, name); - if (!core_package_id.has_value()) { - return SemIR::ErrorInst::SingletonInstId; - } - - auto name_id = SemIR::NameId::ForIdentifier(identifiers().Add(name)); - auto scope_result = LookupNameInExactScope( - loc_id, name_id, core_package_id, name_scopes().Get(core_package_id)); - if (!scope_result.is_found()) { - CARBON_DIAGNOSTIC( - CoreNameNotFound, Error, - "name `Core.{0}` implicitly referenced here, but not found", - SemIR::NameId); - emitter_->Emit(loc_id, CoreNameNotFound, name_id); - return SemIR::ErrorInst::SingletonInstId; - } - - // Look through import_refs and aliases. - return constant_values().GetConstantInstId(scope_result.target_inst_id()); -} - auto Context::Finalize() -> void { // Pop information for the file-level scope. sem_ir().set_top_inst_block_id(inst_block_stack().Pop()); diff --git a/toolchain/check/context.h b/toolchain/check/context.h index 31dfe22610dc..83cd668d0ce2 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -32,35 +32,6 @@ namespace Carbon::Check { -// Information about a scope in which we can perform name lookup. -struct LookupScope { - // The name scope in which names are searched. - SemIR::NameScopeId name_scope_id; - // The specific for the name scope, or `None` if the name scope is not - // defined by a generic or we should perform lookup into the generic itself. - SemIR::SpecificId specific_id; -}; - -// A result produced by name lookup. -struct LookupResult { - // The specific in which the lookup result was found. `None` if the result - // was not found in a specific. - SemIR::SpecificId specific_id; - - // The result from the lookup in the scope. - SemIR::ScopeLookupResult scope_result; -}; - -// Information about an access. -struct AccessInfo { - // The constant being accessed. - SemIR::ConstantId constant_id; - - // The highest allowed access for a lookup. For example, `Protected` allows - // access to `Public` and `Protected` names, but not `Private`. - SemIR::AccessKind highest_allowed_access; -}; - // Context and shared functionality for semantics handlers. class Context { public: @@ -207,59 +178,6 @@ class Context { sem_ir().insts().SetLocId(inst_id, SemIR::LocId(node_id)); } - // Adds a name to name lookup. Prints a diagnostic for name conflicts. If - // specified, `scope_index` specifies which lexical scope the name is inserted - // into, otherwise the name is inserted into the current scope. - auto AddNameToLookup(SemIR::NameId name_id, SemIR::InstId target_id, - ScopeIndex scope_index = ScopeIndex::None) -> void; - - // Performs name lookup in a specified scope for a name appearing in a - // declaration. If scope_id is `None`, performs lookup into the lexical scope - // specified by scope_index instead. - auto LookupNameInDecl(SemIR::LocId loc_id, SemIR::NameId name_id, - SemIR::NameScopeId scope_id, ScopeIndex scope_index) - -> SemIR::ScopeLookupResult; - - // Performs an unqualified name lookup, returning the referenced `InstId`. - auto LookupUnqualifiedName(Parse::NodeId node_id, SemIR::NameId name_id, - bool required = true) -> LookupResult; - - // Performs a name lookup in a specified scope, returning the referenced - // `InstId`. Does not look into extended scopes. Returns `InstId::None` if the - // name is not found. - // - // If `is_being_declared` is false, then this is a regular name lookup, and - // the name will be poisoned if not found so that later lookups will fail; a - // poisoned name will be treated as if it is not declared. Otherwise, this is - // a lookup for a name being declared, so the name will not be poisoned, but - // poison will be returned if it's already been looked up. - auto LookupNameInExactScope(SemIR::LocId loc_id, SemIR::NameId name_id, - SemIR::NameScopeId scope_id, - SemIR::NameScope& scope, - bool is_being_declared = false) - -> SemIR::ScopeLookupResult; - - // Appends the lookup scopes corresponding to `base_const_id` to `*scopes`. - // Returns `false` if not a scope. On invalid scopes, prints a diagnostic, but - // still updates `*scopes` and returns `true`. - auto AppendLookupScopesForConstant(SemIR::LocId loc_id, - SemIR::ConstantId base_const_id, - llvm::SmallVector* scopes) - -> bool; - - // Performs a qualified name lookup in a specified scopes and in scopes that - // they extend, returning the referenced `InstId`. - auto LookupQualifiedName(SemIR::LocId loc_id, SemIR::NameId name_id, - llvm::ArrayRef lookup_scopes, - bool required = true, - std::optional access_info = std::nullopt) - -> LookupResult; - - // Returns the `InstId` corresponding to a name in the core package, or - // BuiltinErrorInst if not found. - auto LookupNameInCore(SemIR::LocId loc_id, llvm::StringRef name) - -> SemIR::InstId; - // Prints a diagnostic for a duplicate name. auto DiagnoseDuplicateName(SemIRLoc dup_def, SemIRLoc prev_def) -> void; @@ -270,11 +188,6 @@ class Context { // Prints a diagnostic for a missing name. auto DiagnoseNameNotFound(SemIRLoc loc, SemIR::NameId name_id) -> void; - // Prints a diagnostic for a missing qualified name. - auto DiagnoseMemberNameNotFound(SemIRLoc loc, SemIR::NameId name_id, - llvm::ArrayRef lookup_scopes) - -> void; - // Adds a note to a diagnostic explaining that a class is incomplete. auto NoteIncompleteClass(SemIR::ClassId class_id, DiagnosticBuilder& builder) -> void; diff --git a/toolchain/check/decl_name_stack.cpp b/toolchain/check/decl_name_stack.cpp index 82439db77153..08750ffa4979 100644 --- a/toolchain/check/decl_name_stack.cpp +++ b/toolchain/check/decl_name_stack.cpp @@ -10,6 +10,7 @@ #include "toolchain/check/generic.h" #include "toolchain/check/merge.h" #include "toolchain/check/name_component.h" +#include "toolchain/check/name_lookup.h" #include "toolchain/diagnostics/diagnostic.h" #include "toolchain/sem_ir/ids.h" #include "toolchain/sem_ir/name_scope.h" @@ -134,8 +135,8 @@ auto DeclNameStack::AddName(NameContext name_context, SemIR::InstId target_id, case NameContext::State::Unresolved: if (!name_context.parent_scope_id.has_value()) { - context_->AddNameToLookup(name_context.unresolved_name_id, target_id, - name_context.initial_scope_index); + AddNameToLookup(*context_, name_context.unresolved_name_id, target_id, + name_context.initial_scope_index); } else { auto& name_scope = context_->name_scopes().Get(name_context.parent_scope_id); @@ -264,9 +265,9 @@ auto DeclNameStack::ApplyAndLookupName(NameContext& name_context, } // For identifier nodes, we need to perform a lookup on the identifier. - auto lookup_result = context_->LookupNameInDecl( - name_context.loc_id, name_id, name_context.parent_scope_id, - name_context.initial_scope_index); + auto lookup_result = LookupNameInDecl(*context_, name_context.loc_id, name_id, + name_context.parent_scope_id, + name_context.initial_scope_index); if (lookup_result.is_poisoned()) { name_context.unresolved_name_id = name_id; name_context.poisoning_loc_id = lookup_result.poisoning_loc_id(); diff --git a/toolchain/check/handle_binding_pattern.cpp b/toolchain/check/handle_binding_pattern.cpp index e4ac7851e90d..2d9bd52d1b0e 100644 --- a/toolchain/check/handle_binding_pattern.cpp +++ b/toolchain/check/handle_binding_pattern.cpp @@ -6,6 +6,7 @@ #include "toolchain/check/convert.h" #include "toolchain/check/handle.h" #include "toolchain/check/interface.h" +#include "toolchain/check/name_lookup.h" #include "toolchain/check/return.h" #include "toolchain/check/subpattern.h" #include "toolchain/check/type_completion.h" @@ -226,7 +227,7 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, break; } if (had_error) { - context.AddNameToLookup(name_id, SemIR::ErrorInst::SingletonInstId); + AddNameToLookup(context, name_id, SemIR::ErrorInst::SingletonInstId); // Replace the parameter with `ErrorInst` so that we don't try // constructing a generic based on it. param_pattern_id = SemIR::ErrorInst::SingletonInstId; diff --git a/toolchain/check/handle_impl.cpp b/toolchain/check/handle_impl.cpp index d80b0ad42484..14c2a4796d12 100644 --- a/toolchain/check/handle_impl.cpp +++ b/toolchain/check/handle_impl.cpp @@ -10,6 +10,7 @@ #include "toolchain/check/impl.h" #include "toolchain/check/merge.h" #include "toolchain/check/modifiers.h" +#include "toolchain/check/name_lookup.h" #include "toolchain/check/pattern_match.h" #include "toolchain/parse/typed_nodes.h" #include "toolchain/sem_ir/generic.h" @@ -65,7 +66,7 @@ auto HandleParseNode(Context& context, Parse::TypeImplAsId node_id) -> bool { // to the `NameScopeId` of the `impl`, because this happens before we enter // the `impl` scope or even identify which `impl` we're declaring. // TODO: Revisit this once #3714 is resolved. - context.AddNameToLookup(SemIR::NameId::SelfType, self_id); + AddNameToLookup(context, SemIR::NameId::SelfType, self_id); return true; } diff --git a/toolchain/check/handle_index.cpp b/toolchain/check/handle_index.cpp index 1103faf2ca60..a1899a762804 100644 --- a/toolchain/check/handle_index.cpp +++ b/toolchain/check/handle_index.cpp @@ -9,6 +9,7 @@ #include "toolchain/check/convert.h" #include "toolchain/check/handle.h" #include "toolchain/check/literal.h" +#include "toolchain/check/name_lookup.h" #include "toolchain/check/operator.h" #include "toolchain/diagnostics/diagnostic.h" #include "toolchain/sem_ir/inst.h" @@ -31,7 +32,7 @@ auto HandleParseNode(Context& /*context*/, Parse::IndexExprStartId /*node_id*/) static auto GetIndexWithArgs(Context& context, Parse::NodeId node_id, SemIR::TypeId self_id) -> std::optional> { - auto index_with_inst_id = context.LookupNameInCore(node_id, "IndexWith"); + auto index_with_inst_id = LookupNameInCore(context, node_id, "IndexWith"); // If the `IndexWith` interface doesn't have generic arguments then return an // empty reference. if (context.insts().Is(index_with_inst_id)) { diff --git a/toolchain/check/handle_literal.cpp b/toolchain/check/handle_literal.cpp index 394a9ad59ecb..b532d0f01d2b 100644 --- a/toolchain/check/handle_literal.cpp +++ b/toolchain/check/handle_literal.cpp @@ -6,6 +6,7 @@ #include "toolchain/check/context.h" #include "toolchain/check/handle.h" #include "toolchain/check/literal.h" +#include "toolchain/check/name_lookup.h" #include "toolchain/diagnostics/format_providers.h" #include "toolchain/sem_ir/typed_insts.h" @@ -92,7 +93,7 @@ auto HandleParseNode(Context& context, Parse::StringLiteralId node_id) -> bool { auto HandleParseNode(Context& context, Parse::BoolTypeLiteralId node_id) -> bool { - auto fn_inst_id = context.LookupNameInCore(node_id, "Bool"); + auto fn_inst_id = LookupNameInCore(context, node_id, "Bool"); auto type_inst_id = PerformCall(context, node_id, fn_inst_id, {}); context.node_stack().Push(node_id, type_inst_id); return true; @@ -143,7 +144,7 @@ auto HandleParseNode(Context& context, Parse::FloatTypeLiteralId node_id) auto tok_id = context.parse_tree().node_token(node_id); auto size_id = context.tokens().GetTypeLiteralSize(tok_id); auto width_id = MakeIntLiteral(context, node_id, size_id); - auto fn_inst_id = context.LookupNameInCore(node_id, "Float"); + auto fn_inst_id = LookupNameInCore(context, node_id, "Float"); auto type_inst_id = PerformCall(context, node_id, fn_inst_id, {width_id}); context.node_stack().Push(node_id, type_inst_id); return true; diff --git a/toolchain/check/handle_name.cpp b/toolchain/check/handle_name.cpp index 19eecd161b7e..fcdc93c982d2 100644 --- a/toolchain/check/handle_name.cpp +++ b/toolchain/check/handle_name.cpp @@ -7,6 +7,7 @@ #include "toolchain/check/handle.h" #include "toolchain/check/member_access.h" #include "toolchain/check/name_component.h" +#include "toolchain/check/name_lookup.h" #include "toolchain/check/pointer_dereference.h" #include "toolchain/lex/token_kind.h" #include "toolchain/sem_ir/inst.h" @@ -102,7 +103,7 @@ static auto GetIdentifierAsName(Context& context, Parse::NodeId node_id) // lookup. static auto HandleNameAsExpr(Context& context, Parse::NodeId node_id, SemIR::NameId name_id) -> SemIR::InstId { - auto result = context.LookupUnqualifiedName(node_id, name_id); + auto result = LookupUnqualifiedName(context, node_id, name_id); SemIR::InstId inst_id = result.scope_result.target_inst_id(); auto value = context.insts().Get(inst_id); auto type_id = SemIR::GetTypeInSpecific(context.sem_ir(), result.specific_id, diff --git a/toolchain/check/impl.cpp b/toolchain/check/impl.cpp index 3a5b37a58ad9..37e1bde3ddf3 100644 --- a/toolchain/check/impl.cpp +++ b/toolchain/check/impl.cpp @@ -12,6 +12,7 @@ #include "toolchain/check/generic.h" #include "toolchain/check/import_ref.h" #include "toolchain/check/interface.h" +#include "toolchain/check/name_lookup.h" #include "toolchain/check/type_completion.h" #include "toolchain/diagnostics/diagnostic_emitter.h" #include "toolchain/sem_ir/generic.h" @@ -377,9 +378,9 @@ auto FinishImplWitness(Context& context, SemIR::Impl& impl) -> void { CARBON_FATAL("Unexpected type: {0}", type_inst); } auto& fn = context.functions().Get(fn_type->function_id); - auto lookup_result = context.LookupNameInExactScope( - context.insts().GetLocId(decl_id), fn.name_id, impl.scope_id, - impl_scope); + auto lookup_result = + LookupNameInExactScope(context, context.insts().GetLocId(decl_id), + fn.name_id, impl.scope_id, impl_scope); if (lookup_result.is_found()) { used_decl_ids.push_back(lookup_result.target_inst_id()); witness_block[index] = CheckAssociatedFunctionImplementation( diff --git a/toolchain/check/literal.cpp b/toolchain/check/literal.cpp index 0171b3ab2883..b38b3b64ecfe 100644 --- a/toolchain/check/literal.cpp +++ b/toolchain/check/literal.cpp @@ -7,6 +7,7 @@ #include "toolchain/check/call.h" #include "toolchain/check/context.h" #include "toolchain/check/convert.h" +#include "toolchain/check/name_lookup.h" namespace Carbon::Check { @@ -22,8 +23,8 @@ auto MakeIntTypeLiteral(Context& context, Parse::NodeId node_id, SemIR::IntKind int_kind, IntId size_id) -> SemIR::InstId { auto width_id = MakeIntLiteral(context, node_id, size_id); - auto fn_inst_id = context.LookupNameInCore( - node_id, int_kind == SemIR::IntKind::Signed ? "Int" : "UInt"); + auto fn_inst_id = LookupNameInCore( + context, node_id, int_kind == SemIR::IntKind::Signed ? "Int" : "UInt"); return PerformCall(context, node_id, fn_inst_id, {width_id}); } diff --git a/toolchain/check/member_access.cpp b/toolchain/check/member_access.cpp index 1304534b298e..646a3f39bb0a 100644 --- a/toolchain/check/member_access.cpp +++ b/toolchain/check/member_access.cpp @@ -13,6 +13,7 @@ #include "toolchain/check/impl_lookup.h" #include "toolchain/check/import_ref.h" #include "toolchain/check/interface.h" +#include "toolchain/check/name_lookup.h" #include "toolchain/check/type_completion.h" #include "toolchain/diagnostics/diagnostic_emitter.h" #include "toolchain/sem_ir/function.h" @@ -60,9 +61,8 @@ static auto GetHighestAllowedAccess(Context& context, SemIR::LocId loc_id, SemIR::ConstantId name_scope_const_id) -> SemIR::AccessKind { SemIR::ScopeLookupResult lookup_result = - context - .LookupUnqualifiedName(loc_id.node_id(), SemIR::NameId::SelfType, - /*required=*/false) + LookupUnqualifiedName(context, loc_id.node_id(), SemIR::NameId::SelfType, + /*required=*/false) .scope_result; CARBON_CHECK(!lookup_result.is_poisoned()); if (!lookup_result.is_found()) { @@ -241,8 +241,8 @@ static auto LookupMemberNameInScope(Context& context, SemIR::LocId loc_id, GetHighestAllowedAccess(context, loc_id, name_scope_const_id), }; LookupResult result = - context.LookupQualifiedName(loc_id, name_id, lookup_scopes, - /*required=*/true, access_info); + LookupQualifiedName(context, loc_id, name_id, lookup_scopes, + /*required=*/true, access_info); if (!result.scope_result.is_found()) { return SemIR::ErrorInst::SingletonInstId; @@ -436,8 +436,8 @@ auto PerformMemberAccess(Context& context, SemIR::LocId loc_id, if (auto base_const_id = context.constant_values().Get(base_id); base_const_id.is_constant()) { llvm::SmallVector lookup_scopes; - if (context.AppendLookupScopesForConstant(loc_id, base_const_id, - &lookup_scopes)) { + if (AppendLookupScopesForConstant(context, loc_id, base_const_id, + &lookup_scopes)) { return LookupMemberNameInScope(context, loc_id, base_id, name_id, base_const_id, lookup_scopes, /*lookup_in_type_of_base=*/false); @@ -465,8 +465,8 @@ auto PerformMemberAccess(Context& context, SemIR::LocId loc_id, // Find the scope corresponding to the base type. llvm::SmallVector lookup_scopes; - if (!context.AppendLookupScopesForConstant(loc_id, base_type_const_id, - &lookup_scopes)) { + if (!AppendLookupScopesForConstant(context, loc_id, base_type_const_id, + &lookup_scopes)) { // The base type is not a name scope. Try some fallback options. if (auto struct_type = context.insts().TryGetAs( context.constant_values().GetInstId(base_type_const_id))) { diff --git a/toolchain/check/name_lookup.cpp b/toolchain/check/name_lookup.cpp new file mode 100644 index 000000000000..22482d70d5a1 --- /dev/null +++ b/toolchain/check/name_lookup.cpp @@ -0,0 +1,483 @@ +// 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/name_lookup.h" + +#include "toolchain/check/generic.h" +#include "toolchain/check/import.h" +#include "toolchain/check/import_ref.h" +#include "toolchain/check/type_completion.h" +#include "toolchain/diagnostics/format_providers.h" + +namespace Carbon::Check { + +auto AddNameToLookup(Context& context, SemIR::NameId name_id, + SemIR::InstId target_id, ScopeIndex scope_index) -> void { + if (auto existing = context.scope_stack().LookupOrAddName(name_id, target_id, + scope_index); + existing.has_value()) { + context.DiagnoseDuplicateName(target_id, existing); + } +} + +auto LookupNameInDecl(Context& context, SemIR::LocId loc_id, + SemIR::NameId name_id, SemIR::NameScopeId scope_id, + ScopeIndex scope_index) -> SemIR::ScopeLookupResult { + if (!scope_id.has_value()) { + // Look for a name in the specified scope or a scope nested within it only. + // There are two cases where the name would be in an outer scope: + // + // - The name is the sole component of the declared name: + // + // class A; + // fn F() { + // class A; + // } + // + // In this case, the inner A is not the same class as the outer A, so + // lookup should not find the outer A. + // + // - The name is a qualifier of some larger declared name: + // + // class A { class B; } + // fn F() { + // class A.B {} + // } + // + // 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. + // + // There is also one case where the name would be in an inner scope: + // + // - The name is redeclared by a parameter of the same entity: + // + // fn F() { + // class C(C:! type); + // } + // + // In this case, the class C is not a redeclaration of its parameter, but + // we find the parameter in order to diagnose a redeclaration error. + return SemIR::ScopeLookupResult::MakeWrappedLookupResult( + context.scope_stack().LookupInLexicalScopesWithin(name_id, scope_index), + SemIR::AccessKind::Public); + } 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 + // introduced: + // + // base class A { fn F(); } + // class B { extend base: A; } + // + // // Error, no `F` in `B`. + // fn B.F() {} + return LookupNameInExactScope(context, loc_id, name_id, scope_id, + context.name_scopes().Get(scope_id), + /*is_being_declared=*/true); + } +} + +auto LookupUnqualifiedName(Context& context, Parse::NodeId node_id, + SemIR::NameId name_id, bool required) + -> LookupResult { + // TODO: Check for shadowed lookup results. + + // Find the results from ancestor lexical scopes. These will be combined with + // results from non-lexical scopes such as namespaces and classes. + auto [lexical_result, non_lexical_scopes] = + context.scope_stack().LookupInLexicalScopes(name_id); + + // Walk the non-lexical scopes and perform lookups into each of them. + for (auto [index, lookup_scope_id, specific_id] : + llvm::reverse(non_lexical_scopes)) { + if (auto non_lexical_result = + LookupQualifiedName(context, node_id, name_id, + LookupScope{.name_scope_id = lookup_scope_id, + .specific_id = specific_id}, + /*required=*/false); + non_lexical_result.scope_result.is_found()) { + return non_lexical_result; + } + } + + if (lexical_result == SemIR::InstId::InitTombstone) { + CARBON_DIAGNOSTIC(UsedBeforeInitialization, Error, + "`{0}` used before initialization", SemIR::NameId); + context.emitter().Emit(node_id, UsedBeforeInitialization, name_id); + return {.specific_id = SemIR::SpecificId::None, + .scope_result = SemIR::ScopeLookupResult::MakeError()}; + } + + if (lexical_result.has_value()) { + // A lexical scope never needs an associated specific. If there's a + // lexically enclosing generic, then it also encloses the point of use of + // the name. + return {.specific_id = SemIR::SpecificId::None, + .scope_result = SemIR::ScopeLookupResult::MakeFound( + lexical_result, SemIR::AccessKind::Public)}; + } + + // We didn't find anything at all. + if (required) { + context.DiagnoseNameNotFound(node_id, name_id); + } + + return {.specific_id = SemIR::SpecificId::None, + .scope_result = SemIR::ScopeLookupResult::MakeError()}; +} + +auto LookupNameInExactScope(Context& context, SemIR::LocId loc_id, + SemIR::NameId name_id, SemIR::NameScopeId scope_id, + SemIR::NameScope& scope, bool is_being_declared) + -> SemIR::ScopeLookupResult { + if (auto entry_id = is_being_declared + ? scope.Lookup(name_id) + : scope.LookupOrPoison(loc_id, name_id)) { + auto lookup_result = scope.GetEntry(*entry_id).result; + if (!lookup_result.is_poisoned()) { + LoadImportRef(context, lookup_result.target_inst_id()); + } + return lookup_result; + } + + if (!scope.import_ir_scopes().empty()) { + // TODO: Enforce other access modifiers for imports. + return SemIR::ScopeLookupResult::MakeWrappedLookupResult( + ImportNameFromOtherPackage(context, loc_id, scope_id, + scope.import_ir_scopes(), name_id), + SemIR::AccessKind::Public); + } + return SemIR::ScopeLookupResult::MakeNotFound(); +} + +// Prints diagnostics on invalid qualified name access. +static auto DiagnoseInvalidQualifiedNameAccess(Context& context, SemIRLoc loc, + SemIR::InstId scope_result_id, + SemIR::NameId name_id, + SemIR::AccessKind access_kind, + bool is_parent_access, + AccessInfo access_info) -> void { + auto class_type = context.insts().TryGetAs( + context.constant_values().GetInstId(access_info.constant_id)); + if (!class_type) { + return; + } + + // TODO: Support scoped entities other than just classes. + const auto& class_info = context.classes().Get(class_type->class_id); + + auto parent_type_id = class_info.self_type_id; + + if (access_kind == SemIR::AccessKind::Private && is_parent_access) { + if (auto base_type_id = + class_info.GetBaseType(context.sem_ir(), class_type->specific_id); + base_type_id.has_value()) { + parent_type_id = base_type_id; + } else if (auto adapted_type_id = class_info.GetAdaptedType( + context.sem_ir(), class_type->specific_id); + adapted_type_id.has_value()) { + parent_type_id = adapted_type_id; + } else { + CARBON_FATAL("Expected parent for parent access"); + } + } + + CARBON_DIAGNOSTIC( + ClassInvalidMemberAccess, Error, + "cannot access {0:private|protected} member `{1}` of type {2}", + BoolAsSelect, SemIR::NameId, SemIR::TypeId); + CARBON_DIAGNOSTIC(ClassMemberDeclaration, Note, "declared here"); + context.emitter() + .Build(loc, ClassInvalidMemberAccess, + access_kind == SemIR::AccessKind::Private, name_id, parent_type_id) + .Note(scope_result_id, ClassMemberDeclaration) + .Emit(); +} + +// Returns whether the access is prohibited by the access modifiers. +static auto IsAccessProhibited(std::optional access_info, + SemIR::AccessKind access_kind, + bool is_parent_access) -> bool { + if (!access_info) { + return false; + } + + switch (access_kind) { + case SemIR::AccessKind::Public: + return false; + case SemIR::AccessKind::Protected: + return access_info->highest_allowed_access == SemIR::AccessKind::Public; + case SemIR::AccessKind::Private: + return access_info->highest_allowed_access != + SemIR::AccessKind::Private || + is_parent_access; + } +} + +// Information regarding a prohibited access. +struct ProhibitedAccessInfo { + // The resulting inst of the lookup. + SemIR::InstId scope_result_id; + // The access kind of the lookup. + SemIR::AccessKind access_kind; + // If the lookup is from an extended scope. For example, if this is a base + // class member access from a class that extends it. + bool is_parent_access; +}; + +auto AppendLookupScopesForConstant(Context& context, SemIR::LocId loc_id, + SemIR::ConstantId base_const_id, + llvm::SmallVector* scopes) + -> bool { + auto base_id = context.constant_values().GetInstId(base_const_id); + auto base = context.insts().Get(base_id); + if (auto base_as_namespace = base.TryAs()) { + scopes->push_back( + LookupScope{.name_scope_id = base_as_namespace->name_scope_id, + .specific_id = SemIR::SpecificId::None}); + return true; + } + if (auto base_as_class = base.TryAs()) { + RequireDefinedType( + context, context.GetTypeIdForTypeConstant(base_const_id), loc_id, [&] { + CARBON_DIAGNOSTIC(QualifiedExprInIncompleteClassScope, Error, + "member access into incomplete class {0}", + InstIdAsType); + return context.emitter().Build( + loc_id, QualifiedExprInIncompleteClassScope, base_id); + }); + auto& class_info = context.classes().Get(base_as_class->class_id); + scopes->push_back(LookupScope{.name_scope_id = class_info.scope_id, + .specific_id = base_as_class->specific_id}); + return true; + } + if (auto base_as_facet_type = base.TryAs()) { + RequireDefinedType( + context, context.GetTypeIdForTypeConstant(base_const_id), loc_id, [&] { + CARBON_DIAGNOSTIC(QualifiedExprInUndefinedInterfaceScope, Error, + "member access into undefined interface {0}", + InstIdAsType); + return context.emitter().Build( + loc_id, QualifiedExprInUndefinedInterfaceScope, base_id); + }); + const auto& facet_type_info = + context.facet_types().Get(base_as_facet_type->facet_type_id); + for (auto interface : facet_type_info.impls_constraints) { + auto& interface_info = context.interfaces().Get(interface.interface_id); + scopes->push_back(LookupScope{.name_scope_id = interface_info.scope_id, + .specific_id = interface.specific_id}); + } + return true; + } + if (base_const_id == SemIR::ErrorInst::SingletonConstantId) { + // Lookup into this scope should fail without producing an error. + scopes->push_back(LookupScope{.name_scope_id = SemIR::NameScopeId::None, + .specific_id = SemIR::SpecificId::None}); + return true; + } + // TODO: Per the design, if `base_id` is any kind of type, then lookup should + // treat it as a name scope, even if it doesn't have members. For example, + // `(i32*).X` should fail because there's no name `X` in `i32*`, not because + // there's no name `X` in `type`. + return false; +} + +// Prints a diagnostic for a missing qualified name. +static auto DiagnoseMemberNameNotFound( + Context& context, SemIRLoc loc, SemIR::NameId name_id, + llvm::ArrayRef lookup_scopes) -> void { + if (lookup_scopes.size() == 1 && + lookup_scopes.front().name_scope_id.has_value()) { + auto specific_id = lookup_scopes.front().specific_id; + auto scope_inst_id = specific_id.has_value() + ? GetInstForSpecific(context, specific_id) + : context.name_scopes() + .Get(lookup_scopes.front().name_scope_id) + .inst_id(); + CARBON_DIAGNOSTIC(MemberNameNotFoundInScope, Error, + "member name `{0}` not found in {1}", SemIR::NameId, + InstIdAsType); + context.emitter().Emit(loc, MemberNameNotFoundInScope, name_id, + scope_inst_id); + return; + } + + CARBON_DIAGNOSTIC(MemberNameNotFound, Error, "member name `{0}` not found", + SemIR::NameId); + context.emitter().Emit(loc, MemberNameNotFound, name_id); +} + +auto LookupQualifiedName(Context& context, SemIR::LocId loc_id, + SemIR::NameId name_id, + llvm::ArrayRef lookup_scopes, + bool required, std::optional access_info) + -> LookupResult { + llvm::SmallVector scopes(lookup_scopes); + + // TODO: Support reporting of multiple prohibited access. + llvm::SmallVector prohibited_accesses; + + LookupResult result = { + .specific_id = SemIR::SpecificId::None, + .scope_result = SemIR::ScopeLookupResult::MakeNotFound()}; + bool has_error = false; + bool is_parent_access = false; + + // Walk this scope and, if nothing is found here, the scopes it extends. + while (!scopes.empty()) { + auto [scope_id, specific_id] = scopes.pop_back_val(); + if (!scope_id.has_value()) { + has_error = true; + continue; + } + auto& name_scope = context.name_scopes().Get(scope_id); + has_error |= name_scope.has_error(); + + const SemIR::ScopeLookupResult scope_result = + LookupNameInExactScope(context, loc_id, name_id, scope_id, name_scope); + SemIR::AccessKind access_kind = scope_result.access_kind(); + + auto is_access_prohibited = + IsAccessProhibited(access_info, access_kind, is_parent_access); + + // Keep track of prohibited accesses, this will be useful for reporting + // multiple prohibited accesses if we can't find a suitable lookup. + if (is_access_prohibited) { + prohibited_accesses.push_back({ + .scope_result_id = scope_result.target_inst_id(), + .access_kind = access_kind, + .is_parent_access = is_parent_access, + }); + } + + if (!scope_result.is_found() || is_access_prohibited) { + // If nothing is found in this scope or if we encountered an invalid + // access, look in its extended scopes. + const auto& extended = name_scope.extended_scopes(); + scopes.reserve(scopes.size() + extended.size()); + for (auto extended_id : llvm::reverse(extended)) { + // Substitute into the constant describing the extended scope to + // determine its corresponding specific. + CARBON_CHECK(extended_id.has_value()); + LoadImportRef(context, extended_id); + SemIR::ConstantId const_id = GetConstantValueInSpecific( + context.sem_ir(), specific_id, extended_id); + + DiagnosticAnnotationScope annotate_diagnostics( + &context.emitter(), [&](auto& builder) { + CARBON_DIAGNOSTIC(FromExtendHere, Note, + "declared as an extended scope here"); + builder.Note(extended_id, FromExtendHere); + }); + if (!AppendLookupScopesForConstant(context, loc_id, const_id, + &scopes)) { + // TODO: Handle case where we have a symbolic type and instead should + // look in its type. + } + } + is_parent_access |= !extended.empty(); + continue; + } + + // If this is our second lookup result, diagnose an ambiguity. + if (result.scope_result.is_found()) { + CARBON_DIAGNOSTIC( + NameAmbiguousDueToExtend, Error, + "ambiguous use of name `{0}` found in multiple extended scopes", + SemIR::NameId); + context.emitter().Emit(loc_id, NameAmbiguousDueToExtend, name_id); + // TODO: Add notes pointing to the scopes. + return {.specific_id = SemIR::SpecificId::None, + .scope_result = SemIR::ScopeLookupResult::MakeError()}; + } + + result.scope_result = scope_result; + result.specific_id = specific_id; + } + + if (required && !result.scope_result.is_found()) { + if (!has_error) { + if (prohibited_accesses.empty()) { + DiagnoseMemberNameNotFound(context, loc_id, name_id, lookup_scopes); + } else { + // TODO: We should report multiple prohibited accesses in case we don't + // find a valid lookup. Reporting the last one should suffice for now. + auto [scope_result_id, access_kind, is_parent_access] = + prohibited_accesses.back(); + + // Note, `access_info` is guaranteed to have a value here, since + // `prohibited_accesses` is non-empty. + DiagnoseInvalidQualifiedNameAccess(context, loc_id, scope_result_id, + name_id, access_kind, + is_parent_access, *access_info); + } + } + + CARBON_CHECK(!result.scope_result.is_poisoned()); + return {.specific_id = SemIR::SpecificId::None, + .scope_result = SemIR::ScopeLookupResult::MakeError()}; + } + + return result; +} + +// Returns the scope of the Core package, or `None` if it's not found. +// +// TODO: Consider tracking the Core package in SemIR so we don't need to use +// name lookup to find it. +static auto GetCorePackage(Context& context, SemIR::LocId loc_id, + llvm::StringRef name) -> SemIR::NameScopeId { + auto packaging = context.parse_tree().packaging_decl(); + if (packaging && packaging->names.package_id == PackageNameId::Core) { + return SemIR::NameScopeId::Package; + } + auto core_name_id = SemIR::NameId::Core; + + // Look up `package.Core`. + auto core_scope_result = LookupNameInExactScope( + context, loc_id, core_name_id, SemIR::NameScopeId::Package, + context.name_scopes().Get(SemIR::NameScopeId::Package)); + if (core_scope_result.is_found()) { + // We expect it to be a namespace. + if (auto namespace_inst = context.insts().TryGetAs( + core_scope_result.target_inst_id())) { + // TODO: Decide whether to allow the case where `Core` is not a package. + return namespace_inst->name_scope_id; + } + } + + CARBON_DIAGNOSTIC( + CoreNotFound, Error, + "`Core.{0}` implicitly referenced here, but package `Core` not found", + std::string); + context.emitter().Emit(loc_id, CoreNotFound, name.str()); + return SemIR::NameScopeId::None; +} + +auto LookupNameInCore(Context& context, SemIR::LocId loc_id, + llvm::StringRef name) -> SemIR::InstId { + auto core_package_id = GetCorePackage(context, loc_id, name); + if (!core_package_id.has_value()) { + return SemIR::ErrorInst::SingletonInstId; + } + + auto name_id = SemIR::NameId::ForIdentifier(context.identifiers().Add(name)); + auto scope_result = + LookupNameInExactScope(context, loc_id, name_id, core_package_id, + context.name_scopes().Get(core_package_id)); + if (!scope_result.is_found()) { + CARBON_DIAGNOSTIC( + CoreNameNotFound, Error, + "name `Core.{0}` implicitly referenced here, but not found", + SemIR::NameId); + context.emitter().Emit(loc_id, CoreNameNotFound, name_id); + return SemIR::ErrorInst::SingletonInstId; + } + + // Look through import_refs and aliases. + return context.constant_values().GetConstantInstId( + scope_result.target_inst_id()); +} + +} // namespace Carbon::Check diff --git a/toolchain/check/name_lookup.h b/toolchain/check/name_lookup.h new file mode 100644 index 000000000000..cf5fea39b105 --- /dev/null +++ b/toolchain/check/name_lookup.h @@ -0,0 +1,102 @@ +// 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_NAME_LOOKUP_H_ +#define CARBON_TOOLCHAIN_CHECK_NAME_LOOKUP_H_ + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "toolchain/check/context.h" +#include "toolchain/sem_ir/ids.h" + +namespace Carbon::Check { + +// Information about a scope in which we can perform name lookup. +struct LookupScope { + // The name scope in which names are searched. + SemIR::NameScopeId name_scope_id; + // The specific for the name scope, or `None` if the name scope is not + // defined by a generic or we should perform lookup into the generic itself. + SemIR::SpecificId specific_id; +}; + +// A result produced by name lookup. +struct LookupResult { + // The specific in which the lookup result was found. `None` if the result + // was not found in a specific. + SemIR::SpecificId specific_id; + + // The result from the lookup in the scope. + SemIR::ScopeLookupResult scope_result; +}; + +// Information about an access. +struct AccessInfo { + // The constant being accessed. + SemIR::ConstantId constant_id; + + // The highest allowed access for a lookup. For example, `Protected` allows + // access to `Public` and `Protected` names, but not `Private`. + SemIR::AccessKind highest_allowed_access; +}; + +// Adds a name to name lookup. Prints a diagnostic for name conflicts. If +// specified, `scope_index` specifies which lexical scope the name is inserted +// into, otherwise the name is inserted into the current scope. +auto AddNameToLookup(Context& context, SemIR::NameId name_id, + SemIR::InstId target_id, + ScopeIndex scope_index = ScopeIndex::None) -> void; + +// Performs name lookup in a specified scope for a name appearing in a +// declaration. If scope_id is `None`, performs lookup into the lexical scope +// specified by scope_index instead. +auto LookupNameInDecl(Context& context, SemIR::LocId loc_id, + SemIR::NameId name_id, SemIR::NameScopeId scope_id, + ScopeIndex scope_index) -> SemIR::ScopeLookupResult; + +// Performs an unqualified name lookup, returning the referenced `InstId`. +auto LookupUnqualifiedName(Context& context, Parse::NodeId node_id, + SemIR::NameId name_id, bool required = true) + -> LookupResult; + +// Performs a name lookup in a specified scope, returning the referenced +// `InstId`. Does not look into extended scopes. Returns `InstId::None` if the +// name is not found. +// +// If `is_being_declared` is false, then this is a regular name lookup, and +// the name will be poisoned if not found so that later lookups will fail; a +// poisoned name will be treated as if it is not declared. Otherwise, this is +// a lookup for a name being declared, so the name will not be poisoned, but +// poison will be returned if it's already been looked up. +auto LookupNameInExactScope(Context& context, SemIR::LocId loc_id, + SemIR::NameId name_id, SemIR::NameScopeId scope_id, + SemIR::NameScope& scope, + bool is_being_declared = false) + -> SemIR::ScopeLookupResult; + +// Appends the lookup scopes corresponding to `base_const_id` to `*scopes`. +// Returns `false` if not a scope. On invalid scopes, prints a diagnostic, but +// still updates `*scopes` and returns `true`. +auto AppendLookupScopesForConstant(Context& context, SemIR::LocId loc_id, + SemIR::ConstantId base_const_id, + llvm::SmallVector* scopes) + -> bool; + +// Performs a qualified name lookup in a specified scopes and in scopes that +// they extend, returning the referenced `InstId`. +auto LookupQualifiedName(Context& context, SemIR::LocId loc_id, + SemIR::NameId name_id, + llvm::ArrayRef lookup_scopes, + bool required = true, + std::optional access_info = std::nullopt) + -> LookupResult; + +// Returns the `InstId` corresponding to a name in the core package, or +// BuiltinErrorInst if not found. +auto LookupNameInCore(Context& context, SemIR::LocId loc_id, + llvm::StringRef name) -> SemIR::InstId; + +} // namespace Carbon::Check + +#endif // CARBON_TOOLCHAIN_CHECK_NAME_LOOKUP_H_ diff --git a/toolchain/check/operator.cpp b/toolchain/check/operator.cpp index a83642ab79b1..ab6690b83cbb 100644 --- a/toolchain/check/operator.cpp +++ b/toolchain/check/operator.cpp @@ -8,6 +8,7 @@ #include "toolchain/check/context.h" #include "toolchain/check/generic.h" #include "toolchain/check/member_access.h" +#include "toolchain/check/name_lookup.h" #include "toolchain/sem_ir/ids.h" #include "toolchain/sem_ir/typed_insts.h" @@ -17,7 +18,7 @@ namespace Carbon::Check { static auto GetOperatorOpFunction(Context& context, SemIR::LocId loc_id, Operator op) -> SemIR::InstId { // Look up the interface, and pass it any generic arguments. - auto interface_id = context.LookupNameInCore(loc_id, op.interface_name); + auto interface_id = LookupNameInCore(context, loc_id, op.interface_name); if (!op.interface_args_ref.empty()) { interface_id = PerformCall(context, loc_id, interface_id, op.interface_args_ref);