Эх сурвалжийг харах

Start adding lazy import references to name lookup. (#3475)

Adds a `LazyImportRef` instruction. Versus `CrossRef`, this is intended
to represent an instruction which cannot be used directly, and must be
replaced when it comes up due to name lookup. The intent is to use this
to avoid recursive loading of imported IR instructions.

Note, under this model, when `ResolveIfLazyImportRef` is called, it
essentially needs to load both inst and type information to a sufficient
point where any further attempts would hit name lookup again. That will
probably be complex, and the current implementation is just touching the
surface of the issue. I was heading down this route because it would
mean we have a limited number of points that need to consider whether
they're going to talk about a `LazyImportRef`.

I'm considering whether `CrossRef` should be dropped in favor of more
specific `Builtin` special-casing, due to the divergence of desired
behaviors. This could mean dropping the `builtins` IR since it's not
looking useful right now.

Modify `NameScope` to track whether the scope is associated with a load
error. This is to handle cases where one or more imports failed, so we
do not want to issue warnings for related scopes.

The 0-size on `ValueStore` comes up due to the changes to `NameScope`,
which make it too large for the default handling. After discussion with
zygoloid, the thought was we might want to try reserving a roughly
correct value based on parse node counts, but the stack default wasn't
buying much.

Fixes a bug where the implicit import used the package name instead of
the invalid identifier.
Jon Ross-Perkins 2 жил өмнө
parent
commit
032c0e017b

+ 6 - 2
toolchain/base/value_store.h

@@ -172,7 +172,9 @@ class ValueStore
   auto size() const -> int { return values_.size(); }
 
  private:
-  llvm::SmallVector<std::decay_t<ValueType>> values_;
+  // Set inline size to 0 because these will typically be too large for the
+  // stack, while this does make File smaller.
+  llvm::SmallVector<std::decay_t<ValueType>, 0> values_;
 };
 
 // Storage for StringRefs. The caller is responsible for ensuring storage is
@@ -207,7 +209,9 @@ class ValueStore<StringId> : public Yaml::Printable<ValueStore<StringId>> {
 
  private:
   llvm::DenseMap<llvm::StringRef, StringId> map_;
-  llvm::SmallVector<llvm::StringRef> values_;
+  // Set inline size to 0 because these will typically be too large for the
+  // stack, while this does make File smaller.
+  llvm::SmallVector<llvm::StringRef, 0> values_;
 };
 
 // A thin wrapper around a `ValueStore<StringId>` that provides a different IdT,

+ 71 - 16
toolchain/check/check.cpp

@@ -69,8 +69,73 @@ struct UnitInfo {
 };
 
 // Add imports to the root block.
-static auto AddImports(Context& context, UnitInfo& unit_info) -> void {
+static auto InitPackageScopeAndImports(Context& context, UnitInfo& unit_info)
+    -> void {
+  // Define the package scope, with an instruction for `package` expressions to
+  // reference.
+  auto package_scope_id = context.name_scopes().Add();
+  CARBON_CHECK(package_scope_id == SemIR::NameScopeId::Package);
+
+  auto package_inst = context.AddInst(SemIR::Namespace{
+      Parse::NodeId::Invalid,
+      context.GetBuiltinType(SemIR::BuiltinKind::NamespaceType),
+      SemIR::NameScopeId::Package});
+  CARBON_CHECK(package_inst == SemIR::InstId::PackageNamespace);
+
+  // 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()) {
+    auto& package_scope =
+        context.name_scopes().Get(SemIR::NameScopeId::Package);
+    package_scope.has_load_error = 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;
+      const auto& import_scope =
+          import_sem_ir.name_scopes().Get(SemIR::NameScopeId::Package);
+
+      // If an import of the current package caused an error for the imported
+      // file, it transitively affects the current file too.
+      package_scope.has_load_error |= import_scope.has_load_error;
+
+      auto ir_id = context.sem_ir().cross_ref_irs().Add(&import_sem_ir);
+
+      for (const auto& [import_name_id, import_inst_id] : import_scope.names) {
+        // Translate the name to the current IR.
+        auto name_id = SemIR::NameId::Invalid;
+        if (auto import_identifier_id = import_name_id.AsIdentifierId();
+            import_identifier_id.is_valid()) {
+          auto name = import_sem_ir.identifiers().Get(import_identifier_id);
+          name_id =
+              SemIR::NameId::ForIdentifier(context.identifiers().Add(name));
+        } else {
+          // A builtin name ID which is equivalent cross-IR.
+          name_id = import_name_id;
+        }
+
+        // Leave a placeholder that the inst comes from the other IR.
+        auto target_id = context.AddInst(
+            SemIR::LazyImportRef{.ir_id = ir_id, .inst_id = import_inst_id});
+        // TODO: The scope's names should be changed to allow for ambiguous
+        // names.
+        package_scope.names.insert({name_id, target_id});
+      }
+    }
+
+    // Push the scope.
+    context.PushScope(package_inst, SemIR::NameScopeId::Package,
+                      package_scope.has_load_error);
+  } else {
+    // Push the scope; there are no names to add.
+    context.PushScope(package_inst, SemIR::NameScopeId::Package);
+  }
+
   for (auto& [package_id, package_imports] : unit_info.package_imports_map) {
+    if (!package_id.is_valid()) {
+      // Current package is handled above.
+      continue;
+    }
+
     llvm::SmallVector<const SemIR::File*> sem_irs;
     for (auto import : package_imports.imports) {
       sem_irs.push_back(&**import.unit_info->unit->sem_ir);
@@ -105,8 +170,6 @@ static auto ProcessParseNodes(Context& context,
 }
 
 // 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.
 static auto CheckParseTree(const SemIR::File& builtin_ir, UnitInfo& unit_info,
                            llvm::raw_ostream* vlog_stream) -> void {
   unit_info.unit->sem_ir->emplace(
@@ -124,17 +187,7 @@ static auto CheckParseTree(const SemIR::File& builtin_ir, UnitInfo& unit_info,
   // Add a block for the file.
   context.inst_block_stack().Push();
 
-  // Define the package scope, with an instruction for `package` expressions to
-  // reference.
-  auto package_scope = context.name_scopes().Add();
-  auto package_inst = context.AddInst(SemIR::Namespace{
-      Parse::NodeId::Invalid,
-      context.GetBuiltinType(SemIR::BuiltinKind::NamespaceType),
-      package_scope});
-  CARBON_CHECK(package_inst == SemIR::InstId::PackageNamespace);
-  context.PushScope(SemIR::InstId::Invalid, package_scope);
-
-  AddImports(context, unit_info);
+  InitPackageScopeAndImports(context, unit_info);
 
   if (!ProcessParseNodes(context, unit_info.err_tracker)) {
     context.sem_ir().set_has_errors(true);
@@ -338,7 +391,7 @@ static auto BuildApiMapAndDiagnosePackaging(
         packaging && packaging->api_or_impl == Parse::Tree::ApiOrImpl::Impl;
 
     // Add to the `api` map and diagnose duplicates. This occurs before the
-    // file extension check because we might emit both diagnostics in situation
+    // file extension check because we might emit both diagnostics in situations
     // where the user forgets (or has syntax errors with) a package line
     // multiple times.
     if (!is_impl) {
@@ -418,7 +471,9 @@ auto CheckParseTrees(const SemIR::File& builtin_ir,
             unit_info.unit->parse_tree->packaging_directive()) {
       if (packaging->api_or_impl == Parse::Tree::ApiOrImpl::Impl) {
         // An `impl` has an implicit import of its `api`.
-        TrackImport(api_map, nullptr, unit_info, packaging->names);
+        auto implicit_names = packaging->names;
+        implicit_names.package_id = IdentifierId::Invalid;
+        TrackImport(api_map, nullptr, unit_info, implicit_names);
       }
     }
 

+ 89 - 34
toolchain/check/context.cpp

@@ -137,20 +137,17 @@ auto Context::AddPackageImports(Parse::NodeId 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.
-  }
+
+  // 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});
 }
 
 auto Context::AddNameToLookup(Parse::NodeId name_node, SemIR::NameId name_id,
@@ -169,6 +166,45 @@ auto Context::AddNameToLookup(Parse::NodeId name_node, SemIR::NameId name_id,
   }
 }
 
+auto Context::ResolveIfLazyImportRef(SemIR::InstId inst_id) -> void {
+  auto inst = insts().Get(inst_id);
+  auto lazy_inst = inst.TryAs<SemIR::LazyImportRef>();
+  if (!lazy_inst) {
+    return;
+  }
+  const SemIR::File& import_ir = *cross_ref_irs().Get(lazy_inst->ir_id);
+  auto import_inst = import_ir.insts().Get(lazy_inst->inst_id);
+  switch (import_inst.kind()) {
+    case SemIR::InstKind::FunctionDecl: {
+      // TODO: Fill this in better.
+      auto function_id =
+          functions().Add({.name_id = SemIR::NameId::Invalid,
+                           .decl_id = inst_id,
+                           .implicit_param_refs_id = SemIR::InstBlockId::Empty,
+                           .param_refs_id = SemIR::InstBlockId::Empty,
+                           .return_type_id = SemIR::TypeId::Invalid,
+                           .return_slot_id = SemIR::InstId::Invalid});
+      insts().Set(inst_id, SemIR::FunctionDecl{
+                               Parse::NodeId::Invalid,
+                               GetBuiltinType(SemIR::BuiltinKind::FunctionType),
+                               function_id});
+      break;
+    }
+
+    default:
+      // TODO: We need more type support. For now we inject an arbitrary
+      // invalid node that's unrelated to the underlying value. The TODO
+      // diagnostic is used since this section shouldn't typically be able to
+      // error.
+      TODO(Parse::NodeId::Invalid,
+           (llvm::Twine("TODO: support ") + import_inst.kind().name()).str());
+      insts().Set(inst_id, SemIR::VarStorage{Parse::NodeId::Invalid,
+                                             SemIR::TypeId::Error,
+                                             SemIR::NameId::PackageNamespace});
+      break;
+  }
+}
+
 auto Context::LookupNameInDecl(Parse::NodeId parse_node, SemIR::NameId name_id,
                                SemIR::NameScopeId scope_id) -> SemIR::InstId {
   if (scope_id == SemIR::NameScopeId::Invalid) {
@@ -201,6 +237,7 @@ auto Context::LookupNameInDecl(Parse::NodeId parse_node, SemIR::NameId name_id,
           << "Should have been erased: " << names().GetFormatted(name_id);
       auto result = name_it->second.back();
       if (result.scope_index == current_scope_index()) {
+        ResolveIfLazyImportRef(result.inst_id);
         return result.inst_id;
       }
     }
@@ -233,23 +270,29 @@ auto Context::LookupUnqualifiedName(Parse::NodeId parse_node,
     // it shadows all further non-lexical results and we're done.
     if (!lexical_results.empty() &&
         lexical_results.back().scope_index > index) {
-      return lexical_results.back().inst_id;
+      auto inst_id = lexical_results.back().inst_id;
+      ResolveIfLazyImportRef(inst_id);
+      return inst_id;
     }
 
-    auto non_lexical_result =
-        LookupQualifiedName(parse_node, name_id, name_scope_id,
-                            /*required=*/false);
-    if (non_lexical_result.is_valid()) {
+    if (auto non_lexical_result =
+            LookupQualifiedName(parse_node, name_id, name_scope_id,
+                                /*required=*/false);
+        non_lexical_result.is_valid()) {
       return non_lexical_result;
     }
   }
 
   if (!lexical_results.empty()) {
-    return lexical_results.back().inst_id;
+    auto inst_id = lexical_results.back().inst_id;
+    ResolveIfLazyImportRef(inst_id);
+    return inst_id;
   }
 
   // We didn't find anything at all.
-  DiagnoseNameNotFound(parse_node, name_id);
+  if (!name_lookup_has_load_error_) {
+    DiagnoseNameNotFound(parse_node, name_id);
+  }
   return SemIR::InstId::BuiltinError;
 }
 
@@ -259,28 +302,36 @@ auto Context::LookupQualifiedName(Parse::NodeId parse_node,
     -> SemIR::InstId {
   CARBON_CHECK(scope_id.is_valid()) << "No scope to perform lookup into";
   const auto& scope = name_scopes().Get(scope_id);
-  auto it = scope.find(name_id);
-  if (it == scope.end()) {
-    // TODO: Also perform lookups into `extend`ed scopes.
-    if (required) {
-      DiagnoseNameNotFound(parse_node, name_id);
-      return SemIR::InstId::BuiltinError;
-    }
-    return SemIR::InstId::Invalid;
+  if (auto it = scope.names.find(name_id); it != scope.names.end()) {
+    ResolveIfLazyImportRef(it->second);
+    return it->second;
   }
 
-  return it->second;
+  // TODO: Also perform lookups into `extend`ed scopes.
+
+  if (!required) {
+    return SemIR::InstId::Invalid;
+  }
+  if (!scope.has_load_error) {
+    DiagnoseNameNotFound(parse_node, name_id);
+  }
+  return SemIR::InstId::BuiltinError;
 }
 
 auto Context::PushScope(SemIR::InstId scope_inst_id,
-                        SemIR::NameScopeId scope_id) -> void {
-  scope_stack_.push_back({.index = next_scope_index_,
-                          .scope_inst_id = scope_inst_id,
-                          .scope_id = scope_id});
+                        SemIR::NameScopeId scope_id,
+                        bool name_lookup_has_load_error) -> void {
+  scope_stack_.push_back(
+      {.index = next_scope_index_,
+       .scope_inst_id = scope_inst_id,
+       .scope_id = scope_id,
+       .prev_name_lookup_has_load_error = name_lookup_has_load_error_});
   if (scope_id.is_valid()) {
     non_lexical_scope_stack_.push_back({next_scope_index_, scope_id});
   }
 
+  name_lookup_has_load_error_ |= name_lookup_has_load_error;
+
   // TODO: Handle this case more gracefully.
   CARBON_CHECK(next_scope_index_.index != std::numeric_limits<int32_t>::max())
       << "Ran out of scopes";
@@ -289,6 +340,9 @@ auto Context::PushScope(SemIR::InstId scope_inst_id,
 
 auto Context::PopScope() -> void {
   auto scope = scope_stack_.pop_back_val();
+
+  name_lookup_has_load_error_ = scope.prev_name_lookup_has_load_error;
+
   for (const auto& str_id : scope.names) {
     auto it = name_lookup_.find(str_id);
     CARBON_CHECK(it->second.back().scope_index == scope.index)
@@ -848,6 +902,7 @@ class TypeCompleter {
       case SemIR::InitializeFrom::Kind:
       case SemIR::InterfaceDecl::Kind:
       case SemIR::IntLiteral::Kind:
+      case SemIR::LazyImportRef::Kind:
       case SemIR::NameRef::Kind:
       case SemIR::Namespace::Kind:
       case SemIR::NoOp::Kind:

+ 17 - 3
toolchain/check/context.h

@@ -102,10 +102,13 @@ class Context {
   auto NoteIncompleteClass(SemIR::ClassId class_id, DiagnosticBuilder& builder)
       -> void;
 
-  // Pushes a new scope onto scope_stack_.
+  // Pushes a scope onto scope_stack_. NameScopeId::Invalid is used for new
+  // scopes. name_lookup_has_load_error is used to limit diagnostics when a
+  // given namespace may contain a mix of both successful and failed name
+  // imports.
   auto PushScope(SemIR::InstId scope_inst_id = SemIR::InstId::Invalid,
-                 SemIR::NameScopeId scope_id = SemIR::NameScopeId::Invalid)
-      -> void;
+                 SemIR::NameScopeId scope_id = SemIR::NameScopeId::Invalid,
+                 bool name_lookup_has_load_error = false) -> void;
 
   // Pops the top scope from scope_stack_, cleaning up names from name_lookup_.
   auto PopScope() -> void;
@@ -387,6 +390,9 @@ class Context {
     // The name scope associated with this entry, if any.
     SemIR::NameScopeId scope_id;
 
+    // The previous state of name_lookup_has_load_error_, restored on pop.
+    bool prev_name_lookup_has_load_error;
+
     // Names which are registered with name_lookup_, and will need to be
     // unregistered when the scope ends.
     llvm::DenseSet<SemIR::NameId> names;
@@ -427,6 +433,10 @@ class Context {
   // instruction to the current block.
   auto CanonicalizeTypeAndAddInstIfNew(SemIR::Inst inst) -> SemIR::TypeId;
 
+  // If the passed in instruction ID is a LazyImportRef, resolves it for use.
+  // Called when name lookup intends to return an inst_id.
+  auto ResolveIfLazyImportRef(SemIR::InstId inst_id) -> void;
+
   auto current_scope() -> ScopeStackEntry& { return scope_stack_.back(); }
   auto current_scope() const -> const ScopeStackEntry& {
     return scope_stack_.back();
@@ -499,6 +509,10 @@ class Context {
   llvm::DenseMap<SemIR::NameId, llvm::SmallVector<LexicalLookupResult>>
       name_lookup_;
 
+  // Whether name_lookup_ has load errors, updated whenever scope_stack_ is
+  // pushed or popped.
+  bool name_lookup_has_load_error_ = false;
+
   // Cache of the mapping from instructions to types, to avoid recomputing the
   // folding set ID.
   llvm::DenseMap<SemIR::InstId, SemIR::TypeId> canonical_types_;

+ 2 - 1
toolchain/check/decl_name_stack.cpp

@@ -145,7 +145,8 @@ auto DeclNameStack::UpdateScopeIfNeeded(NameContext& name_context) -> void {
       auto scope_id = resolved_inst.As<SemIR::Namespace>().name_scope_id;
       name_context.state = NameContext::State::Resolved;
       name_context.target_scope_id = scope_id;
-      context_->PushScope(name_context.resolved_inst_id, scope_id);
+      context_->PushScope(name_context.resolved_inst_id, scope_id,
+                          context_->name_scopes().Get(scope_id).has_load_error);
       break;
     }
     default:

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

@@ -50,7 +50,6 @@ import library "lib";
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {}
-// CHECK:STDOUT:   %import: <namespace> = import ir1, ir2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- different_package.carbon
@@ -71,6 +70,5 @@ import library "lib";
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {}
-// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 3 - 8
toolchain/check/testdata/packages/fail_api_not_found.carbon

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

+ 1 - 3
toolchain/check/testdata/packages/fail_cycle.carbon

@@ -74,9 +74,7 @@ import B;
 // CHECK:STDOUT: --- c.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace {}
-// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
-// CHECK:STDOUT:   %C: <namespace> = bind_name C, %import
+// CHECK:STDOUT:   package: <namespace> = namespace {has_load_error}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- cycle_child.carbon

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

@@ -92,7 +92,6 @@ package SwappedExt impl;
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {}
-// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- package.incorrect
@@ -105,8 +104,6 @@ package SwappedExt impl;
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {}
-// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
-// CHECK:STDOUT:   %Package: <namespace> = bind_name Package, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- package_lib.incorrect
@@ -119,8 +116,6 @@ package SwappedExt impl;
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {}
-// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
-// CHECK:STDOUT:   %Package: <namespace> = bind_name Package, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- swapped_ext.impl.carbon
@@ -133,7 +128,5 @@ package SwappedExt impl;
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {}
-// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
-// CHECK:STDOUT:   %SwappedExt: <namespace> = bind_name SwappedExt, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

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

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

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

@@ -116,8 +116,6 @@ import ImportNotFound;
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {}
-// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
-// CHECK:STDOUT:   %Implicit: <namespace> = bind_name Implicit, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- implicit_lib_api.carbon
@@ -130,8 +128,6 @@ import ImportNotFound;
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {}
-// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
-// CHECK:STDOUT:   %Implicit: <namespace> = bind_name Implicit, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- not_found.carbon

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

@@ -80,15 +80,13 @@ import library default;
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {}
-// 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:   %import: <namespace> = import ir2, ir3
+// CHECK:STDOUT:   %Api: <namespace> = bind_name Api, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- default_import.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {}
-// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 31 - 0
toolchain/check/testdata/packages/fail_name_with_import_failure.carbon

@@ -0,0 +1,31 @@
+// 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
+
+// --- implicit.impl.carbon
+
+// CHECK:STDERR: implicit.impl.carbon:[[@LINE+3]]:1: ERROR: Corresponding API not found.
+// CHECK:STDERR: package Implicit impl;
+// CHECK:STDERR: ^~~~~~~
+package Implicit impl;
+
+var a: () = A();
+
+// CHECK:STDOUT: --- implicit.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.loc7: type = tuple_type ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.a = %a, has_load_error}
+// CHECK:STDOUT:   %.loc7_9.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc7_9.2: type = converted %.loc7_9.1, constants.%.loc7
+// CHECK:STDOUT:   %a.var: ref () = var a
+// CHECK:STDOUT:   %a: ref () = bind_name a, %a.var
+// CHECK:STDOUT:   %A.ref: <error> = name_ref A, <error>
+// CHECK:STDOUT:   assign %a.var, <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 49 - 0
toolchain/check/testdata/packages/fail_todo_lazy_import_ref.carbon

@@ -0,0 +1,49 @@
+// 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
+//
+// When there are no more cases that can hit a TODO, remove this test. Until
+// then, update it whenever its target is implemented.
+//
+// AUTOUPDATE
+// CHECK:STDERR: implicit.impl.carbon: ERROR: Semantics TODO: `TODO: support BindName`.
+
+// --- implicit.carbon
+
+package Implicit api;
+
+var a_ref: i32 = 0;
+
+// --- implicit.impl.carbon
+
+package Implicit impl;
+
+var a: () = a_ref;
+
+// CHECK:STDOUT: --- implicit.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.a_ref = %a_ref}
+// CHECK:STDOUT:   %a_ref.var: ref i32 = var a_ref
+// CHECK:STDOUT:   %a_ref: ref i32 = bind_name a_ref, %a_ref.var
+// CHECK:STDOUT:   %.loc4: i32 = int_literal 0
+// CHECK:STDOUT:   assign %a_ref.var, %.loc4
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- implicit.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.loc4: type = tuple_type ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.a_ref = %package.var, .a = %a}
+// CHECK:STDOUT:   %package.var: ref <error> = var package
+// CHECK:STDOUT:   %.loc4_9.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc4_9.2: type = converted %.loc4_9.1, constants.%.loc4
+// CHECK:STDOUT:   %a.var: ref () = var a
+// CHECK:STDOUT:   %a: ref () = bind_name a, %a.var
+// CHECK:STDOUT:   %a_ref.ref: ref <error> = name_ref a_ref, %package.var
+// CHECK:STDOUT:   assign %a.var, <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

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

@@ -65,16 +65,12 @@ library "lib" impl;
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {}
-// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
-// CHECK:STDOUT:   %WithImpl: <namespace> = bind_name WithImpl, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- with_impl_extra.impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {}
-// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
-// CHECK:STDOUT:   %WithImpl: <namespace> = bind_name WithImpl, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- with_impl_lib.carbon
@@ -87,8 +83,6 @@ library "lib" impl;
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {}
-// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
-// CHECK:STDOUT:   %WithImpl: <namespace> = bind_name WithImpl, %import
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- main.carbon
@@ -107,6 +101,5 @@ library "lib" impl;
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {}
-// CHECK:STDOUT:   %import: <namespace> = import ir1, ir1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 110 - 0
toolchain/check/testdata/packages/loaded_global.carbon

@@ -0,0 +1,110 @@
+// 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
+
+// --- implicit.carbon
+
+package Implicit api;
+
+fn A();
+
+// --- implicit.impl.carbon
+
+package Implicit impl;
+
+var a: () = A();
+
+var package_a: () = package.A();
+
+// --- same_package.carbon
+
+package SamePackage api;
+
+fn B();
+
+// --- same_package_importer.carbon
+
+package SamePackage library "importer" api;
+
+import library default;
+
+var b: () = B();
+
+var package_b: () = package.B();
+
+// CHECK:STDOUT: --- implicit.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.A = %A}
+// CHECK:STDOUT:   %A: <function> = fn_decl @A
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @A();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- implicit.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.loc4: type = tuple_type ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.A = %.2, .a = %a, .package_a = %package_a}
+// CHECK:STDOUT:   %.2: <function> = fn_decl @.1
+// CHECK:STDOUT:   %.loc4_9.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc4_9.2: type = converted %.loc4_9.1, constants.%.loc4
+// CHECK:STDOUT:   %a.var: ref () = var a
+// CHECK:STDOUT:   %a: ref () = bind_name a, %a.var
+// CHECK:STDOUT:   %A.ref.loc4: <function> = name_ref A, %.2
+// CHECK:STDOUT:   %.loc4_14: init () = call %A.ref.loc4()
+// CHECK:STDOUT:   assign %a.var, %.loc4_14
+// CHECK:STDOUT:   %.loc6_17: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc4_9.3: type = converted %.loc6_17, constants.%.loc4
+// CHECK:STDOUT:   %package_a.var: ref () = var package_a
+// CHECK:STDOUT:   %package_a: ref () = bind_name package_a, %package_a.var
+// CHECK:STDOUT:   %package.ref: <namespace> = name_ref package, package
+// CHECK:STDOUT:   %A.ref.loc6: <function> = name_ref A, %.2
+// CHECK:STDOUT:   %.loc6_30: init () = call %A.ref.loc6()
+// CHECK:STDOUT:   assign %package_a.var, %.loc6_30
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- same_package.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.B = %B}
+// CHECK:STDOUT:   %B: <function> = fn_decl @B
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @B();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- same_package_importer.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.loc6: type = tuple_type ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.B = %.2, .b = %b, .package_b = %package_b}
+// CHECK:STDOUT:   %.2: <function> = fn_decl @.1
+// CHECK:STDOUT:   %.loc6_9.1: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc6_9.2: type = converted %.loc6_9.1, constants.%.loc6
+// CHECK:STDOUT:   %b.var: ref () = var b
+// CHECK:STDOUT:   %b: ref () = bind_name b, %b.var
+// CHECK:STDOUT:   %B.ref.loc6: <function> = name_ref B, %.2
+// CHECK:STDOUT:   %.loc6_14: init () = call %B.ref.loc6()
+// CHECK:STDOUT:   assign %b.var, %.loc6_14
+// CHECK:STDOUT:   %.loc8_17: () = tuple_literal ()
+// CHECK:STDOUT:   %.loc6_9.3: type = converted %.loc8_17, constants.%.loc6
+// CHECK:STDOUT:   %package_b.var: ref () = var package_b
+// CHECK:STDOUT:   %package_b: ref () = bind_name package_b, %package_b.var
+// CHECK:STDOUT:   %package.ref: <namespace> = name_ref package, package
+// CHECK:STDOUT:   %B.ref.loc8: <function> = name_ref B, %.2
+// CHECK:STDOUT:   %.loc8_30: init () = call %B.ref.loc8()
+// CHECK:STDOUT:   assign %package_b.var, %.loc8_30
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1();
+// CHECK:STDOUT:

+ 32 - 0
toolchain/check/testdata/packages/unused_lazy_import.carbon

@@ -0,0 +1,32 @@
+// 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
+
+// --- implicit.carbon
+
+package Implicit api;
+
+fn A();
+
+// --- implicit.impl.carbon
+
+package Implicit impl;
+
+// CHECK:STDOUT: --- implicit.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.A = %A}
+// CHECK:STDOUT:   %A: <function> = fn_decl @A
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @A();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- implicit.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.A = %lazy_import_ref}
+// CHECK:STDOUT:   %lazy_import_ref = lazy_import_ref ir1, inst+1
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 22 - 16
toolchain/lower/handle.cpp

@@ -12,13 +12,17 @@
 #include "llvm/Support/Casting.h"
 #include "toolchain/lower/function_context.h"
 #include "toolchain/sem_ir/inst.h"
-#include "toolchain/sem_ir/inst_kind.h"
+#include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Lower {
 
-auto HandleCrossRef(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
-                    SemIR::CrossRef inst) -> void {
-  CARBON_FATAL() << "TODO: Add support: " << inst;
+template <typename InstT>
+static auto FatalErrorIfEncountered(InstT inst) -> void {
+  CARBON_FATAL()
+      << "Encountered an instruction that isn't expected to lower. It's "
+         "possible that logic needs to be changed in order to stop "
+         "showing this instruction in lowered contexts. Instruction: "
+      << inst;
 }
 
 auto HandleAddressOf(FunctionContext& context, SemIR::InstId inst_id,
@@ -166,6 +170,11 @@ auto HandleConverted(FunctionContext& context, SemIR::InstId inst_id,
   context.SetLocal(inst_id, context.GetValue(inst.result_id));
 }
 
+auto HandleCrossRef(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
+                    SemIR::CrossRef inst) -> void {
+  FatalErrorIfEncountered(inst);
+}
+
 auto HandleDeref(FunctionContext& context, SemIR::InstId inst_id,
                  SemIR::Deref inst) -> void {
   context.SetLocal(inst_id, context.GetValue(inst.pointer_id));
@@ -173,18 +182,12 @@ auto HandleDeref(FunctionContext& context, SemIR::InstId inst_id,
 
 auto HandleFunctionDecl(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
                         SemIR::FunctionDecl 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;
+  FatalErrorIfEncountered(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;
+  FatalErrorIfEncountered(inst);
 }
 
 auto HandleInitializeFrom(FunctionContext& context, SemIR::InstId /*inst_id*/,
@@ -208,6 +211,12 @@ auto HandleIntLiteral(FunctionContext& context, SemIR::InstId inst_id,
   context.SetLocal(inst_id, v);
 }
 
+auto HandleLazyImportRef(FunctionContext& /*context*/,
+                         SemIR::InstId /*inst_id*/, SemIR::LazyImportRef inst)
+    -> void {
+  FatalErrorIfEncountered(inst);
+}
+
 auto HandleNameRef(FunctionContext& context, SemIR::InstId inst_id,
                    SemIR::NameRef inst) -> void {
   auto type_inst_id = context.sem_ir().types().GetInstId(inst.type_id);
@@ -220,10 +229,7 @@ auto HandleNameRef(FunctionContext& context, SemIR::InstId inst_id,
 
 auto HandleNamespace(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
                      SemIR::Namespace 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;
+  FatalErrorIfEncountered(inst);
 }
 
 auto HandleNoOp(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,

+ 3 - 0
toolchain/sem_ir/file.cpp

@@ -208,6 +208,7 @@ static auto GetTypePrecedence(InstKind kind) -> int {
     case InitializeFrom::Kind:
     case InterfaceDecl::Kind:
     case IntLiteral::Kind:
+    case LazyImportRef::Kind:
     case Namespace::Kind:
     case NoOp::Kind:
     case Param::Kind:
@@ -409,6 +410,7 @@ auto File::StringifyTypeExpr(InstId outer_inst_id) const -> std::string {
       case InitializeFrom::Kind:
       case InterfaceDecl::Kind:
       case IntLiteral::Kind:
+      case LazyImportRef::Kind:
       case Namespace::Kind:
       case NoOp::Kind:
       case Param::Kind:
@@ -466,6 +468,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case FunctionDecl::Kind:
       case Import::Kind:
       case InterfaceDecl::Kind:
+      case LazyImportRef::Kind:
       case Namespace::Kind:
       case NoOp::Kind:
       case Return::Kind:

+ 26 - 2
toolchain/sem_ir/formatter.cpp

@@ -12,6 +12,7 @@
 #include "toolchain/lex/tokenized_buffer.h"
 #include "toolchain/parse/tree.h"
 #include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::SemIR {
 
@@ -494,6 +495,10 @@ class InstNamer {
                            ".decl");
           continue;
         }
+        case LazyImportRef::Kind: {
+          add_inst_name("lazy_import_ref");
+          continue;
+        }
         case NameRef::Kind: {
           add_inst_name_id(inst.As<NameRef>().name_id, ".ref");
           continue;
@@ -703,10 +708,12 @@ class Formatter {
 
   auto FormatNameScope(NameScopeId id, llvm::StringRef separator,
                        llvm::StringRef prefix) -> void {
+    const auto& scope = sem_ir_.name_scopes().Get(id);
+
     // Name scopes aren't kept in any particular order. Sort the entries before
     // we print them for stability and consistency.
     llvm::SmallVector<std::pair<InstId, NameId>> entries;
-    for (auto [name_id, inst_id] : sem_ir_.name_scopes().Get(id)) {
+    for (auto [name_id, inst_id] : scope.names) {
       entries.push_back({inst_id, name_id});
     }
     llvm::sort(entries,
@@ -719,6 +726,10 @@ class Formatter {
       out_ << " = ";
       FormatInstName(inst_id);
     }
+
+    if (scope.has_load_error) {
+      out_ << sep << "has_load_error";
+    }
   }
 
   auto FormatInstruction(InstId inst_id) -> void {
@@ -794,6 +805,13 @@ class Formatter {
     out_ << " = ";
   }
 
+  // Print LazyImportRef with type-like semantics even though it lacks a
+  // type_id.
+  auto FormatInstructionLHS(InstId inst_id, LazyImportRef /*inst*/) -> void {
+    FormatInstName(inst_id);
+    out_ << " = ";
+  }
+
   template <typename InstT>
   auto FormatInstructionRHS(InstT inst) -> void {
     // By default, an instruction has a comma-separated argument list.
@@ -900,7 +918,13 @@ class Formatter {
   auto FormatInstructionRHS(CrossRef inst) -> void {
     // TODO: Figure out a way to make this meaningful. We'll need some way to
     // name cross-reference IRs, perhaps by the instruction ID of the import?
-    out_ << " " << inst.ir_id << "." << inst.inst_id;
+    out_ << " " << inst.ir_id << ", " << inst.inst_id;
+  }
+
+  auto FormatInstructionRHS(LazyImportRef inst) -> void {
+    // Don't format the inst_id because it refers to a different IR.
+    // TODO: Consider a better way to format the InstID from other IRs.
+    out_ << " " << inst.ir_id << ", " << inst.inst_id;
   }
 
   auto FormatInstructionRHS(SpliceBlock inst) -> void {

+ 6 - 2
toolchain/sem_ir/ids.h

@@ -19,6 +19,7 @@ class Inst;
 struct Class;
 struct Function;
 struct Interface;
+struct NameScope;
 struct TypeInfo;
 
 // The ID of an instruction.
@@ -181,7 +182,7 @@ struct NameId : public IdBase, public Printable<NameId> {
 
   // Returns the IdentifierId corresponding to this NameId, or an invalid
   // IdentifierId if this is a special name.
-  auto AsIdentifierId() -> IdentifierId {
+  auto AsIdentifierId() const -> IdentifierId {
     return index >= 0 ? IdentifierId(index) : IdentifierId::Invalid;
   }
 
@@ -213,10 +214,12 @@ constexpr NameId NameId::Base = NameId(NameId::InvalidIndex - 5);
 
 // The ID of a name scope.
 struct NameScopeId : public IdBase, public Printable<NameScopeId> {
-  using ValueType = llvm::DenseMap<NameId, InstId>;
+  using ValueType = NameScope;
 
   // An explicitly invalid ID.
   static const NameScopeId Invalid;
+  // The package (or file) name scope, guaranteed to be the first added.
+  static const NameScopeId Package;
 
   using IdBase::IdBase;
   auto Print(llvm::raw_ostream& out) const -> void {
@@ -227,6 +230,7 @@ struct NameScopeId : public IdBase, public Printable<NameScopeId> {
 
 constexpr NameScopeId NameScopeId::Invalid =
     NameScopeId(NameScopeId::InvalidIndex);
+constexpr NameScopeId NameScopeId::Package = NameScopeId(0);
 
 // The ID of an instruction block.
 struct InstBlockId : public IdBase, public Printable<InstBlockId> {

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -47,6 +47,7 @@ CARBON_SEM_IR_INST_KIND(Import)
 CARBON_SEM_IR_INST_KIND(InitializeFrom)
 CARBON_SEM_IR_INST_KIND(InterfaceDecl)
 CARBON_SEM_IR_INST_KIND(IntLiteral)
+CARBON_SEM_IR_INST_KIND(LazyImportRef)
 CARBON_SEM_IR_INST_KIND(NameRef)
 CARBON_SEM_IR_INST_KIND(Namespace)
 CARBON_SEM_IR_INST_KIND(NoOp)

+ 14 - 0
toolchain/sem_ir/typed_insts.h

@@ -341,6 +341,20 @@ struct IntLiteral {
   IntId int_id;
 };
 
+// This instruction is not intended for direct use. Instead, it should be loaded
+// if it's referenced through name resolution.
+struct LazyImportRef {
+  static constexpr auto Kind =
+      InstKind::LazyImportRef.Define("lazy_import_ref");
+
+  // No parse node: an instruction's parse tree node must refer to a node in the
+  // current parse tree. This cannot use the cross-referenced instruction's
+  // parse tree node because it will be in a different parse tree.
+
+  CrossRefIRId ir_id;
+  InstId inst_id;
+};
+
 struct NameRef {
   static constexpr auto Kind = InstKind::NameRef.Define("name_ref");
 

+ 14 - 3
toolchain/sem_ir/value_stores.h

@@ -164,6 +164,15 @@ class NameStoreWrapper {
   const StringStoreWrapper<IdentifierId>* identifiers_;
 };
 
+struct NameScope {
+  // Names in the scope.
+  llvm::DenseMap<NameId, InstId> names;
+
+  // Whether the scope corresponds to an import that failed. There may still be
+  // names from successful imports, or the current file.
+  bool has_load_error = false;
+};
+
 // Provides a ValueStore wrapper for an API specific to name scopes.
 class NameScopeStore {
  public:
@@ -174,12 +183,14 @@ class NameScopeStore {
   // duplicates.
   auto AddEntry(NameScopeId scope_id, NameId name_id, InstId target_id)
       -> bool {
-    return values_.Get(scope_id).insert({name_id, target_id}).second;
+    return values_.Get(scope_id).names.insert({name_id, target_id}).second;
   }
 
   // Returns the requested name scope.
-  auto Get(NameScopeId scope_id) const
-      -> const llvm::DenseMap<NameId, InstId>& {
+  auto Get(NameScopeId scope_id) -> NameScope& { return values_.Get(scope_id); }
+
+  // Returns the requested name scope.
+  auto Get(NameScopeId scope_id) const -> const NameScope& {
     return values_.Get(scope_id);
   }