Преглед изворни кода

Add parse support for 'import', brush up 'package' a little. (#3347)

This detects ordering issues with the `package` and `import` statements.
`library` is changed from package-specific to instead be generic between
the two, since structurally it's non-specific.

The next step would be to start exposing the results for the driver to
make ordering decisions for checking. That'll involve further
modifications to this code, but this felt like a reasonable change point
because it's the extent of the parser enforcement, and still causes
significant refactoring.

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Jon Ross-Perkins пре 2 година
родитељ
комит
c9458fe30a
39 измењених фајлова са 703 додато и 148 уклоњено
  1. 16 8
      toolchain/check/handle_import_and_package.cpp
  2. 5 1
      toolchain/diagnostics/diagnostic_kind.def
  3. 7 2
      toolchain/lex/tokenized_buffer.h
  4. 29 0
      toolchain/parse/context.h
  5. 58 33
      toolchain/parse/handle_declaration_scope_loop.cpp
  6. 163 0
      toolchain/parse/handle_import_and_package.cpp
  7. 0 90
      toolchain/parse/handle_package.cpp
  8. 16 3
      toolchain/parse/node_kind.def
  9. 6 0
      toolchain/parse/state.def
  10. 20 0
      toolchain/parse/testdata/packages/import/after_import.carbon
  11. 22 0
      toolchain/parse/testdata/packages/import/after_package.carbon
  12. 16 0
      toolchain/parse/testdata/packages/import/basic.carbon
  13. 28 0
      toolchain/parse/testdata/packages/import/fail_after_decl.carbon
  14. 63 0
      toolchain/parse/testdata/packages/import/fail_after_decl_repeated.carbon
  15. 21 0
      toolchain/parse/testdata/packages/import/fail_extra_string.carbon
  16. 19 0
      toolchain/parse/testdata/packages/import/fail_library_is_identifier.carbon
  17. 18 0
      toolchain/parse/testdata/packages/import/fail_library_skips_name.carbon
  18. 18 0
      toolchain/parse/testdata/packages/import/fail_name_is_keyword.carbon
  19. 18 0
      toolchain/parse/testdata/packages/import/fail_no_name.carbon
  20. 19 0
      toolchain/parse/testdata/packages/import/fail_no_semi.carbon
  21. 19 0
      toolchain/parse/testdata/packages/import/fail_omit_library_keyword.carbon
  22. 19 0
      toolchain/parse/testdata/packages/import/fail_type.carbon
  23. 18 0
      toolchain/parse/testdata/packages/import/library.carbon
  24. 0 0
      toolchain/parse/testdata/packages/package/api.carbon
  25. 1 1
      toolchain/parse/testdata/packages/package/api_library.carbon
  26. 28 0
      toolchain/parse/testdata/packages/package/fail_after_decl.carbon
  27. 26 0
      toolchain/parse/testdata/packages/package/fail_after_import.carbon
  28. 26 0
      toolchain/parse/testdata/packages/package/fail_after_package.carbon
  29. 2 2
      toolchain/parse/testdata/packages/package/fail_extra_string.carbon
  30. 0 0
      toolchain/parse/testdata/packages/package/fail_library_is_identifier.carbon
  31. 0 0
      toolchain/parse/testdata/packages/package/fail_library_skips_name.carbon
  32. 0 0
      toolchain/parse/testdata/packages/package/fail_name_is_keyword.carbon
  33. 0 0
      toolchain/parse/testdata/packages/package/fail_no_name.carbon
  34. 0 0
      toolchain/parse/testdata/packages/package/fail_no_semi.carbon
  35. 1 1
      toolchain/parse/testdata/packages/package/fail_no_type.carbon
  36. 0 0
      toolchain/parse/testdata/packages/package/fail_omit_library_keyword.carbon
  37. 0 0
      toolchain/parse/testdata/packages/package/impl.carbon
  38. 1 1
      toolchain/parse/testdata/packages/package/impl_library.carbon
  39. 0 6
      toolchain/parse/tree.cpp

+ 16 - 8
toolchain/check/handle_package.cpp → toolchain/check/handle_import_and_package.cpp

@@ -6,24 +6,32 @@
 
 namespace Carbon::Check {
 
-auto HandlePackageApi(Context& context, Parse::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandlePackageApi");
+auto HandleImportIntroducer(Context& context, Parse::Node parse_node) -> bool {
+  return context.TODO(parse_node, "HandleImportIntroducer");
 }
 
-auto HandlePackageDirective(Context& context, Parse::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandlePackageDirective");
+auto HandlePackageIntroducer(Context& context, Parse::Node parse_node) -> bool {
+  return context.TODO(parse_node, "HandlePackageIntroducer");
+}
+
+auto HandleLibrary(Context& context, Parse::Node parse_node) -> bool {
+  return context.TODO(parse_node, "HandleLibrary");
+}
+
+auto HandlePackageApi(Context& context, Parse::Node parse_node) -> bool {
+  return context.TODO(parse_node, "HandlePackageApi");
 }
 
 auto HandlePackageImpl(Context& context, Parse::Node parse_node) -> bool {
   return context.TODO(parse_node, "HandlePackageImpl");
 }
 
-auto HandlePackageIntroducer(Context& context, Parse::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandlePackageIntroducer");
+auto HandleImportDirective(Context& context, Parse::Node parse_node) -> bool {
+  return context.TODO(parse_node, "HandleImportDirective");
 }
 
-auto HandlePackageLibrary(Context& context, Parse::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandlePackageLibrary");
+auto HandlePackageDirective(Context& context, Parse::Node parse_node) -> bool {
+  return context.TODO(parse_node, "HandlePackageDirective");
 }
 
 }  // namespace Carbon::Check

+ 5 - 1
toolchain/diagnostics/diagnostic_kind.def

@@ -81,7 +81,11 @@ CARBON_DIAGNOSTIC_KIND(UnexpectedTokenAfterListElement)
 CARBON_DIAGNOSTIC_KIND(UnrecognizedDeclaration)
 
 // Package-related diagnostics.
-CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierAfterPackage)
+CARBON_DIAGNOSTIC_KIND(FirstDeclaration)
+CARBON_DIAGNOSTIC_KIND(FirstNonCommentLine)
+CARBON_DIAGNOSTIC_KIND(PackageTooLate)
+CARBON_DIAGNOSTIC_KIND(ImportTooLate)
+CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierAfterKeyword)
 CARBON_DIAGNOSTIC_KIND(ExpectedLibraryName)
 CARBON_DIAGNOSTIC_KIND(MissingLibraryKeyword)
 CARBON_DIAGNOSTIC_KIND(ExpectedApiOrImpl)

+ 7 - 2
toolchain/lex/tokenized_buffer.h

@@ -40,9 +40,15 @@ class TokenizedBuffer;
 //
 // All other APIs to query a `Token` are on the `TokenizedBuffer`.
 struct Token : public ComparableIndexBase {
+  static const Token Invalid;
+  // Comments aren't tokenized, so this is the first token after StartOfFile.
+  static const Token FirstNonCommentToken;
   using ComparableIndexBase::ComparableIndexBase;
 };
 
+constexpr Token Token::Invalid(Token::InvalidIndex);
+constexpr Token Token::FirstNonCommentToken(1);
+
 // A lightweight handle to a lexed line in a `TokenizedBuffer`.
 //
 // `Line` objects are designed to be passed by value, not reference or
@@ -55,9 +61,8 @@ struct Token : public ComparableIndexBase {
 //
 // All other APIs to query a `Line` are on the `TokenizedBuffer`.
 struct Line : public ComparableIndexBase {
-  using ComparableIndexBase::ComparableIndexBase;
-
   static const Line Invalid;
+  using ComparableIndexBase::ComparableIndexBase;
 };
 
 constexpr Line Line::Invalid(Line::InvalidIndex);

+ 29 - 0
toolchain/parse/context.h

@@ -44,6 +44,17 @@ class Context {
     NamedConstraint,
   };
 
+  // Used for restricting ordering of `package` and `import` directives.
+  enum class PackagingState : int8_t {
+    StartOfFile,
+    InImports,
+    AfterNonPackagingDeclaration,
+    // A warning about `import` placement has been issued so we don't keep
+    // issuing more (when `import` is repeated) until more non-`import`
+    // declarations come up.
+    InImportsAfterNonPackagingDeclaration,
+  };
+
   // Used to track state on state_stack_.
   struct StateStackEntry : public Printable<StateStackEntry> {
     explicit StateStackEntry(State state, PrecedenceGroup ambient_precedence,
@@ -301,6 +312,18 @@ class Context {
     return state_stack_;
   }
 
+  auto packaging_state() const -> PackagingState { return packaging_state_; }
+  auto set_packaging_state(PackagingState packaging_state) -> void {
+    packaging_state_ = packaging_state;
+  }
+  auto first_non_packaging_token() const -> Lex::Token {
+    return first_non_packaging_token_;
+  }
+  auto set_first_non_packaging_token(Lex::Token token) -> void {
+    CARBON_CHECK(!first_non_packaging_token_.is_valid());
+    first_non_packaging_token_ = token;
+  }
+
  private:
   // Prints a single token for a stack dump. Used by PrintForStackDump.
   auto PrintTokenForStackDump(llvm::raw_ostream& output, Lex::Token token) const
@@ -319,6 +342,12 @@ class Context {
   Lex::TokenIterator end_;
 
   llvm::SmallVector<StateStackEntry> state_stack_;
+
+  // The current packaging state, whether `import`/`package` are allowed.
+  PackagingState packaging_state_ = PackagingState::StartOfFile;
+  // The first non-packaging token, starting as invalid. Used for packaging
+  // state warnings.
+  Lex::Token first_non_packaging_token_ = Lex::Token::Invalid;
 };
 
 // `clang-format` has a bug with spacing around `->` returns in macros. See

+ 58 - 33
toolchain/parse/handle_declaration_scope_loop.cpp

@@ -22,49 +22,74 @@ static auto HandleUnrecognizedDeclaration(Context& context) -> void {
 auto HandleDeclarationScopeLoop(Context& context) -> void {
   // This maintains the current state unless we're at the end of the scope.
 
-  switch (context.PositionKind()) {
+  switch (auto position_kind = context.PositionKind()) {
     case Lex::TokenKind::CloseCurlyBrace:
     case Lex::TokenKind::EndOfFile: {
       // This is the end of the scope, so the loop state ends.
       context.PopAndDiscardState();
       break;
     }
-    case Lex::TokenKind::Class: {
-      context.PushState(State::TypeIntroducerAsClass);
+    // `import` and `package` manage their packaging state.
+    case Lex::TokenKind::Import: {
+      context.PushState(State::Import);
       break;
     }
-    case Lex::TokenKind::Constraint: {
-      context.PushState(State::TypeIntroducerAsNamedConstraint);
-      break;
-    }
-    case Lex::TokenKind::Fn: {
-      context.PushState(State::FunctionIntroducer);
-      break;
-    }
-    case Lex::TokenKind::Interface: {
-      context.PushState(State::TypeIntroducerAsInterface);
-      break;
-    }
-    case Lex::TokenKind::Namespace: {
-      context.PushState(State::Namespace);
-      break;
-    }
-    case Lex::TokenKind::Semi: {
-      context.AddLeafNode(NodeKind::EmptyDeclaration, context.Consume());
-      break;
-    }
-    case Lex::TokenKind::Var: {
-      context.PushState(State::VarAsSemicolon);
-      break;
-    }
-    case Lex::TokenKind::Let: {
-      context.PushState(State::Let);
-      break;
-    }
-    default: {
-      HandleUnrecognizedDeclaration(context);
+    case Lex::TokenKind::Package: {
+      context.PushState(State::Package);
       break;
     }
+    default:
+      // Because a non-packaging keyword was encountered, packaging is complete.
+      // Misplaced packaging keywords may lead to this being re-triggered.
+      if (context.packaging_state() !=
+          Context::PackagingState::AfterNonPackagingDeclaration) {
+        if (!context.first_non_packaging_token().is_valid()) {
+          context.set_first_non_packaging_token(*context.position());
+        }
+        context.set_packaging_state(
+            Context::PackagingState::AfterNonPackagingDeclaration);
+      }
+      switch (position_kind) {
+        // Remaining keywords are only valid after imports are complete, and
+        // so all result in a `set_packaging_state` call. Note, this may not
+        // always be necessary but is probably cheaper than validating.
+        case Lex::TokenKind::Class: {
+          context.PushState(State::TypeIntroducerAsClass);
+          break;
+        }
+        case Lex::TokenKind::Constraint: {
+          context.PushState(State::TypeIntroducerAsNamedConstraint);
+          break;
+        }
+        case Lex::TokenKind::Fn: {
+          context.PushState(State::FunctionIntroducer);
+          break;
+        }
+        case Lex::TokenKind::Interface: {
+          context.PushState(State::TypeIntroducerAsInterface);
+          break;
+        }
+        case Lex::TokenKind::Namespace: {
+          context.PushState(State::Namespace);
+          break;
+        }
+        case Lex::TokenKind::Semi: {
+          context.AddLeafNode(NodeKind::EmptyDeclaration, context.Consume());
+          break;
+        }
+        case Lex::TokenKind::Var: {
+          context.PushState(State::VarAsSemicolon);
+          break;
+        }
+        case Lex::TokenKind::Let: {
+          context.PushState(State::Let);
+          break;
+        }
+        default: {
+          HandleUnrecognizedDeclaration(context);
+          break;
+        }
+      }
   }
 }
 

+ 163 - 0
toolchain/parse/handle_import_and_package.cpp

@@ -0,0 +1,163 @@
+// 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/lex/tokenized_buffer.h"
+#include "toolchain/parse/context.h"
+
+namespace Carbon::Parse {
+
+// Provides error exiting logic for `import`/`package`, skipping to the semi.
+static auto ExitOnParseError(Context& context, Context::StateStackEntry state,
+                             NodeKind directive) {
+  auto semi_token = context.SkipPastLikelyEnd(state.token);
+  return context.AddNode(directive, semi_token ? *semi_token : state.token,
+                         state.subtree_start,
+                         /*has_error=*/true);
+}
+
+// Handles the main parsing of `import`/`package`. It's expected that the
+// introducer is already added.
+static auto HandleImportAndPackage(Context& context,
+                                   Context::StateStackEntry state,
+                                   NodeKind directive, bool expect_api_or_impl)
+    -> void {
+  if (!context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::Identifier,
+                                       NodeKind::Name)) {
+    CARBON_DIAGNOSTIC(ExpectedIdentifierAfterKeyword, Error,
+                      "Expected identifier after `{0}`.", Lex::TokenKind);
+    context.emitter().Emit(*context.position(), ExpectedIdentifierAfterKeyword,
+                           context.tokens().GetKind(state.token));
+    ExitOnParseError(context, state, directive);
+    return;
+  }
+
+  bool library_parsed = false;
+  if (auto library_token = context.ConsumeIf(Lex::TokenKind::Library)) {
+    auto library_start = context.tree().size();
+
+    if (!context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::StringLiteral,
+                                         NodeKind::Literal)) {
+      CARBON_DIAGNOSTIC(
+          ExpectedLibraryName, Error,
+          "Expected a string literal to specify the library name.");
+      context.emitter().Emit(*context.position(), ExpectedLibraryName);
+      ExitOnParseError(context, state, directive);
+      return;
+    }
+
+    context.AddNode(NodeKind::Library, *library_token, library_start,
+                    /*has_error=*/false);
+    library_parsed = true;
+  }
+
+  auto next_kind = context.tokens().GetKind(*(context.position()));
+  if (!library_parsed && next_kind == Lex::TokenKind::StringLiteral) {
+    // If we come acroess a string literal and we didn't parse `library
+    // "..."` yet, then most probably the user forgot to add `library`
+    // before the library name.
+    CARBON_DIAGNOSTIC(MissingLibraryKeyword, Error,
+                      "Missing `library` keyword.");
+    context.emitter().Emit(*context.position(), MissingLibraryKeyword);
+    ExitOnParseError(context, state, directive);
+    return;
+  }
+
+  if (expect_api_or_impl) {
+    switch (next_kind) {
+      case Lex::TokenKind::Api: {
+        context.AddLeafNode(NodeKind::PackageApi, context.Consume());
+        break;
+      }
+      case Lex::TokenKind::Impl: {
+        context.AddLeafNode(NodeKind::PackageImpl, context.Consume());
+        break;
+      }
+      default: {
+        CARBON_DIAGNOSTIC(ExpectedApiOrImpl, Error,
+                          "Expected `api` or `impl`.");
+        context.emitter().Emit(*context.position(), ExpectedApiOrImpl);
+        ExitOnParseError(context, state, directive);
+        return;
+      }
+    }
+  }
+
+  if (!context.PositionIs(Lex::TokenKind::Semi)) {
+    context.EmitExpectedDeclarationSemi(context.tokens().GetKind(state.token));
+    ExitOnParseError(context, state, directive);
+    return;
+  }
+
+  context.AddNode(directive, context.Consume(), state.subtree_start,
+                  state.has_error);
+}
+
+auto HandleImport(Context& context) -> void {
+  auto state = context.PopState();
+
+  auto intro_token = context.Consume();
+  context.AddLeafNode(NodeKind::ImportIntroducer, intro_token);
+
+  switch (context.packaging_state()) {
+    case Context::PackagingState::StartOfFile:
+      // `package` is no longer allowed, but `import` may repeat.
+      context.set_packaging_state(Context::PackagingState::InImports);
+      [[clang::fallthrough]];
+
+    case Context::PackagingState::InImports:
+      HandleImportAndPackage(context, state, NodeKind::ImportDirective,
+                             /*expect_api_or_impl=*/false);
+      break;
+
+    case Context::PackagingState::AfterNonPackagingDeclaration: {
+      context.set_packaging_state(
+          Context::PackagingState::InImportsAfterNonPackagingDeclaration);
+      CARBON_DIAGNOSTIC(
+          ImportTooLate, Error,
+          "`import` directives must come after the `package` directive (if "
+          "present) and before any other entities in the file.");
+      CARBON_DIAGNOSTIC(FirstDeclaration, Note, "First declaration is here.");
+      context.emitter()
+          .Build(intro_token, ImportTooLate)
+          .Note(context.first_non_packaging_token(), FirstDeclaration)
+          .Emit();
+      ExitOnParseError(context, state, NodeKind::ImportDirective);
+      break;
+    }
+    case Context::PackagingState::InImportsAfterNonPackagingDeclaration:
+      // There is a sequential block of misplaced `import` statements, which can
+      // occur if a declaration is added above `import`s. Avoid duplicate
+      // warnings.
+      ExitOnParseError(context, state, NodeKind::ImportDirective);
+      break;
+  }
+}
+
+auto HandlePackage(Context& context) -> void {
+  auto state = context.PopState();
+
+  auto intro_token = context.Consume();
+  context.AddLeafNode(NodeKind::PackageIntroducer, intro_token);
+
+  if (intro_token != Lex::Token::FirstNonCommentToken) {
+    CARBON_DIAGNOSTIC(
+        PackageTooLate, Error,
+        "The `package` directive must be the first non-comment line.");
+    CARBON_DIAGNOSTIC(FirstNonCommentLine, Note,
+                      "First non-comment line is here.");
+    context.emitter()
+        .Build(intro_token, PackageTooLate)
+        .Note(Lex::Token::FirstNonCommentToken, FirstNonCommentLine)
+        .Emit();
+    ExitOnParseError(context, state, NodeKind::PackageDirective);
+    return;
+  }
+
+  // `package` is no longer allowed, but `import` may repeat.
+  context.set_packaging_state(Context::PackagingState::InImports);
+  HandleImportAndPackage(context, state, NodeKind::PackageDirective,
+                         /*expect_api_or_impl=*/true);
+}
+
+}  // namespace Carbon::Parse

+ 0 - 90
toolchain/parse/handle_package.cpp

@@ -1,90 +0,0 @@
-// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
-// Exceptions. See /LICENSE for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-#include "toolchain/parse/context.h"
-
-namespace Carbon::Parse {
-
-auto HandlePackage(Context& context) -> void {
-  auto state = context.PopState();
-
-  context.AddLeafNode(NodeKind::PackageIntroducer, context.Consume());
-
-  auto exit_on_parse_error = [&]() {
-    auto semi_token = context.SkipPastLikelyEnd(state.token);
-    return context.AddNode(NodeKind::PackageDirective,
-                           semi_token ? *semi_token : state.token,
-                           state.subtree_start,
-                           /*has_error=*/true);
-  };
-
-  if (!context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::Identifier,
-                                       NodeKind::Name)) {
-    CARBON_DIAGNOSTIC(ExpectedIdentifierAfterPackage, Error,
-                      "Expected identifier after `package`.");
-    context.emitter().Emit(*context.position(), ExpectedIdentifierAfterPackage);
-    exit_on_parse_error();
-    return;
-  }
-
-  bool library_parsed = false;
-  if (auto library_token = context.ConsumeIf(Lex::TokenKind::Library)) {
-    auto library_start = context.tree().size();
-
-    if (!context.ConsumeAndAddLeafNodeIf(Lex::TokenKind::StringLiteral,
-                                         NodeKind::Literal)) {
-      CARBON_DIAGNOSTIC(
-          ExpectedLibraryName, Error,
-          "Expected a string literal to specify the library name.");
-      context.emitter().Emit(*context.position(), ExpectedLibraryName);
-      exit_on_parse_error();
-      return;
-    }
-
-    context.AddNode(NodeKind::PackageLibrary, *library_token, library_start,
-                    /*has_error=*/false);
-    library_parsed = true;
-  }
-
-  switch (auto api_or_impl_token =
-              context.tokens().GetKind(*(context.position()))) {
-    case Lex::TokenKind::Api: {
-      context.AddLeafNode(NodeKind::PackageApi, context.Consume());
-      break;
-    }
-    case Lex::TokenKind::Impl: {
-      context.AddLeafNode(NodeKind::PackageImpl, context.Consume());
-      break;
-    }
-    default: {
-      if (!library_parsed &&
-          api_or_impl_token == Lex::TokenKind::StringLiteral) {
-        // If we come acroess a string literal and we didn't parse `library
-        // "..."` yet, then most probably the user forgot to add `library`
-        // before the library name.
-        CARBON_DIAGNOSTIC(MissingLibraryKeyword, Error,
-                          "Missing `library` keyword.");
-        context.emitter().Emit(*context.position(), MissingLibraryKeyword);
-      } else {
-        CARBON_DIAGNOSTIC(ExpectedApiOrImpl, Error,
-                          "Expected a `api` or `impl`.");
-        context.emitter().Emit(*context.position(), ExpectedApiOrImpl);
-      }
-      exit_on_parse_error();
-      return;
-    }
-  }
-
-  if (!context.PositionIs(Lex::TokenKind::Semi)) {
-    context.EmitExpectedDeclarationSemi(Lex::TokenKind::Package);
-    exit_on_parse_error();
-    return;
-  }
-
-  context.AddNode(NodeKind::PackageDirective, context.Consume(),
-                  state.subtree_start,
-                  /*has_error=*/false);
-}
-
-}  // namespace Carbon::Parse

+ 16 - 3
toolchain/parse/node_kind.def

@@ -92,21 +92,34 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(NameExpression, 0, CARBON_TOKEN(Identifier))
 
 // ----------------------------------------------------------------------------
 
+// `library`:
+//   _external_: Literal
+// Library
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(Library, 1, CARBON_TOKEN(Library))
+
 // `package`:
 //   PackageIntroducer
 //   _external_: Name
-//     _external_: Literal
-//   PackageLibrary
+//   _optional_ _external_: Library
 //   PackageApi or PackageImpl
 // PackageDirective
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageIntroducer, 0, CARBON_TOKEN(Package))
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageApi, 0, CARBON_TOKEN(Api))
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageImpl, 0, CARBON_TOKEN(Impl))
-CARBON_PARSE_NODE_KIND_CHILD_COUNT(PackageLibrary, 1, CARBON_TOKEN(Library))
 CARBON_PARSE_NODE_KIND_BRACKET(PackageDirective, PackageIntroducer,
                                CARBON_TOKEN(Semi)
                                    CARBON_IF_ERROR(CARBON_TOKEN(Package)))
 
+// `import`:
+//   ImportIntroducer
+//   _external_: Name
+//   _optional_ _external_: Library
+// ImportDirective
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ImportIntroducer, 0, CARBON_TOKEN(Import))
+CARBON_PARSE_NODE_KIND_BRACKET(ImportDirective, ImportIntroducer,
+                               CARBON_TOKEN(Semi)
+                                   CARBON_IF_ERROR(CARBON_TOKEN(Import)))
+
 // `namespace`:
 //   NamespaceStart
 //   _external_: Name or QualifiedDeclaration

+ 6 - 0
toolchain/parse/state.def

@@ -419,6 +419,12 @@ CARBON_PARSE_STATE(FunctionSignatureFinish)
 //   (state done)
 CARBON_PARSE_STATE(FunctionDefinitionFinish)
 
+// Handles `import`.
+//
+// Always:
+//   (state done)
+CARBON_PARSE_STATE(Import)
+
 // Handles `namespace`.
 //
 // Always:

+ 20 - 0
toolchain/parse/testdata/packages/import/after_import.carbon

@@ -0,0 +1,20 @@
+// 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
+
+import A;
+import B;
+
+// CHECK:STDOUT: - filename: after_import.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'A'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'B'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 22 - 0
toolchain/parse/testdata/packages/import/after_package.carbon

@@ -0,0 +1,22 @@
+// 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
+
+package A api;
+
+import B;
+
+// CHECK:STDOUT: - filename: after_package.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'A'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'B'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 16 - 0
toolchain/parse/testdata/packages/import/basic.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
+
+import Geometry;
+
+// CHECK:STDOUT: - filename: basic.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'Geometry'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 28 - 0
toolchain/parse/testdata/packages/import/fail_after_decl.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
+
+fn A();
+
+// CHECK:STDERR: fail_after_decl.carbon:[[@LINE+6]]:1: ERROR: `import` directives must come after the `package` directive (if present) and before any other entities in the file.
+// CHECK:STDERR: import B;
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_after_decl.carbon:[[@LINE-5]]:1: First declaration is here.
+// CHECK:STDERR: fn A();
+// CHECK:STDERR: ^
+import B;
+
+// CHECK:STDOUT: - filename: fail_after_decl.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'A'},
+// CHECK:STDOUT:         {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:       {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FunctionDeclaration', text: ';', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 63 - 0
toolchain/parse/testdata/packages/import/fail_after_decl_repeated.carbon

@@ -0,0 +1,63 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+fn A();
+
+// CHECK:STDERR: fail_after_decl_repeated.carbon:[[@LINE+6]]:1: ERROR: `import` directives must come after the `package` directive (if present) and before any other entities in the file.
+// CHECK:STDERR: import B;
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_after_decl_repeated.carbon:[[@LINE-5]]:1: First declaration is here.
+// CHECK:STDERR: fn A();
+// CHECK:STDERR: ^
+import B;
+// Note this is still invalid, but doesn't warn because it's sequential.
+import C;
+
+// CHECK:STDERR: fail_after_decl_repeated.carbon:[[@LINE+6]]:1: ERROR: The `package` directive must be the first non-comment line.
+// CHECK:STDERR: package D;
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_after_decl_repeated.carbon:[[@LINE-15]]:1: First non-comment line is here.
+// CHECK:STDERR: fn A();
+// CHECK:STDERR: ^
+package D;
+
+fn E();
+
+// CHECK:STDERR: fail_after_decl_repeated.carbon:[[@LINE+6]]:1: ERROR: `import` directives must come after the `package` directive (if present) and before any other entities in the file.
+// CHECK:STDERR: import F;
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_after_decl_repeated.carbon:[[@LINE-25]]:1: First declaration is here.
+// CHECK:STDERR: fn A();
+// CHECK:STDERR: ^
+import F;
+// Note this is still invalid, but doesn't warn because it's sequential.
+import G;
+
+// CHECK:STDOUT: - filename: fail_after_decl_repeated.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'A'},
+// CHECK:STDOUT:         {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:       {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FunctionDeclaration', text: ';', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'E'},
+// CHECK:STDOUT:         {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:       {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FunctionDeclaration', text: ';', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 21 - 0
toolchain/parse/testdata/packages/import/fail_extra_string.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
+
+// CHECK:STDERR: fail_extra_string.carbon:[[@LINE+3]]:26: ERROR: `import` declarations must end with a `;`.
+// CHECK:STDERR: import Foo library "bar" "baz";
+// CHECK:STDERR:                          ^
+import Foo library "bar" "baz";
+
+// CHECK:STDOUT: - filename: fail_extra_string.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'Foo'},
+// CHECK:STDOUT:         {kind: 'Literal', text: '"bar"'},
+// CHECK:STDOUT:       {kind: 'Library', text: 'library', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 19 - 0
toolchain/parse/testdata/packages/import/fail_library_is_identifier.carbon

@@ -0,0 +1,19 @@
+// 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:STDERR: fail_library_is_identifier.carbon:[[@LINE+3]]:25: ERROR: Expected a string literal to specify the library name.
+// CHECK:STDERR: import Geometry library Shapes;
+// CHECK:STDERR:                         ^
+import Geometry library Shapes;
+
+// CHECK:STDOUT: - filename: fail_library_is_identifier.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'Geometry'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 18 - 0
toolchain/parse/testdata/packages/import/fail_library_skips_name.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
+
+// CHECK:STDERR: fail_library_skips_name.carbon:[[@LINE+3]]:8: ERROR: Expected identifier after `import`.
+// CHECK:STDERR: import library "Shapes";
+// CHECK:STDERR:        ^
+import library "Shapes";
+
+// CHECK:STDOUT: - filename: fail_library_skips_name.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 18 - 0
toolchain/parse/testdata/packages/import/fail_name_is_keyword.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
+
+// CHECK:STDERR: fail_name_is_keyword.carbon:[[@LINE+3]]:8: ERROR: Expected identifier after `import`.
+// CHECK:STDERR: import fn;
+// CHECK:STDERR:        ^
+import fn;
+
+// CHECK:STDOUT: - filename: fail_name_is_keyword.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 18 - 0
toolchain/parse/testdata/packages/import/fail_no_name.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
+
+// CHECK:STDERR: fail_no_name.carbon:[[@LINE+3]]:7: ERROR: Expected identifier after `import`.
+// CHECK:STDERR: import;
+// CHECK:STDERR:       ^
+import;
+
+// CHECK:STDOUT: - filename: fail_no_name.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 19 - 0
toolchain/parse/testdata/packages/import/fail_no_semi.carbon

@@ -0,0 +1,19 @@
+// 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
+
+import Geometry
+
+// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+10]]:21: ERROR: `import` declarations must end with a `;`.
+// CHECK:STDERR: // CHECK:STDOUT:   ]
+// CHECK:STDERR:                     ^
+// CHECK:STDOUT: - filename: fail_no_semi.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'Geometry'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: 'import', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 19 - 0
toolchain/parse/testdata/packages/import/fail_omit_library_keyword.carbon

@@ -0,0 +1,19 @@
+// 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:STDERR: fail_omit_library_keyword.carbon:[[@LINE+3]]:17: ERROR: Missing `library` keyword.
+// CHECK:STDERR: import Geometry "Shapes";
+// CHECK:STDERR:                 ^
+import Geometry "Shapes";
+
+// CHECK:STDOUT: - filename: fail_omit_library_keyword.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'Geometry'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 19 - 0
toolchain/parse/testdata/packages/import/fail_type.carbon

@@ -0,0 +1,19 @@
+// 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:STDERR: fail_type.carbon:[[@LINE+3]]:17: ERROR: `import` declarations must end with a `;`.
+// CHECK:STDERR: import Geometry api;
+// CHECK:STDERR:                 ^
+import Geometry api;
+
+// CHECK:STDOUT: - filename: fail_type.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'Geometry'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 18 - 0
toolchain/parse/testdata/packages/import/library.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
+
+import Geometry library "Shapes";
+
+// CHECK:STDOUT: - filename: library.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'Geometry'},
+// CHECK:STDOUT:         {kind: 'Literal', text: '"Shapes"'},
+// CHECK:STDOUT:       {kind: 'Library', text: 'library', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 0 - 0
toolchain/parse/testdata/package/api.carbon → toolchain/parse/testdata/packages/package/api.carbon


+ 1 - 1
toolchain/parse/testdata/package/api_library.carbon → toolchain/parse/testdata/packages/package/api_library.carbon

@@ -12,7 +12,7 @@ package Geometry library "Shapes" api;
 // CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
 // CHECK:STDOUT:       {kind: 'Name', text: 'Geometry'},
 // CHECK:STDOUT:         {kind: 'Literal', text: '"Shapes"'},
-// CHECK:STDOUT:       {kind: 'PackageLibrary', text: 'library', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'Library', text: 'library', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
 // CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 6},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 28 - 0
toolchain/parse/testdata/packages/package/fail_after_decl.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
+
+fn A();
+
+// CHECK:STDERR: fail_after_decl.carbon:[[@LINE+6]]:1: ERROR: The `package` directive must be the first non-comment line.
+// CHECK:STDERR: package B api;
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_after_decl.carbon:[[@LINE-5]]:1: First non-comment line is here.
+// CHECK:STDERR: fn A();
+// CHECK:STDERR: ^
+package B api;
+
+// CHECK:STDOUT: - filename: fail_after_decl.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'A'},
+// CHECK:STDOUT:         {kind: 'ParameterListStart', text: '('},
+// CHECK:STDOUT:       {kind: 'ParameterList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FunctionDeclaration', text: ';', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 26 - 0
toolchain/parse/testdata/packages/package/fail_after_import.carbon

@@ -0,0 +1,26 @@
+// 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
+
+import A;
+
+// CHECK:STDERR: fail_after_import.carbon:[[@LINE+6]]:1: ERROR: The `package` directive must be the first non-comment line.
+// CHECK:STDERR: package B api;
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_after_import.carbon:[[@LINE-5]]:1: First non-comment line is here.
+// CHECK:STDERR: import A;
+// CHECK:STDERR: ^
+package B api;
+
+// CHECK:STDOUT: - filename: fail_after_import.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'A'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 26 - 0
toolchain/parse/testdata/packages/package/fail_after_package.carbon

@@ -0,0 +1,26 @@
+// 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
+
+package A api;
+// CHECK:STDERR: fail_after_package.carbon:[[@LINE+6]]:1: ERROR: The `package` directive must be the first non-comment line.
+// CHECK:STDERR: package B api;
+// CHECK:STDERR: ^
+// CHECK:STDERR: fail_after_package.carbon:[[@LINE-4]]:1: First non-comment line is here.
+// CHECK:STDERR: package A api;
+// CHECK:STDERR: ^
+package B api;
+
+// CHECK:STDOUT: - filename: fail_after_package.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'Name', text: 'A'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', has_error: yes, subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 2 - 2
toolchain/parse/testdata/package/fail_extra_string.carbon → toolchain/parse/testdata/packages/package/fail_extra_string.carbon

@@ -4,7 +4,7 @@
 //
 // AUTOUPDATE
 
-// CHECK:STDERR: fail_extra_string.carbon:[[@LINE+3]]:27: ERROR: Expected a `api` or `impl`.
+// CHECK:STDERR: fail_extra_string.carbon:[[@LINE+3]]:27: ERROR: Expected `api` or `impl`.
 // CHECK:STDERR: package Foo library "bar" "baz";
 // CHECK:STDERR:                           ^
 package Foo library "bar" "baz";
@@ -15,7 +15,7 @@ package Foo library "bar" "baz";
 // CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
 // CHECK:STDOUT:       {kind: 'Name', text: 'Foo'},
 // CHECK:STDOUT:         {kind: 'Literal', text: '"bar"'},
-// CHECK:STDOUT:       {kind: 'PackageLibrary', text: 'library', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'Library', text: 'library', subtree_size: 2},
 // CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', has_error: yes, subtree_size: 5},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
 // CHECK:STDOUT:   ]

+ 0 - 0
toolchain/parse/testdata/package/fail_library_is_identifier.carbon → toolchain/parse/testdata/packages/package/fail_library_is_identifier.carbon


+ 0 - 0
toolchain/parse/testdata/package/fail_library_skips_name.carbon → toolchain/parse/testdata/packages/package/fail_library_skips_name.carbon


+ 0 - 0
toolchain/parse/testdata/package/fail_name_is_keyword.carbon → toolchain/parse/testdata/packages/package/fail_name_is_keyword.carbon


+ 0 - 0
toolchain/parse/testdata/package/fail_no_name.carbon → toolchain/parse/testdata/packages/package/fail_no_name.carbon


+ 0 - 0
toolchain/parse/testdata/package/fail_no_semi.carbon → toolchain/parse/testdata/packages/package/fail_no_semi.carbon


+ 1 - 1
toolchain/parse/testdata/package/fail_no_type.carbon → toolchain/parse/testdata/packages/package/fail_no_type.carbon

@@ -4,7 +4,7 @@
 //
 // AUTOUPDATE
 
-// CHECK:STDERR: fail_no_type.carbon:[[@LINE+3]]:17: ERROR: Expected a `api` or `impl`.
+// CHECK:STDERR: fail_no_type.carbon:[[@LINE+3]]:17: ERROR: Expected `api` or `impl`.
 // CHECK:STDERR: package Geometry;
 // CHECK:STDERR:                 ^
 package Geometry;

+ 0 - 0
toolchain/parse/testdata/package/fail_omit_library_keyword.carbon → toolchain/parse/testdata/packages/package/fail_omit_library_keyword.carbon


+ 0 - 0
toolchain/parse/testdata/package/impl.carbon → toolchain/parse/testdata/packages/package/impl.carbon


+ 1 - 1
toolchain/parse/testdata/package/impl_library.carbon → toolchain/parse/testdata/packages/package/impl_library.carbon

@@ -12,7 +12,7 @@ package Geometry library "Shapes" impl;
 // CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
 // CHECK:STDOUT:       {kind: 'Name', text: 'Geometry'},
 // CHECK:STDOUT:         {kind: 'Literal', text: '"Shapes"'},
-// CHECK:STDOUT:       {kind: 'PackageLibrary', text: 'library', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'Library', text: 'library', subtree_size: 2},
 // CHECK:STDOUT:       {kind: 'PackageImpl', text: 'impl'},
 // CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 6},
 // CHECK:STDOUT:     {kind: 'FileEnd', text: ''},

+ 0 - 6
toolchain/parse/tree.cpp

@@ -31,12 +31,6 @@ auto Tree::Parse(Lex::TokenizedBuffer& tokens, DiagnosticConsumer& consumer,
 
   context.PushState(State::DeclarationScopeLoop);
 
-  // The package should always be the first token, if it's present. Any other
-  // use is invalid.
-  if (context.PositionIs(Lex::TokenKind::Package)) {
-    context.PushState(State::Package);
-  }
-
   while (!context.state_stack().empty()) {
     // clang warns on unhandled enum values; clang-tidy is incorrect here.
     // NOLINTNEXTLINE(bugprone-switch-missing-default-case)