diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 07c01466d6fe..61e74727ba86 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -115,52 +115,144 @@ auto Context::NoteIncompleteClass(SemIR::ClassId class_id, auto Context::AddNameToLookup(Parse::Node name_node, IdentifierId name_id, SemIR::InstId target_id) -> void { if (current_scope().names.insert(name_id).second) { - name_lookup_[name_id].push_back(target_id); + // TODO: Reject if we previously performed a failed lookup for this name in + // this scope or a scope nested within it. + auto& lexical_results = name_lookup_[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( + {.node_id = target_id, .scope_index = current_scope_index()}); } else { - DiagnoseDuplicateName(name_node, name_lookup_[name_id].back()); + DiagnoseDuplicateName(name_node, name_lookup_[name_id].back().node_id); } } -auto Context::LookupName(Parse::Node parse_node, IdentifierId name_id, - SemIR::NameScopeId scope_id, bool print_diagnostics) +auto Context::LookupNameInDeclaration(Parse::Node parse_node, + IdentifierId name_id, + SemIR::NameScopeId scope_id) -> SemIR::InstId { if (scope_id == SemIR::NameScopeId::Invalid) { - auto it = name_lookup_.find(name_id); - if (it == name_lookup_.end()) { - if (print_diagnostics) { - DiagnoseNameNotFound(parse_node, name_id); + // Look for a name in the current scope 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. + if (auto name_it = name_lookup_.find(name_id); + name_it != name_lookup_.end()) { + CARBON_CHECK(!name_it->second.empty()) + << "Should have been erased: " << identifiers().Get(name_id); + auto result = name_it->second.back(); + if (result.scope_index == current_scope_index()) { + return result.node_id; } - return SemIR::InstId::BuiltinError; } - CARBON_CHECK(!it->second.empty()) - << "Should have been erased: " << identifiers().Get(name_id); - - // TODO: Check for ambiguous lookups. - return it->second.back(); + return SemIR::InstId::Invalid; } else { - const auto& scope = name_scopes().Get(scope_id); - auto it = scope.find(name_id); - if (it == scope.end()) { - if (print_diagnostics) { - DiagnoseNameNotFound(parse_node, name_id); - } - return SemIR::InstId::BuiltinError; + // TODO: Once we support `extend`, do not look into `extend`ed scopes here, + // following the same logic as above. + return LookupQualifiedName(parse_node, name_id, scope_id, + /*required=*/false); + } +} + +auto Context::LookupUnqualifiedName(Parse::Node parse_node, + IdentifierId name_id) -> SemIR::InstId { + // TODO: Check for shadowed lookup results. + + // Find the results from enclosing lexical scopes. These will be combined with + // results from non-lexical scopes such as namespaces and classes. + llvm::ArrayRef lexical_results; + if (auto name_it = name_lookup_.find(name_id); + name_it != name_lookup_.end()) { + lexical_results = name_it->second; + CARBON_CHECK(!lexical_results.empty()) + << "Should have been erased: " << identifiers().Get(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) { + return lexical_results.back().node_id; } - return it->second; + auto non_lexical_result = + LookupQualifiedName(parse_node, name_id, name_scope_id, + /*required=*/false); + if (non_lexical_result.is_valid()) { + return non_lexical_result; + } } + + if (!lexical_results.empty()) { + return lexical_results.back().node_id; + } + + // We didn't find anything at all. + DiagnoseNameNotFound(parse_node, name_id); + return SemIR::InstId::BuiltinError; +} + +auto Context::LookupQualifiedName(Parse::Node parse_node, IdentifierId name_id, + SemIR::NameScopeId scope_id, bool required) + -> SemIR::InstId { + CARBON_CHECK(scope_id.is_valid()) << "No scope to perform lookup into"; + const auto& scope = name_scopes().Get(scope_id); + auto it = scope.find(name_id); + if (it == scope.end()) { + // TODO: Also perform lookups into `extend`ed scopes. + if (required) { + DiagnoseNameNotFound(parse_node, name_id); + return SemIR::InstId::BuiltinError; + } + return SemIR::InstId::Invalid; + } + + return it->second; } auto Context::PushScope(SemIR::InstId scope_inst_id, SemIR::NameScopeId scope_id) -> void { - scope_stack_.push_back( - {.scope_inst_id = scope_inst_id, .scope_id = scope_id}); + scope_stack_.push_back({.index = next_scope_index_, + .scope_inst_id = scope_inst_id, + .scope_id = scope_id}); + if (scope_id.is_valid()) { + non_lexical_scope_stack_.push_back({next_scope_index_, scope_id}); + } + + // TODO: Handle this case more gracefully. + CARBON_CHECK(next_scope_index_.index != std::numeric_limits::max()) + << "Ran out of scopes"; + ++next_scope_index_.index; } auto Context::PopScope() -> void { auto scope = scope_stack_.pop_back_val(); for (const auto& str_id : scope.names) { auto it = name_lookup_.find(str_id); + CARBON_CHECK(it->second.back().scope_index == scope.index) + << "Inconsistent scope index for name " << identifiers().Get(str_id); if (it->second.size() == 1) { // Erase names that no longer resolve. name_lookup_.erase(it); @@ -168,6 +260,20 @@ auto Context::PopScope() -> void { it->second.pop_back(); } } + + if (scope.scope_id.is_valid()) { + CARBON_CHECK(non_lexical_scope_stack_.back().first == scope.index); + non_lexical_scope_stack_.pop_back(); + } +} + +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::FollowNameReferences(SemIR::InstId inst_id) -> SemIR::InstId { diff --git a/toolchain/check/context.h b/toolchain/check/context.h index f35eba2042d7..585164daf651 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -55,10 +55,20 @@ class Context { auto AddNameToLookup(Parse::Node name_node, IdentifierId name_id, SemIR::InstId target_id) -> void; - // Performs name lookup in a specified scope, returning the referenced - // instruction. If scope_id is invalid, uses the current contextual scope. - auto LookupName(Parse::Node parse_node, IdentifierId name_id, - SemIR::NameScopeId scope_id, bool print_diagnostics) + // Performs name lookup in a specified scope for a name appearing in a + // declaration, returning the referenced instruction. If scope_id is invalid, + // uses the current contextual scope. + auto LookupNameInDeclaration(Parse::Node parse_node, IdentifierId name_id, + SemIR::NameScopeId scope_id) -> SemIR::InstId; + + // Performs an unqualified name lookup, returning the referenced instruction. + auto LookupUnqualifiedName(Parse::Node parse_node, IdentifierId name_id) + -> SemIR::InstId; + + // Performs a qualified name lookup in a specified scope and in scopes that + // it extends, returning the referenced instruction. + auto LookupQualifiedName(Parse::Node parse_node, IdentifierId name_id, + SemIR::NameScopeId scope_id, bool required = true) -> SemIR::InstId; // Prints a diagnostic for a duplicate name. @@ -81,6 +91,14 @@ class Context { // Pops the top scope from scope_stack_, cleaning up names from name_lookup_. auto PopScope() -> void; + // 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; @@ -296,6 +314,9 @@ class Context { // 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 `ClassDeclaration`, for a class definition scope. @@ -314,6 +335,14 @@ class Context { // TODO: This likely needs to track things which need to be destructed. }; + // A lookup result in the lexical lookup table `name_lookup_`. + struct LexicalLookupResult { + // The node that was added to lookup. + SemIR::InstId node_id; + // The scope in which the node was added. + ScopeIndex scope_index; + }; + // Forms a canonical type ID for a type. This function is given two // callbacks: // @@ -383,16 +412,27 @@ class Context { // A stack for scope context. llvm::SmallVector scope_stack_; + // Information about non-lexical scopes. This is a subset of the entries and + // the information in scope_stack_. + llvm::SmallVector> + non_lexical_scope_stack_; + + // The index of the next scope that will be pushed onto scope_stack_. + ScopeIndex next_scope_index_ = ScopeIndex(0); + // The stack used for qualified declaration name construction. DeclarationNameStack declaration_name_stack_; // Maps identifiers to name lookup results. Values are a stack of name lookup // results in the ancestor scopes. This offers constant-time lookup of names, // regardless of how many scopes exist between the name declaration and - // reference. + // reference. The corresponding scope for each lookup result is tracked, so + // that lexical lookup results can be interleaved with lookup results from + // non-lexical scopes such as classes. // // Names which no longer have lookup results are erased. - llvm::DenseMap> name_lookup_; + llvm::DenseMap> + name_lookup_; // Cache of the mapping from instructions to types, to avoid recomputing the // folding set ID. diff --git a/toolchain/check/declaration_name_stack.cpp b/toolchain/check/declaration_name_stack.cpp index 60f85b7356fe..d1bcedc43c59 100644 --- a/toolchain/check/declaration_name_stack.cpp +++ b/toolchain/check/declaration_name_stack.cpp @@ -9,7 +9,8 @@ namespace Carbon::Check { auto DeclarationNameStack::MakeEmptyNameContext() -> NameContext { - return NameContext{.target_scope_id = context_->current_scope_id()}; + return NameContext{.enclosing_scope = context_->current_scope_index(), + .target_scope_id = context_->current_scope_id()}; } auto DeclarationNameStack::MakeUnqualifiedName(Parse::Node parse_node, @@ -20,11 +21,14 @@ auto DeclarationNameStack::MakeUnqualifiedName(Parse::Node parse_node, return context; } -auto DeclarationNameStack::Push() -> void { +auto DeclarationNameStack::PushScopeAndStartName() -> void { declaration_name_stack_.push_back(MakeEmptyNameContext()); } -auto DeclarationNameStack::Pop() -> NameContext { +auto DeclarationNameStack::FinishName() -> NameContext { + CARBON_CHECK(declaration_name_stack_.back().state != + NameContext::State::Finished) + << "Finished name twice"; if (context_->parse_tree().node_kind( context_->node_stack().PeekParseNode()) == Parse::NodeKind::QualifiedDeclaration) { @@ -39,7 +43,17 @@ auto DeclarationNameStack::Pop() -> NameContext { ApplyNameQualifier(parse_node, name_id); } - return declaration_name_stack_.pop_back_val(); + NameContext result = declaration_name_stack_.back(); + declaration_name_stack_.back().state = NameContext::State::Finished; + return result; +} + +auto DeclarationNameStack::PopScope() -> void { + CARBON_CHECK(declaration_name_stack_.back().state == + NameContext::State::Finished) + << "Missing call to FinishName before PopScope"; + context_->PopToScope(declaration_name_stack_.back().enclosing_scope); + declaration_name_stack_.pop_back(); } auto DeclarationNameStack::LookupOrAddName(NameContext name_context, @@ -74,6 +88,9 @@ auto DeclarationNameStack::LookupOrAddName(NameContext name_context, << name_context.target_scope_id; } return SemIR::InstId::Invalid; + + case NameContext::State::Finished: + CARBON_FATAL() << "Finished state should only be used internally"; } } @@ -95,16 +112,10 @@ auto DeclarationNameStack::ApplyNameQualifierTo(NameContext& name_context, IdentifierId name_id) -> void { if (CanResolveQualifier(name_context, parse_node)) { // For identifier nodes, we need to perform a lookup on the identifier. - // This means the input instruction name_id is actually a string ID. - // - // TODO: This doesn't perform the right kind of lookup. We will find names - // from enclosing lexical scopes here, in the case where `target_scope_id` - // is invalid. - auto resolved_inst_id = context_->LookupName( - name_context.parse_node, name_id, name_context.target_scope_id, - /*print_diagnostics=*/false); - if (resolved_inst_id == SemIR::InstId::BuiltinError) { - // Invalid indicates an unresolved instruction. Store it and return. + auto resolved_inst_id = context_->LookupNameInDeclaration( + name_context.parse_node, name_id, name_context.target_scope_id); + if (!resolved_inst_id.is_valid()) { + // Invalid indicates an unresolved name. Store it and return. name_context.state = NameContext::State::Unresolved; name_context.unresolved_name_id = name_id; return; @@ -130,16 +141,19 @@ auto DeclarationNameStack::UpdateScopeIfNeeded(NameContext& name_context) if (class_info.is_defined()) { name_context.state = NameContext::State::Resolved; name_context.target_scope_id = class_info.scope_id; + context_->PushScope(name_context.resolved_inst_id, class_info.scope_id); } else { name_context.state = NameContext::State::ResolvedNonScope; } break; } - case SemIR::Namespace::Kind: + case SemIR::Namespace::Kind: { + auto scope_id = resolved_inst.As().name_scope_id; name_context.state = NameContext::State::Resolved; - name_context.target_scope_id = - resolved_inst.As().name_scope_id; + name_context.target_scope_id = scope_id; + context_->PushScope(name_context.resolved_inst_id, scope_id); break; + } default: name_context.state = NameContext::State::ResolvedNonScope; break; @@ -198,6 +212,9 @@ auto DeclarationNameStack::CanResolveQualifier(NameContext& name_context, name_context.parse_node = parse_node; return true; } + + case NameContext::State::Finished: + CARBON_FATAL() << "Added a qualifier after calling FinishName"; } } diff --git a/toolchain/check/declaration_name_stack.h b/toolchain/check/declaration_name_stack.h index 04a9fd19ed81..b11e89573e11 100644 --- a/toolchain/check/declaration_name_stack.h +++ b/toolchain/check/declaration_name_stack.h @@ -11,6 +11,20 @@ namespace Carbon::Check { +// An index for a pushed scope. This may correspond to a permanent scope with a +// corresponding `NameScope`, in which case a different index will be assigned +// each time the scope is entered. Alternatively, it may be a temporary scope +// such as is created for a block, and will only be entered once. +// +// `ScopeIndex` values are comparable. Lower `ScopeIndex` values correspond to +// scopes entered earlier in the file. +// +// TODO: Move this struct and the name lookup code in context.h to a separate +// file. +struct ScopeIndex : public ComparableIndexBase, public Printable { + using ComparableIndexBase::ComparableIndexBase; +}; + class Context; // Provides support and stacking for qualified declaration name handling. @@ -28,6 +42,19 @@ class Context; // 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`, and the lookup for `V` looks in +// `ClassA.ClassB` then in its enclosing scope `ClassA`. Scopes entered as part +// of processing the name are exited when the name is popped from the stack. +// // Example state transitions: // // ``` @@ -67,11 +94,20 @@ class DeclarationNameStack { // An identifier didn't resolve. Unresolved, + // 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, }; + // The current scope when this name began. This is the scope that we will + // return to at the end of the declaration. + ScopeIndex enclosing_scope; + State state = State::Empty; // The scope which qualified names are added to. For unqualified names in @@ -95,13 +131,24 @@ class DeclarationNameStack { explicit DeclarationNameStack(Context* context) : context_(context) {} // Pushes processing of a new declaration name, which will be used - // contextually. - auto Push() -> void; + // 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; - // Pops the current declaration name processing, returning the final context - // for adding the name to lookup. This also pops the final name node from the - // name stack, which will be applied to the declaration name if appropriate. - auto Pop() -> NameContext; + // Finishes the current declaration name processing, returning the final + // context for adding the name to lookup. + // + // This also pops the final name instruction from the instruction stack, + // which will be applied to the declaration name if appropriate. + auto FinishName() -> 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; // Creates and returns a name context corresponding to declaring an // unqualified name in the current context. This is suitable for adding to @@ -111,7 +158,8 @@ class DeclarationNameStack { -> NameContext; // Applies a Name from the name stack to the top of the declaration name - // stack. + // stack. 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(Parse::Node parse_node, IdentifierId name_id) -> void; // Adds a name to name lookup. Prints a diagnostic for name conflicts. diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index 8b072cc58ff9..1474d8a7d195 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -14,13 +14,13 @@ auto HandleClassIntroducer(Context& context, Parse::Node parse_node) -> bool { // Push the bracketing node. context.node_stack().Push(parse_node); // A name should always follow. - context.declaration_name_stack().Push(); + context.declaration_name_stack().PushScopeAndStartName(); return true; } static auto BuildClassDeclaration(Context& context) -> std::tuple { - auto name_context = context.declaration_name_stack().Pop(); + auto name_context = context.declaration_name_stack().FinishName(); auto class_keyword = context.node_stack() .PopForSoloParseNode(); @@ -76,6 +76,7 @@ static auto BuildClassDeclaration(Context& context) auto HandleClassDeclaration(Context& context, Parse::Node /*parse_node*/) -> bool { BuildClassDeclaration(context); + context.declaration_name_stack().PopScope(); return true; } @@ -137,6 +138,7 @@ auto HandleClassDefinition(Context& context, Parse::Node parse_node) -> bool { context.node_stack().Pop(); context.inst_block_stack().Pop(); context.PopScope(); + context.declaration_name_stack().PopScope(); // The class type is now fully defined. auto& class_info = context.classes().Get(class_id); diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 6417a70e8342..52683a273b99 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -55,7 +55,7 @@ static auto BuildFunctionDeclaration(Context& context, bool is_definition) context.node_stack() .PopIf() .value_or(SemIR::InstBlockId::Empty); - auto name_context = context.declaration_name_stack().Pop(); + auto name_context = context.declaration_name_stack().FinishName(); auto fn_node = context.node_stack() .PopForSoloParseNode(); @@ -132,6 +132,7 @@ static auto BuildFunctionDeclaration(Context& context, bool is_definition) auto HandleFunctionDeclaration(Context& context, Parse::Node /*parse_node*/) -> bool { BuildFunctionDeclaration(context, /*is_definition=*/false); + context.declaration_name_stack().PopScope(); return true; } @@ -156,6 +157,7 @@ auto HandleFunctionDefinition(Context& context, Parse::Node parse_node) context.PopScope(); context.inst_block_stack().Pop(); context.return_scope_stack().pop_back(); + context.declaration_name_stack().PopScope(); return true; } @@ -233,7 +235,7 @@ auto HandleFunctionIntroducer(Context& context, Parse::Node parse_node) // Push the bracketing node. context.node_stack().Push(parse_node); // A name should always follow. - context.declaration_name_stack().Push(); + context.declaration_name_stack().PushScopeAndStartName(); return true; } diff --git a/toolchain/check/handle_name.cpp b/toolchain/check/handle_name.cpp index 1a98ef7a5e3d..c1abb2c20fb2 100644 --- a/toolchain/check/handle_name.cpp +++ b/toolchain/check/handle_name.cpp @@ -62,10 +62,10 @@ auto HandleMemberAccessExpression(Context& context, Parse::Node parse_node) // If the base is a name scope, such as a class or namespace, perform lookup // into that scope. if (auto name_scope_id = GetAsNameScope(context, base_id)) { - auto inst_id = name_scope_id->is_valid() - ? context.LookupName(parse_node, name_id, *name_scope_id, - /*print_diagnostics=*/true) - : SemIR::InstId::BuiltinError; + auto inst_id = + name_scope_id->is_valid() + ? context.LookupQualifiedName(parse_node, name_id, *name_scope_id) + : SemIR::InstId::BuiltinError; inst_id = GetExpressionValueForLookupResult(context, inst_id); auto inst = context.insts().Get(inst_id); // TODO: Track that this instruction was named within `base_id`. @@ -103,8 +103,8 @@ auto HandleMemberAccessExpression(Context& context, Parse::Node parse_node) auto class_scope_id = context.classes() .Get(base_type.As().class_id) .scope_id; - auto member_id = context.LookupName(parse_node, name_id, class_scope_id, - /*print_diagnostics=*/true); + auto member_id = + context.LookupQualifiedName(parse_node, name_id, class_scope_id); member_id = GetExpressionValueForLookupResult(context, member_id); // Perform instance binding if we found an instance member. @@ -234,10 +234,7 @@ auto HandleName(Context& context, Parse::Node parse_node) -> bool { auto HandleNameExpression(Context& context, Parse::Node parse_node) -> bool { auto name_id = context.tokens().GetIdentifier( context.parse_tree().node_token(parse_node)); - auto value_id = - context.LookupName(parse_node, name_id, SemIR::NameScopeId::Invalid, - /*print_diagnostics=*/true); - + auto value_id = context.LookupUnqualifiedName(parse_node, name_id); value_id = GetExpressionValueForLookupResult(context, value_id); auto value = context.insts().Get(value_id); context.AddInstAndPush( @@ -285,9 +282,7 @@ auto HandleSelfTypeNameExpression(Context& context, Parse::Node parse_node) // HandleClassDefinitionStart. auto name_id = context.identifiers().Add( Lex::TokenKind::SelfTypeIdentifier.fixed_spelling()); - auto value_id = - context.LookupName(parse_node, name_id, SemIR::NameScopeId::Invalid, - /*print_diagnostics=*/true); + auto value_id = context.LookupUnqualifiedName(parse_node, name_id); auto value = context.insts().Get(value_id); context.AddInstAndPush( parse_node, @@ -306,9 +301,7 @@ auto HandleSelfValueNameExpression(Context& context, Parse::Node parse_node) // should not. See #2984 and the corresponding code in // HandleFunctionDefinitionStart. auto name_id = context.identifiers().Add(SemIR::SelfParameter::Name); - auto value_id = - context.LookupName(parse_node, name_id, SemIR::NameScopeId::Invalid, - /*print_diagnostics=*/true); + auto value_id = context.LookupUnqualifiedName(parse_node, name_id); auto value = context.insts().Get(value_id); context.AddInstAndPush( parse_node, diff --git a/toolchain/check/handle_namespace.cpp b/toolchain/check/handle_namespace.cpp index 7311001bf7c5..87df48c6bfd9 100644 --- a/toolchain/check/handle_namespace.cpp +++ b/toolchain/check/handle_namespace.cpp @@ -9,16 +9,17 @@ namespace Carbon::Check { auto HandleNamespaceStart(Context& context, Parse::Node /*parse_node*/) -> bool { - context.declaration_name_stack().Push(); + context.declaration_name_stack().PushScopeAndStartName(); return true; } auto HandleNamespace(Context& context, Parse::Node parse_node) -> bool { - auto name_context = context.declaration_name_stack().Pop(); + auto name_context = context.declaration_name_stack().FinishName(); auto namespace_id = context.AddInst(SemIR::Namespace{ parse_node, context.GetBuiltinType(SemIR::BuiltinKind::NamespaceType), context.name_scopes().Add()}); context.declaration_name_stack().AddNameToLookup(name_context, namespace_id); + context.declaration_name_stack().PopScope(); return true; } diff --git a/toolchain/check/testdata/class/fail_redeclaration_scope.carbon b/toolchain/check/testdata/class/fail_redeclaration_scope.carbon new file mode 100644 index 000000000000..f8cc450aa467 --- /dev/null +++ b/toolchain/check/testdata/class/fail_redeclaration_scope.carbon @@ -0,0 +1,80 @@ +// 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 +// +// AUTOUPDATE + +class A; + +class X { + // OK, a different A. + class A { class B; } + class A.B {} +} + +class A { class B; } + +class Y { + // CHECK:STDERR: fail_redeclaration_scope.carbon:[[@LINE+3]]:9: ERROR: Name `A` not found. + // CHECK:STDERR: class A.B {} + // CHECK:STDERR: ^ + class A.B {} +} + +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc11: type = struct_type {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file "fail_redeclaration_scope.carbon" { +// CHECK:STDOUT: class_declaration @A.1, () +// CHECK:STDOUT: %A: type = class_type @A.1 +// CHECK:STDOUT: class_declaration @X, () +// CHECK:STDOUT: %X: type = class_type @X +// CHECK:STDOUT: class_declaration @A.1, () +// CHECK:STDOUT: class_declaration @Y, () +// CHECK:STDOUT: %Y: type = class_type @Y +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @A.1 { +// CHECK:STDOUT: class_declaration @B.2, () +// CHECK:STDOUT: %B: type = class_type @B.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .B = +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @X { +// CHECK:STDOUT: class_declaration @A.2, () +// CHECK:STDOUT: %A: type = class_type @A.2 +// CHECK:STDOUT: class_declaration @B.1, () +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .A = +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @A.2 { +// CHECK:STDOUT: class_declaration @B.1, () +// CHECK:STDOUT: %B: type = class_type @B.1 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .B = +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @B.1 { +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @B.2; +// CHECK:STDOUT: +// CHECK:STDOUT: class @Y { +// CHECK:STDOUT: class_declaration @.1, () +// CHECK:STDOUT: %.loc21: type = class_type @.1 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @.1 { +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/fail_scope.carbon b/toolchain/check/testdata/class/fail_scope.carbon index 9756a0eb03a0..b2b306dd9527 100644 --- a/toolchain/check/testdata/class/fail_scope.carbon +++ b/toolchain/check/testdata/class/fail_scope.carbon @@ -8,32 +8,30 @@ class Class { fn F() -> i32 { return 1; } +} - // TODO: This `F()` should find `Class.F`. - fn G() -> i32 { - // CHECK:STDERR: fail_scope.carbon:[[@LINE+3]]:12: ERROR: Name `F` not found. - // CHECK:STDERR: return F(); - // CHECK:STDERR: ^ - return F(); - } +fn G() -> i32 { + // CHECK:STDERR: fail_scope.carbon:[[@LINE+3]]:10: ERROR: Name `F` not found. + // CHECK:STDERR: return F(); + // CHECK:STDERR: ^ + return F(); } // CHECK:STDOUT: constants { -// CHECK:STDOUT: %.loc19: type = struct_type {} +// CHECK:STDOUT: %.loc11: type = struct_type {} // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file "fail_scope.carbon" { // CHECK:STDOUT: class_declaration @Class, () // CHECK:STDOUT: %Class: type = class_type @Class +// CHECK:STDOUT: %G: = fn_decl @G // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: class @Class { // CHECK:STDOUT: %F: = fn_decl @F -// CHECK:STDOUT: %G: = fn_decl @G // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .F = %F -// CHECK:STDOUT: .G = %G // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @F() -> i32 { diff --git a/toolchain/check/testdata/class/fail_unbound_field.carbon b/toolchain/check/testdata/class/fail_unbound_field.carbon index 5e4d9d49e577..dac3d1100bcf 100644 --- a/toolchain/check/testdata/class/fail_unbound_field.carbon +++ b/toolchain/check/testdata/class/fail_unbound_field.carbon @@ -7,8 +7,7 @@ class Class { var field: i32; fn F() -> i32 { - // TODO: Unqualified lookup should find this name. - // CHECK:STDERR: fail_unbound_field.carbon:[[@LINE+3]]:12: ERROR: Name `field` not found. + // CHECK:STDERR: fail_unbound_field.carbon:[[@LINE+3]]:12: ERROR: Expression cannot be used as a value. // CHECK:STDERR: return field; // CHECK:STDERR: ^ return field; @@ -23,7 +22,7 @@ fn G() -> i32 { } // CHECK:STDOUT: constants { -// CHECK:STDOUT: %.loc16: type = struct_type {.field: i32} +// CHECK:STDOUT: %.loc15: type = struct_type {.field: i32} // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file "fail_unbound_field.carbon" { @@ -45,7 +44,7 @@ fn G() -> i32 { // CHECK:STDOUT: // CHECK:STDOUT: fn @F() -> i32 { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %field.ref: = name_reference "field", +// CHECK:STDOUT: %field.ref: = name_reference "field", @Class.%field // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/class/nested.carbon b/toolchain/check/testdata/class/nested.carbon new file mode 100644 index 000000000000..f80293e35fe3 --- /dev/null +++ b/toolchain/check/testdata/class/nested.carbon @@ -0,0 +1,144 @@ +// 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 +// +// AUTOUPDATE + +class Outer { + class Inner { + var pi: Self*; + var po: Outer*; + var qi: Inner*; + } + + var po: Self*; + var qo: Outer*; + var pi: Inner*; +} + +fn F(a: Outer*) { + // TODO: Simplify this once `Outer.Inner` works. + // let b: Outer.Inner* = (*a).pi; + + (*a).po = a; + (*a).qo = a; + (*a).pi = (*a).pi; + (*(*a).pi).po = a; + (*(*a).pi).pi = (*a).pi; + (*(*a).pi).qi = (*a).pi; +} + +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc12_3.1: type = struct_type {.pi: Inner*, .po: Outer*, .qi: Inner*} +// CHECK:STDOUT: %.loc17_1.1: type = struct_type {.po: Outer*, .qo: Outer*, .pi: Inner*} +// CHECK:STDOUT: %.loc17_1.2: type = ptr_type {.po: Outer*, .qo: Outer*, .pi: Inner*} +// CHECK:STDOUT: %.loc12_3.2: type = ptr_type {.pi: Inner*, .po: Outer*, .qi: Inner*} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file "nested.carbon" { +// CHECK:STDOUT: class_declaration @Outer, () +// CHECK:STDOUT: %Outer: type = class_type @Outer +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Outer { +// CHECK:STDOUT: class_declaration @Inner, () +// CHECK:STDOUT: %Inner: type = class_type @Inner +// CHECK:STDOUT: %Self.ref: type = name_reference "Self", file.%Outer +// CHECK:STDOUT: %.loc14_15: type = ptr_type Outer +// CHECK:STDOUT: %.loc14_9.1: type = unbound_field_type Outer, Outer* +// CHECK:STDOUT: %.loc14_9.2: = field "po", member0 +// CHECK:STDOUT: %po: = bind_name "po", %.loc14_9.2 +// CHECK:STDOUT: %Outer.ref: type = name_reference "Outer", file.%Outer +// CHECK:STDOUT: %.loc15_16: type = ptr_type Outer +// CHECK:STDOUT: %.loc15_9.1: type = unbound_field_type Outer, Outer* +// CHECK:STDOUT: %.loc15_9.2: = field "qo", member1 +// CHECK:STDOUT: %qo: = bind_name "qo", %.loc15_9.2 +// CHECK:STDOUT: %Inner.ref: type = name_reference "Inner", %Inner +// CHECK:STDOUT: %.loc16_16: type = ptr_type Inner +// CHECK:STDOUT: %.loc16_9.1: type = unbound_field_type Outer, Inner* +// CHECK:STDOUT: %.loc16_9.2: = field "pi", member2 +// CHECK:STDOUT: %pi: = bind_name "pi", %.loc16_9.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Inner = +// CHECK:STDOUT: .po = %po +// CHECK:STDOUT: .qo = %qo +// CHECK:STDOUT: .pi = %pi +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Inner { +// CHECK:STDOUT: %Self.ref: type = name_reference "Self", @Outer.%Inner +// CHECK:STDOUT: %.loc9_17: type = ptr_type Inner +// CHECK:STDOUT: %.loc9_11.1: type = unbound_field_type Inner, Inner* +// CHECK:STDOUT: %.loc9_11.2: = field "pi", member0 +// CHECK:STDOUT: %pi: = bind_name "pi", %.loc9_11.2 +// CHECK:STDOUT: %Outer.ref: type = name_reference "Outer", file.%Outer +// CHECK:STDOUT: %.loc10_18: type = ptr_type Outer +// CHECK:STDOUT: %.loc10_11.1: type = unbound_field_type Inner, Outer* +// CHECK:STDOUT: %.loc10_11.2: = field "po", member1 +// CHECK:STDOUT: %po: = bind_name "po", %.loc10_11.2 +// CHECK:STDOUT: %Inner.ref: type = name_reference "Inner", @Outer.%Inner +// CHECK:STDOUT: %.loc11_18: type = ptr_type Inner +// CHECK:STDOUT: %.loc11_11.1: type = unbound_field_type Inner, Inner* +// CHECK:STDOUT: %.loc11_11.2: = field "qi", member2 +// CHECK:STDOUT: %qi: = bind_name "qi", %.loc11_11.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .pi = %pi +// CHECK:STDOUT: .po = %po +// CHECK:STDOUT: .qi = %qi +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(%a: Outer*) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %a.ref.loc23_5: Outer* = name_reference "a", %a +// CHECK:STDOUT: %.loc23_4: ref Outer = dereference %a.ref.loc23_5 +// CHECK:STDOUT: %.loc23_7: ref Outer* = class_field_access %.loc23_4, member0 +// CHECK:STDOUT: %a.ref.loc23_13: Outer* = name_reference "a", %a +// CHECK:STDOUT: assign %.loc23_7, %a.ref.loc23_13 +// CHECK:STDOUT: %a.ref.loc24_5: Outer* = name_reference "a", %a +// CHECK:STDOUT: %.loc24_4: ref Outer = dereference %a.ref.loc24_5 +// CHECK:STDOUT: %.loc24_7: ref Outer* = class_field_access %.loc24_4, member1 +// CHECK:STDOUT: %a.ref.loc24_13: Outer* = name_reference "a", %a +// CHECK:STDOUT: assign %.loc24_7, %a.ref.loc24_13 +// CHECK:STDOUT: %a.ref.loc25_5: Outer* = name_reference "a", %a +// CHECK:STDOUT: %.loc25_4: ref Outer = dereference %a.ref.loc25_5 +// CHECK:STDOUT: %.loc25_7: ref Inner* = class_field_access %.loc25_4, member2 +// CHECK:STDOUT: %a.ref.loc25_15: Outer* = name_reference "a", %a +// CHECK:STDOUT: %.loc25_14: ref Outer = dereference %a.ref.loc25_15 +// CHECK:STDOUT: %.loc25_17.1: ref Inner* = class_field_access %.loc25_14, member2 +// CHECK:STDOUT: %.loc25_17.2: Inner* = bind_value %.loc25_17.1 +// CHECK:STDOUT: assign %.loc25_7, %.loc25_17.2 +// CHECK:STDOUT: %a.ref.loc26_7: Outer* = name_reference "a", %a +// CHECK:STDOUT: %.loc26_6: ref Outer = dereference %a.ref.loc26_7 +// CHECK:STDOUT: %.loc26_9.1: ref Inner* = class_field_access %.loc26_6, member2 +// CHECK:STDOUT: %.loc26_9.2: Inner* = bind_value %.loc26_9.1 +// CHECK:STDOUT: %.loc26_4: ref Inner = dereference %.loc26_9.2 +// CHECK:STDOUT: %.loc26_13: ref Outer* = class_field_access %.loc26_4, member1 +// CHECK:STDOUT: %a.ref.loc26_19: Outer* = name_reference "a", %a +// CHECK:STDOUT: assign %.loc26_13, %a.ref.loc26_19 +// CHECK:STDOUT: %a.ref.loc27_7: Outer* = name_reference "a", %a +// CHECK:STDOUT: %.loc27_6: ref Outer = dereference %a.ref.loc27_7 +// CHECK:STDOUT: %.loc27_9.1: ref Inner* = class_field_access %.loc27_6, member2 +// CHECK:STDOUT: %.loc27_9.2: Inner* = bind_value %.loc27_9.1 +// CHECK:STDOUT: %.loc27_4: ref Inner = dereference %.loc27_9.2 +// CHECK:STDOUT: %.loc27_13: ref Inner* = class_field_access %.loc27_4, member0 +// CHECK:STDOUT: %a.ref.loc27_21: Outer* = name_reference "a", %a +// CHECK:STDOUT: %.loc27_20: ref Outer = dereference %a.ref.loc27_21 +// CHECK:STDOUT: %.loc27_23.1: ref Inner* = class_field_access %.loc27_20, member2 +// CHECK:STDOUT: %.loc27_23.2: Inner* = bind_value %.loc27_23.1 +// CHECK:STDOUT: assign %.loc27_13, %.loc27_23.2 +// CHECK:STDOUT: %a.ref.loc28_7: Outer* = name_reference "a", %a +// CHECK:STDOUT: %.loc28_6: ref Outer = dereference %a.ref.loc28_7 +// CHECK:STDOUT: %.loc28_9.1: ref Inner* = class_field_access %.loc28_6, member2 +// CHECK:STDOUT: %.loc28_9.2: Inner* = bind_value %.loc28_9.1 +// CHECK:STDOUT: %.loc28_4: ref Inner = dereference %.loc28_9.2 +// CHECK:STDOUT: %.loc28_13: ref Inner* = class_field_access %.loc28_4, member2 +// CHECK:STDOUT: %a.ref.loc28_21: Outer* = name_reference "a", %a +// CHECK:STDOUT: %.loc28_20: ref Outer = dereference %a.ref.loc28_21 +// CHECK:STDOUT: %.loc28_23.1: ref Inner* = class_field_access %.loc28_20, member2 +// CHECK:STDOUT: %.loc28_23.2: Inner* = bind_value %.loc28_23.1 +// CHECK:STDOUT: assign %.loc28_13, %.loc28_23.2 +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/class/reenter_scope.carbon b/toolchain/check/testdata/class/reenter_scope.carbon new file mode 100644 index 000000000000..33420e019059 --- /dev/null +++ b/toolchain/check/testdata/class/reenter_scope.carbon @@ -0,0 +1,43 @@ +// 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 +// +// AUTOUPDATE + +class Class { + fn F() -> i32; + fn G() -> i32; +} + +fn Class.F() -> i32 { + return G(); +} + +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc10: type = struct_type {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file "reenter_scope.carbon" { +// CHECK:STDOUT: class_declaration @Class, () +// CHECK:STDOUT: %Class: type = class_type @Class +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Class { +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .F = %F +// CHECK:STDOUT: .G = %G +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %G.ref: = name_reference "G", @Class.%G +// CHECK:STDOUT: %.loc13_11: init i32 = call %G.ref() +// CHECK:STDOUT: %.loc13_13: i32 = value_of_initializer %.loc13_11 +// CHECK:STDOUT: return %.loc13_13 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() -> i32; diff --git a/toolchain/check/testdata/class/scope.carbon b/toolchain/check/testdata/class/scope.carbon index 86cf142ce9b1..80fb039a21fd 100644 --- a/toolchain/check/testdata/class/scope.carbon +++ b/toolchain/check/testdata/class/scope.carbon @@ -8,6 +8,10 @@ class Class { fn F() -> i32 { return 1; } + + fn G() -> i32 { + return F(); + } } fn F() -> i32 { @@ -19,7 +23,7 @@ fn Run() -> i32 { } // CHECK:STDOUT: constants { -// CHECK:STDOUT: %.loc11: type = struct_type {} +// CHECK:STDOUT: %.loc15: type = struct_type {} // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file "scope.carbon" { @@ -31,9 +35,11 @@ fn Run() -> i32 { // CHECK:STDOUT: // CHECK:STDOUT: class @Class { // CHECK:STDOUT: %F: = fn_decl @F.1 +// CHECK:STDOUT: %G: = fn_decl @G // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .F = %F +// CHECK:STDOUT: .G = %G // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @F.1() -> i32 { @@ -42,21 +48,29 @@ fn Run() -> i32 { // CHECK:STDOUT: return %.loc9 // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: fn @G() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref: = name_reference "F", @Class.%F +// CHECK:STDOUT: %.loc13_13: init i32 = call %F.ref() +// CHECK:STDOUT: %.loc13_15: i32 = value_of_initializer %.loc13_13 +// CHECK:STDOUT: return %.loc13_15 +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: fn @F.2() -> i32 { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %.loc14: i32 = int_literal 2 -// CHECK:STDOUT: return %.loc14 +// CHECK:STDOUT: %.loc18: i32 = int_literal 2 +// CHECK:STDOUT: return %.loc18 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @Run() -> i32 { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %F.ref.loc18_10: = name_reference "F", file.%F -// CHECK:STDOUT: %.loc18_11: init i32 = call %F.ref.loc18_10() +// CHECK:STDOUT: %F.ref.loc22_10: = name_reference "F", file.%F +// CHECK:STDOUT: %.loc22_11: init i32 = call %F.ref.loc22_10() // CHECK:STDOUT: %Class.ref: type = name_reference "Class", file.%Class -// CHECK:STDOUT: %F.ref.loc18_21: = name_reference "F", @Class.%F -// CHECK:STDOUT: %.loc18_23.1: init i32 = call %F.ref.loc18_21() -// CHECK:STDOUT: %.loc18_14.1: i32 = value_of_initializer %.loc18_11 -// CHECK:STDOUT: %.loc18_23.2: i32 = value_of_initializer %.loc18_23.1 -// CHECK:STDOUT: %.loc18_14.2: i32 = add %.loc18_14.1, %.loc18_23.2 -// CHECK:STDOUT: return %.loc18_14.2 +// CHECK:STDOUT: %F.ref.loc22_21: = name_reference "F", @Class.%F +// CHECK:STDOUT: %.loc22_23.1: init i32 = call %F.ref.loc22_21() +// CHECK:STDOUT: %.loc22_14.1: i32 = value_of_initializer %.loc22_11 +// CHECK:STDOUT: %.loc22_23.2: i32 = value_of_initializer %.loc22_23.1 +// CHECK:STDOUT: %.loc22_14.2: i32 = add %.loc22_14.1, %.loc22_23.2 +// CHECK:STDOUT: return %.loc22_14.2 // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/namespace/shadow.carbon b/toolchain/check/testdata/namespace/shadow.carbon new file mode 100644 index 000000000000..540d0a184b62 --- /dev/null +++ b/toolchain/check/testdata/namespace/shadow.carbon @@ -0,0 +1,61 @@ +// 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 +// +// AUTOUPDATE + +fn A(); + +namespace N; +fn N.A(); + +namespace N.M; + +fn N.M.B() -> i32 { + // This is N.A, not package.A. + A(); + if (true) { + var A: i32 = 0; + // This is the local A. + // TODO: Decide if we should warn or error on the shadowing of N.A here. + return A; + } + return 0; +} + +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc16: type = tuple_type () +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file "shadow.carbon" { +// CHECK:STDOUT: %A.loc7: = fn_decl @A.1 +// CHECK:STDOUT: %.loc9: = namespace {.A = %A.loc10, .M = %.loc12} +// CHECK:STDOUT: %A.loc10: = fn_decl @A.2 +// CHECK:STDOUT: %.loc12: = namespace {.B = %B} +// CHECK:STDOUT: %B: = fn_decl @B +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @A.1(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @A.2(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @B() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %A.ref.loc16: = name_reference "A", file.%A.loc10 +// CHECK:STDOUT: %.loc16: init () = call %A.ref.loc16() +// CHECK:STDOUT: %.loc17: bool = bool_literal true +// CHECK:STDOUT: if %.loc17 br !if.then else br !if.else +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then: +// CHECK:STDOUT: %A.var: ref i32 = var "A" +// CHECK:STDOUT: %A: ref i32 = bind_name "A", %A.var +// CHECK:STDOUT: %.loc18: i32 = int_literal 0 +// CHECK:STDOUT: assign %A.var, %.loc18 +// CHECK:STDOUT: %A.ref.loc21: ref i32 = name_reference "A", %A +// CHECK:STDOUT: %.loc21: i32 = bind_value %A.ref.loc21 +// CHECK:STDOUT: return %.loc21 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else: +// CHECK:STDOUT: %.loc23: i32 = int_literal 0 +// CHECK:STDOUT: return %.loc23 +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/namespace/unqualified_lookup.carbon b/toolchain/check/testdata/namespace/unqualified_lookup.carbon new file mode 100644 index 000000000000..b9b07a70b396 --- /dev/null +++ b/toolchain/check/testdata/namespace/unqualified_lookup.carbon @@ -0,0 +1,75 @@ +// 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 +// +// AUTOUPDATE + +namespace OuterN; +namespace OuterN.InnerN; + +fn A(); +fn OuterN.B(); +fn OuterN.InnerN.C(); + +fn CallA() { + A(); +} + +fn OuterN.CallAB() { + A(); + B(); +} + +fn OuterN.InnerN.CallABC() { + A(); + B(); + C(); +} + +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc15: type = tuple_type () +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file "unqualified_lookup.carbon" { +// CHECK:STDOUT: %.loc7: = namespace {.InnerN = %.loc8, .B = %B, .CallAB = %CallAB} +// CHECK:STDOUT: %.loc8: = namespace {.C = %C, .CallABC = %CallABC} +// CHECK:STDOUT: %A: = fn_decl @A +// CHECK:STDOUT: %B: = fn_decl @B +// CHECK:STDOUT: %C: = fn_decl @C +// CHECK:STDOUT: %CallA: = fn_decl @CallA +// CHECK:STDOUT: %CallAB: = fn_decl @CallAB +// CHECK:STDOUT: %CallABC: = fn_decl @CallABC +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @A(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @B(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @C(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @CallA() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %A.ref: = name_reference "A", file.%A +// CHECK:STDOUT: %.loc15: init () = call %A.ref() +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @CallAB() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %A.ref: = name_reference "A", file.%A +// CHECK:STDOUT: %.loc19: init () = call %A.ref() +// CHECK:STDOUT: %B.ref: = name_reference "B", file.%B +// CHECK:STDOUT: %.loc20: init () = call %B.ref() +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @CallABC() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %A.ref: = name_reference "A", file.%A +// CHECK:STDOUT: %.loc24: init () = call %A.ref() +// CHECK:STDOUT: %B.ref: = name_reference "B", file.%B +// CHECK:STDOUT: %.loc25: init () = call %B.ref() +// CHECK:STDOUT: %C.ref: = name_reference "C", file.%C +// CHECK:STDOUT: %.loc26: init () = call %C.ref() +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/var/fail_shadowing.carbon b/toolchain/check/testdata/var/fail_shadowing.carbon deleted file mode 100644 index 1dfc5cbe2457..000000000000 --- a/toolchain/check/testdata/var/fail_shadowing.carbon +++ /dev/null @@ -1,49 +0,0 @@ -// 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 -// -// AUTOUPDATE - -fn Main() { - var x: i32 = 0; - if (true) { - // TODO: We should reject the use of the shadowed variable `x`, not this - // shadowing declaration. Also the diagnostic text is wrong ("same scope"). - // CHECK:STDERR: fail_shadowing.carbon:[[@LINE+6]]:9: ERROR: Duplicate name being declared in the same scope. - // CHECK:STDERR: var x: i32 = 0; - // CHECK:STDERR: ^ - // CHECK:STDERR: fail_shadowing.carbon:[[@LINE-7]]:7: Name is previously declared here. - // CHECK:STDERR: var x: i32 = 0; - // CHECK:STDERR: ^ - var x: i32 = 0; - - x = 1; - } -} - -// CHECK:STDOUT: file "fail_shadowing.carbon" { -// CHECK:STDOUT: %Main: = fn_decl @Main -// CHECK:STDOUT: } -// CHECK:STDOUT: -// CHECK:STDOUT: fn @Main() { -// CHECK:STDOUT: !entry: -// CHECK:STDOUT: %x.var.loc8: ref i32 = var "x" -// CHECK:STDOUT: %x.loc8: ref i32 = bind_name "x", %x.var.loc8 -// CHECK:STDOUT: %.loc8: i32 = int_literal 0 -// CHECK:STDOUT: assign %x.var.loc8, %.loc8 -// CHECK:STDOUT: %.loc9: bool = bool_literal true -// CHECK:STDOUT: if %.loc9 br !if.then else br !if.else -// CHECK:STDOUT: -// CHECK:STDOUT: !if.then: -// CHECK:STDOUT: %x.var.loc18: ref i32 = var "x" -// CHECK:STDOUT: %x.loc18: ref i32 = bind_name "x", %x.var.loc18 -// CHECK:STDOUT: %.loc18: i32 = int_literal 0 -// CHECK:STDOUT: assign %x.var.loc18, %.loc18 -// CHECK:STDOUT: %x.ref: ref i32 = name_reference "x", %x.loc8 -// CHECK:STDOUT: %.loc20: i32 = int_literal 1 -// CHECK:STDOUT: assign %x.ref, %.loc20 -// CHECK:STDOUT: br !if.else -// CHECK:STDOUT: -// CHECK:STDOUT: !if.else: -// CHECK:STDOUT: return -// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/var/shadowing.carbon b/toolchain/check/testdata/var/shadowing.carbon new file mode 100644 index 000000000000..95cafdb24254 --- /dev/null +++ b/toolchain/check/testdata/var/shadowing.carbon @@ -0,0 +1,42 @@ +// 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 +// +// AUTOUPDATE + +fn Main() { + var x: i32 = 0; + if (true) { + var x: i32 = 0; + + // TODO: We should reject this use of the shadowed variable `x`. + x = 1; + } +} + +// CHECK:STDOUT: file "shadowing.carbon" { +// CHECK:STDOUT: %Main: = fn_decl @Main +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Main() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %x.var.loc8: ref i32 = var "x" +// CHECK:STDOUT: %x.loc8: ref i32 = bind_name "x", %x.var.loc8 +// CHECK:STDOUT: %.loc8: i32 = int_literal 0 +// CHECK:STDOUT: assign %x.var.loc8, %.loc8 +// CHECK:STDOUT: %.loc9: bool = bool_literal true +// CHECK:STDOUT: if %.loc9 br !if.then else br !if.else +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then: +// CHECK:STDOUT: %x.var.loc10: ref i32 = var "x" +// CHECK:STDOUT: %x.loc10: ref i32 = bind_name "x", %x.var.loc10 +// CHECK:STDOUT: %.loc10: i32 = int_literal 0 +// CHECK:STDOUT: assign %x.var.loc10, %.loc10 +// CHECK:STDOUT: %x.ref: ref i32 = name_reference "x", %x.loc10 +// CHECK:STDOUT: %.loc13: i32 = int_literal 1 +// CHECK:STDOUT: assign %x.ref, %.loc13 +// CHECK:STDOUT: br !if.else +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else: +// CHECK:STDOUT: return +// CHECK:STDOUT: }