Quellcode durchsuchen

Pass imports to SemIR. (#3415)

This adds instructions so that we get printing. I may adjust the
instruction format a little further to add a type, but I think the basic
setup will remain.

Note this builds on #3414
Jon Ross-Perkins vor 2 Jahren
Ursprung
Commit
239f8030a4

+ 1 - 0
toolchain/check/BUILD

@@ -68,6 +68,7 @@ cc_library(
         "//toolchain/sem_ir:builtin_kind",
         "//toolchain/sem_ir:entry_point",
         "//toolchain/sem_ir:file",
+        "//toolchain/sem_ir:ids",
         "//toolchain/sem_ir:inst",
         "//toolchain/sem_ir:inst_kind",
         "@llvm-project//llvm:Support",

+ 96 - 35
toolchain/check/check.cpp

@@ -17,6 +17,27 @@
 namespace Carbon::Check {
 
 struct UnitInfo {
+  // A given import within the file, with its destination.
+  struct Import {
+    Parse::Tree::PackagingNames names;
+    UnitInfo* unit_info;
+  };
+  // A file's imports corresponding to a single package, for the map.
+  struct PackageImports {
+    // Use the constructor so that the SmallVector is only constructed
+    // as-needed.
+    explicit PackageImports(Parse::Node node) : node(node) {}
+
+    // The first `import` directive in the file, which declared the package's
+    // identifier (even if the import failed). Used for associating diagnostics
+    // not specific to a single import.
+    Parse::Node node;
+    // Whether there's an import that failed to load.
+    bool has_load_error = false;
+    // The list of valid imports.
+    llvm::SmallVector<Import> imports;
+  };
+
   explicit UnitInfo(Unit& unit)
       : unit(&unit),
         translator(unit.tokens, unit.tokens->source().filename(),
@@ -31,8 +52,11 @@ struct UnitInfo {
   ErrorTrackingDiagnosticConsumer err_tracker;
   DiagnosticEmitter<Parse::Node> emitter;
 
-  // A list of outgoing imports.
-  llvm::SmallVector<std::pair<Parse::Node, UnitInfo*>> imports;
+  // A map of package names to outgoing imports. If the
+  // import's target isn't available, the unit will be nullptr to assist with
+  // name lookup. Invalid imports (for example, `import Main;`) aren't added
+  // because they won't add identifiers to name lookup.
+  llvm::DenseMap<IdentifierId, PackageImports> package_imports_map;
 
   // The remaining number of imports which must be checked before this unit can
   // be processed.
@@ -43,6 +67,42 @@ struct UnitInfo {
   llvm::SmallVector<UnitInfo*> incoming_imports;
 };
 
+// Add imports to the root block.
+static auto AddImports(Context& context, UnitInfo& unit_info) -> void {
+  for (auto& [package_id, package_imports] : unit_info.package_imports_map) {
+    llvm::SmallVector<const SemIR::File*> sem_irs;
+    for (auto import : package_imports.imports) {
+      sem_irs.push_back(&**import.unit_info->unit->sem_ir);
+    }
+    context.AddPackageImports(package_imports.node, package_id, sem_irs,
+                              package_imports.has_load_error);
+  }
+}
+
+// Loops over all nodes in the tree. On some errors, this may return early,
+// for example if an unrecoverable state is encountered.
+static auto ProcessParseNodes(Context& context,
+                              ErrorTrackingDiagnosticConsumer& err_tracker)
+    -> bool {
+  for (auto parse_node : context.parse_tree().postorder()) {
+    // clang warns on unhandled enum values; clang-tidy is incorrect here.
+    // NOLINTNEXTLINE(bugprone-switch-missing-default-case)
+    switch (auto parse_kind = context.parse_tree().node_kind(parse_node)) {
+#define CARBON_PARSE_NODE_KIND(Name)                                         \
+  case Parse::NodeKind::Name: {                                              \
+    if (!Check::Handle##Name(context, parse_node)) {                         \
+      CARBON_CHECK(err_tracker.seen_error())                                 \
+          << "Handle" #Name " returned false without printing a diagnostic"; \
+      return false;                                                          \
+    }                                                                        \
+    break;                                                                   \
+  }
+#include "toolchain/parse/node_kind.def"
+    }
+  }
+  return true;
+}
+
 // Produces and checks the IR for the provided Parse::Tree.
 // TODO: Both valid and invalid imports should be recorded on the SemIR. Invalid
 // imports should suppress errors where it makes sense.
@@ -54,10 +114,9 @@ static auto CheckParseTree(const SemIR::File& builtin_ir, UnitInfo& unit_info,
 
   // For ease-of-access.
   SemIR::File& sem_ir = **unit_info.unit->sem_ir;
-  const Parse::Tree& parse_tree = *unit_info.unit->parse_tree;
 
-  Check::Context context(*unit_info.unit->tokens, unit_info.emitter, parse_tree,
-                         sem_ir, vlog_stream);
+  Context context(*unit_info.unit->tokens, unit_info.emitter,
+                  *unit_info.unit->parse_tree, sem_ir, vlog_stream);
   PrettyStackTraceFunction context_dumper(
       [&](llvm::raw_ostream& output) { context.PrintForStackDump(output); });
 
@@ -65,24 +124,11 @@ static auto CheckParseTree(const SemIR::File& builtin_ir, UnitInfo& unit_info,
   context.inst_block_stack().Push();
   context.PushScope();
 
-  // Loops over all nodes in the tree. On some errors, this may return early,
-  // for example if an unrecoverable state is encountered.
-  for (auto parse_node : parse_tree.postorder()) {
-    // clang warns on unhandled enum values; clang-tidy is incorrect here.
-    // NOLINTNEXTLINE(bugprone-switch-missing-default-case)
-    switch (auto parse_kind = parse_tree.node_kind(parse_node)) {
-#define CARBON_PARSE_NODE_KIND(Name)                                         \
-  case Parse::NodeKind::Name: {                                              \
-    if (!Check::Handle##Name(context, parse_node)) {                         \
-      CARBON_CHECK(unit_info.err_tracker.seen_error())                       \
-          << "Handle" #Name " returned false without printing a diagnostic"; \
-      sem_ir.set_has_errors(true);                                           \
-      return;                                                                \
-    }                                                                        \
-    break;                                                                   \
-  }
-#include "toolchain/parse/node_kind.def"
-    }
+  AddImports(context, unit_info);
+
+  if (!ProcessParseNodes(context, unit_info.err_tracker)) {
+    context.sem_ir().set_has_errors(true);
+    return;
   }
 
   // Pop information for the file-level scope.
@@ -228,11 +274,19 @@ static auto TrackImport(
     return;
   }
 
+  // Get the package imports.
+  auto package_imports_it =
+      unit_info.package_imports_map.try_emplace(import.package_id, import.node)
+          .first;
+
   if (auto api = api_map.find(import_key); api != api_map.end()) {
-    unit_info.imports.push_back({import.node, api->second});
+    // Add references between the file and imported api.
+    package_imports_it->second.imports.push_back({import, api->second});
     ++unit_info.imports_remaining;
     api->second->incoming_imports.push_back(&unit_info);
   } else {
+    // The imported api is missing.
+    package_imports_it->second.has_load_error = true;
     CARBON_DIAGNOSTIC(LibraryApiNotFound, Error,
                       "Corresponding API not found.");
     CARBON_DIAGNOSTIC(ImportNotFound, Error, "Imported API not found.");
@@ -392,17 +446,24 @@ auto CheckParseTrees(const SemIR::File& builtin_ir,
     // TODO: Better identify cycles, maybe try to untangle them.
     for (auto& unit_info : unit_infos) {
       if (unit_info.imports_remaining > 0) {
-        for (auto* import_it = unit_info.imports.begin();
-             import_it != unit_info.imports.end();) {
-          auto* import_unit = import_it->second->unit;
-          if (*import_unit->sem_ir) {
-            ++import_it;
-          } else {
-            CARBON_DIAGNOSTIC(ImportCycleDetected, Error,
-                              "Import cannot be used due to a cycle. Cycle "
-                              "must be fixed to import.");
-            unit_info.emitter.Emit(import_it->first, ImportCycleDetected);
-            import_it = unit_info.imports.erase(import_it);
+        for (auto& [package_id, package_imports] :
+             unit_info.package_imports_map) {
+          for (auto* import_it = package_imports.imports.begin();
+               import_it != package_imports.imports.end();) {
+            if (*import_it->unit_info->unit->sem_ir) {
+              // The import is checked, so continue.
+              ++import_it;
+            } else {
+              // The import hasn't been checked, indicating a cycle.
+              CARBON_DIAGNOSTIC(ImportCycleDetected, Error,
+                                "Import cannot be used due to a cycle. Cycle "
+                                "must be fixed to import.");
+              unit_info.emitter.Emit(import_it->names.node,
+                                     ImportCycleDetected);
+              // Make this look the same as an import which wasn't found.
+              package_imports.has_load_error = true;
+              import_it = package_imports.imports.erase(import_it);
+            }
           }
         }
       }

+ 42 - 0
toolchain/check/context.cpp

@@ -15,8 +15,10 @@
 #include "toolchain/lex/tokenized_buffer.h"
 #include "toolchain/parse/node_kind.h"
 #include "toolchain/sem_ir/file.h"
+#include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/inst_kind.h"
+#include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
 
@@ -112,6 +114,45 @@ auto Context::NoteIncompleteClass(SemIR::ClassId class_id,
   }
 }
 
+auto Context::AddPackageImports(Parse::Node import_node,
+                                IdentifierId package_id,
+                                llvm::ArrayRef<const SemIR::File*> sem_irs,
+                                bool has_load_error) -> void {
+  CARBON_CHECK(has_load_error || !sem_irs.empty())
+      << "There should be either a load error or at least one IR.";
+
+  auto name_id = SemIR::NameId::ForIdentifier(package_id);
+
+  SemIR::CrossRefIRId first_id(cross_ref_irs().size());
+  for (const auto* sem_ir : sem_irs) {
+    cross_ref_irs().Add(sem_ir);
+  }
+  if (has_load_error) {
+    cross_ref_irs().Add(nullptr);
+  }
+  SemIR::CrossRefIRId last_id(cross_ref_irs().size() - 1);
+
+  auto type_id = GetBuiltinType(SemIR::BuiltinKind::NamespaceType);
+  auto inst_id = AddInst(SemIR::Import{.parse_node = import_node,
+                                       .type_id = type_id,
+                                       .first_cross_ref_ir_id = first_id,
+                                       .last_cross_ref_ir_id = last_id});
+  if (name_id.is_valid()) {
+    // Add the import to lookup. Should always succeed because imports will be
+    // uniquely named.
+    AddNameToLookup(import_node, name_id, inst_id);
+    // Add a name for formatted output. This isn't used in name lookup in order
+    // to reduce indirection, but it's separate from the Import because it
+    // otherwise fits in an Inst.
+    AddInst(SemIR::BindName{.parse_node = import_node,
+                            .type_id = type_id,
+                            .name_id = name_id,
+                            .value_id = inst_id});
+  } else {
+    // TODO: All names from the current package should be added.
+  }
+}
+
 auto Context::AddNameToLookup(Parse::Node name_node, SemIR::NameId name_id,
                               SemIR::InstId target_id) -> void {
   if (current_scope().names.insert(name_id).second) {
@@ -811,6 +852,7 @@ class TypeCompleter {
       case SemIR::Deref::Kind:
       case SemIR::Field::Kind:
       case SemIR::FunctionDecl::Kind:
+      case SemIR::Import::Kind:
       case SemIR::InitializeFrom::Kind:
       case SemIR::IntegerLiteral::Kind:
       case SemIR::NameRef::Kind:

+ 7 - 0
toolchain/check/context.h

@@ -14,6 +14,7 @@
 #include "toolchain/check/node_stack.h"
 #include "toolchain/parse/tree.h"
 #include "toolchain/sem_ir/file.h"
+#include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
 
 namespace Carbon::Check {
@@ -62,6 +63,12 @@ class Context {
   // result.
   auto AddInstAndPush(Parse::Node parse_node, SemIR::Inst inst) -> void;
 
+  // Adds a package's imports to name lookup, with all libraries together.
+  // sem_irs will all be non-null; has_load_error must be used for any errors.
+  auto AddPackageImports(Parse::Node import_node, IdentifierId package_id,
+                         llvm::ArrayRef<const SemIR::File*> sem_irs,
+                         bool has_load_error) -> void;
+
   // Adds a name to name lookup. Prints a diagnostic for name conflicts.
   auto AddNameToLookup(Parse::Node name_node, SemIR::NameId name_id,
                        SemIR::InstId target_id) -> void;

+ 4 - 0
toolchain/check/testdata/packages/explicit_imports.carbon

@@ -39,10 +39,14 @@ import library "lib";
 // CHECK:STDOUT: file "api_lib.carbon" {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "same_package.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir2
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "different_package.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir2
+// CHECK:STDOUT:   %Api: <namespace> = bind_name Api, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "main_lib_api.carbon" {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "main_import.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
 // CHECK:STDOUT: }

+ 5 - 0
toolchain/check/testdata/packages/fail_api_not_found.carbon

@@ -26,8 +26,13 @@ package Foo library "Bar" impl;
 library "Bar" impl;
 
 // CHECK:STDOUT: file "no_api.impl.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %Foo: <namespace> = bind_name Foo, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "no_api_lib.impl.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %Foo: <namespace> = bind_name Foo, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "no_api_main_lib.impl.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
 // CHECK:STDOUT: }

+ 10 - 0
toolchain/check/testdata/packages/fail_cycle.carbon

@@ -48,12 +48,22 @@ package CycleChild api;
 import B;
 
 // CHECK:STDOUT: file "a.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %B: <namespace> = bind_name B, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "b.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %C: <namespace> = bind_name C, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "c.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %A: <namespace> = bind_name A, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "c.impl.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %C: <namespace> = bind_name C, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "cycle_child.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %B: <namespace> = bind_name B, %import
 // CHECK:STDOUT: }

+ 7 - 0
toolchain/check/testdata/packages/fail_extension.carbon

@@ -77,16 +77,23 @@ package SwappedExt impl;
 // CHECK:STDOUT: file "main_lib.incorrect" {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "main_lib_impl.incorrect" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "package.incorrect" {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "package_impl.incorrect" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %Package: <namespace> = bind_name Package, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "package_lib.incorrect" {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "package_lib_impl.incorrect" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %Package: <namespace> = bind_name Package, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "swapped_ext.impl.carbon" {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "swapped_ext.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %SwappedExt: <namespace> = bind_name SwappedExt, %import
 // CHECK:STDOUT: }

+ 2 - 0
toolchain/check/testdata/packages/fail_import_default.carbon

@@ -41,6 +41,8 @@ import library default;
 // CHECK:STDOUT: file "default_api.carbon" {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "default.impl.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %A: <namespace> = bind_name A, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "main_import_default.carbon" {
 // CHECK:STDOUT: }

+ 6 - 0
toolchain/check/testdata/packages/fail_import_invalid.carbon

@@ -93,10 +93,16 @@ import ImportNotFound;
 // CHECK:STDOUT: file "implicit_api.carbon" {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "implicit.impl.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %Implicit: <namespace> = bind_name Implicit, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "implicit_lib_api.carbon" {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "implicit_lib.impl.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %Implicit: <namespace> = bind_name Implicit, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "not_found.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %ImportNotFound: <namespace> = bind_name ImportNotFound, %import
 // CHECK:STDOUT: }

+ 4 - 0
toolchain/check/testdata/packages/fail_import_repeat.carbon

@@ -65,6 +65,10 @@ import library default;
 // CHECK:STDOUT: file "main_lib.carbon" {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "import.carbon" {
+// CHECK:STDOUT:   %import.loc2: <namespace> = import ir1, ir2
+// CHECK:STDOUT:   %Api: <namespace> = bind_name Api, %import.loc2
+// CHECK:STDOUT:   %import.loc20: <namespace> = import ir3, ir3
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "default_import.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
 // CHECK:STDOUT: }

+ 7 - 0
toolchain/check/testdata/packages/implicit_imports.carbon

@@ -50,16 +50,23 @@ library "lib" impl;
 // CHECK:STDOUT: file "with_impl.carbon" {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "with_impl.impl.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %WithImpl: <namespace> = bind_name WithImpl, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "with_impl_extra.impl.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %WithImpl: <namespace> = bind_name WithImpl, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "with_impl_lib.carbon" {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "with_impl_lib.impl.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
+// CHECK:STDOUT:   %WithImpl: <namespace> = bind_name WithImpl, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "main.carbon" {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "main_lib.carbon" {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: file "main_lib.impl.carbon" {
+// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
 // CHECK:STDOUT: }

+ 8 - 0
toolchain/lower/handle.cpp

@@ -186,6 +186,14 @@ auto HandleFunctionDecl(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
       << inst;
 }
 
+auto HandleImport(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
+                  SemIR::Import inst) -> void {
+  CARBON_FATAL()
+      << "Should not be encountered. If that changes, we may want to change "
+         "higher-level logic to skip them rather than calling this. "
+      << inst;
+}
+
 auto HandleInitializeFrom(FunctionContext& context, SemIR::InstId /*inst_id*/,
                           SemIR::InitializeFrom inst) -> void {
   auto storage_type_id = context.sem_ir().insts().Get(inst.dest_id).type_id();

+ 2 - 0
toolchain/sem_ir/BUILD

@@ -65,6 +65,7 @@ cc_library(
     hdrs = ["file.h"],
     deps = [
         ":builtin_kind",
+        ":ids",
         ":inst",
         ":inst_kind",
         ":value_stores",
@@ -81,6 +82,7 @@ cc_library(
     hdrs = ["formatter.h"],
     deps = [
         ":file",
+        ":ids",
         ":inst_kind",
         "//toolchain/base:value_store",
         "//toolchain/lex:tokenized_buffer",

+ 1 - 1
toolchain/sem_ir/builtin_kind.def

@@ -70,7 +70,7 @@ CARBON_SEM_IR_BUILTIN_KIND(FunctionType, "<function>")
 // The type of bound method values.
 CARBON_SEM_IR_BUILTIN_KIND(BoundMethodType, "<bound method>")
 
-// The type of namespace names.
+// The type of namespace and imported package names.
 CARBON_SEM_IR_BUILTIN_KIND(NamespaceType, "<namespace>")
 
 // Keep invalid last, so that we can use values as array indices without needing

+ 4 - 0
toolchain/sem_ir/file.cpp

@@ -10,6 +10,7 @@
 #include "toolchain/base/value_store.h"
 #include "toolchain/base/yaml.h"
 #include "toolchain/sem_ir/builtin_kind.h"
+#include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/inst_kind.h"
 
@@ -203,6 +204,7 @@ static auto GetTypePrecedence(InstKind kind) -> int {
     case Deref::Kind:
     case Field::Kind:
     case FunctionDecl::Kind:
+    case Import::Kind:
     case InitializeFrom::Kind:
     case IntegerLiteral::Kind:
     case Namespace::Kind:
@@ -406,6 +408,7 @@ auto File::StringifyTypeExpr(InstId outer_inst_id, bool in_type_context) const
       case Deref::Kind:
       case Field::Kind:
       case FunctionDecl::Kind:
+      case Import::Kind:
       case InitializeFrom::Kind:
       case IntegerLiteral::Kind:
       case Namespace::Kind:
@@ -473,6 +476,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case ClassDecl::Kind:
       case Field::Kind:
       case FunctionDecl::Kind:
+      case Import::Kind:
       case Namespace::Kind:
       case NoOp::Kind:
       case Return::Kind:

+ 1 - 0
toolchain/sem_ir/file.h

@@ -11,6 +11,7 @@
 #include "llvm/Support/FormatVariadic.h"
 #include "toolchain/base/value_store.h"
 #include "toolchain/base/yaml.h"
+#include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/value_stores.h"
 

+ 7 - 2
toolchain/sem_ir/formatter.cpp

@@ -11,6 +11,7 @@
 #include "toolchain/base/value_store.h"
 #include "toolchain/lex/tokenized_buffer.h"
 #include "toolchain/parse/tree.h"
+#include "toolchain/sem_ir/ids.h"
 
 namespace Carbon::SemIR {
 
@@ -440,6 +441,10 @@ class InstNamer {
               sem_ir_.classes().Get(inst.As<ClassType>().class_id).name_id);
           continue;
         }
+        case Import::Kind: {
+          add_inst_name("import");
+          continue;
+        }
         case NameRef::Kind: {
           add_inst_name_id(inst.As<NameRef>().name_id, ".ref");
           continue;
@@ -494,8 +499,6 @@ class Formatter {
     FormatConstants();
 
     out_ << "file \"" << sem_ir_.filename() << "\" {\n";
-    // TODO: Include information from the `package` declaration, once we
-    // fully support it.
     // TODO: Handle the case where there are multiple top-level instruction
     // blocks. For example, there may be branching in the initializer of a
     // global or a type expression.
@@ -853,6 +856,8 @@ class Formatter {
 
   auto FormatArg(ClassId id) -> void { FormatClassName(id); }
 
+  auto FormatArg(CrossRefIRId id) -> void { out_ << id; }
+
   auto FormatArg(IntegerId id) -> void {
     sem_ir_.integers().Get(id).print(out_, /*isSigned=*/false);
   }

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -43,6 +43,7 @@ CARBON_SEM_IR_INST_KIND(CrossRef)
 CARBON_SEM_IR_INST_KIND(Deref)
 CARBON_SEM_IR_INST_KIND(Field)
 CARBON_SEM_IR_INST_KIND(FunctionDecl)
+CARBON_SEM_IR_INST_KIND(Import)
 CARBON_SEM_IR_INST_KIND(InitializeFrom)
 CARBON_SEM_IR_INST_KIND(IntegerLiteral)
 CARBON_SEM_IR_INST_KIND(NameRef)

+ 13 - 0
toolchain/sem_ir/typed_insts.h

@@ -290,6 +290,19 @@ struct FunctionDecl {
   FunctionId function_id;
 };
 
+// An import corresponds to some number of IRs. The range of imported IRs is
+// inclusive of last_cross_ref_ir_id, and will always be non-empty. If
+// there was an import error, first_cross_ref_ir_id will reference a
+// nullptr IR; there should only ever be one nullptr in the range.
+struct Import {
+  static constexpr auto Kind = InstKind::Import.Define("import");
+
+  Parse::Node parse_node;
+  TypeId type_id;
+  CrossRefIRId first_cross_ref_ir_id;
+  CrossRefIRId last_cross_ref_ir_id;
+};
+
 // Finalizes the initialization of `dest_id` from the initializer expression
 // `src_id`, by performing a final copy from source to destination, for types
 // whose initialization is not in-place.