diff --git a/common/ostream.h b/common/ostream.h index 1f1e9d68523e..be1e250c3811 100644 --- a/common/ostream.h +++ b/common/ostream.h @@ -61,6 +61,28 @@ void PrintTo(T* p, std::ostream* out) { } } +// Helper to support printing the ID for a type that has a method +// `void PrintID(llvm::raw_ostream& out) const`. Usage: +// +// out << PrintAsID(obj); +template +class PrintAsID { + public: + explicit PrintAsID(const T& object) : object_(&object) {} + + friend auto operator<<(llvm::raw_ostream& out, const PrintAsID& self) + -> llvm::raw_ostream& { + self.object_->PrintID(out); + return out; + } + + private: + const T* object_; +}; + +template +PrintAsID(const T&) -> PrintAsID; + } // namespace Carbon namespace llvm { diff --git a/explorer/ast/ast_test_matchers_internal.cpp b/explorer/ast/ast_test_matchers_internal.cpp index f39735b07d76..53a3c805e517 100644 --- a/explorer/ast/ast_test_matchers_internal.cpp +++ b/explorer/ast/ast_test_matchers_internal.cpp @@ -150,7 +150,8 @@ auto MatchesFunctionDeclarationMatcher::MatchAndExplainImpl( llvm::ListSeparator sep(", and"); if (name_matcher_.has_value()) { out << sep << "whose name "; - if (!name_matcher_->MatchAndExplain(decl->name(), listener)) { + if (!name_matcher_->MatchAndExplain(std::string(decl->name().inner_name()), + listener)) { // We short-circuit here because if the name doesn't match, that's // probably the only information the user cares about. return false; diff --git a/explorer/ast/ast_test_matchers_test.cpp b/explorer/ast/ast_test_matchers_test.cpp index 9c9474607c53..b59872454b79 100644 --- a/explorer/ast/ast_test_matchers_test.cpp +++ b/explorer/ast/ast_test_matchers_test.cpp @@ -101,9 +101,9 @@ TEST(MatchesReturnTest, BasicUsage) { TEST(MatchesFunctionDeclarationTest, BasicUsage) { TuplePattern params(DummyLoc, {}); Block body(DummyLoc, {}); - FunctionDeclaration decl(DummyLoc, "Foo", {}, std::nullopt, ¶ms, - ReturnTerm::Omitted(DummyLoc), &body, - VirtualOverride::None); + FunctionDeclaration decl(DummyLoc, DeclaredName(DummyLoc, "Foo"), {}, + std::nullopt, ¶ms, ReturnTerm::Omitted(DummyLoc), + &body, VirtualOverride::None); EXPECT_THAT(decl, MatchesFunctionDeclaration()); EXPECT_THAT(&decl, MatchesFunctionDeclaration()); @@ -115,9 +115,9 @@ TEST(MatchesFunctionDeclarationTest, BasicUsage) { EXPECT_THAT(decl, Not(MatchesFunctionDeclaration().WithBody(MatchesLiteral(0)))); - FunctionDeclaration forward_decl(DummyLoc, "Foo", {}, std::nullopt, ¶ms, - ReturnTerm::Omitted(DummyLoc), std::nullopt, - VirtualOverride::None); + FunctionDeclaration forward_decl( + DummyLoc, DeclaredName(DummyLoc, "Foo"), {}, std::nullopt, ¶ms, + ReturnTerm::Omitted(DummyLoc), std::nullopt, VirtualOverride::None); EXPECT_THAT(forward_decl, MatchesFunctionDeclaration().WithName("Foo")); EXPECT_THAT(forward_decl, Not(MatchesFunctionDeclaration().WithBody(_))); @@ -149,9 +149,9 @@ TEST(MatchesUnimplementedExpressionTest, BasicUsage) { TEST(ASTDeclarationsTest, BasicUsage) { TuplePattern params(DummyLoc, {}); Block body(DummyLoc, {}); - FunctionDeclaration decl(DummyLoc, "Foo", {}, std::nullopt, ¶ms, - ReturnTerm::Omitted(DummyLoc), &body, - VirtualOverride::None); + FunctionDeclaration decl(DummyLoc, DeclaredName(DummyLoc, "Foo"), {}, + std::nullopt, ¶ms, ReturnTerm::Omitted(DummyLoc), + &body, VirtualOverride::None); AST ast = {.declarations = {&decl}}; EXPECT_THAT(ast, ASTDeclarations(ElementsAre(MatchesFunctionDeclaration()))); diff --git a/explorer/ast/declaration.cpp b/explorer/ast/declaration.cpp index 65158865b499..ea4e9e33f8b8 100644 --- a/explorer/ast/declaration.cpp +++ b/explorer/ast/declaration.cpp @@ -225,13 +225,20 @@ void Declaration::PrintID(llvm::raw_ostream& out) const { } } +void DeclaredName::Print(llvm::raw_ostream& out) const { + for (const auto& [loc, name] : qualifiers()) { + out << name << "."; + } + out << inner_name(); +} + auto GetName(const Declaration& declaration) -> std::optional { switch (declaration.kind()) { case DeclarationKind::NamespaceDeclaration: return cast(declaration).name(); case DeclarationKind::FunctionDeclaration: - return cast(declaration).name(); + return cast(declaration).name().inner_name(); case DeclarationKind::DestructorDeclaration: return "destructor"; case DeclarationKind::ClassDeclaration: @@ -360,7 +367,7 @@ auto DestructorDeclaration::CreateDestructor( } auto FunctionDeclaration::Create(Nonnull arena, - SourceLocation source_loc, std::string name, + SourceLocation source_loc, DeclaredName name, std::vector> deduced_params, Nonnull param_pattern, ReturnTerm return_term, @@ -371,7 +378,7 @@ auto FunctionDeclaration::Create(Nonnull arena, CARBON_ASSIGN_OR_RETURN(split_params, SplitDeducedParameters(source_loc, deduced_params)); return arena->New( - source_loc, name, std::move(split_params.resolved_params), + source_loc, std::move(name), std::move(split_params.resolved_params), split_params.self_pattern, param_pattern, return_term, body, virt_override); } diff --git a/explorer/ast/declaration.h b/explorer/ast/declaration.h index 20e9cc7386a2..af973d4c74fa 100644 --- a/explorer/ast/declaration.h +++ b/explorer/ast/declaration.h @@ -126,6 +126,48 @@ class Declaration : public AstNode { bool is_type_checked_ = false; }; +// A name being declared in a named declaration. +class DeclaredName { + public: + struct NameComponent { + SourceLocation source_loc; + std::string name; + }; + + explicit DeclaredName(SourceLocation loc, std::string name) + : components_{{loc, std::move(name)}} {} + + void Print(llvm::raw_ostream& out) const; + + void Append(SourceLocation loc, std::string name) { + components_.push_back({loc, std::move(name)}); + } + + // Returns the location of the first name component. + auto source_loc() const -> SourceLocation { + return components_.front().source_loc; + } + + // Returns whether this is a qualified name, as opposed to a simple + // single-identifier name. + auto is_qualified() const { return components_.size() > 1; } + + // Returns a range containing the components of the name other than the final + // component. + auto qualifiers() const -> llvm::ArrayRef { + return llvm::makeArrayRef(components_).drop_back(); + } + + // Returns the innermost name, which is the unqualified name of the entity + // being declared. For example in `fn Namespace.Func();`, returns `"Func"`. + auto inner_name() const -> std::string_view { + return components_.back().name; + } + + private: + std::vector components_; +}; + // A declaration of a namespace. class NamespaceDeclaration : public Declaration { public: @@ -203,7 +245,7 @@ class FunctionDeclaration : public CallableDeclaration { using ImplementsCarbonValueNode = void; static auto Create(Nonnull arena, SourceLocation source_loc, - std::string name, + DeclaredName name, std::vector> deduced_params, Nonnull param_pattern, ReturnTerm return_term, @@ -212,7 +254,7 @@ class FunctionDeclaration : public CallableDeclaration { -> ErrorOr>; // Use `Create()` instead. This is public only so Arena::New() can call it. - FunctionDeclaration(SourceLocation source_loc, std::string name, + FunctionDeclaration(SourceLocation source_loc, DeclaredName name, std::vector> deduced_params, std::optional> self_pattern, Nonnull param_pattern, @@ -228,10 +270,10 @@ class FunctionDeclaration : public CallableDeclaration { return InheritsFromFunctionDeclaration(node->kind()); } - auto name() const -> const std::string& { return name_; } + auto name() const -> const DeclaredName& { return name_; } private: - std::string name_; + DeclaredName name_; }; class DestructorDeclaration : public CallableDeclaration { @@ -829,7 +871,7 @@ class AliasDeclaration : public Declaration { Nonnull target_; }; -// Return the name of a declaration, if it has one. +// Return the unqualified name of a declaration, if it has one. auto GetName(const Declaration&) -> std::optional; } // namespace Carbon diff --git a/explorer/ast/static_scope.cpp b/explorer/ast/static_scope.cpp index 0f1e6ce64221..38626c9dd99e 100644 --- a/explorer/ast/static_scope.cpp +++ b/explorer/ast/static_scope.cpp @@ -52,29 +52,57 @@ auto StaticScope::Resolve(std::string_view name, return *result; } +auto StaticScope::ResolveHere(std::string_view name, SourceLocation source_loc, + bool allow_undeclared) const + -> ErrorOr { + CARBON_ASSIGN_OR_RETURN(std::optional result, + TryResolveHere(name, source_loc, allow_undeclared)); + if (!result) { + return ProgramError(source_loc) + << "could not resolve '" << name << "' in this scope"; + } + return *result; +} + auto StaticScope::TryResolve(std::string_view name, SourceLocation source_loc) const -> ErrorOr> { for (const StaticScope* scope = this; scope; scope = scope->parent_scope_.value_or(nullptr)) { - auto it = scope->declared_names_.find(name); - if (it != scope->declared_names_.end()) { - switch (it->second.status) { - case NameStatus::KnownButNotDeclared: - return ProgramError(source_loc) - << "'" << name << "' has not been declared yet"; - case NameStatus::DeclaredButNotUsable: - return ProgramError(source_loc) << "'" << name - << "' is not usable until after it " - "has been completely declared"; - case NameStatus::Usable: - return std::make_optional(it->second.entity); - } + CARBON_ASSIGN_OR_RETURN( + std::optional value, + scope->TryResolveHere(name, source_loc, /*allow_undeclared=*/false)); + if (value) { + return value; } } return {std::nullopt}; } +auto StaticScope::TryResolveHere(std::string_view name, + SourceLocation source_loc, + bool allow_undeclared) const + -> ErrorOr> { + auto it = declared_names_.find(name); + if (it == declared_names_.end()) { + return {std::nullopt}; + } + if (allow_undeclared) { + return {it->second.entity}; + } + switch (it->second.status) { + case NameStatus::KnownButNotDeclared: + return ProgramError(source_loc) + << "'" << name << "' has not been declared yet"; + case NameStatus::DeclaredButNotUsable: + return ProgramError(source_loc) << "'" << name + << "' is not usable until after it " + "has been completely declared"; + case NameStatus::Usable: + return {it->second.entity}; + } +} + auto StaticScope::AddReturnedVar(ValueNodeView returned_var_def_view) -> ErrorOr { std::optional resolved_returned_var = ResolveReturned(); diff --git a/explorer/ast/static_scope.h b/explorer/ast/static_scope.h index e689c03a362a..d3b89cb0002a 100644 --- a/explorer/ast/static_scope.h +++ b/explorer/ast/static_scope.h @@ -53,12 +53,21 @@ class StaticScope { // Marks `name` as being completely declared and hence usable. void MarkUsable(std::string_view name); - // Returns the nearest definition of `name` in the ancestor graph of this - // scope, or reports a compilation error at `source_loc` there isn't exactly - // one such definition. + // Returns the nearest declaration of `name` in the ancestor graph of this + // scope, or reports a compilation error at `source_loc` there isn't such a + // declaration. + // TODO: This should also diagnose if there's a shadowed declaration of the + // name in an enclosing scope, but does not do so yet. auto Resolve(std::string_view name, SourceLocation source_loc) const -> ErrorOr; + // Returns the declaration of `name` in this scope, or reports a compilation + // error at `source_loc` if the name is not declared in this scope. If + // `allow_undeclared` is `true`, names that have been added but not yet marked + // declared or usable do not result in an error. + auto ResolveHere(std::string_view name, SourceLocation source_loc, + bool allow_undeclared) const -> ErrorOr; + // Returns the value node of the BindingPattern of the returned var definition // if it exists in the ancestor graph. auto ResolveReturned() const -> std::optional; @@ -70,11 +79,16 @@ class StaticScope { private: // Equivalent to Resolve, but returns `nullopt` instead of raising an error - // if no definition can be found. Still raises a compilation error if more - // than one definition is found. + // if no declaration can be found. auto TryResolve(std::string_view name, SourceLocation source_loc) const -> ErrorOr>; + // Equivalent to ResolveHere, but returns `nullopt` if no definition can be + // found. Raises an error if the name is found but is not usable yet. + auto TryResolveHere(std::string_view name, SourceLocation source_loc, + bool allow_undeclared) const + -> ErrorOr>; + struct Entry { ValueNodeView entity; NameStatus status; diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index ba4375072242..478b5c920af5 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/explorer/fuzzing/ast_to_proto.cpp @@ -657,7 +657,7 @@ static auto DeclarationToProto(const Declaration& declaration) case DeclarationKind::FunctionDeclaration: { const auto& function = cast(declaration); auto* function_proto = declaration_proto.mutable_function(); - function_proto->set_name(function.name()); + function_proto->set_name(std::string(function.name().inner_name())); for (Nonnull binding : function.deduced_parameters()) { *function_proto->add_deduced_parameters() = diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index de7a9a0f118b..758c72886042 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/explorer/interpreter/resolve_names.cpp @@ -11,22 +11,138 @@ #include "explorer/ast/pattern.h" #include "explorer/ast/statement.h" #include "explorer/ast/static_scope.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Error.h" using llvm::cast; +using llvm::dyn_cast; +using llvm::isa; namespace Carbon { +namespace { -// Adds the names exposed by the given AST node to enclosing_scope. -static auto AddExposedNames(const Declaration& declaration, - StaticScope& enclosing_scope) -> ErrorOr { +class NameResolver { + public: + enum class ResolveFunctionBodies { + // Do not resolve names in function bodies. + Skip, + // Resolve all names. When visiting a declaration with members, resolve + // names in member function bodies after resolving the names in all member + // declarations, as if the bodies appeared after all the declarations. + AfterDeclarations, + // Resolve names in function bodies immediately. This is appropriate when + // the declarations of all members of enclosing classes, interfaces, and + // similar have already been resolved. + Immediately, + }; + + // Resolve the qualifier of the given declared name to a scope. + auto ResolveQualifier(DeclaredName name, StaticScope& enclosing_scope, + bool allow_undeclared = false) + -> ErrorOr>; + + // Add the given name to enclosing_scope. + auto AddExposedName(DeclaredName name, ValueNodeView value, + StaticScope& enclosing_scope, bool allow_qualified_names) + -> ErrorOr; + + // Add the names exposed by the given AST node to enclosing_scope. + auto AddExposedNames(const Declaration& declaration, + StaticScope& enclosing_scope, + bool allow_qualified_names = false) -> ErrorOr; + + // Traverse the sub-AST rooted at the given node, resolve all names within + // it using enclosing_scope, and update enclosing_scope to add names to + // it as they become available. In scopes where names are only visible below + // their point of declaration (such as block scopes in C++), this is + // implemented as a single pass, recursively calling ResolveNames on the + // elements of the scope in order. In scopes where names are also visible + // above their point of declaration (such as class scopes in C++), this + // requires three passes: first calling AddExposedNames on each element of the + // scope to populate a StaticScope, and then calling ResolveNames on each + // element, passing it the already-populated StaticScope but skipping member + // function bodies, and finally calling ResolvedNames again on each element, + // and this time resolving member function bodies. + auto ResolveNames(Expression& expression, const StaticScope& enclosing_scope) + -> ErrorOr; + auto ResolveNames(WhereClause& clause, const StaticScope& enclosing_scope) + -> ErrorOr; + auto ResolveNames(Pattern& pattern, StaticScope& enclosing_scope) + -> ErrorOr; + auto ResolveNames(Statement& statement, StaticScope& enclosing_scope) + -> ErrorOr; + auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope, + ResolveFunctionBodies bodies) -> ErrorOr; + + auto ResolveMemberNames(llvm::ArrayRef> members, + StaticScope& scope, ResolveFunctionBodies bodies) + -> ErrorOr; + + private: + // Mapping from namespaces to their scopes. + llvm::DenseMap namespace_scopes_; + + // Mapping from declarations to the scope in which they expose a name. + llvm::DenseMap exposed_name_scopes_; +}; + +} // namespace + +auto NameResolver::ResolveQualifier(DeclaredName name, + StaticScope& enclosing_scope, + bool allow_undeclared) + -> ErrorOr> { + Nonnull scope = &enclosing_scope; + for (const auto& [loc, qualifier] : name.qualifiers()) { + // TODO: If we permit qualified names anywhere other than the top level, we + // will need to decide whether the first name in the qualifier is looked up + // only in the innermost enclosing scope or in all enclosing scopes. + CARBON_ASSIGN_OR_RETURN( + ValueNodeView node, + scope->ResolveHere(qualifier, loc, allow_undeclared)); + + if (const auto* namespace_decl = + dyn_cast(&node.base())) { + scope = &namespace_scopes_[namespace_decl]; + } else { + return ProgramError(name.source_loc()) + << PrintAsID(node.base()) << " cannot be used as a name qualifier"; + } + } + return scope; +} + +auto NameResolver::AddExposedName(DeclaredName name, ValueNodeView value, + StaticScope& enclosing_scope, + bool allow_qualified_names) + -> ErrorOr { + if (name.is_qualified() && !allow_qualified_names) { + return ProgramError(name.source_loc()) + << "qualified declaration names are not permitted in this context"; + } + + // We are just collecting names at this stage, so nothing is marked as + // declared yet. Therefore we don't complain if the qualifier contains a + // known but not declared namespace name. + CARBON_ASSIGN_OR_RETURN( + Nonnull scope, + ResolveQualifier(name, enclosing_scope, /*allow_undeclared=*/true)); + return scope->Add(name.inner_name(), value, + StaticScope::NameStatus::KnownButNotDeclared); +} + +auto NameResolver::AddExposedNames(const Declaration& declaration, + StaticScope& enclosing_scope, + bool allow_qualified_names) + -> ErrorOr { switch (declaration.kind()) { case DeclarationKind::NamespaceDeclaration: { const auto& namespace_decl = cast(declaration); CARBON_RETURN_IF_ERROR( enclosing_scope.Add(namespace_decl.name(), &namespace_decl, StaticScope::NameStatus::KnownButNotDeclared)); + namespace_scopes_.try_emplace(&namespace_decl, &enclosing_scope); break; } case DeclarationKind::InterfaceDeclaration: @@ -38,18 +154,21 @@ static auto AddExposedNames(const Declaration& declaration, break; } case DeclarationKind::DestructorDeclaration: { - // TODO: Remove this code. With this code, it is possible to create not - // useful carbon code. - // Without this code, a Segfault is generated + // TODO: It should not be possible to name the destructor by unqualified + // name. const auto& func = cast(declaration); + // TODO: Add support for qualified destructor declarations. Currently the + // syntax for this is + // destructor Class [self: Self] { ... } + // but see #2567. CARBON_RETURN_IF_ERROR(enclosing_scope.Add( "destructor", &func, StaticScope::NameStatus::KnownButNotDeclared)); break; } case DeclarationKind::FunctionDeclaration: { const auto& func = cast(declaration); - CARBON_RETURN_IF_ERROR(enclosing_scope.Add( - func.name(), &func, StaticScope::NameStatus::KnownButNotDeclared)); + CARBON_RETURN_IF_ERROR(AddExposedName(func.name(), &func, enclosing_scope, + allow_qualified_names)); break; } case DeclarationKind::ClassDeclaration: { @@ -112,48 +231,8 @@ static auto AddExposedNames(const Declaration& declaration, return Success(); } -namespace { -enum class ResolveFunctionBodies { - // Do not resolve names in function bodies. - Skip, - // Resolve all names. When visiting a declaration with members, resolve - // names in member function bodies after resolving the names in all member - // declarations, as if the bodies appeared after all the declarations. - AfterDeclarations, - // Resolve names in function bodies immediately. This is appropriate when - // the declarations of all members of enclosing classes, interfaces, and - // similar have already been resolved. - Immediately, -}; -} // namespace - -// Traverses the sub-AST rooted at the given node, resolving all names within -// it using enclosing_scope, and updating enclosing_scope to add names to -// it as they become available. In scopes where names are only visible below -// their point of declaration (such as block scopes in C++), this is implemented -// as a single pass, recursively calling ResolveNames on the elements of the -// scope in order. In scopes where names are also visible above their point of -// declaration (such as class scopes in C++), this requires three passes: first -// calling AddExposedNames on each element of the scope to populate a -// StaticScope, and then calling ResolveNames on each element, passing it the -// already-populated StaticScope but skipping member function bodies, and -// finally calling ResolvedNames again on each element, and this time resolving -// member function bodies. -static auto ResolveNames(Expression& expression, - const StaticScope& enclosing_scope) - -> ErrorOr; -static auto ResolveNames(WhereClause& clause, - const StaticScope& enclosing_scope) - -> ErrorOr; -static auto ResolveNames(Pattern& pattern, StaticScope& enclosing_scope) - -> ErrorOr; -static auto ResolveNames(Statement& statement, StaticScope& enclosing_scope) - -> ErrorOr; -static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope, - ResolveFunctionBodies bodies) -> ErrorOr; - -static auto ResolveNames(Expression& expression, - const StaticScope& enclosing_scope) +auto NameResolver::ResolveNames(Expression& expression, + const StaticScope& enclosing_scope) -> ErrorOr { switch (expression.kind()) { case ExpressionKind::CallExpression: { @@ -290,8 +369,8 @@ static auto ResolveNames(Expression& expression, return Success(); } -static auto ResolveNames(WhereClause& clause, - const StaticScope& enclosing_scope) +auto NameResolver::ResolveNames(WhereClause& clause, + const StaticScope& enclosing_scope) -> ErrorOr { switch (clause.kind()) { case WhereClauseKind::IsWhereClause: { @@ -319,7 +398,7 @@ static auto ResolveNames(WhereClause& clause, return Success(); } -static auto ResolveNames(Pattern& pattern, StaticScope& enclosing_scope) +auto NameResolver::ResolveNames(Pattern& pattern, StaticScope& enclosing_scope) -> ErrorOr { switch (pattern.kind()) { case PatternKind::BindingPattern: { @@ -372,7 +451,8 @@ static auto ResolveNames(Pattern& pattern, StaticScope& enclosing_scope) return Success(); } -static auto ResolveNames(Statement& statement, StaticScope& enclosing_scope) +auto NameResolver::ResolveNames(Statement& statement, + StaticScope& enclosing_scope) -> ErrorOr { switch (statement.kind()) { case StatementKind::ExpressionStatement: @@ -502,9 +582,9 @@ static auto ResolveNames(Statement& statement, StaticScope& enclosing_scope) return Success(); } -static auto ResolveMemberNames(llvm::ArrayRef> members, - StaticScope& scope, ResolveFunctionBodies bodies) - -> ErrorOr { +auto NameResolver::ResolveMemberNames( + llvm::ArrayRef> members, StaticScope& scope, + ResolveFunctionBodies bodies) -> ErrorOr { for (Nonnull member : members) { CARBON_RETURN_IF_ERROR(AddExposedNames(*member, scope)); } @@ -523,8 +603,10 @@ static auto ResolveMemberNames(llvm::ArrayRef> members, return Success(); } -static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope, - ResolveFunctionBodies bodies) -> ErrorOr { +auto NameResolver::ResolveNames(Declaration& declaration, + StaticScope& enclosing_scope, + ResolveFunctionBodies bodies) + -> ErrorOr { switch (declaration.kind()) { case DeclarationKind::NamespaceDeclaration: { auto& namespace_decl = cast(declaration); @@ -578,10 +660,15 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope, case DeclarationKind::DestructorDeclaration: case DeclarationKind::FunctionDeclaration: { auto& function = cast(declaration); - StaticScope function_scope(&enclosing_scope); - const auto name = GetName(function); - CARBON_CHECK(name) << "Unexpected missing name for `" << function << "`."; - enclosing_scope.MarkDeclared(*name); + // TODO: Destructors should track their qualified name. + const DeclaredName& name = + isa(declaration) + ? cast(declaration).name() + : DeclaredName(function.source_loc(), "destructor"); + CARBON_ASSIGN_OR_RETURN(Nonnull scope, + ResolveQualifier(name, enclosing_scope)); + StaticScope function_scope(scope); + scope->MarkDeclared(name.inner_name()); for (Nonnull binding : function.deduced_parameters()) { CARBON_RETURN_IF_ERROR(ResolveNames(*binding, function_scope)); } @@ -595,7 +682,7 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope, CARBON_RETURN_IF_ERROR(ResolveNames( **function.return_term().type_expression(), function_scope)); } - enclosing_scope.MarkUsable(*name); + scope->MarkUsable(name.inner_name()); if (function.body().has_value() && bodies != ResolveFunctionBodies::Skip) { CARBON_RETURN_IF_ERROR(ResolveNames(**function.body(), function_scope)); @@ -706,15 +793,19 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope, } auto ResolveNames(AST& ast) -> ErrorOr { + NameResolver resolver; + StaticScope file_scope; for (auto* declaration : ast.declarations) { - CARBON_RETURN_IF_ERROR(AddExposedNames(*declaration, file_scope)); + CARBON_RETURN_IF_ERROR(resolver.AddExposedNames( + *declaration, file_scope, /*allow_qualified_names=*/true)); } for (auto* declaration : ast.declarations) { - CARBON_RETURN_IF_ERROR(ResolveNames( - *declaration, file_scope, ResolveFunctionBodies::AfterDeclarations)); + CARBON_RETURN_IF_ERROR(resolver.ResolveNames( + *declaration, file_scope, + NameResolver::ResolveFunctionBodies::AfterDeclarations)); } - return ResolveNames(**ast.main_call, file_scope); + return resolver.ResolveNames(**ast.main_call, file_scope); } } // namespace Carbon diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 192ff03f6e76..fbc9793e6ccd 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -4668,8 +4668,10 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull class_decl, << "Error declaring `" << fun->name() << "`" << ": class functions cannot be virtual."; } + CARBON_CHECK(!fun->name().is_qualified()) + << "qualified function name not permitted in class scope"; bool has_vtable_entry = - class_vtable.find(fun->name()) != class_vtable.end(); + class_vtable.find(fun->name().inner_name()) != class_vtable.end(); // TODO: Implement complete declaration logic from // `/docs/design/classes.md#virtual-methods`. switch (fun->virt_override()) { @@ -4702,7 +4704,7 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull class_decl, } break; } - class_vtable[fun->name()] = {fun, class_level}; + class_vtable[fun->name().inner_name()] = {fun, class_level}; } // For class declaration `class MyType(T:! type, U:! AnInterface)`, `Self` diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index fb01cdf614f7..35f72b5e4f2a 100644 --- a/explorer/interpreter/value.cpp +++ b/explorer/interpreter/value.cpp @@ -1173,7 +1173,7 @@ auto FindFunction(std::string_view name, } case DeclarationKind::FunctionDeclaration: { const auto& fun = cast(*member); - if (fun.name() == name) { + if (fun.name().inner_name() == name) { return &cast(**fun.constant_value()); } break; @@ -1201,7 +1201,7 @@ auto MixinPseudoType::FindFunction(const std::string_view& name) const } case DeclarationKind::FunctionDeclaration: { const auto& fun = cast(*member); - if (fun.name() == name) { + if (fun.name().inner_name() == name) { return &cast(**fun.constant_value()); } break; diff --git a/explorer/syntax/bison_wrap.h b/explorer/syntax/bison_wrap.h index 8bdef1a6c381..488d6ae8439b 100644 --- a/explorer/syntax/bison_wrap.h +++ b/explorer/syntax/bison_wrap.h @@ -27,6 +27,9 @@ class BisonWrap { // NOLINTNEXTLINE(google-explicit-constructor) operator T() { return Release(); } + // Access the contained object, which must already have been set. + auto Unwrap() & -> T& { return *val_; } + // Deliberately releases the contained value. Errors if not initialized. // Called directly in parser.ypp when releasing pairs. auto Release() -> T { diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index 4834dde4fa13..41bc82cc68b5 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -108,6 +108,7 @@ %type class_declaration_extensibility %type >> class_declaration_extends %type > declaration +%type > declared_name %type > function_declaration %type > destructor_declaration %type > mix_declaration @@ -1115,6 +1116,17 @@ impl_deduced_params: | FORALL LEFT_SQUARE_BRACKET deduced_param_list RIGHT_SQUARE_BRACKET { $$ = $3; } ; +declared_name: + identifier + { $$ = DeclaredName(context.source_loc(), $1); } +| declared_name PERIOD identifier + { + $$ = std::move($1); + $$.Unwrap().Append(context.source_loc(), $3); + } +| LEFT_PARENTHESIS declared_name RIGHT_PARENTHESIS + { $$ = $2; } +; // This includes the FN keyword to work around a shift-reduce conflict between virtual function's `IMPL FN` and interfaces `IMPL`. fn_virtual_override_intro: FN @@ -1127,10 +1139,10 @@ fn_virtual_override_intro: { $$ = VirtualOverride::Impl; } ; function_declaration: - fn_virtual_override_intro identifier deduced_params maybe_empty_tuple_pattern return_term block + fn_virtual_override_intro declared_name deduced_params maybe_empty_tuple_pattern return_term block { ErrorOr fn = FunctionDeclaration::Create( - arena, context.source_loc(), $2, $3, $4, $5, $6, $1); + arena, context.source_loc(), std::move($2), $3, $4, $5, $6, $1); if (fn.ok()) { $$ = *fn; } else { @@ -1138,10 +1150,11 @@ function_declaration: YYERROR; } } -| fn_virtual_override_intro identifier deduced_params maybe_empty_tuple_pattern return_term SEMICOLON +| fn_virtual_override_intro declared_name deduced_params maybe_empty_tuple_pattern return_term SEMICOLON { ErrorOr fn = FunctionDeclaration::Create( - arena, context.source_loc(), $2, $3, $4, $5, std::nullopt, $1); + arena, context.source_loc(), std::move($2), $3, $4, $5, std::nullopt, + $1); if (fn.ok()) { $$ = *fn; } else { diff --git a/explorer/testdata/class/fail_qualified_method.carbon b/explorer/testdata/class/fail_qualified_method.carbon new file mode 100644 index 000000000000..faafc5909244 --- /dev/null +++ b/explorer/testdata/class/fail_qualified_method.carbon @@ -0,0 +1,18 @@ +// 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 +// RUN: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +class Point { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_qualified_method.carbon:[[@LINE+1]]: qualified declaration names are not permitted in this context + fn Point.F[self: Self]() {} +} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/interface/fail_qualified_method.carbon b/explorer/testdata/interface/fail_qualified_method.carbon new file mode 100644 index 000000000000..3765e3627832 --- /dev/null +++ b/explorer/testdata/interface/fail_qualified_method.carbon @@ -0,0 +1,18 @@ +// 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 +// RUN: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +interface Vector { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_qualified_method.carbon:[[@LINE+1]]: qualified declaration names are not permitted in this context + fn Vector.Zero() -> i32; +} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/mixin/fail_qualified_method.carbon b/explorer/testdata/mixin/fail_qualified_method.carbon new file mode 100644 index 000000000000..1b5722f2e73c --- /dev/null +++ b/explorer/testdata/mixin/fail_qualified_method.carbon @@ -0,0 +1,18 @@ +// 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 +// RUN: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +__mixin Operations { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail_qualified_method.carbon:[[@LINE+1]]: qualified declaration names are not permitted in this context + fn Operations.Square[self: Self](x:i32) -> i32{ + return x * x; + } +} + +fn Main() -> i32 { return 0; } diff --git a/explorer/testdata/namespace/fail_members_not_in_outer_scope.carbon b/explorer/testdata/namespace/fail_members_not_in_outer_scope.carbon new file mode 100644 index 000000000000..3d7d33b080c8 --- /dev/null +++ b/explorer/testdata/namespace/fail_members_not_in_outer_scope.carbon @@ -0,0 +1,18 @@ +// 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 +// RUN: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +namespace N; + +fn N.F() -> i32 { return 0; } + +fn Main() -> i32 { + // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/namespace/fail_members_not_in_outer_scope.carbon:[[@LINE+1]]: could not resolve 'F' + return F(); +} diff --git a/explorer/testdata/namespace/fail_qualifier_not_namespace.carbon b/explorer/testdata/namespace/fail_qualifier_not_namespace.carbon new file mode 100644 index 000000000000..4277b7a18d4c --- /dev/null +++ b/explorer/testdata/namespace/fail_qualifier_not_namespace.carbon @@ -0,0 +1,18 @@ +// 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 +// RUN: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +fn F() {} + +// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/namespace/fail_qualifier_not_namespace.carbon:[[@LINE+1]]: fn F cannot be used as a name qualifier +fn F.G() {} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/namespace/fail_unknown_namespace.carbon b/explorer/testdata/namespace/fail_unknown_namespace.carbon new file mode 100644 index 000000000000..8df20bbe31d1 --- /dev/null +++ b/explorer/testdata/namespace/fail_unknown_namespace.carbon @@ -0,0 +1,16 @@ +// 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 +// RUN: %{not} %{explorer-run} +// RUN: %{not} %{explorer-run-trace} + +package ExplorerTest api; + +// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/namespace/fail_unknown_namespace.carbon:[[@LINE+1]]: could not resolve 'N' in this scope +fn N.F() {} + +fn Main() -> i32 { + return 0; +} diff --git a/explorer/testdata/namespace/lookup_scope.carbon b/explorer/testdata/namespace/lookup_scope.carbon new file mode 100644 index 000000000000..67af0342c7c0 --- /dev/null +++ b/explorer/testdata/namespace/lookup_scope.carbon @@ -0,0 +1,21 @@ +// 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 +// RUN: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +namespace N; + +fn OuterI32() -> type { return i32; } +fn One() -> i32 { return 1; } + +fn N.I32() -> type { return i32; } +fn N.Five() -> I32() { return 5; } +fn N.Six() -> OuterI32() { return Five() + One(); } + +fn Main() -> i32 { return 0; }