Sfoglia il codice sorgente

Support for declaring functions within a namespace (#2569)

First step towards permitting declarations within namespaces. Supports only functions within namespaces for now, with no way to call those functions except from within other such functions. Unqualified lookups within a function in a namespace look in that namespace first.

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 3 anni fa
parent
commit
c172487395

+ 22 - 0
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 <typename T>
+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 <typename T>
+PrintAsID(const T&) -> PrintAsID<T>;
+
 }  // namespace Carbon
 
 namespace llvm {

+ 2 - 1
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;

+ 9 - 9
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, &params,
-                           ReturnTerm::Omitted(DummyLoc), &body,
-                           VirtualOverride::None);
+  FunctionDeclaration decl(DummyLoc, DeclaredName(DummyLoc, "Foo"), {},
+                           std::nullopt, &params, 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, &params,
-                                   ReturnTerm::Omitted(DummyLoc), std::nullopt,
-                                   VirtualOverride::None);
+  FunctionDeclaration forward_decl(
+      DummyLoc, DeclaredName(DummyLoc, "Foo"), {}, std::nullopt, &params,
+      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, &params,
-                           ReturnTerm::Omitted(DummyLoc), &body,
-                           VirtualOverride::None);
+  FunctionDeclaration decl(DummyLoc, DeclaredName(DummyLoc, "Foo"), {},
+                           std::nullopt, &params, ReturnTerm::Omitted(DummyLoc),
+                           &body, VirtualOverride::None);
   AST ast = {.declarations = {&decl}};
 
   EXPECT_THAT(ast, ASTDeclarations(ElementsAre(MatchesFunctionDeclaration())));

+ 10 - 3
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<std::string_view> {
   switch (declaration.kind()) {
     case DeclarationKind::NamespaceDeclaration:
       return cast<NamespaceDeclaration>(declaration).name();
     case DeclarationKind::FunctionDeclaration:
-      return cast<FunctionDeclaration>(declaration).name();
+      return cast<FunctionDeclaration>(declaration).name().inner_name();
     case DeclarationKind::DestructorDeclaration:
       return "destructor";
     case DeclarationKind::ClassDeclaration:
@@ -360,7 +367,7 @@ auto DestructorDeclaration::CreateDestructor(
 }
 
 auto FunctionDeclaration::Create(Nonnull<Arena*> arena,
-                                 SourceLocation source_loc, std::string name,
+                                 SourceLocation source_loc, DeclaredName name,
                                  std::vector<Nonnull<AstNode*>> deduced_params,
                                  Nonnull<TuplePattern*> param_pattern,
                                  ReturnTerm return_term,
@@ -371,7 +378,7 @@ auto FunctionDeclaration::Create(Nonnull<Arena*> arena,
   CARBON_ASSIGN_OR_RETURN(split_params,
                           SplitDeducedParameters(source_loc, deduced_params));
   return arena->New<FunctionDeclaration>(
-      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);
 }

+ 47 - 5
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<NameComponent> {
+    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<NameComponent> 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*> arena, SourceLocation source_loc,
-                     std::string name,
+                     DeclaredName name,
                      std::vector<Nonnull<AstNode*>> deduced_params,
                      Nonnull<TuplePattern*> param_pattern,
                      ReturnTerm return_term,
@@ -212,7 +254,7 @@ class FunctionDeclaration : public CallableDeclaration {
       -> ErrorOr<Nonnull<FunctionDeclaration*>>;
 
   // 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<Nonnull<GenericBinding*>> deduced_params,
                       std::optional<Nonnull<Pattern*>> self_pattern,
                       Nonnull<TuplePattern*> 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<Expression*> 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<std::string_view>;
 
 }  // namespace Carbon

+ 41 - 13
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<ValueNodeView> {
+  CARBON_ASSIGN_OR_RETURN(std::optional<ValueNodeView> 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<std::optional<ValueNodeView>> {
   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<ValueNodeView> 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<std::optional<ValueNodeView>> {
+  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<Success> {
   std::optional<ValueNodeView> resolved_returned_var = ResolveReturned();

+ 19 - 5
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<ValueNodeView>;
 
+  // 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<ValueNodeView>;
+
   // Returns the value node of the BindingPattern of the returned var definition
   // if it exists in the ancestor graph.
   auto ResolveReturned() const -> std::optional<ValueNodeView>;
@@ -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<std::optional<ValueNodeView>>;
 
+  // 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<std::optional<ValueNodeView>>;
+
   struct Entry {
     ValueNodeView entity;
     NameStatus status;

+ 1 - 1
explorer/fuzzing/ast_to_proto.cpp

@@ -657,7 +657,7 @@ static auto DeclarationToProto(const Declaration& declaration)
     case DeclarationKind::FunctionDeclaration: {
       const auto& function = cast<FunctionDeclaration>(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<const GenericBinding*> binding :
            function.deduced_parameters()) {
         *function_proto->add_deduced_parameters() =

+ 159 - 68
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 {
+
+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<Nonnull<StaticScope*>>;
+
+  // Add the given name to enclosing_scope.
+  auto AddExposedName(DeclaredName name, ValueNodeView value,
+                      StaticScope& enclosing_scope, bool allow_qualified_names)
+      -> ErrorOr<Success>;
+
+  // 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<Success>;
+
+  // 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<Success>;
+  auto ResolveNames(WhereClause& clause, const StaticScope& enclosing_scope)
+      -> ErrorOr<Success>;
+  auto ResolveNames(Pattern& pattern, StaticScope& enclosing_scope)
+      -> ErrorOr<Success>;
+  auto ResolveNames(Statement& statement, StaticScope& enclosing_scope)
+      -> ErrorOr<Success>;
+  auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope,
+                    ResolveFunctionBodies bodies) -> ErrorOr<Success>;
+
+  auto ResolveMemberNames(llvm::ArrayRef<Nonnull<Declaration*>> members,
+                          StaticScope& scope, ResolveFunctionBodies bodies)
+      -> ErrorOr<Success>;
+
+ private:
+  // Mapping from namespaces to their scopes.
+  llvm::DenseMap<const NamespaceDeclaration*, StaticScope> namespace_scopes_;
+
+  // Mapping from declarations to the scope in which they expose a name.
+  llvm::DenseMap<const Declaration*, StaticScope*> exposed_name_scopes_;
+};
+
+}  // namespace
+
+auto NameResolver::ResolveQualifier(DeclaredName name,
+                                    StaticScope& enclosing_scope,
+                                    bool allow_undeclared)
+    -> ErrorOr<Nonnull<StaticScope*>> {
+  Nonnull<StaticScope*> 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<NamespaceDeclaration>(&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<Success> {
+  if (name.is_qualified() && !allow_qualified_names) {
+    return ProgramError(name.source_loc())
+           << "qualified declaration names are not permitted in this context";
+  }
 
-// Adds the names exposed by the given AST node to enclosing_scope.
-static auto AddExposedNames(const Declaration& declaration,
-                            StaticScope& enclosing_scope) -> ErrorOr<Success> {
+  // 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<StaticScope*> 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<Success> {
   switch (declaration.kind()) {
     case DeclarationKind::NamespaceDeclaration: {
       const auto& namespace_decl = cast<NamespaceDeclaration>(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<DestructorDeclaration>(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<FunctionDeclaration>(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<Success>;
-static auto ResolveNames(WhereClause& clause,
-                         const StaticScope& enclosing_scope)
-    -> ErrorOr<Success>;
-static auto ResolveNames(Pattern& pattern, StaticScope& enclosing_scope)
-    -> ErrorOr<Success>;
-static auto ResolveNames(Statement& statement, StaticScope& enclosing_scope)
-    -> ErrorOr<Success>;
-static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope,
-                         ResolveFunctionBodies bodies) -> ErrorOr<Success>;
-
-static auto ResolveNames(Expression& expression,
-                         const StaticScope& enclosing_scope)
+auto NameResolver::ResolveNames(Expression& expression,
+                                const StaticScope& enclosing_scope)
     -> ErrorOr<Success> {
   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<Success> {
   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<Success> {
   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<Success> {
   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<Nonnull<Declaration*>> members,
-                               StaticScope& scope, ResolveFunctionBodies bodies)
-    -> ErrorOr<Success> {
+auto NameResolver::ResolveMemberNames(
+    llvm::ArrayRef<Nonnull<Declaration*>> members, StaticScope& scope,
+    ResolveFunctionBodies bodies) -> ErrorOr<Success> {
   for (Nonnull<Declaration*> member : members) {
     CARBON_RETURN_IF_ERROR(AddExposedNames(*member, scope));
   }
@@ -523,8 +603,10 @@ static auto ResolveMemberNames(llvm::ArrayRef<Nonnull<Declaration*>> members,
   return Success();
 }
 
-static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope,
-                         ResolveFunctionBodies bodies) -> ErrorOr<Success> {
+auto NameResolver::ResolveNames(Declaration& declaration,
+                                StaticScope& enclosing_scope,
+                                ResolveFunctionBodies bodies)
+    -> ErrorOr<Success> {
   switch (declaration.kind()) {
     case DeclarationKind::NamespaceDeclaration: {
       auto& namespace_decl = cast<NamespaceDeclaration>(declaration);
@@ -578,10 +660,15 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope,
     case DeclarationKind::DestructorDeclaration:
     case DeclarationKind::FunctionDeclaration: {
       auto& function = cast<CallableDeclaration>(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<FunctionDeclaration>(declaration)
+              ? cast<FunctionDeclaration>(declaration).name()
+              : DeclaredName(function.source_loc(), "destructor");
+      CARBON_ASSIGN_OR_RETURN(Nonnull<StaticScope*> scope,
+                              ResolveQualifier(name, enclosing_scope));
+      StaticScope function_scope(scope);
+      scope->MarkDeclared(name.inner_name());
       for (Nonnull<GenericBinding*> 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<Success> {
+  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

+ 4 - 2
explorer/interpreter/type_checker.cpp

@@ -4668,8 +4668,10 @@ auto TypeChecker::DeclareClassDeclaration(Nonnull<ClassDeclaration*> 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<ClassDeclaration*> 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`

+ 2 - 2
explorer/interpreter/value.cpp

@@ -1173,7 +1173,7 @@ auto FindFunction(std::string_view name,
       }
       case DeclarationKind::FunctionDeclaration: {
         const auto& fun = cast<FunctionDeclaration>(*member);
-        if (fun.name() == name) {
+        if (fun.name().inner_name() == name) {
           return &cast<FunctionValue>(**fun.constant_value());
         }
         break;
@@ -1201,7 +1201,7 @@ auto MixinPseudoType::FindFunction(const std::string_view& name) const
       }
       case DeclarationKind::FunctionDeclaration: {
         const auto& fun = cast<FunctionDeclaration>(*member);
-        if (fun.name() == name) {
+        if (fun.name().inner_name() == name) {
           return &cast<FunctionValue>(**fun.constant_value());
         }
         break;

+ 3 - 0
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 {

+ 17 - 4
explorer/syntax/parser.ypp

@@ -108,6 +108,7 @@
 %type <ClassExtensibility> class_declaration_extensibility
 %type <std::optional<Nonnull<Expression*>>> class_declaration_extends
 %type <Nonnull<Declaration*>> declaration
+%type <BisonWrap<DeclaredName>> declared_name
 %type <Nonnull<FunctionDeclaration*>> function_declaration
 %type <Nonnull<DestructorDeclaration*>> destructor_declaration
 %type <Nonnull<MixDeclaration*>> 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<FunctionDeclaration*> 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<FunctionDeclaration*> 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 {

+ 18 - 0
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;
+}

+ 18 - 0
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;
+}

+ 18 - 0
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; }

+ 18 - 0
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();
+}

+ 18 - 0
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;
+}

+ 16 - 0
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;
+}

+ 21 - 0
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; }