Bläddra i källkod

Add namespace support. (#2940)

This handles namespacing of functions. Parsing and semantics are changed
significantly, while lowering works without changes. Variables can't be
namespaced yet because they're dealing with patterns, and I didn't dig
through that code.

Most of the logic is done through the new name declaration stack, which
is necessary because semantics isn't quite sure where the declaration
name ends. It'd be complex for parsing to send a signal about this,
probably involving node variants and rewrites of the tree, and this
solution seems to work well. Unfortunately this means a new stack, but
that may be inevitable due to the extra information needing to be
tracked.

Note this doesn't deal with scoped lookups of non-namespace things,
which we'll need for generics. That'll probably involve pushing resolved
scopes onto a stack (or maybe just setting a singleton value?) to affect
contextual name lookup. But, I think the basics are there to make it
work when we can test the behavior.

This renames "designator expression" to "qualified expression" and adds
"qualified declaration" in order to use terminology more consistent with
C++.

Namespaces will probably need to be considered for name mangling down
the line, but this still uses the basic name.
Jon Ross-Perkins 2 år sedan
förälder
incheckning
918c089e03
63 ändrade filer med 1135 tillägg och 178 borttagningar
  1. 9 8
      toolchain/diagnostics/diagnostic_kind.def
  2. 6 0
      toolchain/lowering/lowering_handle.cpp
  3. 35 0
      toolchain/lowering/testdata/namespace/function.carbon
  4. 28 0
      toolchain/lowering/testdata/namespace/nested.carbon
  5. 25 8
      toolchain/parser/parse_node_kind.def
  6. 8 1
      toolchain/parser/parser_context.cpp
  7. 12 0
      toolchain/parser/parser_context.h
  8. 1 1
      toolchain/parser/parser_handle_brace_expression.cpp
  9. 67 9
      toolchain/parser/parser_handle_declaration_name_and_params.cpp
  10. 4 0
      toolchain/parser/parser_handle_declaration_scope_loop.cpp
  11. 4 4
      toolchain/parser/parser_handle_expression.cpp
  12. 2 2
      toolchain/parser/parser_handle_function.cpp
  13. 39 0
      toolchain/parser/parser_handle_namespace.cpp
  14. 1 4
      toolchain/parser/parser_handle_package.cpp
  15. 16 11
      toolchain/parser/parser_handle_period.cpp
  16. 3 3
      toolchain/parser/parser_handle_statement.cpp
  17. 2 2
      toolchain/parser/parser_handle_type.cpp
  18. 1 3
      toolchain/parser/parser_handle_var.cpp
  19. 54 5
      toolchain/parser/parser_state.def
  20. 3 3
      toolchain/parser/testdata/basics/fail_invalid_designators.carbon
  21. 1 1
      toolchain/parser/testdata/basics/fail_paren_match_regression.carbon
  22. 4 4
      toolchain/parser/testdata/basics/function_call.carbon
  23. 1 1
      toolchain/parser/testdata/class/fn_definitions.carbon
  24. 2 1
      toolchain/parser/testdata/function/declaration/fail_missing_name.carbon
  25. 2 1
      toolchain/parser/testdata/function/declaration/fail_only_fn_and_semi.carbon
  26. 2 1
      toolchain/parser/testdata/function/declaration/fail_repeated_fn_and_semi.carbon
  27. 2 1
      toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_until_outdent.carbon
  28. 2 1
      toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_with_semi.carbon
  29. 2 1
      toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_without_semi.carbon
  30. 2 1
      toolchain/parser/testdata/function/declaration/fail_skip_to_newline_without_semi.carbon
  31. 2 1
      toolchain/parser/testdata/function/declaration/fail_without_name_and_many_tokens_in_params.carbon
  32. 1 1
      toolchain/parser/testdata/function/definition/fail_identifier_in_statements.carbon
  33. 2 1
      toolchain/parser/testdata/generics/interface/fail_missing_name.carbon
  34. 2 2
      toolchain/parser/testdata/generics/interface/fail_missing_open_curly.carbon
  35. 29 0
      toolchain/parser/testdata/namespace/basic.carbon
  36. 14 0
      toolchain/parser/testdata/namespace/fail_args.carbon
  37. 16 0
      toolchain/parser/testdata/namespace/fail_incomplete_name.carbon
  38. 14 0
      toolchain/parser/testdata/namespace/fail_no_name.carbon
  39. 41 0
      toolchain/parser/testdata/namespace/nested.carbon
  40. 1 1
      toolchain/parser/testdata/operators/fail_infix_uneven_space_after.carbon
  41. 1 1
      toolchain/parser/testdata/operators/fail_star_star_no_space.carbon
  42. 1 1
      toolchain/parser/testdata/package/fail_no_semi.carbon
  43. 1 1
      toolchain/parser/testdata/return/fail_expr_no_semi.carbon
  44. 1 1
      toolchain/parser/testdata/return/fail_no_semi.carbon
  45. 2 2
      toolchain/parser/testdata/while/fail_no_semi.carbon
  46. 172 22
      toolchain/semantics/semantics_context.cpp
  47. 103 2
      toolchain/semantics/semantics_context.h
  48. 90 52
      toolchain/semantics/semantics_handle.cpp
  49. 1 2
      toolchain/semantics/semantics_handle_call_expression.cpp
  50. 9 6
      toolchain/semantics/semantics_handle_function.cpp
  51. 25 0
      toolchain/semantics/semantics_handle_namespace.cpp
  52. 1 0
      toolchain/semantics/semantics_ir.cpp
  53. 25 0
      toolchain/semantics/semantics_ir.h
  54. 18 0
      toolchain/semantics/semantics_node.h
  55. 1 0
      toolchain/semantics/semantics_node_kind.def
  56. 1 0
      toolchain/semantics/testdata/basics/fail_name_lookup.carbon
  57. 1 1
      toolchain/semantics/testdata/basics/fail_qualifier_unsupported.carbon
  58. 2 2
      toolchain/semantics/testdata/function/definition/fail_param_name_conflict.carbon
  59. 53 0
      toolchain/semantics/testdata/namespace/fail_duplicate.carbon
  60. 38 0
      toolchain/semantics/testdata/namespace/fail_unresolved_scope.carbon
  61. 66 0
      toolchain/semantics/testdata/namespace/function.carbon
  62. 59 0
      toolchain/semantics/testdata/namespace/nested.carbon
  63. 2 2
      toolchain/semantics/testdata/var/fail_duplicate_decl.carbon

+ 9 - 8
toolchain/diagnostics/diagnostic_kind.def

@@ -56,9 +56,8 @@ CARBON_DIAGNOSTIC_KIND(ExpectedExpression)
 CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierAfterDot)
 CARBON_DIAGNOSTIC_KIND(ExpectedParameterName)
 CARBON_DIAGNOSTIC_KIND(ExpectedParenAfter)
-CARBON_DIAGNOSTIC_KIND(ExpectedSemiAfter)
-CARBON_DIAGNOSTIC_KIND(ExpectedSemiAfterExpression)
-CARBON_DIAGNOSTIC_KIND(ExpectedSemiAfterVar)
+CARBON_DIAGNOSTIC_KIND(ExpectedExpressionSemi)
+CARBON_DIAGNOSTIC_KIND(ExpectedStatementSemi)
 CARBON_DIAGNOSTIC_KIND(ExpectedStructLiteralField)
 CARBON_DIAGNOSTIC_KIND(ExpectedVariableDeclaration)
 CARBON_DIAGNOSTIC_KIND(ExpectedVariableName)
@@ -73,7 +72,6 @@ CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierAfterPackage)
 CARBON_DIAGNOSTIC_KIND(ExpectedLibraryName)
 CARBON_DIAGNOSTIC_KIND(MissingLibraryKeyword)
 CARBON_DIAGNOSTIC_KIND(ExpectedApiOrImpl)
-CARBON_DIAGNOSTIC_KIND(ExpectedSemiToEndPackageDirective)
 
 // For-specific diagnostics.
 CARBON_DIAGNOSTIC_KIND(ExpectedIn)
@@ -85,6 +83,7 @@ CARBON_DIAGNOSTIC_KIND(ExpectedElseAfterIf)
 
 // Declaration diagnostics.
 CARBON_DIAGNOSTIC_KIND(ExpectedDeclarationName)
+CARBON_DIAGNOSTIC_KIND(ExpectedDeclarationSemi)
 CARBON_DIAGNOSTIC_KIND(ExpectedDeclarationSemiOrDefinition)
 CARBON_DIAGNOSTIC_KIND(MethodImplNotAllowed)
 CARBON_DIAGNOSTIC_KIND(ParametersRequiredByIntroducer)
@@ -97,8 +96,8 @@ CARBON_DIAGNOSTIC_KIND(ParametersRequiredByDeduced)
 CARBON_DIAGNOSTIC_KIND(SemanticsTodo)
 
 CARBON_DIAGNOSTIC_KIND(NameNotFound)
-CARBON_DIAGNOSTIC_KIND(NameRedefined)
-CARBON_DIAGNOSTIC_KIND(PreviousDefinition)
+CARBON_DIAGNOSTIC_KIND(NameDeclarationDuplicate)
+CARBON_DIAGNOSTIC_KIND(NameDeclarationPrevious)
 CARBON_DIAGNOSTIC_KIND(NoMatchingCall)
 CARBON_DIAGNOSTIC_KIND(CallArgCountMismatch)
 CARBON_DIAGNOSTIC_KIND(CallArgTypeMismatch)
@@ -107,8 +106,10 @@ CARBON_DIAGNOSTIC_KIND(ReturnStatementDisallowExpression)
 CARBON_DIAGNOSTIC_KIND(ReturnStatementImplicitNote)
 CARBON_DIAGNOSTIC_KIND(ReturnStatementMissingExpression)
 CARBON_DIAGNOSTIC_KIND(ImplicitAsConversionFailure)
-CARBON_DIAGNOSTIC_KIND(DesignatorExpressionUnsupported)
-CARBON_DIAGNOSTIC_KIND(DesignatorExpressionNameNotFound)
+CARBON_DIAGNOSTIC_KIND(QualifiedDeclarationInNonScope)
+CARBON_DIAGNOSTIC_KIND(QualifiedDeclarationNonScopeEntity)
+CARBON_DIAGNOSTIC_KIND(QualifiedExpressionUnsupported)
+CARBON_DIAGNOSTIC_KIND(QualifiedExpressionNameNotFound)
 
 // ============================================================================
 // Other diagnostics

+ 6 - 0
toolchain/lowering/lowering_handle.cpp

@@ -148,6 +148,12 @@ auto LoweringHandleIntegerLiteral(LoweringFunctionContext& context,
   context.SetLocal(node_id, v);
 }
 
+auto LoweringHandleNamespace(LoweringFunctionContext& /*context*/,
+                             SemanticsNodeId /*node_id*/,
+                             SemanticsNode /*node*/) -> void {
+  // No action to take.
+}
+
 auto LoweringHandleRealLiteral(LoweringFunctionContext& context,
                                SemanticsNodeId node_id, SemanticsNode node)
     -> void {

+ 35 - 0
toolchain/lowering/testdata/namespace/function.carbon

@@ -0,0 +1,35 @@
+// 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
+// CHECK:STDOUT: ; ModuleID = 'function.carbon'
+// CHECK:STDOUT: source_filename = "function.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: %EmptyTupleType = type {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %EmptyTupleType @Baz() {
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %EmptyTupleType @Baz.1() {
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %EmptyTupleType @Bar() {
+// CHECK:STDOUT:   %Baz.1 = call %EmptyTupleType @Baz.1()
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+
+namespace Foo;
+
+// Never called, just here to help catch bugs in name lookup.
+fn Baz() {
+}
+
+fn Foo.Baz() {
+}
+
+fn Bar() {
+  Foo.Baz();
+}

+ 28 - 0
toolchain/lowering/testdata/namespace/nested.carbon

@@ -0,0 +1,28 @@
+// 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
+// CHECK:STDOUT: ; ModuleID = 'nested.carbon'
+// CHECK:STDOUT: source_filename = "nested.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: %EmptyTupleType = type {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %EmptyTupleType @Wiz() {
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define %EmptyTupleType @Baz() {
+// CHECK:STDOUT:   %Wiz = call %EmptyTupleType @Wiz()
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+
+namespace Foo;
+namespace Foo.Bar;
+
+fn Foo.Bar.Wiz() {
+}
+
+fn Foo.Bar.Baz() {
+  Foo.Bar.Wiz();
+}

+ 25 - 8
toolchain/parser/parse_node_kind.def

@@ -68,6 +68,13 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageImpl, 0)
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageLibrary, 1)
 CARBON_PARSE_NODE_KIND_BRACKET(PackageDirective, PackageIntroducer)
 
+// `namespace`:
+//   NamespaceStart
+//   _external_: Identifier or QualifiedDeclaration
+// Namespace
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(NamespaceStart, 0)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(Namespace, 2)
+
 // A code block:
 //   CodeBlockStart
 //   _external_: statements
@@ -77,7 +84,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(CodeBlock, CodeBlockStart)
 
 // `fn`:
 //     FunctionIntroducer
-//     Name
+//     _external_: Identifier or QualifiedDeclaration
 //     _external_: ParameterList
 //       _external_: type expression
 //     ReturnType
@@ -233,11 +240,21 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(CallExpressionStart, 1)
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(CallExpressionComma, 0)
 CARBON_PARSE_NODE_KIND_BRACKET(CallExpression, CallExpressionStart)
 
-// A designator expression, such as `a.b`:
+// A qualified declaration, such as `a.b`:
+//   _external_: Identifier or QualifiedDeclaration
+//   _external_: Identifier
+// QualifiedDeclaration
+//
+// TODO: This will eventually more general expressions, for example with
+// `GenericType(type_args).ChildType(child_type_args).Name`.
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(QualifiedDeclaration, 2)
+
+// A member access expression, such as `a.b` or
+// `GetObject().(Interface.member)`:
 //   _external_: lhs expression
-//   _external_: Name
-// DesignatorExpression
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(DesignatorExpression, 2)
+//   _external_: rhs expression
+// QualifiedExpression
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(MemberAccessExpression, 2)
 
 // A literal.
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(Literal, 0)
@@ -312,7 +329,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(StructTypeLiteral,
 
 // `class`:
 //     ClassIntroducer
-//     Name
+//     _external_: Identifier or QualifiedDeclaration
 //   ClassDefinitionStart
 //   _external_: declarations
 // ClassDefinition
@@ -327,7 +344,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(ClassDeclaration, ClassIntroducer)
 
 // `interface`:
 //     InterfaceIntroducer
-//     Name
+//     _external_: Identifier or QualifiedDeclaration
 //   InterfaceDefinitionStart
 //   _external_: declarations
 // InterfaceDefinition
@@ -342,7 +359,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(InterfaceDeclaration, InterfaceIntroducer)
 
 // `constraint`:
 //     NamedConstraintIntroducer
-//     Name
+//     _external_: Identifier or QualifiedDeclaration
 //   NamedConstraintDefinitionStart
 //   _external_: declarations
 // NamedConstraintDefinition

+ 8 - 1
toolchain/parser/parser_context.cpp

@@ -425,10 +425,17 @@ auto ParserContext::RecoverFromDeclarationError(StateStackEntry state,
           /*has_error=*/true);
 }
 
+auto ParserContext::EmitExpectedDeclarationSemi(TokenKind expected_kind)
+    -> void {
+  CARBON_DIAGNOSTIC(ExpectedDeclarationSemi, Error,
+                    "`{0}` declarations must end with a `;`.", TokenKind);
+  emitter().Emit(*position(), ExpectedDeclarationSemi, expected_kind);
+}
+
 auto ParserContext::EmitExpectedDeclarationSemiOrDefinition(
     TokenKind expected_kind) -> void {
   CARBON_DIAGNOSTIC(ExpectedDeclarationSemiOrDefinition, Error,
-                    "`{0}` should either end with a `;` for a declaration or "
+                    "`{0}` declarations must either end with a `;` or "
                     "have a `{{ ... }` block for a definition.",
                     TokenKind);
   emitter().Emit(*position(), ExpectedDeclarationSemiOrDefinition,

+ 12 - 0
toolchain/parser/parser_context.h

@@ -215,6 +215,14 @@ class ParserContext {
                               *position_, tree_->size()));
   }
 
+  // Pushes a new state with a specific token for context. Used when forming a
+  // new subtree with a token that isn't the start of the subtree.
+  auto PushState(ParserState state, TokenizedBuffer::Token token) -> void {
+    PushState(StateStackEntry(state, PrecedenceGroup::ForTopLevelExpression(),
+                              PrecedenceGroup::ForTopLevelExpression(), token,
+                              tree_->size()));
+  }
+
   // Pushes a new expression state with specific precedence.
   auto PushStateForExpression(PrecedenceGroup ambient_precedence) -> void {
     PushState(StateStackEntry(ParserState::Expression, ambient_precedence,
@@ -255,6 +263,10 @@ class ParserContext {
                                ParserState keyword_state, int subtree_start)
       -> void;
 
+  // Emits a diagnostic for a declaration missing a semi.
+  auto EmitExpectedDeclarationSemi(TokenKind expected_kind) -> void;
+
+  // Emits a diagnostic for a declaration missing a semi or definition.
   auto EmitExpectedDeclarationSemiOrDefinition(TokenKind expected_kind) -> void;
 
   // Handles error recovery in a declaration, particularly before any possible

+ 1 - 1
toolchain/parser/parser_handle_brace_expression.cpp

@@ -57,7 +57,7 @@ static auto ParserHandleBraceExpressionParameter(
 
   state.state = after_designator_state;
   context.PushState(state);
-  context.PushState(ParserState::DesignatorAsStruct);
+  context.PushState(ParserState::PeriodAsStruct);
 }
 
 auto ParserHandleBraceExpressionParameterAsType(ParserContext& context)

+ 67 - 9
toolchain/parser/parser_handle_declaration_name_and_params.cpp

@@ -8,17 +8,70 @@ namespace Carbon {
 
 // Handles DeclarationNameAndParamsAs(Optional|Required).
 static auto ParserHandleDeclarationNameAndParams(ParserContext& context,
-                                                 bool params_required) -> void {
+                                                 ParserState after_name)
+    -> void {
   auto state = context.PopState();
 
-  if (!context.ConsumeAndAddLeafNodeIf(TokenKind::Identifier,
-                                       ParseNodeKind::Name)) {
+  // TODO: Should handle designated names.
+  if (auto identifier = context.ConsumeIf(TokenKind::Identifier)) {
+    state.state = after_name;
+    context.PushState(state);
+
+    if (context.PositionIs(TokenKind::Period)) {
+      context.AddLeafNode(ParseNodeKind::Name, *identifier);
+      state.state = ParserState::PeriodAsDeclaration;
+      context.PushState(state);
+    } else {
+      context.AddLeafNode(ParseNodeKind::Name, *identifier);
+    }
+  } else {
     CARBON_DIAGNOSTIC(ExpectedDeclarationName, Error,
                       "`{0}` introducer should be followed by a name.",
                       TokenKind);
     context.emitter().Emit(*context.position(), ExpectedDeclarationName,
                            context.tokens().GetKind(state.token));
     context.ReturnErrorOnState();
+    context.AddLeafNode(ParseNodeKind::InvalidParse, *context.position());
+  }
+}
+
+auto ParserHandleDeclarationNameAndParamsAsNone(ParserContext& context)
+    -> void {
+  ParserHandleDeclarationNameAndParams(
+      context, ParserState::DeclarationNameAndParamsAfterNameAsNone);
+}
+
+auto ParserHandleDeclarationNameAndParamsAsOptional(ParserContext& context)
+    -> void {
+  ParserHandleDeclarationNameAndParams(
+      context, ParserState::DeclarationNameAndParamsAfterNameAsOptional);
+}
+
+auto ParserHandleDeclarationNameAndParamsAsRequired(ParserContext& context)
+    -> void {
+  ParserHandleDeclarationNameAndParams(
+      context, ParserState::DeclarationNameAndParamsAfterNameAsRequired);
+}
+
+enum class Params {
+  None,
+  Optional,
+  Required,
+};
+
+static auto ParserHandleDeclarationNameAndParamsAfterName(
+    ParserContext& context, Params params) -> void {
+  auto state = context.PopState();
+
+  if (context.PositionIs(TokenKind::Period)) {
+    // Continue designator processing.
+    context.PushState(state);
+    state.state = ParserState::PeriodAsDeclaration;
+    context.PushState(state);
+    return;
+  }
+
+  if (params == Params::None) {
     return;
   }
 
@@ -27,7 +80,7 @@ static auto ParserHandleDeclarationNameAndParams(ParserContext& context,
     context.PushState(ParserState::ParameterListAsDeduced);
   } else if (context.PositionIs(TokenKind::OpenParen)) {
     context.PushState(ParserState::ParameterListAsRegular);
-  } else if (params_required) {
+  } else if (params == Params::Required) {
     CARBON_DIAGNOSTIC(ParametersRequiredByIntroducer, Error,
                       "`{0}` requires a `(` for parameters.", TokenKind);
     context.emitter().Emit(*context.position(), ParametersRequiredByIntroducer,
@@ -36,14 +89,19 @@ static auto ParserHandleDeclarationNameAndParams(ParserContext& context,
   }
 }
 
-auto ParserHandleDeclarationNameAndParamsAsOptional(ParserContext& context)
+auto ParserHandleDeclarationNameAndParamsAfterNameAsNone(ParserContext& context)
     -> void {
-  ParserHandleDeclarationNameAndParams(context, /*params_required=*/false);
+  ParserHandleDeclarationNameAndParamsAfterName(context, Params::None);
 }
 
-auto ParserHandleDeclarationNameAndParamsAsRequired(ParserContext& context)
-    -> void {
-  ParserHandleDeclarationNameAndParams(context, /*params_required=*/true);
+auto ParserHandleDeclarationNameAndParamsAfterNameAsOptional(
+    ParserContext& context) -> void {
+  ParserHandleDeclarationNameAndParamsAfterName(context, Params::Optional);
+}
+
+auto ParserHandleDeclarationNameAndParamsAfterNameAsRequired(
+    ParserContext& context) -> void {
+  ParserHandleDeclarationNameAndParamsAfterName(context, Params::Required);
 }
 
 auto ParserHandleDeclarationNameAndParamsAfterDeduced(ParserContext& context)

+ 4 - 0
toolchain/parser/parser_handle_declaration_scope_loop.cpp

@@ -46,6 +46,10 @@ auto ParserHandleDeclarationScopeLoop(ParserContext& context) -> void {
       context.PushState(ParserState::TypeIntroducerAsInterface);
       break;
     }
+    case TokenKind::Namespace: {
+      context.PushState(ParserState::Namespace);
+      break;
+    }
     case TokenKind::Semi: {
       context.AddLeafNode(ParseNodeKind::EmptyDeclaration, context.Consume());
       break;

+ 4 - 4
toolchain/parser/parser_handle_expression.cpp

@@ -119,7 +119,7 @@ auto ParserHandleExpressionInPostfixLoop(ParserContext& context) -> void {
   switch (context.PositionKind()) {
     case TokenKind::Period: {
       context.PushState(state);
-      state.state = ParserState::DesignatorAsExpression;
+      state.state = ParserState::PeriodAsExpression;
       context.PushState(state);
       break;
     }
@@ -298,9 +298,9 @@ auto ParserHandleExpressionStatementFinish(ParserContext& context) -> void {
   }
 
   if (!state.has_error) {
-    CARBON_DIAGNOSTIC(ExpectedSemiAfterExpression, Error,
-                      "Expected `;` after expression.");
-    context.emitter().Emit(*context.position(), ExpectedSemiAfterExpression);
+    CARBON_DIAGNOSTIC(ExpectedExpressionSemi, Error,
+                      "Expected `;` after expression statement.");
+    context.emitter().Emit(*context.position(), ExpectedExpressionSemi);
   }
 
   if (auto semi_token = context.SkipPastLikelyEnd(state.token)) {

+ 2 - 2
toolchain/parser/parser_handle_function.cpp

@@ -13,8 +13,8 @@ auto ParserHandleFunctionIntroducer(ParserContext& context) -> void {
 
   state.state = ParserState::FunctionAfterParameters;
   context.PushState(state);
-  state.state = ParserState::DeclarationNameAndParamsAsRequired;
-  context.PushState(state);
+  context.PushState(ParserState::DeclarationNameAndParamsAsRequired,
+                    state.token);
 }
 
 auto ParserHandleFunctionAfterParameters(ParserContext& context) -> void {

+ 39 - 0
toolchain/parser/parser_handle_namespace.cpp

@@ -0,0 +1,39 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "toolchain/parser/parser_context.h"
+
+namespace Carbon {
+
+auto ParserHandleNamespace(ParserContext& context) -> void {
+  auto state = context.PopState();
+
+  context.AddLeafNode(ParseNodeKind::NamespaceStart, context.Consume());
+
+  state.state = ParserState::NamespaceFinish;
+  context.PushState(state);
+
+  context.PushState(ParserState::DeclarationNameAndParamsAsNone, state.token);
+}
+
+auto ParserHandleNamespaceFinish(ParserContext& context) -> void {
+  auto state = context.PopState();
+
+  if (state.has_error) {
+    context.RecoverFromDeclarationError(state, ParseNodeKind::Namespace,
+                                        /*skip_past_likely_end=*/true);
+    return;
+  }
+
+  if (auto semi = context.ConsumeIf(TokenKind::Semi)) {
+    context.AddNode(ParseNodeKind::Namespace, *semi, state.subtree_start,
+                    state.has_error);
+  } else {
+    context.EmitExpectedDeclarationSemi(TokenKind::Namespace);
+    context.RecoverFromDeclarationError(state, ParseNodeKind::Namespace,
+                                        /*skip_past_likely_end=*/true);
+  }
+}
+
+}  // namespace Carbon

+ 1 - 4
toolchain/parser/parser_handle_package.cpp

@@ -77,10 +77,7 @@ auto ParserHandlePackage(ParserContext& context) -> void {
   }
 
   if (!context.PositionIs(TokenKind::Semi)) {
-    CARBON_DIAGNOSTIC(ExpectedSemiToEndPackageDirective, Error,
-                      "Expected `;` to end package directive.");
-    context.emitter().Emit(*context.position(),
-                           ExpectedSemiToEndPackageDirective);
+    context.EmitExpectedDeclarationSemi(TokenKind::Package);
     exit_on_parse_error();
     return;
   }

+ 16 - 11
toolchain/parser/parser_handle_designator.cpp → toolchain/parser/parser_handle_period.cpp

@@ -6,8 +6,11 @@
 
 namespace Carbon {
 
-// Handles DesignatorAs.
-auto ParserHandleDesignator(ParserContext& context, bool as_struct) -> void {
+// Handles PeriodAs variants.
+// TODO: This currently only supports identifiers on the rhs, but will in the
+// future need to handle things like `object.(Interface.member)` for qualifiers.
+auto ParserHandlePeriod(ParserContext& context, ParseNodeKind node_kind)
+    -> void {
   auto state = context.PopState();
 
   // `.` identifier
@@ -18,8 +21,8 @@ auto ParserHandleDesignator(ParserContext& context, bool as_struct) -> void {
     CARBON_DIAGNOSTIC(ExpectedIdentifierAfterDot, Error,
                       "Expected identifier after `.`.");
     context.emitter().Emit(*context.position(), ExpectedIdentifierAfterDot);
-    // If we see a keyword, assume it was intended to be the designated name.
-    // TODO: Should keywords be valid in designators?
+    // If we see a keyword, assume it was intended to be a name.
+    // TODO: Should keywords be valid here?
     if (context.PositionKind().is_keyword()) {
       context.AddLeafNode(ParseNodeKind::Name, context.Consume(),
                           /*has_error=*/true);
@@ -32,17 +35,19 @@ auto ParserHandleDesignator(ParserContext& context, bool as_struct) -> void {
     }
   }
 
-  context.AddNode(as_struct ? ParseNodeKind::StructFieldDesignator
-                            : ParseNodeKind::DesignatorExpression,
-                  dot, state.subtree_start, state.has_error);
+  context.AddNode(node_kind, dot, state.subtree_start, state.has_error);
 }
 
-auto ParserHandleDesignatorAsExpression(ParserContext& context) -> void {
-  ParserHandleDesignator(context, /*as_struct=*/false);
+auto ParserHandlePeriodAsDeclaration(ParserContext& context) -> void {
+  ParserHandlePeriod(context, ParseNodeKind::QualifiedDeclaration);
 }
 
-auto ParserHandleDesignatorAsStruct(ParserContext& context) -> void {
-  ParserHandleDesignator(context, /*as_struct=*/true);
+auto ParserHandlePeriodAsExpression(ParserContext& context) -> void {
+  ParserHandlePeriod(context, ParseNodeKind::MemberAccessExpression);
+}
+
+auto ParserHandlePeriodAsStruct(ParserContext& context) -> void {
+  ParserHandlePeriod(context, ParseNodeKind::StructFieldDesignator);
 }
 
 }  // namespace Carbon

+ 3 - 3
toolchain/parser/parser_handle_statement.cpp

@@ -60,9 +60,9 @@ static auto ParserHandleStatementKeywordFinish(ParserContext& context,
 
   auto semi = context.ConsumeIf(TokenKind::Semi);
   if (!semi) {
-    CARBON_DIAGNOSTIC(ExpectedSemiAfter, Error, "Expected `;` after `{0}`.",
-                      TokenKind);
-    context.emitter().Emit(*context.position(), ExpectedSemiAfter,
+    CARBON_DIAGNOSTIC(ExpectedStatementSemi, Error,
+                      "`{0}` statements must end with a `;`.", TokenKind);
+    context.emitter().Emit(*context.position(), ExpectedStatementSemi,
                            context.tokens().GetKind(state.token));
     state.has_error = true;
     // Recover to the next semicolon if possible, otherwise indicate the

+ 2 - 2
toolchain/parser/parser_handle_type.cpp

@@ -16,8 +16,8 @@ static auto ParserHandleTypeIntroducer(ParserContext& context,
 
   state.state = after_params_state;
   context.PushState(state);
-  state.state = ParserState::DeclarationNameAndParamsAsOptional;
-  context.PushState(state);
+  context.PushState(ParserState::DeclarationNameAndParamsAsOptional,
+                    state.token);
 }
 
 auto ParserHandleTypeIntroducerAsClass(ParserContext& context) -> void {

+ 1 - 3
toolchain/parser/parser_handle_var.cpp

@@ -53,9 +53,7 @@ auto ParserHandleVarFinishAsSemicolon(ParserContext& context) -> void {
     end_token = context.Consume();
   } else {
     // TODO: Disambiguate between statement and member declaration.
-    CARBON_DIAGNOSTIC(ExpectedSemiAfterVar, Error,
-                      "Expected `;` to terminate `var` declaration.");
-    context.emitter().Emit(*context.position(), ExpectedSemiAfterVar);
+    context.EmitExpectedDeclarationSemi(TokenKind::Var);
     state.has_error = true;
     if (auto semi_token = context.SkipPastLikelyEnd(state.token)) {
       end_token = *semi_token;

+ 54 - 5
toolchain/parser/parser_state.def

@@ -127,8 +127,31 @@ CARBON_PARSER_STATE(CodeBlock)
 //   (state done)
 CARBON_PARSER_STATE(CodeBlockFinish)
 
-// Handles a general declaration name and parameters, such as `Foo[...](...)`.
+// Handles a declaration name and parameters, such as `Foo[...](...)`.
+//
+// Allowed parameters:
+// - None: `Foo` only.
+// - Optional: `Foo`, `Foo(...)`, or `Foo[...](...)`.
+// - Required: ``Foo(...)` or `Foo[...](...)`.
+//
+// If `Identifier` followed by `Period`:
+//   1. DeclarationNameAndParamsAfterNameAs(None|Optional|Required)
+// If `Identifier`:
+//   1. DeclarationNameAndParamsAfterNameAs(None|Optional|Required)
+//   2. PeriodAsDeclaration
+// Else:
+//   (state done)
+CARBON_PARSER_STATE_VARIANTS3(DeclarationNameAndParams, None, Optional,
+                              Required)
+
+// Handles a declaration name between the main name and deduced parameters.
+//
+// For `None`, parameters aren't supported so only `Period` or `Else` paths are
+// used.
 //
+// If `Period`:
+//   1. DeclarationNameAndParamsAfterNameAs(None|Optional|Required)
+//   2. PeriodAsDeclaration
 // If `OpenSquareBracket`:
 //   1. ParameterListAsDeduced
 //   2. DeclarationNameAndParamsAfterDeduced
@@ -136,7 +159,8 @@ CARBON_PARSER_STATE(CodeBlockFinish)
 //   1. ParameterListAsRegular
 // Else:
 //   (state done)
-CARBON_PARSER_STATE_VARIANTS2(DeclarationNameAndParams, Optional, Required)
+CARBON_PARSER_STATE_VARIANTS3(DeclarationNameAndParamsAfterName, None, Optional,
+                              Required)
 
 // Handles regular parameters such as `(...)` for the general declaration case.
 // Only used after deduced parameters.
@@ -164,6 +188,9 @@ CARBON_PARSER_STATE(DeclarationNameAndParamsAfterDeduced)
 // If `Interface`:
 //   1. TypeIntroducerAsInterface
 //   2. DeclarationScopeLoop
+// If `Namespace`:
+//   2. Namespace
+//   3. DeclarationScopeLoop
 // If `Semi`:
 //   1. DeclarationScopeLoop
 // If `Var`:
@@ -173,11 +200,20 @@ CARBON_PARSER_STATE(DeclarationNameAndParamsAfterDeduced)
 //   1. DeclarationScopeLoop
 CARBON_PARSER_STATE(DeclarationScopeLoop)
 
-// Handles a designator expression, such as `.z` in `x.(y.z)`.
+// Handles periods. Only does one `.<expression>` segment; the source is
+// responsible for handling chaining.
+//
+// The forms of this are:
+// - Designated names in structs.
+// - Qualified names in declarations.
+// - Member access expressions.
+//
+// Declarations and expressions have qualifiers such as `x.y`, while structs
+// have designators such as `.z`.
 //
 // Always:
 //   (state done)
-CARBON_PARSER_STATE_VARIANTS2(Designator, Expression, Struct)
+CARBON_PARSER_STATE_VARIANTS3(Period, Declaration, Expression, Struct)
 
 // Handles processing of an expression.
 //
@@ -212,7 +248,7 @@ CARBON_PARSER_STATE(ExpressionInPostfix)
 // such as designators or parenthesized parameters.
 //
 // If `Period`:
-//   1. DesignatorAsExpression
+//   1. PeriodAsExpression
 //   2. ExpressionInPostfixLoop
 // If `OpenParen`:
 //   1. CallExpression
@@ -329,6 +365,19 @@ CARBON_PARSER_STATE(FunctionSignatureFinish)
 //   (state done)
 CARBON_PARSER_STATE(FunctionDefinitionFinish)
 
+// Handles `namespace`.
+//
+// Always:
+//   1. DeclarationNameAndParamsAsNone
+//   2. NamespaceFinish
+CARBON_PARSER_STATE(Namespace)
+
+// Handles `namespace` after the name.
+//
+// Always:
+//   (state done)
+CARBON_PARSER_STATE(NamespaceFinish)
+
 // Handles `package`.
 //
 // Always:

+ 3 - 3
toolchain/parser/testdata/basics/fail_invalid_designators.carbon

@@ -11,15 +11,15 @@
 // CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
 // CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
 // CHECK:STDOUT:       {kind: 'Name', text: ';', has_error: yes},
-// CHECK:STDOUT:     {kind: 'DesignatorExpression', text: '.', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'MemberAccessExpression', text: '.', subtree_size: 3},
 // CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', has_error: yes, subtree_size: 4},
 // CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
 // CHECK:STDOUT:       {kind: 'Name', text: 'fn', has_error: yes},
-// CHECK:STDOUT:     {kind: 'DesignatorExpression', text: '.', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'MemberAccessExpression', text: '.', subtree_size: 3},
 // CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 4},
 // CHECK:STDOUT:       {kind: 'NameExpression', text: 'a'},
 // CHECK:STDOUT:       {kind: 'Name', text: '42', has_error: yes},
-// CHECK:STDOUT:     {kind: 'DesignatorExpression', text: '.', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'MemberAccessExpression', text: '.', subtree_size: 3},
 // CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', has_error: yes, subtree_size: 4},
 // CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},

+ 1 - 1
toolchain/parser/testdata/basics/fail_paren_match_regression.carbon

@@ -18,5 +18,5 @@
 
 // CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+3]]:5: Expected pattern in `var` declaration.
 // CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+2]]:12: Expected `,` or `)`.
-// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+1]]:15: Expected `;` to terminate `var` declaration.
+// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+1]]:15: `var` declarations must end with a `;`.
 var = (foo {})

+ 4 - 4
toolchain/parser/testdata/basics/function_call.carbon

@@ -11,20 +11,20 @@
 // CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
 // CHECK:STDOUT:                   {kind: 'NameExpression', text: 'a'},
 // CHECK:STDOUT:                   {kind: 'Name', text: 'b'},
-// CHECK:STDOUT:                 {kind: 'DesignatorExpression', text: '.', subtree_size: 3},
+// CHECK:STDOUT:                 {kind: 'MemberAccessExpression', text: '.', subtree_size: 3},
 // CHECK:STDOUT:                 {kind: 'Name', text: 'f'},
-// CHECK:STDOUT:               {kind: 'DesignatorExpression', text: '.', subtree_size: 5},
+// CHECK:STDOUT:               {kind: 'MemberAccessExpression', text: '.', subtree_size: 5},
 // CHECK:STDOUT:             {kind: 'CallExpressionStart', text: '(', subtree_size: 6},
 // CHECK:STDOUT:               {kind: 'NameExpression', text: 'c'},
 // CHECK:STDOUT:               {kind: 'Name', text: 'd'},
-// CHECK:STDOUT:             {kind: 'DesignatorExpression', text: '.', subtree_size: 3},
+// CHECK:STDOUT:             {kind: 'MemberAccessExpression', text: '.', subtree_size: 3},
 // CHECK:STDOUT:             {kind: 'CallExpressionComma', text: ','},
 // CHECK:STDOUT:               {kind: 'ParenExpressionOrTupleLiteralStart', text: '('},
 // CHECK:STDOUT:               {kind: 'NameExpression', text: 'e'},
 // CHECK:STDOUT:             {kind: 'ParenExpression', text: ')', subtree_size: 3},
 // CHECK:STDOUT:           {kind: 'CallExpression', text: ')', subtree_size: 14},
 // CHECK:STDOUT:           {kind: 'Name', text: 'g'},
-// CHECK:STDOUT:         {kind: 'DesignatorExpression', text: '.', subtree_size: 16},
+// CHECK:STDOUT:         {kind: 'MemberAccessExpression', text: '.', subtree_size: 16},
 // CHECK:STDOUT:       {kind: 'CallExpressionStart', text: '(', subtree_size: 17},
 // CHECK:STDOUT:     {kind: 'CallExpression', text: ')', subtree_size: 18},
 // CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 19},

+ 1 - 1
toolchain/parser/testdata/class/fn_definitions.carbon

@@ -38,7 +38,7 @@
 // CHECK:STDOUT:       {kind: 'ReturnStatementStart', text: 'return'},
 // CHECK:STDOUT:         {kind: 'SelfValueName', text: 'self'},
 // CHECK:STDOUT:         {kind: 'Name', text: 'x'},
-// CHECK:STDOUT:       {kind: 'DesignatorExpression', text: '.', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'MemberAccessExpression', text: '.', subtree_size: 3},
 // CHECK:STDOUT:     {kind: 'ReturnStatement', text: ';', subtree_size: 5},
 // CHECK:STDOUT:   {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
 // CHECK:STDOUT: {kind: 'ClassDefinition', text: '}', subtree_size: 38},

+ 2 - 1
toolchain/parser/testdata/function/declaration/fail_missing_name.carbon

@@ -5,7 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: [
 // CHECK:STDOUT:   {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'InvalidParse', text: '('},
+// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 

+ 2 - 1
toolchain/parser/testdata/function/declaration/fail_only_fn_and_semi.carbon

@@ -5,7 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: [
 // CHECK:STDOUT:   {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'InvalidParse', text: ';'},
+// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 

+ 2 - 1
toolchain/parser/testdata/function/declaration/fail_repeated_fn_and_semi.carbon

@@ -5,7 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: [
 // CHECK:STDOUT:   {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'InvalidParse', text: 'fn'},
+// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 

+ 2 - 1
toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_until_outdent.carbon

@@ -5,7 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: [
 // CHECK:STDOUT:   {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'InvalidParse', text: '('},
+// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:   {kind: 'FunctionIntroducer', text: 'fn'},
 // CHECK:STDOUT:   {kind: 'Name', text: 'F'},
 // CHECK:STDOUT:     {kind: 'ParameterListStart', text: '('},

+ 2 - 1
toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_with_semi.carbon

@@ -5,7 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: [
 // CHECK:STDOUT:   {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'InvalidParse', text: '('},
+// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:   {kind: 'FunctionIntroducer', text: 'fn'},
 // CHECK:STDOUT:   {kind: 'Name', text: 'F'},
 // CHECK:STDOUT:     {kind: 'ParameterListStart', text: '('},

+ 2 - 1
toolchain/parser/testdata/function/declaration/fail_skip_indented_newline_without_semi.carbon

@@ -5,7 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: [
 // CHECK:STDOUT:   {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'InvalidParse', text: '('},
+// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:   {kind: 'FunctionIntroducer', text: 'fn'},
 // CHECK:STDOUT:   {kind: 'Name', text: 'F'},
 // CHECK:STDOUT:     {kind: 'ParameterListStart', text: '('},

+ 2 - 1
toolchain/parser/testdata/function/declaration/fail_skip_to_newline_without_semi.carbon

@@ -5,7 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: [
 // CHECK:STDOUT:   {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'InvalidParse', text: '('},
+// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: 'fn', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT:   {kind: 'FunctionIntroducer', text: 'fn'},
 // CHECK:STDOUT:   {kind: 'Name', text: 'F'},
 // CHECK:STDOUT:     {kind: 'ParameterListStart', text: '('},

+ 2 - 1
toolchain/parser/testdata/function/declaration/fail_without_name_and_many_tokens_in_params.carbon

@@ -5,7 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: [
 // CHECK:STDOUT:   {kind: 'FunctionIntroducer', text: 'fn'},
-// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'InvalidParse', text: '('},
+// CHECK:STDOUT: {kind: 'FunctionDeclaration', text: ';', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 

+ 1 - 1
toolchain/parser/testdata/function/definition/fail_identifier_in_statements.carbon

@@ -18,5 +18,5 @@ fn F() {
   // Note: this might become valid depending on the expression syntax. This test
   // shouldn't be taken as a sign it should remain invalid.
   bar
-// CHECK:STDERR: fail_identifier_in_statements.carbon:[[@LINE+1]]:1: Expected `;` after expression.
+// CHECK:STDERR: fail_identifier_in_statements.carbon:[[@LINE+1]]:1: Expected `;` after expression statement.
 }

+ 2 - 1
toolchain/parser/testdata/generics/interface/fail_missing_name.carbon

@@ -5,7 +5,8 @@
 // AUTOUPDATE
 // CHECK:STDOUT: [
 // CHECK:STDOUT:   {kind: 'InterfaceIntroducer', text: 'interface'},
-// CHECK:STDOUT: {kind: 'InterfaceDeclaration', text: 'interface', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'InvalidParse', text: '{'},
+// CHECK:STDOUT: {kind: 'InterfaceDeclaration', text: 'interface', has_error: yes, subtree_size: 3},
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 

+ 2 - 2
toolchain/parser/testdata/generics/interface/fail_missing_open_curly.carbon

@@ -13,8 +13,8 @@
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 
-// CHECK:STDERR: fail_missing_open_curly.carbon:[[@LINE+1]]:15: `interface` should either end with a `;` for a declaration or have a `{ ... }` block for a definition.
+// CHECK:STDERR: fail_missing_open_curly.carbon:[[@LINE+1]]:15: `interface` declarations must either end with a `;` or have a `{ ... }` block for a definition.
 interface Bar Baz {}
 
-// CHECK:STDERR: fail_missing_open_curly.carbon:[[@LINE+1]]:14: `interface` should either end with a `;` for a declaration or have a `{ ... }` block for a definition.
+// CHECK:STDERR: fail_missing_open_curly.carbon:[[@LINE+1]]:14: `interface` declarations must either end with a `;` or have a `{ ... }` block for a definition.
 interface Foo

+ 29 - 0
toolchain/parser/testdata/namespace/basic.carbon

@@ -0,0 +1,29 @@
+// 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
+// CHECK:STDOUT: [
+// CHECK:STDOUT:   {kind: 'NamespaceStart', text: 'namespace'},
+// CHECK:STDOUT:   {kind: 'Name', text: 'Foo'},
+// CHECK:STDOUT: {kind: 'Namespace', text: ';', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:     {kind: 'Name', text: 'Bar'},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'NameExpression', text: 'Foo'},
+// CHECK:STDOUT:           {kind: 'Name', text: 'Baz'},
+// CHECK:STDOUT:         {kind: 'MemberAccessExpression', text: '.', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'CallExpressionStart', text: '(', subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'CallExpression', text: ')', subtree_size: 5},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 6},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 12},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]
+
+namespace Foo;
+
+fn Bar() {
+  Foo.Baz();
+}

+ 14 - 0
toolchain/parser/testdata/namespace/fail_args.carbon

@@ -0,0 +1,14 @@
+// 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
+// CHECK:STDOUT: [
+// CHECK:STDOUT:   {kind: 'NamespaceStart', text: 'namespace'},
+// CHECK:STDOUT:   {kind: 'Name', text: 'Foo'},
+// CHECK:STDOUT: {kind: 'Namespace', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]
+
+// CHECK:STDERR: fail_args.carbon:[[@LINE+1]]:14: `namespace` declarations must end with a `;`.
+namespace Foo();

+ 16 - 0
toolchain/parser/testdata/namespace/fail_incomplete_name.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
+// CHECK:STDOUT: [
+// CHECK:STDOUT:   {kind: 'NamespaceStart', text: 'namespace'},
+// CHECK:STDOUT:     {kind: 'Name', text: 'Foo'},
+// CHECK:STDOUT:     {kind: 'Name', text: ';', has_error: yes},
+// CHECK:STDOUT:   {kind: 'QualifiedDeclaration', text: '.', subtree_size: 3},
+// CHECK:STDOUT: {kind: 'Namespace', text: ';', subtree_size: 5},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]
+
+// CHECK:STDERR: fail_incomplete_name.carbon:[[@LINE+1]]:15: Expected identifier after `.`.
+namespace Foo.;

+ 14 - 0
toolchain/parser/testdata/namespace/fail_no_name.carbon

@@ -0,0 +1,14 @@
+// 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
+// CHECK:STDOUT: [
+// CHECK:STDOUT:   {kind: 'NamespaceStart', text: 'namespace'},
+// CHECK:STDOUT:   {kind: 'InvalidParse', text: ';'},
+// CHECK:STDOUT: {kind: 'Namespace', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]
+
+// CHECK:STDERR: fail_no_name.carbon:[[@LINE+1]]:10: `namespace` introducer should be followed by a name.
+namespace;

+ 41 - 0
toolchain/parser/testdata/namespace/nested.carbon

@@ -0,0 +1,41 @@
+// 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
+// CHECK:STDOUT: [
+// CHECK:STDOUT:   {kind: 'NamespaceStart', text: 'namespace'},
+// CHECK:STDOUT:   {kind: 'Name', text: 'Foo'},
+// CHECK:STDOUT: {kind: 'Namespace', text: ';', subtree_size: 3},
+// CHECK:STDOUT:   {kind: 'NamespaceStart', text: 'namespace'},
+// CHECK:STDOUT:     {kind: 'Name', text: 'Foo'},
+// CHECK:STDOUT:     {kind: 'Name', text: 'Bar'},
+// CHECK:STDOUT:   {kind: 'QualifiedDeclaration', text: '.', subtree_size: 3},
+// CHECK:STDOUT: {kind: 'Namespace', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'Name', text: 'Foo'},
+// CHECK:STDOUT:         {kind: 'Name', text: 'Bar'},
+// CHECK:STDOUT:       {kind: 'QualifiedDeclaration', text: '.', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'Name', text: 'Baz'},
+// CHECK:STDOUT:     {kind: 'QualifiedDeclaration', text: '.', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:     {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:   {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 9},
+// CHECK:STDOUT:             {kind: 'NameExpression', text: 'Foo'},
+// CHECK:STDOUT:             {kind: 'Name', text: 'Bar'},
+// CHECK:STDOUT:           {kind: 'MemberAccessExpression', text: '.', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'Name', text: 'Wiz'},
+// CHECK:STDOUT:         {kind: 'MemberAccessExpression', text: '.', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'CallExpressionStart', text: '(', subtree_size: 6},
+// CHECK:STDOUT:     {kind: 'CallExpression', text: ')', subtree_size: 7},
+// CHECK:STDOUT:   {kind: 'ExpressionStatement', text: ';', subtree_size: 8},
+// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18},
+// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT: ]
+
+namespace Foo;
+namespace Foo.Bar;
+
+fn Foo.Bar.Baz() {
+  Foo.Bar.Wiz();
+}

+ 1 - 1
toolchain/parser/testdata/operators/fail_infix_uneven_space_after.carbon

@@ -17,5 +17,5 @@
 
 // TODO: We could figure out that this first Failed example is infix
 // with one-token lookahead.
-// CHECK:STDERR: fail_infix_uneven_space_after.carbon:[[@LINE+1]]:16: Expected `;` to terminate `var` declaration.
+// CHECK:STDERR: fail_infix_uneven_space_after.carbon:[[@LINE+1]]:16: `var` declarations must end with a `;`.
 var n: i8 = n* n;

+ 1 - 1
toolchain/parser/testdata/operators/fail_star_star_no_space.carbon

@@ -20,5 +20,5 @@
 // before we notice the missing whitespace around the second `*`.
 // It'd be better to (somehow) form n*(*p) and reject due to the missing
 // whitespace around the first `*`.
-// CHECK:STDERR: fail_star_star_no_space.carbon:[[@LINE+1]]:16: Expected `;` to terminate `var` declaration.
+// CHECK:STDERR: fail_star_star_no_space.carbon:[[@LINE+1]]:16: `var` declarations must end with a `;`.
 var n: i8 = n**p;

+ 1 - 1
toolchain/parser/testdata/package/fail_no_semi.carbon

@@ -11,5 +11,5 @@
 // CHECK:STDOUT: {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT: ]
 
-// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:21: Expected `;` to end package directive.
+// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:21: `package` declarations must end with a `;`.
 package Geometry api

+ 1 - 1
toolchain/parser/testdata/return/fail_expr_no_semi.carbon

@@ -18,5 +18,5 @@
 
 fn F() {
   return x
-// CHECK:STDERR: fail_expr_no_semi.carbon:[[@LINE+1]]:1: Expected `;` after `return`.
+// CHECK:STDERR: fail_expr_no_semi.carbon:[[@LINE+1]]:1: `return` statements must end with a `;`.
 }

+ 1 - 1
toolchain/parser/testdata/return/fail_no_semi.carbon

@@ -19,5 +19,5 @@
 fn F() {
   return
 // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+2]]:1: Expected expression.
-// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:1: Expected `;` after `return`.
+// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:1: `return` statements must end with a `;`.
 }

+ 2 - 2
toolchain/parser/testdata/while/fail_no_semi.carbon

@@ -39,11 +39,11 @@ fn F() {
   while (a) {
     if (b) {
       break
-    // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:5: Expected `;` after `break`.
+    // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:5: `break` statements must end with a `;`.
     }
     if (c) {
       continue
-    // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:5: Expected `;` after `continue`.
+    // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+1]]:5: `continue` statements must end with a `;`.
     }
   }
 }

+ 172 - 22
toolchain/semantics/semantics_context.cpp

@@ -8,7 +8,6 @@
 
 #include "common/vlog.h"
 #include "toolchain/diagnostics/diagnostic_kind.h"
-#include "toolchain/lexer/token_kind.h"
 #include "toolchain/lexer/tokenized_buffer.h"
 #include "toolchain/parser/parse_node_kind.h"
 #include "toolchain/semantics/semantics_ir.h"
@@ -17,6 +16,8 @@
 
 namespace Carbon {
 
+CARBON_DIAGNOSTIC(NameNotFound, Error, "Name {0} not found", llvm::StringRef);
+
 SemanticsContext::SemanticsContext(const TokenizedBuffer& tokens,
                                    DiagnosticEmitter<ParseTree::Node>& emitter,
                                    const ParseTree& parse_tree,
@@ -74,43 +75,93 @@ auto SemanticsContext::AddNodeAndPush(ParseTree::Node parse_node,
   node_stack_.Push(parse_node, node_id);
 }
 
+CARBON_DIAGNOSTIC(NameDeclarationDuplicate, Error,
+                  "Duplicate name being declared in the same scope.");
+CARBON_DIAGNOSTIC(NameDeclarationPrevious, Note,
+                  "Name is previously declared here.");
+
 auto SemanticsContext::AddNameToLookup(ParseTree::Node name_node,
                                        SemanticsStringId name_id,
                                        SemanticsNodeId target_id) -> void {
   if (current_scope().names.insert(name_id).second) {
     name_lookup_[name_id].push_back(target_id);
   } else {
-    CARBON_DIAGNOSTIC(NameRedefined, Error, "Redefining {0} in the same scope.",
-                      llvm::StringRef);
-    CARBON_DIAGNOSTIC(PreviousDefinition, Note, "Previous definition is here.");
     auto prev_def_id = name_lookup_[name_id].back();
     auto prev_def = semantics_ir_->GetNode(prev_def_id);
-
-    emitter_->Build(name_node, NameRedefined, semantics_ir_->GetString(name_id))
-        .Note(prev_def.parse_node(), PreviousDefinition)
+    emitter_->Build(name_node, NameDeclarationDuplicate)
+        .Note(prev_def.parse_node(), NameDeclarationPrevious)
         .Emit();
   }
 }
 
-auto SemanticsContext::LookupName(ParseTree::Node parse_node,
-                                  llvm::StringRef name) -> SemanticsNodeId {
-  CARBON_DIAGNOSTIC(NameNotFound, Error, "Name {0} not found", llvm::StringRef);
+auto SemanticsContext::AddNameToLookup(DeclarationNameContext name_context,
+                                       SemanticsNodeId target_id) -> void {
+  switch (name_context.state) {
+    case DeclarationNameContext::State::Error:
+      // The name is invalid and a diagnostic has already been emitted.
+      return;
+
+    case DeclarationNameContext::State::New:
+      CARBON_FATAL() << "Name is missing, not expected to call AddNameToLookup "
+                        "(but that may change based on error handling).";
+
+    case DeclarationNameContext::State::Resolved:
+    case DeclarationNameContext::State::ResolvedNonScope: {
+      auto prev_def = semantics_ir_->GetNode(name_context.resolved_node_id);
+      emitter_->Build(name_context.parse_node, NameDeclarationDuplicate)
+          .Note(prev_def.parse_node(), NameDeclarationPrevious)
+          .Emit();
+      return;
+    }
 
-  auto name_id = semantics_ir_->GetStringID(name);
-  if (!name_id) {
-    emitter_->Emit(parse_node, NameNotFound, name);
-    return SemanticsNodeId::BuiltinInvalidType;
+    case DeclarationNameContext::State::Unresolved:
+      if (name_context.target_scope_id == SemanticsNameScopeId::Invalid) {
+        AddNameToLookup(name_context.parse_node,
+                        name_context.unresolved_name_id, target_id);
+      } else {
+        bool success = semantics_ir_->AddNameScopeEntry(
+            name_context.target_scope_id, name_context.unresolved_name_id,
+            target_id);
+        CARBON_CHECK(success)
+            << "Duplicate names should have been resolved previously: "
+            << name_context.unresolved_name_id << " in "
+            << name_context.target_scope_id;
+      }
+      return;
   }
+}
 
-  auto it = name_lookup_.find(*name_id);
-  if (it == name_lookup_.end()) {
-    emitter_->Emit(parse_node, NameNotFound, name);
-    return SemanticsNodeId::BuiltinInvalidType;
-  }
-  CARBON_CHECK(!it->second.empty()) << "Should have been erased: " << name;
+auto SemanticsContext::LookupName(ParseTree::Node parse_node,
+                                  SemanticsStringId name_id,
+                                  SemanticsNameScopeId scope_id,
+                                  bool print_diagnostics) -> SemanticsNodeId {
+  if (scope_id == SemanticsNameScopeId::Invalid) {
+    auto it = name_lookup_.find(name_id);
+    if (it == name_lookup_.end()) {
+      if (print_diagnostics) {
+        emitter_->Emit(parse_node, NameNotFound,
+                       semantics_ir_->GetString(name_id));
+      }
+      return SemanticsNodeId::BuiltinInvalidType;
+    }
+    CARBON_CHECK(!it->second.empty())
+        << "Should have been erased: " << semantics_ir_->GetString(name_id);
 
-  // TODO: Check for ambiguous lookups.
-  return it->second.back();
+    // TODO: Check for ambiguous lookups.
+    return it->second.back();
+  } else {
+    const auto& scope = semantics_ir_->GetNameScope(scope_id);
+    auto it = scope.find(name_id);
+    if (it == scope.end()) {
+      if (print_diagnostics) {
+        emitter_->Emit(parse_node, NameNotFound,
+                       semantics_ir_->GetString(name_id));
+      }
+      return SemanticsNodeId::BuiltinInvalidType;
+    }
+
+    return it->second;
+  }
 }
 
 auto SemanticsContext::PushScope() -> void { scope_stack_.push_back({}); }
@@ -244,6 +295,105 @@ auto SemanticsContext::is_current_position_reachable() -> bool {
   }
 }
 
+auto SemanticsContext::PushDeclarationName() -> void {
+  declaration_name_stack_.push_back(
+      {.state = DeclarationNameContext::State::New,
+       .target_scope_id = SemanticsNameScopeId::Invalid,
+       .resolved_node_id = SemanticsNodeId::Invalid});
+}
+
+auto SemanticsContext::PopDeclarationName() -> DeclarationNameContext {
+  if (parse_tree_->node_kind(node_stack().PeekParseNode()) ==
+      ParseNodeKind::QualifiedDeclaration) {
+    // Any parts from a QualifiedDeclaration will already have been processed
+    // into the name.
+    node_stack_.PopAndDiscardSoloParseNode(ParseNodeKind::QualifiedDeclaration);
+  } else {
+    // The name had no qualifiers, so we need to process the node now.
+    auto [parse_node, node_or_name_id] =
+        node_stack_.PopWithParseNode<SemanticsNodeId>();
+    ApplyDeclarationNameQualifier(parse_node, node_or_name_id);
+  }
+
+  return declaration_name_stack_.pop_back_val();
+}
+
+auto SemanticsContext::ApplyDeclarationNameQualifier(
+    ParseTree::Node parse_node, SemanticsNodeId node_or_name_id) -> void {
+  auto& name_context = declaration_name_stack_.back();
+  switch (name_context.state) {
+    case DeclarationNameContext::State::Error:
+      // Already in an error state, so return without examining.
+      return;
+
+    case DeclarationNameContext::State::Unresolved:
+      // Because more qualifiers were found, we diagnose that the earlier
+      // qualifier failed to resolve.
+      name_context.state = DeclarationNameContext::State::Error;
+      emitter_->Emit(name_context.parse_node, NameNotFound,
+                     semantics_ir_->GetString(name_context.unresolved_name_id));
+      return;
+
+    case DeclarationNameContext::State::ResolvedNonScope: {
+      // Because more qualifiers were found, we diagnose that the earlier
+      // qualifier didn't resolve to a scoped entity.
+      name_context.state = DeclarationNameContext::State::Error;
+      CARBON_DIAGNOSTIC(QualifiedDeclarationInNonScope, Error,
+                        "Declaration qualifiers are only allowed for entities "
+                        "that provide a scope.");
+      CARBON_DIAGNOSTIC(QualifiedDeclarationNonScopeEntity, Note,
+                        "Non-scope entity referenced here.");
+      emitter_->Build(parse_node, QualifiedDeclarationInNonScope)
+          .Note(name_context.parse_node, QualifiedDeclarationNonScopeEntity)
+          .Emit();
+      return;
+    }
+
+    case DeclarationNameContext::State::New:
+    case DeclarationNameContext::State::Resolved: {
+      name_context.parse_node = parse_node;
+      if (parse_tree().node_kind(name_context.parse_node) ==
+          ParseNodeKind::Name) {
+        // For identifier nodes, we need to perform a lookup on the identifier.
+        // This means the input node_id is actually a string ID.
+        SemanticsStringId name_id(node_or_name_id.index);
+        auto resolved_node_id = LookupName(name_context.parse_node, name_id,
+                                           name_context.target_scope_id,
+                                           /*print_diagnostics=*/false);
+        if (resolved_node_id == SemanticsNodeId::BuiltinInvalidType) {
+          // Invalid indicates an unresolved node. Store it and return.
+          name_context.state = DeclarationNameContext::State::Unresolved;
+          name_context.unresolved_name_id = name_id;
+          return;
+        } else {
+          // Store the resolved node and continue for the target scope update.
+          name_context.resolved_node_id = resolved_node_id;
+        }
+      } else {
+        // For other nodes, we expect a regular resolved node, for example a
+        // namespace or generic type. Store it and continue for the target scope
+        // update.
+        name_context.resolved_node_id = node_or_name_id;
+      }
+
+      // This will only be reached for resolved nodes. We update the target
+      // scope based on the resolved type.
+      auto resolved_node =
+          semantics_ir_->GetNode(name_context.resolved_node_id);
+      switch (resolved_node.kind()) {
+        case SemanticsNodeKind::Namespace:
+          name_context.state = DeclarationNameContext::State::Resolved;
+          name_context.target_scope_id = resolved_node.GetAsNamespace();
+          break;
+        default:
+          name_context.state = DeclarationNameContext::State::ResolvedNonScope;
+          break;
+      }
+      return;
+    }
+  }
+}
+
 auto SemanticsContext::ImplicitAsForArgs(
     SemanticsNodeBlockId arg_refs_id, ParseTree::Node param_parse_node,
     SemanticsNodeBlockId param_refs_id,

+ 103 - 2
toolchain/semantics/semantics_context.h

@@ -20,6 +20,83 @@ namespace Carbon {
 // Context and shared functionality for semantics handlers.
 class SemanticsContext {
  public:
+  // Context for declaration name construction, tracked in
+  // declaration_name_stack_.
+  //
+  // A qualified declaration name will consist of entries which are either
+  // Identifiers or full expressions. Expressions are expected to resolve to
+  // types, such as how `fn Vector(i32).Clear() { ... }` uses the expression
+  // `Vector(i32)` to indicate the type whose member is being declared.
+  // Identifiers such as `Clear` will be resolved to a name if possible, for
+  // example when declaring things that are in a non-generic type or namespace,
+  // and are otherwise marked as an unresolved identifier.
+  //
+  // Unresolved identifiers are valid if and only if they are the last step of a
+  // qualified name; all resolved qualifiers must resolve to an entity with
+  // members, such as a namespace. Resolved identifiers in the last step will
+  // occur for both out-of-line definitions and new declarations, depending on
+  // context.
+  //
+  // Example state transitions:
+  //
+  // ```
+  // // New -> Unresolved, because `MyNamespace` is newly declared.
+  // namespace MyNamespace;
+  //
+  // // New -> Resolved -> Unresolved, because `MyType` is newly declared.
+  // class MyNamespace.MyType;
+  //
+  // // New -> Resolved -> Resolved, because `MyType` was forward declared.
+  // class MyNamespace.MyType {
+  //   // New -> Unresolved, because `DoSomething` is newly declared.
+  //   fn DoSomething();
+  // }
+  //
+  // // New -> Resolved -> Resolved -> ResolvedNonScope, because `DoSomething`
+  // // is forward declared in `MyType`, but is not a scope itself.
+  // fn MyNamespace.MyType.DoSomething() { ... }
+  // ```
+  struct DeclarationNameContext {
+    enum class State {
+      // A new context which has not processed any parts of the qualifier.
+      New,
+
+      // A node ID has been resolved, whether through an identifier or
+      // expression. This provided a new scope, such as a type.
+      Resolved,
+
+      // A node ID has been resolved, whether through an identifier or
+      // expression. It did not provide a new scope, so must be the final part,
+      // such as an out-of-line function definition.
+      ResolvedNonScope,
+
+      // An identifier didn't resolve.
+      Unresolved,
+
+      // An error has occurred, such as an additional qualifier past an
+      // unresolved name. No new diagnostics should be emitted.
+      Error,
+    };
+
+    State state = State::New;
+
+    // The scope which qualified names are added to. For unqualified names,
+    // this will be Invalid to indicate the current scope should be used.
+    SemanticsNameScopeId target_scope_id = SemanticsNameScopeId::Invalid;
+
+    // The last parse node used.
+    ParseTree::Node parse_node = ParseTree::Node::Invalid;
+
+    union {
+      // The ID of a resolved qualifier, including both identifiers and
+      // expressions. Invalid indicates resolution failed.
+      SemanticsNodeId resolved_node_id = SemanticsNodeId::Invalid;
+
+      // The ID of an unresolved identifier.
+      SemanticsStringId unresolved_name_id;
+    };
+  };
+
   // Stores references for work.
   explicit SemanticsContext(const TokenizedBuffer& tokens,
                             DiagnosticEmitter<ParseTree::Node>& emitter,
@@ -47,8 +124,14 @@ class SemanticsContext {
   auto AddNameToLookup(ParseTree::Node name_node, SemanticsStringId name_id,
                        SemanticsNodeId target_id) -> void;
 
-  // Lookup up a name, returning the referenced node.
-  auto LookupName(ParseTree::Node parse_node, llvm::StringRef name)
+  // Adds a name to name lookup. Prints a diagnostic for name conflicts.
+  auto AddNameToLookup(DeclarationNameContext name_context,
+                       SemanticsNodeId target_id) -> void;
+
+  // Performs name lookup in a specified scope, returning the referenced node.
+  // If scope_id is invalid, uses the current contextual scope.
+  auto LookupName(ParseTree::Node parse_node, SemanticsStringId name_id,
+                  SemanticsNameScopeId scope_id, bool print_diagnostics)
       -> SemanticsNodeId;
 
   // Pushes a new scope onto scope_stack_.
@@ -99,6 +182,20 @@ class SemanticsContext {
   // Returns whether the current position in the current block is reachable.
   auto is_current_position_reachable() -> bool;
 
+  // Pushes processing of a new declaration name, which will be used
+  // contextually.
+  auto PushDeclarationName() -> 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
+  // node stack, which will be applied to the declaration name if appropriate.
+  auto PopDeclarationName() -> DeclarationNameContext;
+
+  // Applies an entry from the node stack to the top of the declaration name
+  // stack.
+  auto ApplyDeclarationNameQualifier(ParseTree::Node parse_node,
+                                     SemanticsNodeId node_or_name_id) -> void;
+
   // Runs ImplicitAsImpl for a set of arguments and parameters.
   //
   // This will eventually need to support checking against multiple possible
@@ -275,6 +372,10 @@ class SemanticsContext {
   // A stack for scope context.
   llvm::SmallVector<ScopeStackEntry> scope_stack_;
 
+  // A stack for declaration name context. See DeclarationNameContext for
+  // details.
+  llvm::SmallVector<DeclarationNameContext> 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

+ 90 - 52
toolchain/semantics/semantics_handle.cpp

@@ -23,56 +23,6 @@ auto SemanticsHandleDeducedParameterListStart(SemanticsContext& context,
   return context.TODO(parse_node, "HandleDeducedParameterListStart");
 }
 
-auto SemanticsHandleDesignatorExpression(SemanticsContext& context,
-                                         ParseTree::Node parse_node) -> bool {
-  auto name_id =
-      context.node_stack().Pop<SemanticsStringId>(ParseNodeKind::Name);
-
-  auto base_id = context.node_stack().Pop<SemanticsNodeId>();
-  auto base = context.semantics_ir().GetNode(base_id);
-  auto base_type = context.semantics_ir().GetNode(
-      context.semantics_ir().GetType(base.type_id()));
-
-  switch (base_type.kind()) {
-    case SemanticsNodeKind::StructType: {
-      auto refs =
-          context.semantics_ir().GetNodeBlock(base_type.GetAsStructType());
-      // TODO: Do we need to optimize this with a lookup table for O(1)?
-      for (int i = 0; i < static_cast<int>(refs.size()); ++i) {
-        auto ref = context.semantics_ir().GetNode(refs[i]);
-        if (name_id == ref.GetAsStructTypeField()) {
-          context.AddNodeAndPush(
-              parse_node,
-              SemanticsNode::StructMemberAccess::Make(
-                  parse_node, ref.type_id(), base_id, SemanticsMemberIndex(i)));
-          return true;
-        }
-      }
-      CARBON_DIAGNOSTIC(DesignatorExpressionNameNotFound, Error,
-                        "Type `{0}` does not have a member `{1}`.", std::string,
-                        llvm::StringRef);
-      context.emitter().Emit(
-          parse_node, DesignatorExpressionNameNotFound,
-          context.semantics_ir().StringifyType(base.type_id()),
-          context.semantics_ir().GetString(name_id));
-      break;
-    }
-    default: {
-      CARBON_DIAGNOSTIC(DesignatorExpressionUnsupported, Error,
-                        "Type `{0}` does not support designator expressions.",
-                        std::string);
-      context.emitter().Emit(
-          parse_node, DesignatorExpressionUnsupported,
-          context.semantics_ir().StringifyType(base.type_id()));
-      break;
-    }
-  }
-
-  // Should only be reached on error.
-  context.node_stack().Push(parse_node, SemanticsNodeId::BuiltinInvalidType);
-  return true;
-}
-
 auto SemanticsHandleEmptyDeclaration(SemanticsContext& /*context*/,
                                      ParseTree::Node /*parse_node*/) -> bool {
   // Empty declarations have no actions associated.
@@ -233,6 +183,65 @@ auto SemanticsHandleLiteral(SemanticsContext& context,
   return true;
 }
 
+auto SemanticsHandleMemberAccessExpression(SemanticsContext& context,
+                                           ParseTree::Node parse_node) -> bool {
+  auto name_id =
+      context.node_stack().Pop<SemanticsStringId>(ParseNodeKind::Name);
+
+  auto base_id = context.node_stack().Pop<SemanticsNodeId>();
+  auto base = context.semantics_ir().GetNode(base_id);
+  if (base.kind() == SemanticsNodeKind::Namespace) {
+    // For a namespace, just resolve the name.
+    auto node_id =
+        context.LookupName(parse_node, name_id, base.GetAsNamespace(),
+                           /*print_diagnostics=*/true);
+    context.node_stack().Push(parse_node, node_id);
+    return true;
+  }
+
+  auto base_type = context.semantics_ir().GetNode(
+      context.semantics_ir().GetType(base.type_id()));
+
+  switch (base_type.kind()) {
+    case SemanticsNodeKind::StructType: {
+      auto refs =
+          context.semantics_ir().GetNodeBlock(base_type.GetAsStructType());
+      // TODO: Do we need to optimize this with a lookup table for O(1)?
+      for (int i = 0; i < static_cast<int>(refs.size()); ++i) {
+        auto ref = context.semantics_ir().GetNode(refs[i]);
+        if (name_id == ref.GetAsStructTypeField()) {
+          context.AddNodeAndPush(
+              parse_node,
+              SemanticsNode::StructMemberAccess::Make(
+                  parse_node, ref.type_id(), base_id, SemanticsMemberIndex(i)));
+          return true;
+        }
+      }
+      CARBON_DIAGNOSTIC(QualifiedExpressionNameNotFound, Error,
+                        "Type `{0}` does not have a member `{1}`.", std::string,
+                        llvm::StringRef);
+      context.emitter().Emit(
+          parse_node, QualifiedExpressionNameNotFound,
+          context.semantics_ir().StringifyType(base.type_id()),
+          context.semantics_ir().GetString(name_id));
+      break;
+    }
+    default: {
+      CARBON_DIAGNOSTIC(QualifiedExpressionUnsupported, Error,
+                        "Type `{0}` does not support qualified expressions.",
+                        std::string);
+      context.emitter().Emit(
+          parse_node, QualifiedExpressionUnsupported,
+          context.semantics_ir().StringifyType(base.type_id()));
+      break;
+    }
+  }
+
+  // Should only be reached on error.
+  context.node_stack().Push(parse_node, SemanticsNodeId::BuiltinInvalidType);
+  return true;
+}
+
 auto SemanticsHandleName(SemanticsContext& context, ParseTree::Node parse_node)
     -> bool {
   auto name_str = context.parse_tree().GetNodeText(parse_node);
@@ -244,8 +253,12 @@ auto SemanticsHandleName(SemanticsContext& context, ParseTree::Node parse_node)
 
 auto SemanticsHandleNameExpression(SemanticsContext& context,
                                    ParseTree::Node parse_node) -> bool {
-  auto name = context.parse_tree().GetNodeText(parse_node);
-  context.node_stack().Push(parse_node, context.LookupName(parse_node, name));
+  auto name_str = context.parse_tree().GetNodeText(parse_node);
+  auto name_id = context.semantics_ir().AddString(name_str);
+  context.node_stack().Push(
+      parse_node,
+      context.LookupName(parse_node, name_id, SemanticsNameScopeId::Invalid,
+                         /*print_diagnostics=*/true));
   return true;
 }
 
@@ -372,6 +385,31 @@ auto SemanticsHandlePrefixOperator(SemanticsContext& context,
   return true;
 }
 
+auto SemanticsHandleQualifiedDeclaration(SemanticsContext& context,
+                                         ParseTree::Node parse_node) -> bool {
+  // The first two qualifiers in a chain will be a QualifiedDeclaration with two
+  // Identifier or expression children. Later qualifiers will have a
+  // QualifiedDeclaration as the first child, and an Identifier or expression as
+  // the second child.
+  auto [parse_node2, node_or_name_id2] =
+      context.node_stack().PopWithParseNode<SemanticsNodeId>();
+  if (context.parse_tree().node_kind(context.node_stack().PeekParseNode()) !=
+      ParseNodeKind::QualifiedDeclaration) {
+    // First QualifiedDeclaration in a chain.
+    auto [parse_node1, node_or_name_id1] =
+        context.node_stack().PopWithParseNode<SemanticsNodeId>();
+    context.ApplyDeclarationNameQualifier(parse_node1, node_or_name_id1);
+    // Add the QualifiedDeclaration so that it can be used for bracketing.
+    context.node_stack().Push(parse_node);
+  } else {
+    // Nothing to do: the QualifiedDeclaration remains as a bracketing node for
+    // later QualifiedDeclarations.
+  }
+  context.ApplyDeclarationNameQualifier(parse_node2, node_or_name_id2);
+
+  return true;
+}
+
 auto SemanticsHandleReturnType(SemanticsContext& context,
                                ParseTree::Node parse_node) -> bool {
   // Propagate the type expression.

+ 1 - 2
toolchain/semantics/semantics_handle_call_expression.cpp

@@ -57,8 +57,7 @@ auto SemanticsHandleCallExpressionComma(SemanticsContext& context,
 
 auto SemanticsHandleCallExpressionStart(SemanticsContext& context,
                                         ParseTree::Node parse_node) -> bool {
-  auto name_id =
-      context.node_stack().Pop<SemanticsNodeId>(ParseNodeKind::NameExpression);
+  auto name_id = context.node_stack().Pop<SemanticsNodeId>();
   context.node_stack().Push(parse_node, name_id);
   context.ParamOrArgStart();
   return true;

+ 9 - 6
toolchain/semantics/semantics_handle_function.cpp

@@ -51,9 +51,7 @@ auto SemanticsHandleFunctionDefinitionStart(SemanticsContext& context,
   }
   auto param_refs_id = context.node_stack().Pop<SemanticsNodeBlockId>(
       ParseNodeKind::ParameterList);
-  auto [name_node, name_id] =
-      context.node_stack().PopWithParseNode<SemanticsStringId>(
-          ParseNodeKind::Name);
+  auto name_context = context.PopDeclarationName();
   auto fn_node = context.node_stack().PopForSoloParseNode(
       ParseNodeKind::FunctionIntroducer);
 
@@ -61,16 +59,19 @@ auto SemanticsHandleFunctionDefinitionStart(SemanticsContext& context,
   auto outer_block = context.node_block_stack().PeekForAdd();
   context.node_block_stack().Push();
 
+  // TODO: Support out-of-line definitions, which will have a resolved
+  // name_context. Right now, those become errors in AddNameToLookup.
+
   // Add the callable.
   auto function_id = context.semantics_ir().AddFunction(
-      {.name_id = name_id,
+      {.name_id = name_context.unresolved_name_id,
        .param_refs_id = param_refs_id,
        .return_type_id = return_type_id,
        .body_block_ids = {context.node_block_stack().PeekForAdd()}});
   auto decl_id = context.AddNodeToBlock(
       outer_block,
       SemanticsNode::FunctionDeclaration::Make(fn_node, function_id));
-  context.AddNameToLookup(name_node, name_id, decl_id);
+  context.AddNameToLookup(name_context, decl_id);
 
   context.PushScope();
   for (auto ref_id : context.semantics_ir().GetNodeBlock(param_refs_id)) {
@@ -86,8 +87,10 @@ auto SemanticsHandleFunctionDefinitionStart(SemanticsContext& context,
 
 auto SemanticsHandleFunctionIntroducer(SemanticsContext& context,
                                        ParseTree::Node parse_node) -> bool {
-  // No action, just a bracketing node.
+  // Push the bracketing node.
   context.node_stack().Push(parse_node);
+  // A name should always follow.
+  context.PushDeclarationName();
   return true;
 }
 

+ 25 - 0
toolchain/semantics/semantics_handle_namespace.cpp

@@ -0,0 +1,25 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "toolchain/semantics/semantics_context.h"
+#include "toolchain/semantics/semantics_node.h"
+
+namespace Carbon {
+
+auto SemanticsHandleNamespaceStart(SemanticsContext& context,
+                                   ParseTree::Node /*parse_node*/) -> bool {
+  context.PushDeclarationName();
+  return true;
+}
+
+auto SemanticsHandleNamespace(SemanticsContext& context,
+                              ParseTree::Node parse_node) -> bool {
+  auto name_context = context.PopDeclarationName();
+  auto namespace_id = context.AddNode(SemanticsNode::Namespace::Make(
+      parse_node, context.semantics_ir().AddNameScope()));
+  context.AddNameToLookup(name_context, namespace_id);
+  return true;
+}
+
+}  // namespace Carbon

+ 1 - 0
toolchain/semantics/semantics_ir.cpp

@@ -254,6 +254,7 @@ auto SemanticsIR::StringifyType(SemanticsTypeId type_id) -> std::string {
       case SemanticsNodeKind::CrossReference:
       case SemanticsNodeKind::FunctionDeclaration:
       case SemanticsNodeKind::IntegerLiteral:
+      case SemanticsNodeKind::Namespace:
       case SemanticsNodeKind::RealLiteral:
       case SemanticsNodeKind::Return:
       case SemanticsNodeKind::ReturnExpression:

+ 25 - 0
toolchain/semantics/semantics_ir.h

@@ -114,6 +114,27 @@ class SemanticsIR {
     return integer_literals_[int_id.index];
   }
 
+  // Adds a name scope, returning an ID to reference it.
+  auto AddNameScope() -> SemanticsNameScopeId {
+    SemanticsNameScopeId name_scopes_id(name_scopes_.size());
+    name_scopes_.resize(name_scopes_id.index + 1);
+    return name_scopes_id;
+  }
+
+  // Adds an entry to a name scope. Returns true on success, false on
+  // duplicates.
+  auto AddNameScopeEntry(SemanticsNameScopeId scope_id,
+                         SemanticsStringId name_id, SemanticsNodeId target_id)
+      -> bool {
+    return name_scopes_[scope_id.index].insert({name_id, target_id}).second;
+  }
+
+  // Returns the requested name scope.
+  auto GetNameScope(SemanticsNameScopeId scope_id)
+      -> const llvm::DenseMap<SemanticsStringId, SemanticsNodeId>& {
+    return name_scopes_[scope_id.index];
+  }
+
   // Adds a node to a specified block, returning an ID to reference the node.
   auto AddNode(SemanticsNodeBlockId block_id, SemanticsNode node)
       -> SemanticsNodeId {
@@ -271,6 +292,10 @@ class SemanticsIR {
   // Storage for integer literals.
   llvm::SmallVector<llvm::APInt> integer_literals_;
 
+  // Storage for name scopes.
+  llvm::SmallVector<llvm::DenseMap<SemanticsStringId, SemanticsNodeId>>
+      name_scopes_;
+
   // Storage for real literals.
   llvm::SmallVector<SemanticsRealLiteral> real_literals_;
 

+ 18 - 0
toolchain/semantics/semantics_node.h

@@ -101,6 +101,21 @@ struct SemanticsIntegerLiteralId : public IndexBase {
   }
 };
 
+// The ID of a name scope.
+struct SemanticsNameScopeId : public IndexBase {
+  // An explicitly invalid ID.
+  static const SemanticsNameScopeId Invalid;
+
+  using IndexBase::IndexBase;
+  auto Print(llvm::raw_ostream& out) const -> void {
+    out << "name_scope";
+    IndexBase::Print(out);
+  }
+};
+
+constexpr SemanticsNameScopeId SemanticsNameScopeId::Invalid =
+    SemanticsNameScopeId(SemanticsNameScopeId::InvalidIndex);
+
 // The ID of a node block.
 struct SemanticsNodeBlockId : public IndexBase {
   // All SemanticsIR instances must provide the 0th node block as empty.
@@ -355,6 +370,9 @@ class SemanticsNode {
   using IntegerLiteral = Factory<SemanticsNodeKind::IntegerLiteral,
                                  SemanticsIntegerLiteralId /*integer_id*/>;
 
+  using Namespace = FactoryNoType<SemanticsNodeKind::Namespace,
+                                  SemanticsNameScopeId /*name_scope_id*/>;
+
   using RealLiteral = Factory<SemanticsNodeKind::RealLiteral,
                               SemanticsRealLiteralId /*real_id*/>;
 

+ 1 - 0
toolchain/semantics/semantics_node_kind.def

@@ -39,6 +39,7 @@ CARBON_SEMANTICS_NODE_KIND(Builtin)
 CARBON_SEMANTICS_NODE_KIND(Call)
 CARBON_SEMANTICS_NODE_KIND(FunctionDeclaration)
 CARBON_SEMANTICS_NODE_KIND(IntegerLiteral)
+CARBON_SEMANTICS_NODE_KIND(Namespace)
 CARBON_SEMANTICS_NODE_KIND(RealLiteral)
 CARBON_SEMANTICS_NODE_KIND_WITH_TERMINATOR_KIND(Return, Terminator)
 CARBON_SEMANTICS_NODE_KIND_WITH_TERMINATOR_KIND(ReturnExpression, Terminator)

+ 1 - 0
toolchain/semantics/testdata/basics/fail_name_lookup.carbon

@@ -13,6 +13,7 @@
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: strings: [
 // CHECK:STDOUT:   Main,
+// CHECK:STDOUT:   x,
 // CHECK:STDOUT: ]
 // CHECK:STDOUT: types: [
 // CHECK:STDOUT:   nodeEmptyTupleType,

+ 1 - 1
toolchain/semantics/testdata/designators/fail_unsupported.carbon → toolchain/semantics/testdata/basics/fail_qualifier_unsupported.carbon

@@ -38,5 +38,5 @@
 // CHECK:STDOUT: ]
 
 var x: i32;
-// CHECK:STDERR: fail_unsupported.carbon:[[@LINE+1]]:15: Type `i32` does not support designator expressions.
+// CHECK:STDERR: fail_qualifier_unsupported.carbon:[[@LINE+1]]:15: Type `i32` does not support qualified expressions.
 var y: i32 = x.b;

+ 2 - 2
toolchain/semantics/testdata/function/definition/fail_param_name_conflict.carbon

@@ -48,6 +48,6 @@
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]
 
-// CHECK:STDERR: fail_param_name_conflict.carbon:[[@LINE+2]]:16: Redefining a in the same scope.
-// CHECK:STDERR: fail_param_name_conflict.carbon:[[@LINE+1]]:8: Previous definition is here.
+// CHECK:STDERR: fail_param_name_conflict.carbon:[[@LINE+2]]:16: Duplicate name being declared in the same scope.
+// CHECK:STDERR: fail_param_name_conflict.carbon:[[@LINE+1]]:8: Name is previously declared here.
 fn Bar(a: i32, a: i32) {}

+ 53 - 0
toolchain/semantics/testdata/namespace/fail_duplicate.carbon

@@ -0,0 +1,53 @@
+// 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
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str1, param_refs: block0, body: {block2}}},
+// CHECK:STDOUT:   {name: str8, param_refs: block0, body: {block3}}},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   Foo,
+// CHECK:STDOUT:   Baz,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeEmptyTupleType,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: Namespace, arg0: name_scope0},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: Return},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
+// CHECK:STDOUT:   {kind: Return},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+namespace Foo;
+
+fn Foo.Baz() {
+}
+
+// CHECK:STDERR: fail_duplicate.carbon:[[@LINE+2]]:8: Duplicate name being declared in the same scope.
+// CHECK:STDERR: fail_duplicate.carbon:[[@LINE-4]]:1: Name is previously declared here.
+fn Foo.Baz() {
+}

+ 38 - 0
toolchain/semantics/testdata/namespace/fail_unresolved_scope.carbon

@@ -0,0 +1,38 @@
+// 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
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str0, param_refs: block0, body: {block2}}},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   Foo,
+// CHECK:STDOUT:   Baz,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeEmptyTupleType,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: Return},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+// CHECK:STDERR: fail_unresolved_scope.carbon:[[@LINE+1]]:4: Name Foo not found
+fn Foo.Baz() {
+}

+ 66 - 0
toolchain/semantics/testdata/namespace/function.carbon

@@ -0,0 +1,66 @@
+// 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
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str1, param_refs: block0, body: {block2}}},
+// CHECK:STDOUT:   {name: str1, param_refs: block0, body: {block3}}},
+// CHECK:STDOUT:   {name: str2, param_refs: block0, body: {block4}}},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   Foo,
+// CHECK:STDOUT:   Baz,
+// CHECK:STDOUT:   Bar,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeEmptyTupleType,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: Namespace, arg0: name_scope0},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: Return},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
+// CHECK:STDOUT:   {kind: Return},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function2},
+// CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function1},
+// CHECK:STDOUT:   {kind: Return},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+namespace Foo;
+
+// Never called, just here to help catch bugs in name lookup.
+fn Baz() {
+}
+
+fn Foo.Baz() {
+}
+
+fn Bar() {
+  Foo.Baz();
+}

+ 59 - 0
toolchain/semantics/testdata/namespace/nested.carbon

@@ -0,0 +1,59 @@
+// 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
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str2, param_refs: block0, body: {block2}}},
+// CHECK:STDOUT:   {name: str3, param_refs: block0, body: {block3}}},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   Foo,
+// CHECK:STDOUT:   Bar,
+// CHECK:STDOUT:   Wiz,
+// CHECK:STDOUT:   Baz,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeEmptyTupleType,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: Namespace, arg0: name_scope0},
+// CHECK:STDOUT:   {kind: Namespace, arg0: name_scope1},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: Return},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
+// CHECK:STDOUT:   {kind: Call, arg0: block0, arg1: function0},
+// CHECK:STDOUT:   {kind: Return},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+namespace Foo;
+namespace Foo.Bar;
+
+fn Foo.Bar.Wiz() {
+}
+
+fn Foo.Bar.Baz() {
+  Foo.Bar.Wiz();
+}

+ 2 - 2
toolchain/semantics/testdata/var/fail_duplicate_decl.carbon

@@ -55,7 +55,7 @@
 
 fn Main() {
   var x: i32 = 0;
-  // CHECK:STDERR: fail_duplicate_decl.carbon:[[@LINE+2]]:7: Redefining x in the same scope.
-  // CHECK:STDERR: fail_duplicate_decl.carbon:[[@LINE-2]]:7: Previous definition is here.
+  // CHECK:STDERR: fail_duplicate_decl.carbon:[[@LINE+2]]:7: Duplicate name being declared in the same scope.
+  // CHECK:STDERR: fail_duplicate_decl.carbon:[[@LINE-2]]:7: Name is previously declared here.
   var x: i32 = 0;
 }