Quellcode durchsuchen

Add export keyword handling. (#3944)

This provides `export import` logic in lex, parse, and check; `export
name` logic is only in lex and parse, not check.

I think with `export name` I'm going to need to modify import_ref and
some consolidation logic, whereas `export import` seems feasible to keep
as primarily import logic. Given the implementations were looking like
they'd diverge more substantially, I thought it'd be helpful to cut the
PR here.
Jon Ross-Perkins vor 2 Jahren
Ursprung
Commit
a16842ab37

+ 82 - 25
toolchain/check/check.cpp

@@ -200,6 +200,82 @@ struct UnitInfo {
   UnitInfo* api_for_impl = nullptr;
 };
 
+// Imports the current package.
+static auto ImportCurrentPackage(Context& context, UnitInfo& unit_info,
+                                 int total_ir_count,
+                                 SemIR::InstId package_inst_id,
+                                 SemIR::TypeId namespace_type_id) -> void {
+  // Add imports from the current package.
+  auto self_import = unit_info.package_imports_map.find(IdentifierId::Invalid);
+  if (self_import == unit_info.package_imports_map.end()) {
+    // Push the scope; there are no names to add.
+    context.scope_stack().Push(package_inst_id, SemIR::NameScopeId::Package);
+    return;
+  }
+
+  // Track whether an IR was imported in full, including `export import`. This
+  // distinguishes from IRs that are indirectly added without all names being
+  // exported to this IR.
+  llvm::SmallVector<bool> imported_irs(total_ir_count, false);
+  if (self_import->second.has_load_error) {
+    context.name_scopes().Get(SemIR::NameScopeId::Package).has_error = true;
+  }
+
+  for (const auto& import : self_import->second.imports) {
+    const auto& import_sem_ir = **import.unit_info->unit->sem_ir;
+
+    auto& imported_ir = imported_irs[import_sem_ir.check_ir_id().index];
+    if (!imported_ir) {
+      imported_ir = true;
+
+      // Import the IR and its exported imports.
+      ImportLibraryFromCurrentPackage(context, namespace_type_id,
+                                      import.names.node_id, import_sem_ir,
+                                      import.names.is_export);
+
+      for (const auto& indirect_ir : import_sem_ir.import_irs().array_ref()) {
+        if (indirect_ir.is_export) {
+          auto& imported_indirect_ir =
+              imported_irs[indirect_ir.sem_ir->check_ir_id().index];
+          if (!imported_indirect_ir) {
+            imported_indirect_ir = true;
+
+            ImportLibraryFromCurrentPackage(
+                context, namespace_type_id, import.names.node_id,
+                *indirect_ir.sem_ir, import.names.is_export);
+          } else if (import.names.is_export) {
+            // The indirect IR was previously indirectly imported, but it's
+            // found through `export import`. We need to mark it for re-export.
+            context.import_irs()
+                .Get(context.check_ir_map()[indirect_ir.sem_ir->check_ir_id()
+                                                .index])
+                .is_export = true;
+          }
+        }
+      }
+    } else if (import.names.is_export) {
+      // The IR was previously indirectly imported, but it's `export import`.
+      // We need to mark it -- and transitive `export import`s -- for re-export.
+      context.import_irs()
+          .Get(context.check_ir_map()[import_sem_ir.check_ir_id().index])
+          .is_export = true;
+
+      for (const auto& indirect_ir : import_sem_ir.import_irs().array_ref()) {
+        if (indirect_ir.is_export) {
+          context.import_irs()
+              .Get(context
+                       .check_ir_map()[indirect_ir.sem_ir->check_ir_id().index])
+              .is_export = true;
+        }
+      }
+    }
+  }
+
+  context.scope_stack().Push(
+      package_inst_id, SemIR::NameScopeId::Package,
+      context.name_scopes().Get(SemIR::NameScopeId::Package).has_error);
+}
+
 // Add imports to the root block.
 static auto InitPackageScopeAndImports(Context& context, UnitInfo& unit_info,
                                        int total_ir_count) -> void {
@@ -248,30 +324,8 @@ static auto InitPackageScopeAndImports(Context& context, UnitInfo& unit_info,
                    {.node_id = Parse::InvalidNodeId(), .sem_ir = nullptr});
   }
 
-  // Add imports from the current package.
-  auto self_import = unit_info.package_imports_map.find(IdentifierId::Invalid);
-  if (self_import != unit_info.package_imports_map.end()) {
-    bool error_in_import = self_import->second.has_load_error;
-    for (const auto& import : self_import->second.imports) {
-      const auto& import_sem_ir = **import.unit_info->unit->sem_ir;
-      ImportLibraryFromCurrentPackage(context, namespace_type_id,
-                                      import.names.node_id, import_sem_ir);
-      error_in_import |= import_sem_ir.name_scopes()
-                             .Get(SemIR::NameScopeId::Package)
-                             .has_error;
-    }
-
-    // If an import of the current package caused an error for the imported
-    // file, it transitively affects the current file too.
-    if (error_in_import) {
-      context.name_scopes().Get(SemIR::NameScopeId::Package).has_error = true;
-    }
-    context.scope_stack().Push(package_inst_id, SemIR::NameScopeId::Package,
-                               error_in_import);
-  } else {
-    // Push the scope; there are no names to add.
-    context.scope_stack().Push(package_inst_id, SemIR::NameScopeId::Package);
-  }
+  ImportCurrentPackage(context, unit_info, total_ir_count, package_inst_id,
+                       namespace_type_id);
   CARBON_CHECK(context.scope_stack().PeekIndex() == ScopeIndex::Package);
 
   for (auto& [package_id, package_imports] : unit_info.package_imports_map) {
@@ -283,7 +337,10 @@ static auto InitPackageScopeAndImports(Context& context, UnitInfo& unit_info,
     llvm::SmallVector<SemIR::ImportIR> import_irs;
     for (auto import : package_imports.imports) {
       import_irs.push_back({.node_id = import.names.node_id,
-                            .sem_ir = &**import.unit_info->unit->sem_ir});
+                            .sem_ir = &**import.unit_info->unit->sem_ir,
+                            .is_export = false});
+      CARBON_CHECK(!import.names.is_export)
+          << "Imports from other packages can't be exported.";
     }
     ImportLibrariesFromOtherPackage(context, namespace_type_id,
                                     package_imports.node_id, package_id,

+ 20 - 0
toolchain/check/handle_export.cpp

@@ -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
+
+#include "toolchain/check/context.h"
+#include "toolchain/parse/typed_nodes.h"
+
+namespace Carbon::Check {
+
+auto HandleExportIntroducer(Context& /*context*/,
+                            Parse::ExportIntroducerId /*node_id*/) -> bool {
+  return true;
+}
+
+auto HandleExportDirective(Context& context, Parse::ExportDirectiveId node_id)
+    -> bool {
+  return context.TODO(node_id, "ExportDirective");
+}
+
+}  // namespace Carbon::Check

+ 5 - 0
toolchain/check/handle_import_and_package.cpp

@@ -14,6 +14,11 @@ auto HandleImportIntroducer(Context& /*context*/,
   return true;
 }
 
+auto HandleImportExport(Context& /*context*/, Parse::ImportExportId /*node_id*/)
+    -> bool {
+  return true;
+}
+
 auto HandleImportDirective(Context& /*context*/,
                            Parse::ImportDirectiveId /*node_id*/) -> bool {
   return true;

+ 11 - 3
toolchain/check/import.cpp

@@ -213,9 +213,11 @@ static auto CopyEnclosingNameScopesFromImportIR(
 auto ImportLibraryFromCurrentPackage(Context& context,
                                      SemIR::TypeId namespace_type_id,
                                      Parse::ImportDirectiveId node_id,
-                                     const SemIR::File& import_sem_ir) -> void {
-  auto ir_id =
-      AddImportIR(context, {.node_id = node_id, .sem_ir = &import_sem_ir});
+                                     const SemIR::File& import_sem_ir,
+                                     bool is_export) -> void {
+  auto ir_id = AddImportIR(
+      context,
+      {.node_id = node_id, .sem_ir = &import_sem_ir, .is_export = is_export});
 
   context.import_ir_constant_values()[ir_id.index].Set(
       SemIR::InstId::PackageNamespace,
@@ -257,6 +259,12 @@ auto ImportLibraryFromCurrentPackage(Context& context,
       }
     }
   }
+
+  // If an import of the current package caused an error for the imported
+  // file, it transitively affects the current file too.
+  if (import_sem_ir.name_scopes().Get(SemIR::NameScopeId::Package).has_error) {
+    context.name_scopes().Get(SemIR::NameScopeId::Package).has_error = true;
+  }
 }
 
 auto ImportLibrariesFromOtherPackage(Context& context,

+ 2 - 1
toolchain/check/import.h

@@ -17,7 +17,8 @@ namespace Carbon::Check {
 auto ImportLibraryFromCurrentPackage(Context& context,
                                      SemIR::TypeId namespace_type_id,
                                      Parse::ImportDirectiveId node_id,
-                                     const SemIR::File& import_sem_ir) -> void;
+                                     const SemIR::File& import_sem_ir,
+                                     bool is_export) -> void;
 
 // Adds another package's imports to name lookup, with all libraries together.
 // This only adds the package name to lookup, so that `package.ImportedPackage`

+ 7 - 2
toolchain/check/import_ref.cpp

@@ -44,6 +44,10 @@ auto AddImportIR(Context& context, SemIR::ImportIR import_ir)
   if (!ir_id.is_valid()) {
     // Note this updates check_ir_map.
     ir_id = InternalAddImportIR(context, import_ir);
+  } else if (import_ir.is_export) {
+    // We're processing an `export import`. In case the IR was indirectly added
+    // as a non-export, mark it as an export.
+    context.import_irs().Get(ir_id).is_export = true;
   }
   return ir_id;
 }
@@ -246,8 +250,9 @@ class ImportRefResolver {
       cursor_ir_id = context_.check_ir_map()[cursor_ir->check_ir_id().index];
       if (!cursor_ir_id.is_valid()) {
         // TODO: Should we figure out a location to assign here?
-        cursor_ir_id = AddImportIR(
-            context_, {.node_id = Parse::NodeId::Invalid, .sem_ir = cursor_ir});
+        cursor_ir_id = AddImportIR(context_, {.node_id = Parse::NodeId::Invalid,
+                                              .sem_ir = cursor_ir,
+                                              .is_export = false});
       }
       cursor_inst_id = ir_inst.inst_id;
 

+ 726 - 0
toolchain/check/testdata/packages/no_prelude/export_import.carbon

@@ -0,0 +1,726 @@
+// 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
+
+// ============================================================================
+// Setup files
+// ============================================================================
+
+// --- base.carbon
+
+library "base" api;
+
+class C {
+  var x: ();
+};
+
+// --- export.carbon
+
+library "export" api;
+
+export import library "base";
+
+// --- export_copy.carbon
+
+library "export_copy" api;
+
+export import library "base";
+
+// --- non_export.carbon
+
+library "non_export" api;
+
+import library "export";
+
+// --- export_export.carbon
+
+library "export_export" api;
+
+export import library "export";
+
+// --- import_then_export.carbon
+
+library "import_then_export" api;
+
+import library "base";
+export import library "export";
+
+// --- export_in_impl.carbon
+
+// This is just providing an API for the implicit import.
+library "export_in_impl" api;
+
+// ============================================================================
+// Test files
+// ============================================================================
+
+// --- use_export.carbon
+
+library "use_export" api;
+
+import library "export";
+
+var c: C = {.x = ()};
+
+// --- fail_export_in_impl.impl.carbon
+
+library "export_in_impl" impl;
+
+// CHECK:STDERR: fail_export_in_impl.impl.carbon:[[@LINE+4]]:1: ERROR: `export` is only allowed in API files.
+// CHECK:STDERR: export import library "base";
+// CHECK:STDERR: ^~~~~~
+// CHECK:STDERR:
+export import library "base";
+
+// Note the import still occurs.
+var c: C = {.x = ()};
+
+// --- export_export.impl.carbon
+
+library "export_export" impl;
+
+var c: C = {.x = ()};
+
+// --- use_export_export.carbon
+
+library "use_export_export" api;
+
+import library "export_export";
+
+var c: C = {.x = ()};
+
+// --- use_import_then_export.carbon
+
+library "use_import_then_export" api;
+
+import library "import_then_export";
+
+var c: C = {.x = ()};
+
+// --- use_import_when_export.carbon
+
+library "use_import_when_export" api;
+
+export import library "base";
+
+var c: C = {.x = ()};
+
+// --- use_base_and_export.carbon
+
+library "use_base_and_export" api;
+
+import library "base";
+import library "export";
+
+var c: C = {.x = ()};
+
+// --- use_export_and_base.carbon
+
+library "import_both_reversed" api;
+
+import library "export";
+import library "base";
+
+var c: C = {.x = ()};
+
+// --- use_export_copy.carbon
+
+library "use_export_copy" api;
+
+import library "export";
+import library "export_copy";
+
+var c: C = {.x = ()};
+
+// --- fail_use_non_export.carbon
+
+library "fail_use_non_export" api;
+
+import library "non_export";
+
+// CHECK:STDERR: fail_use_non_export.carbon:[[@LINE+3]]:15: ERROR: Name `C` not found.
+// CHECK:STDERR: alias Local = C;
+// CHECK:STDERR:               ^
+alias Local = C;
+
+// --- use_non_export_then_base.carbon
+
+library "use_non_export_then_base" api;
+
+import library "non_export";
+export import library "base";
+
+var c: C = {.x = ()};
+
+// --- indirect_use_non_export_then_base.carbon
+
+library "indirect_use_non_export_then_base" api;
+
+import library "use_non_export_then_base";
+
+var indirect_c: C = {.x = ()};
+
+// CHECK:STDOUT: --- base.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = unbound_element_type C, () [template]
+// CHECK:STDOUT:   %.3: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %.loc5_11.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc5_11.2: type = converted %.loc5_11.1, constants.%.1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc5_8: <unbound element of class C> = field_decl x, element0 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   .x = %.loc5_8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- export.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref = import_ref ir1, inst+1, unloaded
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- export_copy.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref = import_ref ir1, inst+1, unloaded
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- non_export.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref = import_ref ir2, inst+1, unloaded
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- export_export.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref = import_ref ir2, inst+1, unloaded
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_then_export.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref = import_ref ir1, inst+1, unloaded
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- export_in_impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_export.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: ()} [template]
+// CHECK:STDOUT:   %tuple: () = tuple_value () [template]
+// CHECK:STDOUT:   %struct: C = struct_value (%tuple) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref.1
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir2, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir2, inst+7, unloaded
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir2, inst+2, unloaded
+// CHECK:STDOUT:   %C.ref: type = name_ref C, %import_ref.1 [template = constants.%C]
+// CHECK:STDOUT:   %c.var: ref C = var c
+// CHECK:STDOUT:   %c: ref C = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .x = file.%import_ref.2
+// CHECK:STDOUT:   .Self = file.%import_ref.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc6_19.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc6_20.1: {.x: ()} = struct_literal (%.loc6_19.1)
+// CHECK:STDOUT:   %.loc6_20.2: ref () = class_element_access file.%c.var, element0
+// CHECK:STDOUT:   %.loc6_19.2: init () = tuple_init () to %.loc6_20.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc6_20.3: init () = converted %.loc6_19.1, %.loc6_19.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc6_20.4: init C = class_init (%.loc6_20.3), file.%c.var [template = constants.%struct]
+// CHECK:STDOUT:   %.loc6_21: init C = converted %.loc6_20.1, %.loc6_20.4 [template = constants.%struct]
+// CHECK:STDOUT:   assign file.%c.var, %.loc6_21
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_export_in_impl.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: ()} [template]
+// CHECK:STDOUT:   %tuple: () = tuple_value () [template]
+// CHECK:STDOUT:   %struct: C = struct_value (%tuple) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref.1
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir1, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir1, inst+7, unloaded
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir1, inst+2, unloaded
+// CHECK:STDOUT:   %C.ref: type = name_ref C, %import_ref.1 [template = constants.%C]
+// CHECK:STDOUT:   %c.var: ref C = var c
+// CHECK:STDOUT:   %c: ref C = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .x = file.%import_ref.2
+// CHECK:STDOUT:   .Self = file.%import_ref.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc11_19.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc11_20.1: {.x: ()} = struct_literal (%.loc11_19.1)
+// CHECK:STDOUT:   %.loc11_20.2: ref () = class_element_access file.%c.var, element0
+// CHECK:STDOUT:   %.loc11_19.2: init () = tuple_init () to %.loc11_20.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc11_20.3: init () = converted %.loc11_19.1, %.loc11_19.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc11_20.4: init C = class_init (%.loc11_20.3), file.%c.var [template = constants.%struct]
+// CHECK:STDOUT:   %.loc11_21: init C = converted %.loc11_20.1, %.loc11_20.4 [template = constants.%struct]
+// CHECK:STDOUT:   assign file.%c.var, %.loc11_21
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- export_export.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: ()} [template]
+// CHECK:STDOUT:   %tuple: () = tuple_value () [template]
+// CHECK:STDOUT:   %struct: C = struct_value (%tuple) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref.1
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir2, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir2, inst+7, unloaded
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir2, inst+2, unloaded
+// CHECK:STDOUT:   %C.ref: type = name_ref C, %import_ref.1 [template = constants.%C]
+// CHECK:STDOUT:   %c.var: ref C = var c
+// CHECK:STDOUT:   %c: ref C = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .x = file.%import_ref.2
+// CHECK:STDOUT:   .Self = file.%import_ref.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc4_19.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc4_20.1: {.x: ()} = struct_literal (%.loc4_19.1)
+// CHECK:STDOUT:   %.loc4_20.2: ref () = class_element_access file.%c.var, element0
+// CHECK:STDOUT:   %.loc4_19.2: init () = tuple_init () to %.loc4_20.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc4_20.3: init () = converted %.loc4_19.1, %.loc4_19.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc4_20.4: init C = class_init (%.loc4_20.3), file.%c.var [template = constants.%struct]
+// CHECK:STDOUT:   %.loc4_21: init C = converted %.loc4_20.1, %.loc4_20.4 [template = constants.%struct]
+// CHECK:STDOUT:   assign file.%c.var, %.loc4_21
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_export_export.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: ()} [template]
+// CHECK:STDOUT:   %tuple: () = tuple_value () [template]
+// CHECK:STDOUT:   %struct: C = struct_value (%tuple) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref.1
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir3, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir3, inst+7, unloaded
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir3, inst+2, unloaded
+// CHECK:STDOUT:   %C.ref: type = name_ref C, %import_ref.1 [template = constants.%C]
+// CHECK:STDOUT:   %c.var: ref C = var c
+// CHECK:STDOUT:   %c: ref C = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .x = file.%import_ref.2
+// CHECK:STDOUT:   .Self = file.%import_ref.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc6_19.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc6_20.1: {.x: ()} = struct_literal (%.loc6_19.1)
+// CHECK:STDOUT:   %.loc6_20.2: ref () = class_element_access file.%c.var, element0
+// CHECK:STDOUT:   %.loc6_19.2: init () = tuple_init () to %.loc6_20.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc6_20.3: init () = converted %.loc6_19.1, %.loc6_19.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc6_20.4: init C = class_init (%.loc6_20.3), file.%c.var [template = constants.%struct]
+// CHECK:STDOUT:   %.loc6_21: init C = converted %.loc6_20.1, %.loc6_20.4 [template = constants.%struct]
+// CHECK:STDOUT:   assign file.%c.var, %.loc6_21
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_import_then_export.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: ()} [template]
+// CHECK:STDOUT:   %tuple: () = tuple_value () [template]
+// CHECK:STDOUT:   %struct: C = struct_value (%tuple) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref.1
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir2, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir2, inst+7, unloaded
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir2, inst+2, unloaded
+// CHECK:STDOUT:   %C.ref: type = name_ref C, %import_ref.1 [template = constants.%C]
+// CHECK:STDOUT:   %c.var: ref C = var c
+// CHECK:STDOUT:   %c: ref C = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .x = file.%import_ref.2
+// CHECK:STDOUT:   .Self = file.%import_ref.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc6_19.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc6_20.1: {.x: ()} = struct_literal (%.loc6_19.1)
+// CHECK:STDOUT:   %.loc6_20.2: ref () = class_element_access file.%c.var, element0
+// CHECK:STDOUT:   %.loc6_19.2: init () = tuple_init () to %.loc6_20.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc6_20.3: init () = converted %.loc6_19.1, %.loc6_19.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc6_20.4: init C = class_init (%.loc6_20.3), file.%c.var [template = constants.%struct]
+// CHECK:STDOUT:   %.loc6_21: init C = converted %.loc6_20.1, %.loc6_20.4 [template = constants.%struct]
+// CHECK:STDOUT:   assign file.%c.var, %.loc6_21
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_import_when_export.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: ()} [template]
+// CHECK:STDOUT:   %tuple: () = tuple_value () [template]
+// CHECK:STDOUT:   %struct: C = struct_value (%tuple) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref.1
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir1, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir1, inst+7, unloaded
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir1, inst+2, unloaded
+// CHECK:STDOUT:   %C.ref: type = name_ref C, %import_ref.1 [template = constants.%C]
+// CHECK:STDOUT:   %c.var: ref C = var c
+// CHECK:STDOUT:   %c: ref C = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .x = file.%import_ref.2
+// CHECK:STDOUT:   .Self = file.%import_ref.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc6_19.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc6_20.1: {.x: ()} = struct_literal (%.loc6_19.1)
+// CHECK:STDOUT:   %.loc6_20.2: ref () = class_element_access file.%c.var, element0
+// CHECK:STDOUT:   %.loc6_19.2: init () = tuple_init () to %.loc6_20.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc6_20.3: init () = converted %.loc6_19.1, %.loc6_19.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc6_20.4: init C = class_init (%.loc6_20.3), file.%c.var [template = constants.%struct]
+// CHECK:STDOUT:   %.loc6_21: init C = converted %.loc6_20.1, %.loc6_20.4 [template = constants.%struct]
+// CHECK:STDOUT:   assign file.%c.var, %.loc6_21
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_base_and_export.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: ()} [template]
+// CHECK:STDOUT:   %tuple: () = tuple_value () [template]
+// CHECK:STDOUT:   %struct: C = struct_value (%tuple) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref.1
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir1, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir1, inst+7, unloaded
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir1, inst+2, unloaded
+// CHECK:STDOUT:   %C.ref: type = name_ref C, %import_ref.1 [template = constants.%C]
+// CHECK:STDOUT:   %c.var: ref C = var c
+// CHECK:STDOUT:   %c: ref C = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .x = file.%import_ref.2
+// CHECK:STDOUT:   .Self = file.%import_ref.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc7_19.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc7_20.1: {.x: ()} = struct_literal (%.loc7_19.1)
+// CHECK:STDOUT:   %.loc7_20.2: ref () = class_element_access file.%c.var, element0
+// CHECK:STDOUT:   %.loc7_19.2: init () = tuple_init () to %.loc7_20.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc7_20.3: init () = converted %.loc7_19.1, %.loc7_19.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc7_20.4: init C = class_init (%.loc7_20.3), file.%c.var [template = constants.%struct]
+// CHECK:STDOUT:   %.loc7_21: init C = converted %.loc7_20.1, %.loc7_20.4 [template = constants.%struct]
+// CHECK:STDOUT:   assign file.%c.var, %.loc7_21
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_export_and_base.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: ()} [template]
+// CHECK:STDOUT:   %tuple: () = tuple_value () [template]
+// CHECK:STDOUT:   %struct: C = struct_value (%tuple) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref.1
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir2, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir2, inst+7, unloaded
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir2, inst+2, unloaded
+// CHECK:STDOUT:   %C.ref: type = name_ref C, %import_ref.1 [template = constants.%C]
+// CHECK:STDOUT:   %c.var: ref C = var c
+// CHECK:STDOUT:   %c: ref C = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .x = file.%import_ref.2
+// CHECK:STDOUT:   .Self = file.%import_ref.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc7_19.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc7_20.1: {.x: ()} = struct_literal (%.loc7_19.1)
+// CHECK:STDOUT:   %.loc7_20.2: ref () = class_element_access file.%c.var, element0
+// CHECK:STDOUT:   %.loc7_19.2: init () = tuple_init () to %.loc7_20.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc7_20.3: init () = converted %.loc7_19.1, %.loc7_19.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc7_20.4: init C = class_init (%.loc7_20.3), file.%c.var [template = constants.%struct]
+// CHECK:STDOUT:   %.loc7_21: init C = converted %.loc7_20.1, %.loc7_20.4 [template = constants.%struct]
+// CHECK:STDOUT:   assign file.%c.var, %.loc7_21
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_export_copy.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: ()} [template]
+// CHECK:STDOUT:   %tuple: () = tuple_value () [template]
+// CHECK:STDOUT:   %struct: C = struct_value (%tuple) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref.1
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir2, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir2, inst+7, unloaded
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir2, inst+2, unloaded
+// CHECK:STDOUT:   %C.ref: type = name_ref C, %import_ref.1 [template = constants.%C]
+// CHECK:STDOUT:   %c.var: ref C = var c
+// CHECK:STDOUT:   %c: ref C = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .x = file.%import_ref.2
+// CHECK:STDOUT:   .Self = file.%import_ref.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc7_19.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc7_20.1: {.x: ()} = struct_literal (%.loc7_19.1)
+// CHECK:STDOUT:   %.loc7_20.2: ref () = class_element_access file.%c.var, element0
+// CHECK:STDOUT:   %.loc7_19.2: init () = tuple_init () to %.loc7_20.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc7_20.3: init () = converted %.loc7_19.1, %.loc7_19.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc7_20.4: init C = class_init (%.loc7_20.3), file.%c.var [template = constants.%struct]
+// CHECK:STDOUT:   %.loc7_21: init C = converted %.loc7_20.1, %.loc7_20.4 [template = constants.%struct]
+// CHECK:STDOUT:   assign file.%c.var, %.loc7_21
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_use_non_export.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Local = %Local
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.ref: <error> = name_ref C, <error> [template = <error>]
+// CHECK:STDOUT:   %Local: <error> = bind_alias Local, <error> [template = <error>]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_non_export_then_base.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: ()} [template]
+// CHECK:STDOUT:   %tuple: () = tuple_value () [template]
+// CHECK:STDOUT:   %struct: C = struct_value (%tuple) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref.1
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir2, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %import_ref.2 = import_ref ir2, inst+7, unloaded
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir2, inst+2, unloaded
+// CHECK:STDOUT:   %C.ref: type = name_ref C, %import_ref.1 [template = constants.%C]
+// CHECK:STDOUT:   %c.var: ref C = var c
+// CHECK:STDOUT:   %c: ref C = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .x = file.%import_ref.2
+// CHECK:STDOUT:   .Self = file.%import_ref.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc7_19.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc7_20.1: {.x: ()} = struct_literal (%.loc7_19.1)
+// CHECK:STDOUT:   %.loc7_20.2: ref () = class_element_access file.%c.var, element0
+// CHECK:STDOUT:   %.loc7_19.2: init () = tuple_init () to %.loc7_20.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc7_20.3: init () = converted %.loc7_19.1, %.loc7_19.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc7_20.4: init C = class_init (%.loc7_20.3), file.%c.var [template = constants.%struct]
+// CHECK:STDOUT:   %.loc7_21: init C = converted %.loc7_20.1, %.loc7_20.4 [template = constants.%struct]
+// CHECK:STDOUT:   assign file.%c.var, %.loc7_21
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- indirect_use_non_export_then_base.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: ()} [template]
+// CHECK:STDOUT:   %tuple: () = tuple_value () [template]
+// CHECK:STDOUT:   %struct: C = struct_value (%tuple) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .c = %import_ref.1
+// CHECK:STDOUT:     .C = %import_ref.2
+// CHECK:STDOUT:     .indirect_c = %indirect_c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+13, unloaded
+// CHECK:STDOUT:   %import_ref.2: type = import_ref ir2, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir2, inst+7, unloaded
+// CHECK:STDOUT:   %import_ref.4 = import_ref ir2, inst+2, unloaded
+// CHECK:STDOUT:   %C.ref: type = name_ref C, %import_ref.2 [template = constants.%C]
+// CHECK:STDOUT:   %indirect_c.var: ref C = var indirect_c
+// CHECK:STDOUT:   %indirect_c: ref C = bind_name indirect_c, %indirect_c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .x = file.%import_ref.3
+// CHECK:STDOUT:   .Self = file.%import_ref.4
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc6_28.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc6_29.1: {.x: ()} = struct_literal (%.loc6_28.1)
+// CHECK:STDOUT:   %.loc6_29.2: ref () = class_element_access file.%indirect_c.var, element0
+// CHECK:STDOUT:   %.loc6_28.2: init () = tuple_init () to %.loc6_29.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc6_29.3: init () = converted %.loc6_28.1, %.loc6_28.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc6_29.4: init C = class_init (%.loc6_29.3), file.%indirect_c.var [template = constants.%struct]
+// CHECK:STDOUT:   %.loc6_30: init C = converted %.loc6_29.1, %.loc6_29.4 [template = constants.%struct]
+// CHECK:STDOUT:   assign file.%indirect_c.var, %.loc6_30
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 385 - 0
toolchain/check/testdata/packages/no_prelude/export_name.carbon

@@ -0,0 +1,385 @@
+// 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
+
+// ============================================================================
+// Setup files
+// ============================================================================
+
+// --- class.carbon
+
+library "class" api;
+
+class C {
+  var x: ();
+};
+
+namespace NS;
+class NS.NSC {
+  var x: ();
+};
+
+// --- fail_todo_export_class.carbon
+
+library "export_class" api;
+
+import library "class";
+// CHECK:STDERR: fail_todo_export_class.carbon:[[@LINE+4]]:1: ERROR: Semantics TODO: `ExportDirective`.
+// CHECK:STDERR: export C;
+// CHECK:STDERR: ^~~~~~~~~
+// CHECK:STDERR:
+export C;
+
+// --- non_export_class.carbon
+
+library "non_export_class" api;
+
+import library "export_class";
+
+// --- fail_todo_export_export_class.carbon
+
+library "export_export_class" api;
+
+import library "export_class";
+// CHECK:STDERR: fail_todo_export_export_class.carbon:[[@LINE+4]]:1: ERROR: Semantics TODO: `ExportDirective`.
+// CHECK:STDERR: export C;
+// CHECK:STDERR: ^~~~~~~~~
+// CHECK:STDERR:
+export C;
+
+// --- export_in_impl.carbon
+
+// This is just providing an API for the implicit import.
+library "export_in_impl" api;
+
+// ============================================================================
+// Test files
+// ============================================================================
+
+// --- fail_todo_use_export_class.carbon
+
+library "use_export_class" api;
+
+import library "export_class";
+
+// CHECK:STDERR: fail_todo_use_export_class.carbon:[[@LINE+4]]:8: ERROR: Name `C` not found.
+// CHECK:STDERR: var c: C = {.x = ()};
+// CHECK:STDERR:        ^
+// CHECK:STDERR:
+var c: C = {.x = ()};
+
+// --- fail_todo_export_in_impl.impl.carbon
+
+library "export_in_impl" impl;
+
+import library "class";
+// CHECK:STDERR: fail_todo_export_in_impl.impl.carbon:[[@LINE+8]]:1: ERROR: `export` is only allowed in API files.
+// CHECK:STDERR: export C;
+// CHECK:STDERR: ^~~~~~
+// CHECK:STDERR:
+// CHECK:STDERR: fail_todo_export_in_impl.impl.carbon:[[@LINE+4]]:1: ERROR: Semantics TODO: `ExportDirective`.
+// CHECK:STDERR: export C;
+// CHECK:STDERR: ^~~~~~~~~
+// CHECK:STDERR:
+export C;
+
+// --- fail_todo_export_export_class.impl.carbon
+
+library "export_export_class" impl;
+
+// CHECK:STDERR: fail_todo_export_export_class.impl.carbon:[[@LINE+4]]:8: ERROR: Name `C` not found.
+// CHECK:STDERR: var c: C = {.x = ()};
+// CHECK:STDERR:        ^
+// CHECK:STDERR:
+var c: C = {.x = ()};
+
+// --- fail_todo_use_export_export_class.carbon
+
+library "use_export_export_class" api;
+
+import library "export_export_class";
+
+// CHECK:STDERR: fail_todo_use_export_export_class.carbon:[[@LINE+4]]:8: ERROR: Name `C` not found.
+// CHECK:STDERR: var c: C = {.x = ()};
+// CHECK:STDERR:        ^
+// CHECK:STDERR:
+var c: C = {.x = ()};
+
+// --- import_both.carbon
+
+library "import_both" api;
+
+import library "class";
+import library "export_class";
+
+var c: C = {.x = ()};
+
+// --- import_both_reversed.carbon
+
+library "import_both_reversed" api;
+
+import library "export_class";
+import library "class";
+
+var c: C = {.x = ()};
+
+// --- fail_use_non_export_class.carbon
+
+library "fail_use_non_export_class" api;
+
+import library "non_export_class";
+
+// CHECK:STDERR: fail_use_non_export_class.carbon:[[@LINE+3]]:15: ERROR: Name `C` not found.
+// CHECK:STDERR: alias Local = C;
+// CHECK:STDERR:               ^
+alias Local = C;
+
+// CHECK:STDOUT: --- class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = unbound_element_type C, () [template]
+// CHECK:STDOUT:   %.3: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT:   %NSC: type = class_type @NSC [template]
+// CHECK:STDOUT:   %.4: type = unbound_element_type NSC, () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .NS = %NS
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {}
+// CHECK:STDOUT:   %NS: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .NSC = %NSC.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %NSC.decl: type = class_decl @NSC [template = constants.%NSC] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %.loc5_11.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc5_11.2: type = converted %.loc5_11.1, constants.%.1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc5_8: <unbound element of class C> = field_decl x, element0 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   .x = %.loc5_8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @NSC {
+// CHECK:STDOUT:   %.loc10_11.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc10_11.2: type = converted %.loc10_11.1, constants.%.1 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc10_8: <unbound element of class NSC> = field_decl x, element0 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%NSC
+// CHECK:STDOUT:   .x = %.loc10_8
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_export_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- non_export_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_export_export_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- export_in_impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_use_export_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.ref: <error> = name_ref C, <error> [template = <error>]
+// CHECK:STDOUT:   %c.var: ref <error> = var c
+// CHECK:STDOUT:   %c: ref <error> = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc10_19: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc10_20: {.x: ()} = struct_literal (%.loc10_19)
+// CHECK:STDOUT:   assign file.%c.var, <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_export_in_impl.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_export_export_class.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.ref: <error> = name_ref C, <error> [template = <error>]
+// CHECK:STDOUT:   %c.var: ref <error> = var c
+// CHECK:STDOUT:   %c: ref <error> = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc8_19: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc8_20: {.x: ()} = struct_literal (%.loc8_19)
+// CHECK:STDOUT:   assign file.%c.var, <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_use_export_export_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.ref: <error> = name_ref C, <error> [template = <error>]
+// CHECK:STDOUT:   %c.var: ref <error> = var c
+// CHECK:STDOUT:   %c: ref <error> = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc10_19: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc10_20: {.x: ()} = struct_literal (%.loc10_19)
+// CHECK:STDOUT:   assign file.%c.var, <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_both.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: ()} [template]
+// CHECK:STDOUT:   %tuple: () = tuple_value () [template]
+// CHECK:STDOUT:   %struct: C = struct_value (%tuple) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref.1
+// CHECK:STDOUT:     .NS = %NS
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir1, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %import_ref.2: <namespace> = import_ref ir1, inst+11, loaded
+// CHECK:STDOUT:   %NS: <namespace> = namespace %import_ref.2, [template] {
+// CHECK:STDOUT:     .NSC = %import_ref.3
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir1, inst+12, unloaded
+// CHECK:STDOUT:   %import_ref.4 = import_ref ir1, inst+7, unloaded
+// CHECK:STDOUT:   %import_ref.5 = import_ref ir1, inst+2, unloaded
+// CHECK:STDOUT:   %C.ref: type = name_ref C, %import_ref.1 [template = constants.%C]
+// CHECK:STDOUT:   %c.var: ref C = var c
+// CHECK:STDOUT:   %c: ref C = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .x = file.%import_ref.4
+// CHECK:STDOUT:   .Self = file.%import_ref.5
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc7_19.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc7_20.1: {.x: ()} = struct_literal (%.loc7_19.1)
+// CHECK:STDOUT:   %.loc7_20.2: ref () = class_element_access file.%c.var, element0
+// CHECK:STDOUT:   %.loc7_19.2: init () = tuple_init () to %.loc7_20.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc7_20.3: init () = converted %.loc7_19.1, %.loc7_19.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc7_20.4: init C = class_init (%.loc7_20.3), file.%c.var [template = constants.%struct]
+// CHECK:STDOUT:   %.loc7_21: init C = converted %.loc7_20.1, %.loc7_20.4 [template = constants.%struct]
+// CHECK:STDOUT:   assign file.%c.var, %.loc7_21
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_both_reversed.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %.2: type = struct_type {.x: ()} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type {.x: ()} [template]
+// CHECK:STDOUT:   %tuple: () = tuple_value () [template]
+// CHECK:STDOUT:   %struct: C = struct_value (%tuple) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %import_ref.1
+// CHECK:STDOUT:     .NS = %NS
+// CHECK:STDOUT:     .c = %c
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.1: type = import_ref ir2, inst+1, loaded [template = constants.%C]
+// CHECK:STDOUT:   %import_ref.2: <namespace> = import_ref ir2, inst+11, loaded
+// CHECK:STDOUT:   %NS: <namespace> = namespace %import_ref.2, [template] {
+// CHECK:STDOUT:     .NSC = %import_ref.3
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref.3 = import_ref ir2, inst+12, unloaded
+// CHECK:STDOUT:   %import_ref.4 = import_ref ir2, inst+7, unloaded
+// CHECK:STDOUT:   %import_ref.5 = import_ref ir2, inst+2, unloaded
+// CHECK:STDOUT:   %C.ref: type = name_ref C, %import_ref.1 [template = constants.%C]
+// CHECK:STDOUT:   %c.var: ref C = var c
+// CHECK:STDOUT:   %c: ref C = bind_name c, %c.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .x = file.%import_ref.4
+// CHECK:STDOUT:   .Self = file.%import_ref.5
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc7_19.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc7_20.1: {.x: ()} = struct_literal (%.loc7_19.1)
+// CHECK:STDOUT:   %.loc7_20.2: ref () = class_element_access file.%c.var, element0
+// CHECK:STDOUT:   %.loc7_19.2: init () = tuple_init () to %.loc7_20.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc7_20.3: init () = converted %.loc7_19.1, %.loc7_19.2 [template = constants.%tuple]
+// CHECK:STDOUT:   %.loc7_20.4: init C = class_init (%.loc7_20.3), file.%c.var [template = constants.%struct]
+// CHECK:STDOUT:   %.loc7_21: init C = converted %.loc7_20.1, %.loc7_20.4 [template = constants.%struct]
+// CHECK:STDOUT:   assign file.%c.var, %.loc7_21
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_use_non_export_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Local = %Local
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.ref: <error> = name_ref C, <error> [template = <error>]
+// CHECK:STDOUT:   %Local: <error> = bind_alias Local, <error> [template = <error>]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 2 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -99,6 +99,7 @@ CARBON_DIAGNOSTIC_KIND(ExpectedLibraryName)
 CARBON_DIAGNOSTIC_KIND(ExpectedLibraryNameOrDefault)
 CARBON_DIAGNOSTIC_KIND(MissingLibraryKeyword)
 CARBON_DIAGNOSTIC_KIND(ExpectedApiOrImpl)
+CARBON_DIAGNOSTIC_KIND(ExportImportPackage)
 
 // For-specific diagnostics.
 CARBON_DIAGNOSTIC_KIND(ExpectedIn)
@@ -148,6 +149,7 @@ CARBON_DIAGNOSTIC_KIND(ImportSelf)
 CARBON_DIAGNOSTIC_KIND(ExplicitImportApi)
 CARBON_DIAGNOSTIC_KIND(RepeatedImport)
 CARBON_DIAGNOSTIC_KIND(FirstImported)
+CARBON_DIAGNOSTIC_KIND(ExportFromImpl)
 
 // Merge-related redeclaration checking.
 CARBON_DIAGNOSTIC_KIND(RedeclPrevDecl)

+ 1 - 0
toolchain/lex/token_kind.def

@@ -159,6 +159,7 @@ CARBON_KEYWORD_TOKEN(Continue,            "continue")
 CARBON_KEYWORD_TOKEN(Default,             "default")
 CARBON_KEYWORD_TOKEN(Destructor,          "destructor")
 CARBON_KEYWORD_TOKEN(Else,                "else")
+CARBON_KEYWORD_TOKEN(Export,              "export")
 CARBON_KEYWORD_TOKEN(Extend,              "extend")
 CARBON_KEYWORD_TOKEN(Extern,              "extern")
 CARBON_KEYWORD_TOKEN(False,               "false")

+ 11 - 8
toolchain/parse/handle_decl_scope_loop.cpp

@@ -19,7 +19,11 @@ static auto TryHandleEndOrPackagingDecl(Context& context) -> bool {
       context.PopAndDiscardState();
       return true;
     }
-    // `import`, `library`, and `package` manage their packaging state.
+    // Packaging-related keywords manage their packaging state.
+    case Lex::TokenKind::Export: {
+      context.PushState(State::Export);
+      return true;
+    }
     case Lex::TokenKind::Import: {
       context.PushState(State::Import);
       return true;
@@ -32,7 +36,7 @@ static auto TryHandleEndOrPackagingDecl(Context& context) -> bool {
       context.PushState(State::Package);
       return true;
     }
-    default: {
+    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() !=
@@ -44,7 +48,6 @@ static auto TryHandleEndOrPackagingDecl(Context& context) -> bool {
             Context::PackagingState::AfterNonPackagingDecl);
       }
       return false;
-    }
   }
 }
 
@@ -124,6 +127,11 @@ static auto TryHandleAsDecl(Context& context, Context::StateStackEntry state,
       HandleBaseAsDecl(context, state);
       return true;
     }
+    case Lex::TokenKind::Choice: {
+      ApplyIntroducer(context, state, NodeKind::ChoiceIntroducer,
+                      State::ChoiceIntroducer);
+      return true;
+    }
     case Lex::TokenKind::Class: {
       ApplyIntroducer(context, state, NodeKind::ClassIntroducer,
                       State::TypeAfterIntroducerAsClass);
@@ -168,11 +176,6 @@ static auto TryHandleAsDecl(Context& context, Context::StateStackEntry state,
                       State::VarAsDecl);
       return true;
     }
-    case Lex::TokenKind::Choice: {
-      ApplyIntroducer(context, state, NodeKind::ChoiceIntroducer,
-                      State::ChoiceIntroducer);
-      return true;
-    }
 
     case Lex::TokenKind::Semi: {
       if (saw_modifier) {

+ 90 - 23
toolchain/parse/handle_import_and_package.cpp

@@ -73,14 +73,23 @@ static auto HandleApiOrImpl(Context& context)
 // Handles everything after the directive's introducer.
 static auto HandleDirectiveContent(Context& context,
                                    Context::StateStackEntry state,
-                                   NodeKind directive,
+                                   NodeKind directive, bool is_export,
                                    llvm::function_ref<void()> on_parse_error)
     -> void {
   Tree::PackagingNames names{
-      .node_id = ImportDirectiveId(NodeId(state.subtree_start))};
+      .node_id = ImportDirectiveId(NodeId(state.subtree_start)),
+      .is_export = is_export};
   if (directive != NodeKind::LibraryDirective) {
     if (auto package_name_token =
             context.ConsumeIf(Lex::TokenKind::Identifier)) {
+      if (names.is_export) {
+        names.is_export = false;
+        state.has_error = true;
+
+        CARBON_DIAGNOSTIC(ExportImportPackage, Error,
+                          "`export` cannot be used when importing a package.");
+        context.emitter().Emit(*package_name_token, ExportImportPackage);
+      }
       names.package_id = context.tokens().GetIdentifier(*package_name_token);
       context.AddLeafNode(NodeKind::PackageName, *package_name_token);
     } else if (directive == NodeKind::PackageDirective ||
@@ -157,49 +166,106 @@ static auto HandleDirectiveContent(Context& context,
   }
 }
 
-auto HandleImport(Context& context) -> void {
-  auto state = context.PopState();
-
-  auto directive = NodeKind::ImportDirective;
-  auto on_parse_error = [&] { OnParseError(context, state, directive); };
-
-  auto intro_token = context.ConsumeChecked(Lex::TokenKind::Import);
-  context.AddLeafNode(NodeKind::ImportIntroducer, intro_token);
-
+// Returns true if currently in a valid state for imports, false otherwise. May
+// update the packaging state respectively.
+static auto VerifyInImports(Context& context, Lex::TokenIndex intro_token)
+    -> bool {
   switch (context.packaging_state()) {
     case Context::PackagingState::FileStart:
       // `package` is no longer allowed, but `import` may repeat.
       context.set_packaging_state(Context::PackagingState::InImports);
-      [[fallthrough]];
+      return true;
 
     case Context::PackagingState::InImports:
-      HandleDirectiveContent(context, state, directive, on_parse_error);
-      break;
+      return true;
 
     case Context::PackagingState::AfterNonPackagingDecl: {
       context.set_packaging_state(
           Context::PackagingState::InImportsAfterNonPackagingDecl);
-      CARBON_DIAGNOSTIC(
-          ImportTooLate, Error,
-          "`import` directives must come after the `package` directive (if "
-          "present) and before any other entities in the file.");
+      CARBON_DIAGNOSTIC(ImportTooLate, Error,
+                        "`import` and `export` directives must come after the "
+                        "`package` directive (if present) and before any other "
+                        "entities in the file.");
       CARBON_DIAGNOSTIC(FirstDecl, Note, "First declaration is here.");
       context.emitter()
           .Build(intro_token, ImportTooLate)
           .Note(context.first_non_packaging_token(), FirstDecl)
           .Emit();
-      on_parse_error();
-      break;
+      return false;
     }
+
     case Context::PackagingState::InImportsAfterNonPackagingDecl:
       // There is a sequential block of misplaced `import` statements, which can
       // occur if a declaration is added above `import`s. Avoid duplicate
       // warnings.
-      on_parse_error();
-      break;
+      return false;
   }
 }
 
+// Common logic for both `import` and `export import`, distinguished by whether
+// `export_token` is valid.
+static auto HandleImportHelper(Context& context,
+                               const Context::StateStackEntry& state,
+                               Lex::TokenIndex export_token) -> void {
+  auto directive = NodeKind::ImportDirective;
+  auto on_parse_error = [&] { OnParseError(context, state, directive); };
+
+  auto intro_token = context.ConsumeChecked(Lex::TokenKind::Import);
+  context.AddLeafNode(NodeKind::ImportIntroducer, intro_token);
+
+  if (export_token.is_valid()) {
+    context.AddLeafNode(NodeKind::ImportExport, export_token);
+  }
+
+  if (VerifyInImports(context, intro_token)) {
+    HandleDirectiveContent(context, state, directive, export_token.is_valid(),
+                           on_parse_error);
+  } else {
+    on_parse_error();
+  }
+}
+
+auto HandleExport(Context& context) -> void {
+  auto state = context.PopState();
+
+  context.ConsumeChecked(Lex::TokenKind::Export);
+
+  // Error for both Main//default and every implementation file.
+  auto packaging = context.tree().packaging_directive();
+  if (!packaging || packaging->api_or_impl == Tree::ApiOrImpl::Impl) {
+    CARBON_DIAGNOSTIC(ExportFromImpl, Error,
+                      "`export` is only allowed in API files.");
+    context.emitter().Emit(state.token, ExportFromImpl);
+    state.has_error = true;
+  }
+
+  if (context.PositionIs(Lex::TokenKind::Import)) {
+    HandleImportHelper(context, state, state.token);
+  } else {
+    if (!VerifyInImports(context, state.token)) {
+      state.has_error = true;
+    }
+    context.AddLeafNode(NodeKind::ExportIntroducer, state.token,
+                        state.has_error);
+    context.PushState(state, State::ExportFinish);
+    context.PushState(State::DeclNameAndParamsAsNone, state.token);
+  }
+}
+
+auto HandleExportFinish(Context& context) -> void {
+  auto state = context.PopState();
+
+  context.AddNodeExpectingDeclSemi(state, NodeKind::ExportDirective,
+                                   Lex::TokenKind::Export,
+                                   /*is_def_allowed=*/false);
+}
+
+auto HandleImport(Context& context) -> void {
+  auto state = context.PopState();
+
+  HandleImportHelper(context, state, /*export_token=*/Lex::TokenIndex::Invalid);
+}
+
 // Handles common logic for `package` and `library`.
 static auto HandlePackageAndLibraryDirectives(Context& context,
                                               Lex::TokenKind intro_token_kind,
@@ -229,7 +295,8 @@ static auto HandlePackageAndLibraryDirectives(Context& context,
   // `package`/`library` is no longer allowed, but `import` may repeat.
   context.set_packaging_state(Context::PackagingState::InImports);
 
-  HandleDirectiveContent(context, state, directive, on_parse_error);
+  HandleDirectiveContent(context, state, directive, /*is_export=*/false,
+                         on_parse_error);
 }
 
 auto HandlePackage(Context& context) -> void {

+ 10 - 0
toolchain/parse/node_kind.def

@@ -194,12 +194,15 @@ CARBON_PARSE_NODE_KIND_BRACKET(PackageDirective, PackageIntroducer,
 
 // `import`:
 //   ImportIntroducer
+//   _optional_: ImportExport
 //   _optional_ _external_: PackageName
 //   _optional_ _external_: LibrarySpecifier
 // ImportDirective
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(ImportIntroducer, 0, Import)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ImportExport, 0, Export)
 CARBON_PARSE_NODE_KIND_BRACKET(ImportDirective, ImportIntroducer,
                                CARBON_IF_VALID(Semi))
+
 // `library` as directive:
 //   LibraryIntroducer
 //   DefaultLibrary or _external_: LibraryName
@@ -215,6 +218,13 @@ CARBON_PARSE_NODE_KIND_BRACKET(LibraryDirective, LibraryIntroducer,
 // LibrarySpecifier
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(LibrarySpecifier, 1, Library)
 
+// `export`:
+//   ExportIntroducer
+//   _external_: IdentifierName or QualifiedName
+// ExportDirective
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ExportIntroducer, 0, Export)
+CARBON_PARSE_NODE_KIND_CHILD_COUNT(ExportDirective, 2, CARBON_IF_VALID(Semi))
+
 // `namespace`:
 //   NamespaceStart
 //   _repeated_ _external_: modifier

+ 21 - 0
toolchain/parse/state.def

@@ -670,12 +670,33 @@ CARBON_PARSE_STATE(FunctionSignatureFinish)
 //   (state done)
 CARBON_PARSE_STATE(FunctionDefinitionFinish)
 
+// Handles `export`.
+//
+// export import library "libname";
+// ^~~~~~
+//   1. Import
+//
+// export Name;
+// ^~~~~~
+//   1. DeclNameAndParamsAsNone
+//   2. ExportFinish
+CARBON_PARSE_STATE(Export)
+
+// Finishes an `export`.
+//
+// export Name;
+//            ^
+//   (state done)
+CARBON_PARSE_STATE(ExportFinish)
+
 // Handles `import`.
 //
 // import pkgname [library "libname"] ;
 // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // import ??? ;
 // ^~~~~~~~~~~~
+// export import library "libname";
+//        ^~~~~~~~~~~~~~~~~~~~~~~~~
 //   (state done)
 CARBON_PARSE_STATE(Import)
 

+ 267 - 0
toolchain/parse/testdata/packages/export.carbon

@@ -0,0 +1,267 @@
+// 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
+
+// --- name.carbon
+
+package Pkg api;
+
+export Foo;
+
+// --- qual_name.carbon
+
+package Pkg api;
+
+export Foo.Bar;
+
+// --- fail_expr.carbon
+
+package Pkg api;
+
+// CHECK:STDERR: fail_expr.carbon:[[@LINE+4]]:8: ERROR: `export` introducer should be followed by a name.
+// CHECK:STDERR: export ();
+// CHECK:STDERR:        ^
+// CHECK:STDERR:
+export ();
+
+// --- fail_package_in_name.carbon
+
+package Pkg api;
+
+// CHECK:STDERR: fail_package_in_name.carbon:[[@LINE+4]]:8: ERROR: `export` introducer should be followed by a name.
+// CHECK:STDERR: export package.Bar;
+// CHECK:STDERR:        ^~~~~~~
+// CHECK:STDERR:
+export package.Bar;
+
+// --- fail_keyword_only
+
+package Pkg api;
+
+// CHECK:STDERR: fail_keyword_only:[[@LINE+4]]:1: ERROR: `export` introducer should be followed by a name.
+// CHECK:STDERR: export
+// CHECK:STDERR: ^~~~~~
+// CHECK:STDERR:
+export
+
+// --- fail_no_name.carbon
+
+package Pkg api;
+
+// CHECK:STDERR: fail_no_name.carbon:[[@LINE+4]]:8: ERROR: `export` introducer should be followed by a name.
+// CHECK:STDERR: export ;
+// CHECK:STDERR:        ^
+// CHECK:STDERR:
+export ;
+
+// --- fail_no_semi.carbon
+
+package Pkg api;
+
+export Foo
+// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+4]]:1: ERROR: `export` declarations must end with a `;`.
+// CHECK:STDERR:
+// CHECK:STDERR: ^
+// CHECK:STDERR:
+
+// --- fail_incomplete_qual_name.carbon
+
+package Pkg api;
+
+// CHECK:STDERR: fail_incomplete_qual_name.carbon:[[@LINE+4]]:12: ERROR: Expected identifier after `.`.
+// CHECK:STDERR: export Foo.;
+// CHECK:STDERR:            ^
+// CHECK:STDERR:
+export Foo.;
+
+// --- fail_incomplete_qual_name2.carbon
+
+package Pkg api;
+
+// CHECK:STDERR: fail_incomplete_qual_name2.carbon:[[@LINE+4]]:8: ERROR: `export` introducer should be followed by a name.
+// CHECK:STDERR: export .Bar;
+// CHECK:STDERR:        ^
+// CHECK:STDERR:
+export .Bar;
+
+// --- fail_after_decl.carbon
+
+package Pkg api;
+
+class C;
+
+// CHECK:STDERR: fail_after_decl.carbon:[[@LINE+7]]:1: ERROR: `import` and `export` directives must come after the `package` directive (if present) and before any other entities in the file.
+// CHECK:STDERR: export Foo;
+// CHECK:STDERR: ^~~~~~
+// CHECK:STDERR: fail_after_decl.carbon:[[@LINE-5]]:1: First declaration is here.
+// CHECK:STDERR: class C;
+// CHECK:STDERR: ^~~~~
+// CHECK:STDERR:
+export Foo;
+
+// --- fail_in_default_library.carbon
+
+// CHECK:STDERR: fail_in_default_library.carbon:[[@LINE+4]]:1: ERROR: `export` is only allowed in API files.
+// CHECK:STDERR: export Foo;
+// CHECK:STDERR: ^~~~~~
+// CHECK:STDERR:
+export Foo;
+
+// --- fail_in_impl.carbon
+
+package Pkg impl;
+
+// CHECK:STDERR: fail_in_impl.carbon:[[@LINE+3]]:1: ERROR: `export` is only allowed in API files.
+// CHECK:STDERR: export Foo;
+// CHECK:STDERR: ^~~~~~
+export Foo;
+
+// CHECK:STDOUT: - filename: name.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ExportIntroducer', text: 'export'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'Foo'},
+// CHECK:STDOUT:     {kind: 'ExportDirective', text: ';', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: qual_name.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ExportIntroducer', text: 'export'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'Foo'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'Bar'},
+// CHECK:STDOUT:       {kind: 'QualifiedName', text: '.', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'ExportDirective', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_expr.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ExportIntroducer', text: 'export'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: '(', has_error: yes},
+// CHECK:STDOUT:     {kind: 'ExportDirective', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_package_in_name.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ExportIntroducer', text: 'export'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: 'package', has_error: yes},
+// CHECK:STDOUT:     {kind: 'ExportDirective', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_keyword_only
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ExportIntroducer', text: 'export'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: '', has_error: yes},
+// CHECK:STDOUT:     {kind: 'ExportDirective', text: 'export', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_no_name.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ExportIntroducer', text: 'export'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: ';', has_error: yes},
+// CHECK:STDOUT:     {kind: 'ExportDirective', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_no_semi.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ExportIntroducer', text: 'export'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'Foo'},
+// CHECK:STDOUT:     {kind: 'ExportDirective', text: 'Foo', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_incomplete_qual_name.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ExportIntroducer', text: 'export'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'Foo'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'QualifiedName', text: '.', subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'ExportDirective', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_incomplete_qual_name2.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ExportIntroducer', text: 'export'},
+// CHECK:STDOUT:       {kind: 'InvalidParse', text: '.', has_error: yes},
+// CHECK:STDOUT:     {kind: 'ExportDirective', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_after_decl.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ClassIntroducer', text: 'class'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'C'},
+// CHECK:STDOUT:     {kind: 'ClassDecl', text: ';', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'ExportIntroducer', text: 'export', has_error: yes},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'Foo'},
+// CHECK:STDOUT:     {kind: 'ExportDirective', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_in_default_library.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ExportIntroducer', text: 'export', has_error: yes},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'Foo'},
+// CHECK:STDOUT:     {kind: 'ExportDirective', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_in_impl.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageImpl', text: 'impl'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ExportIntroducer', text: 'export', has_error: yes},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'Foo'},
+// CHECK:STDOUT:     {kind: 'ExportDirective', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 147 - 0
toolchain/parse/testdata/packages/import/export.carbon

@@ -0,0 +1,147 @@
+// 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
+
+// --- fail_package.carbon
+
+package Pkg api;
+
+// CHECK:STDERR: fail_package.carbon:[[@LINE+4]]:15: ERROR: `export` cannot be used when importing a package.
+// CHECK:STDERR: export import Bar;
+// CHECK:STDERR:               ^~~
+// CHECK:STDERR:
+export import Bar;
+
+// --- default.carbon
+
+package Pkg api;
+
+export import library default;
+
+// --- named.carbon
+
+package Pkg api;
+
+export import library "lib";
+
+// --- fail_order.carbon
+
+package Pkg api;
+
+export import library "a";
+
+class C;
+
+// CHECK:STDERR: fail_order.carbon:[[@LINE+7]]:8: ERROR: `import` and `export` directives must come after the `package` directive (if present) and before any other entities in the file.
+// CHECK:STDERR: export import library "b";
+// CHECK:STDERR:        ^~~~~~
+// CHECK:STDERR: fail_order.carbon:[[@LINE-5]]:1: First declaration is here.
+// CHECK:STDERR: class C;
+// CHECK:STDERR: ^~~~~
+// CHECK:STDERR:
+export import library "b";
+
+// --- fail_in_default_library.carbon
+
+// CHECK:STDERR: fail_in_default_library.carbon:[[@LINE+4]]:1: ERROR: `export` is only allowed in API files.
+// CHECK:STDERR: export import library "lib";
+// CHECK:STDERR: ^~~~~~
+// CHECK:STDERR:
+export import library "lib";
+
+// --- fail_in_impl.carbon
+
+package Pkg impl;
+
+// CHECK:STDERR: fail_in_impl.carbon:[[@LINE+3]]:1: ERROR: `export` is only allowed in API files.
+// CHECK:STDERR: export import library "lib";
+// CHECK:STDERR: ^~~~~~
+export import library "lib";
+
+// CHECK:STDOUT: - filename: fail_package.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'ImportExport', text: 'export'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Bar'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: default.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'ImportExport', text: 'export'},
+// CHECK:STDOUT:         {kind: 'DefaultLibrary', text: 'default'},
+// CHECK:STDOUT:       {kind: 'LibrarySpecifier', text: 'library', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: named.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'ImportExport', text: 'export'},
+// CHECK:STDOUT:         {kind: 'LibraryName', text: '"lib"'},
+// CHECK:STDOUT:       {kind: 'LibrarySpecifier', text: 'library', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_order.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageApi', text: 'api'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'ImportExport', text: 'export'},
+// CHECK:STDOUT:         {kind: 'LibraryName', text: '"a"'},
+// CHECK:STDOUT:       {kind: 'LibrarySpecifier', text: 'library', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ClassIntroducer', text: 'class'},
+// CHECK:STDOUT:       {kind: 'IdentifierName', text: 'C'},
+// CHECK:STDOUT:     {kind: 'ClassDecl', text: ';', subtree_size: 3},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'ImportExport', text: 'export'},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_in_default_library.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'ImportExport', text: 'export'},
+// CHECK:STDOUT:         {kind: 'LibraryName', text: '"lib"'},
+// CHECK:STDOUT:       {kind: 'LibrarySpecifier', text: 'library', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_in_impl.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'PackageName', text: 'Pkg'},
+// CHECK:STDOUT:       {kind: 'PackageImpl', text: 'impl'},
+// CHECK:STDOUT:     {kind: 'PackageDirective', text: ';', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'ImportExport', text: 'export'},
+// CHECK:STDOUT:         {kind: 'LibraryName', text: '"lib"'},
+// CHECK:STDOUT:       {kind: 'LibrarySpecifier', text: 'library', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDirective', text: ';', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/packages/import/fail_after_decl.carbon

@@ -6,7 +6,7 @@
 
 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: fail_after_decl.carbon:[[@LINE+6]]:1: ERROR: `import` and `export` 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.

+ 2 - 2
toolchain/parse/testdata/packages/import/fail_after_decl_repeated.carbon

@@ -6,7 +6,7 @@
 
 fn A();
 
-// CHECK:STDERR: fail_after_decl_repeated.carbon:[[@LINE+7]]:1: ERROR: `import` directives must come after the `package` directive (if present) and before any other entities in the file.
+// CHECK:STDERR: fail_after_decl_repeated.carbon:[[@LINE+7]]:1: ERROR: `import` and `export` 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.
@@ -28,7 +28,7 @@ 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: fail_after_decl_repeated.carbon:[[@LINE+6]]:1: ERROR: `import` and `export` 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-27]]:1: First declaration is here.

+ 3 - 0
toolchain/parse/tree.h

@@ -92,6 +92,9 @@ class Tree : public Printable<Tree> {
     ImportDirectiveId node_id;
     IdentifierId package_id = IdentifierId::Invalid;
     StringLiteralValueId library_id = StringLiteralValueId::Invalid;
+    // Whether an import is exported. This is on the file's packaging directive
+    // even though it doesn't apply, for consistency in structure.
+    bool is_export = false;
   };
 
   // The file's packaging.

+ 15 - 3
toolchain/parse/typed_nodes.h

@@ -143,8 +143,8 @@ struct QualifiedName {
   IdentifierNameId rhs;
 };
 
-// Library, package, import
-// ------------------------
+// Library, package, import, export
+// --------------------------------
 
 // The `package` keyword in an expression.
 using PackageExpr = LeafNode<NodeKind::PackageExpr, NodeCategory::Expr>;
@@ -179,11 +179,13 @@ struct PackageDirective {
 
 // `import TheirPackage library "TheirLibrary";`
 using ImportIntroducer = LeafNode<NodeKind::ImportIntroducer>;
+using ImportExport = LeafNode<NodeKind::ImportExport>;
 struct ImportDirective {
   static constexpr auto Kind =
       NodeKind::ImportDirective.Define(NodeCategory::Decl);
 
   ImportIntroducerId introducer;
+  std::optional<ImportExportId> export_modifier;
   std::optional<PackageNameId> name;
   std::optional<LibrarySpecifierId> library;
 };
@@ -199,6 +201,16 @@ struct LibraryDirective {
   NodeIdOneOf<PackageApi, PackageImpl> api_or_impl;
 };
 
+// `export` as a directive.
+using ExportIntroducer = LeafNode<NodeKind::ExportIntroducer>;
+struct ExportDirective {
+  static constexpr auto Kind =
+      NodeKind::ExportDirective.Define(NodeCategory::Decl);
+
+  ExportIntroducerId introducer;
+  AnyNameComponentId name;
+};
+
 // Namespace nodes
 // ---------------
 
@@ -210,7 +222,7 @@ struct Namespace {
 
   NamespaceStartId introducer;
   llvm::SmallVector<AnyModifierId> modifiers;
-  NodeIdOneOf<IdentifierName, QualifiedName> name;
+  AnyNameComponentId name;
 };
 
 // Pattern nodes

+ 2 - 0
toolchain/sem_ir/import_ir.h

@@ -19,6 +19,8 @@ struct ImportIR : public Printable<ImportIR> {
   Parse::ImportDirectiveId node_id;
   // The imported IR.
   const File* sem_ir;
+  // True if this is part of an `export import`.
+  bool is_export;
 };
 
 // A reference to an instruction in an imported IR. Used for diagnostics with