Jelajahi Sumber

Start adding ImportRefUsed, removing older copy behavior. (#3657)

Builds on #3656.

Under the prior "lazy" model, we had been planning to copy instructions.
Under the current "unused+used" model, we're restricting that to
constants. I'm trimming back some of the ResolveIfImportRefUnused logic
because it was more appropriate for the former model.

Right now, I'm adding ImportRefUsed direct creation in import.cpp for
namespaces. I think I'll need to do something similar in
RseolveIfImportRefUnused... I'm still trying to think about how to
manage type information there (which needs to come in as an import
reference itself, and probably have some amount of deduplication before
forming a TypeId). So ImportRefUnused lacks a type because I'm hesitant
to aggressively load it, whereas ImportRefUsed should *always* have a
type but it's just an error while I think things through.

For reference, ImportRefUnused and ImportRefUsed are mainly split in
order to track the boolean "used" without making fundamental
modifications to Inst for bit packing (this effectively instead packs a
bit into InstKind). AnyImportRef currently excludes the type because
it's mainly for diagnostic printing at the moment. It could end up with
a TypeId that would end up Invalid for ImportRefUnused, though it could
also be that the TypeId is only accessed when using ImportRefUsed
explicitly.
Jon Ross-Perkins 2 tahun lalu
induk
melakukan
c1a894fca7

+ 1 - 1
toolchain/check/check.cpp

@@ -52,7 +52,7 @@ class SemIRLocationTranslator
 
       // If the parse node was invalid, recurse through import references when
       // possible.
-      if (auto import_ref = cursor_ir->insts().TryGetAs<SemIR::ImportRefUnused>(
+      if (auto import_ref = cursor_ir->insts().TryGetAs<SemIR::AnyImportRef>(
               cursor_inst_id)) {
         cursor_ir = cursor_ir->cross_ref_irs().Get(import_ref->ir_id);
         cursor_inst_id = import_ref->inst_id;

+ 11 - 30
toolchain/check/context.cpp

@@ -221,46 +221,26 @@ auto Context::AddNameToLookup(SemIR::NameId name_id, SemIR::InstId target_id)
 
 auto Context::ResolveIfImportRefUnused(SemIR::InstId inst_id) -> void {
   auto inst = insts().Get(inst_id);
-  auto lazy_inst = inst.TryAs<SemIR::ImportRefUnused>();
-  if (!lazy_inst) {
+  auto unused_inst = inst.TryAs<SemIR::ImportRefUnused>();
+  if (!unused_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);
+  const SemIR::File& import_ir = *cross_ref_irs().Get(unused_inst->ir_id);
+  auto import_inst = import_ir.insts().Get(unused_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,
-                           .enclosing_scope_id = SemIR::NameScopeId::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});
-      ReplaceInstBeforeConstantUse(
-          inst_id,
-          // TODO: For diagnostic purposes, we should provide some form of
-          // location for the function.
-          {Parse::NodeId::Invalid,
-           SemIR::FunctionDecl{GetBuiltinType(SemIR::BuiltinKind::FunctionType),
-                               function_id}});
-      constant_values().Set(inst_id,
-                            SemIR::ConstantId::ForTemplateConstant(inst_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());
+           (llvm::Twine("TODO: ResolveIfImportRefUnused for ") +
+            import_inst.kind().name())
+               .str());
       ReplaceInstBeforeConstantUse(
-          inst_id, {Parse::NodeId::Invalid,
-                    SemIR::VarStorage{SemIR::TypeId::Error,
-                                      SemIR::NameId::PackageNamespace}});
+          inst_id,
+          {SemIR::ImportRefUsed{SemIR::TypeId::Error, unused_inst->ir_id,
+                                unused_inst->inst_id}});
       break;
   }
 }
@@ -970,6 +950,7 @@ class TypeCompleter {
       case SemIR::InterfaceDecl::Kind:
       case SemIR::IntLiteral::Kind:
       case SemIR::ImportRefUnused::Kind:
+      case SemIR::ImportRefUsed::Kind:
       case SemIR::NameRef::Kind:
       case SemIR::Namespace::Kind:
       case SemIR::Param::Kind:

+ 1 - 0
toolchain/check/eval.cpp

@@ -477,6 +477,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     case SemIR::ClassDecl::Kind:
     case SemIR::Import::Kind:
     case SemIR::ImportRefUnused::Kind:
+    case SemIR::ImportRefUsed::Kind:
     case SemIR::InterfaceDecl::Kind:
     case SemIR::Param::Kind:
     case SemIR::ReturnExpr::Kind:

+ 2 - 2
toolchain/check/import.cpp

@@ -103,8 +103,8 @@ static auto CopySingleNameScopeFromImportIR(
   }
 
   // Produce the namespace for the entry.
-  auto ref_id = context.AddInst(
-      SemIR::ImportRefUnused{.ir_id = ir_id, .inst_id = import_inst_id});
+  auto ref_id = context.AddInst(SemIR::ImportRefUsed{
+      .type_id = namespace_type_id, .ir_id = ir_id, .inst_id = import_inst_id});
   auto namespace_inst =
       SemIR::Namespace{namespace_type_id, SemIR::NameScopeId::Invalid, ref_id};
   // Use the invalid node because there's no node to associate with.

+ 1 - 1
toolchain/check/testdata/namespace/add_to_import.carbon

@@ -33,7 +33,7 @@ var a: i32 = NS.A();
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.NS = %.2, .a = %a} [template]
-// CHECK:STDOUT:   %import_ref = import_ref ir1, inst+1, unused
+// CHECK:STDOUT:   %import_ref: <namespace> = import_ref ir1, inst+1, used
 // CHECK:STDOUT:   %.2: <namespace> = namespace {.A = %A}, %import_ref [template]
 // CHECK:STDOUT:   %A: <function> = fn_decl @A [template]
 // CHECK:STDOUT:   %a.var: ref i32 = var a

+ 1 - 1
toolchain/check/testdata/namespace/fail_conflict_after_merge.carbon

@@ -51,7 +51,7 @@ fn NS();
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.NS = %.loc8_13.1} [template]
-// CHECK:STDOUT:   %import_ref = import_ref ir1, inst+1, unused
+// CHECK:STDOUT:   %import_ref: <namespace> = import_ref ir1, inst+1, used
 // CHECK:STDOUT:   %.loc8_13.1: <namespace> = namespace {}, %import_ref [template]
 // CHECK:STDOUT:   %.loc8_13.2: <namespace> = namespace {} [template]
 // CHECK:STDOUT:   %.loc16: <function> = fn_decl @.1 [template]

+ 1 - 1
toolchain/check/testdata/namespace/fail_conflict_imported_namespace_first.carbon

@@ -37,7 +37,7 @@ fn NS.Foo();
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.NS = %.2} [template]
-// CHECK:STDOUT:   %import_ref = import_ref ir1, inst+1, unused
+// CHECK:STDOUT:   %import_ref: <namespace> = import_ref ir1, inst+1, used
 // CHECK:STDOUT:   %.2: <namespace> = namespace {.Foo = %Foo}, %import_ref [template]
 // CHECK:STDOUT:   %.loc12: <function> = fn_decl @.1 [template]
 // CHECK:STDOUT:   %Foo: <function> = fn_decl @Foo [template]

+ 9 - 8
toolchain/check/testdata/namespace/fail_conflict_imported_namespace_second.carbon

@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 // AUTOUPDATE
+// CHECK:STDERR: conflict.carbon: ERROR: Semantics TODO: `TODO: ResolveIfImportRefUnused for FunctionDecl`.
 
 // --- fn.carbon
 
@@ -16,10 +17,12 @@ package Example api;
 
 import library "fn";
 
-// CHECK:STDERR: conflict.carbon:[[@LINE+4]]:1: ERROR: Duplicate name being declared in the same scope.
+// CHECK:STDERR: conflict.carbon:[[@LINE+6]]:1: ERROR: Duplicate name being declared in the same scope.
 // CHECK:STDERR: namespace NS;
 // CHECK:STDERR: ^~~~~~~~~~~~~
-// CHECK:STDERR: conflict.carbon: Name is previously declared here.
+// CHECK:STDERR: fn.carbon:4:1: Name is previously declared here.
+// CHECK:STDERR: fn NS();
+// CHECK:STDERR: ^~~~~~~~
 namespace NS;
 
 // CHECK:STDERR: conflict.carbon:[[@LINE+6]]:7: ERROR: Name qualifiers are only allowed for entities that provide a scope.
@@ -42,13 +45,11 @@ fn NS.Foo();
 // CHECK:STDOUT: --- conflict.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace {.NS = %.2} [template]
-// CHECK:STDOUT:   %.2: <function> = fn_decl @.1 [template]
-// CHECK:STDOUT:   %.loc10: <namespace> = namespace {} [template]
-// CHECK:STDOUT:   %.loc18: <function> = fn_decl @.2 [template]
+// CHECK:STDOUT:   package: <namespace> = namespace {.NS = %import_ref} [template]
+// CHECK:STDOUT:   %import_ref: <error> = import_ref ir1, inst+1, used
+// CHECK:STDOUT:   %.loc12: <namespace> = namespace {} [template]
+// CHECK:STDOUT:   %.loc20: <function> = fn_decl @.1 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @.1();
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.2();
-// CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/namespace/fail_conflict_in_imports_namespace_first.carbon

@@ -61,7 +61,7 @@ fn NS.Bar() {}
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.NS = %.2} [template]
-// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+1, unused
+// CHECK:STDOUT:   %import_ref.1: <namespace> = import_ref ir1, inst+1, used
 // CHECK:STDOUT:   %.2: <namespace> = namespace {.Foo = %import_ref.2, .Bar = %Bar}, %import_ref.1 [template]
 // CHECK:STDOUT:   %import_ref.2 = import_ref ir1, inst+2, unused
 // CHECK:STDOUT:   %import_ref.3 = import_ref ir2, inst+1, unused

+ 1 - 1
toolchain/check/testdata/namespace/fail_conflict_in_imports_namespace_second.carbon

@@ -62,7 +62,7 @@ fn NS.Bar() {}
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.NS = %.2} [template]
 // CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+1, unused
-// CHECK:STDOUT:   %import_ref.2 = import_ref ir2, inst+1, unused
+// CHECK:STDOUT:   %import_ref.2: <namespace> = import_ref ir2, inst+1, used
 // CHECK:STDOUT:   %.2: <namespace> = namespace {.Foo = %import_ref.3, .Bar = %Bar}, %import_ref.2 [template]
 // CHECK:STDOUT:   %import_ref.3 = import_ref ir2, inst+2, unused
 // CHECK:STDOUT:   %Bar: <function> = fn_decl @Bar [template]

+ 16 - 22
toolchain/check/testdata/namespace/imported.carbon → toolchain/check/testdata/namespace/fail_todo_imported.carbon

@@ -3,6 +3,8 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 // AUTOUPDATE
+// CHECK:STDERR: implicit.impl.carbon: ERROR: Semantics TODO: `TODO: ResolveIfImportRefUnused for FunctionDecl`.
+// CHECK:STDERR: implicit.impl.carbon: ERROR: Semantics TODO: `TODO: ResolveIfImportRefUnused for FunctionDecl`.
 
 // --- implicit.carbon
 
@@ -46,38 +48,35 @@ var package_b: () = package.NS.ChildNS.B();
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.NS = %.2, .a = %a, .b = %b, .package_a = %package_a, .package_b = %package_b} [template]
-// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+1, unused
-// CHECK:STDOUT:   %.2: <namespace> = namespace {.ChildNS = %.3, .A = %.4}, %import_ref.1 [template]
-// CHECK:STDOUT:   %import_ref.2 = import_ref ir1, inst+2, unused
-// CHECK:STDOUT:   %.3: <namespace> = namespace {.B = %.5}, %import_ref.2 [template]
-// CHECK:STDOUT:   %.4: <function> = fn_decl @.1 [template]
-// CHECK:STDOUT:   %.5: <function> = fn_decl @.2 [template]
+// CHECK:STDOUT:   %import_ref.1: <namespace> = import_ref ir1, inst+1, used
+// CHECK:STDOUT:   %.2: <namespace> = namespace {.ChildNS = %.3, .A = %import_ref.3}, %import_ref.1 [template]
+// CHECK:STDOUT:   %import_ref.2: <namespace> = import_ref ir1, inst+2, used
+// CHECK:STDOUT:   %.3: <namespace> = namespace {.B = %import_ref.4}, %import_ref.2 [template]
+// CHECK:STDOUT:   %import_ref.3: <error> = import_ref ir1, inst+3, used
+// CHECK:STDOUT:   %import_ref.4: <error> = import_ref ir1, inst+4, used
 // CHECK:STDOUT:   %.loc4_9.1: () = tuple_literal ()
 // CHECK:STDOUT:   %.loc4_9.2: type = converted %.loc4_9.1, constants.%.1 [template = constants.%.1]
 // CHECK:STDOUT:   %a.var: ref () = var a
 // CHECK:STDOUT:   %a: ref () = bind_name a, %a.var
 // CHECK:STDOUT:   %NS.ref.loc4: <namespace> = name_ref NS, %.2 [template = %.2]
-// CHECK:STDOUT:   %A.ref.loc4: <function> = name_ref A, %.4 [template = %.4]
-// CHECK:STDOUT:   %.loc4_17: init () = call %A.ref.loc4()
-// CHECK:STDOUT:   assign %a.var, %.loc4_17
+// CHECK:STDOUT:   %A.ref.loc4: <error> = name_ref A, %import_ref.3
+// CHECK:STDOUT:   assign %a.var, <error>
 // CHECK:STDOUT:   %.loc5_9.1: () = tuple_literal ()
 // CHECK:STDOUT:   %.loc5_9.2: type = converted %.loc5_9.1, constants.%.1 [template = constants.%.1]
 // CHECK:STDOUT:   %b.var: ref () = var b
 // CHECK:STDOUT:   %b: ref () = bind_name b, %b.var
 // CHECK:STDOUT:   %NS.ref.loc5: <namespace> = name_ref NS, %.2 [template = %.2]
 // CHECK:STDOUT:   %ChildNS.ref.loc5: <namespace> = name_ref ChildNS, %.3 [template = %.3]
-// CHECK:STDOUT:   %B.ref.loc5: <function> = name_ref B, %.5 [template = %.5]
-// CHECK:STDOUT:   %.loc5_25: init () = call %B.ref.loc5()
-// CHECK:STDOUT:   assign %b.var, %.loc5_25
+// CHECK:STDOUT:   %B.ref.loc5: <error> = name_ref B, %import_ref.4
+// CHECK:STDOUT:   assign %b.var, <error>
 // CHECK:STDOUT:   %.loc7_17.1: () = tuple_literal ()
 // CHECK:STDOUT:   %.loc7_17.2: type = converted %.loc7_17.1, constants.%.1 [template = constants.%.1]
 // 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.loc7: <namespace> = name_ref package, package [template = package]
 // CHECK:STDOUT:   %NS.ref.loc7: <namespace> = name_ref NS, %.2 [template = %.2]
-// CHECK:STDOUT:   %A.ref.loc7: <function> = name_ref A, %.4 [template = %.4]
-// CHECK:STDOUT:   %.loc7_33: init () = call %A.ref.loc7()
-// CHECK:STDOUT:   assign %package_a.var, %.loc7_33
+// CHECK:STDOUT:   %A.ref.loc7: <error> = name_ref A, %import_ref.3
+// CHECK:STDOUT:   assign %package_a.var, <error>
 // CHECK:STDOUT:   %.loc8_17.1: () = tuple_literal ()
 // CHECK:STDOUT:   %.loc8_17.2: type = converted %.loc8_17.1, constants.%.1 [template = constants.%.1]
 // CHECK:STDOUT:   %package_b.var: ref () = var package_b
@@ -85,12 +84,7 @@ var package_b: () = package.NS.ChildNS.B();
 // CHECK:STDOUT:   %package.ref.loc8: <namespace> = name_ref package, package [template = package]
 // CHECK:STDOUT:   %NS.ref.loc8: <namespace> = name_ref NS, %.2 [template = %.2]
 // CHECK:STDOUT:   %ChildNS.ref.loc8: <namespace> = name_ref ChildNS, %.3 [template = %.3]
-// CHECK:STDOUT:   %B.ref.loc8: <function> = name_ref B, %.5 [template = %.5]
-// CHECK:STDOUT:   %.loc8_41: init () = call %B.ref.loc8()
-// CHECK:STDOUT:   assign %package_b.var, %.loc8_41
+// CHECK:STDOUT:   %B.ref.loc8: <error> = name_ref B, %import_ref.4
+// CHECK:STDOUT:   assign %package_b.var, <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1();
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @.2();
-// CHECK:STDOUT:

+ 14 - 16
toolchain/check/testdata/namespace/imported_indirect.carbon → toolchain/check/testdata/namespace/fail_todo_imported_indirect.carbon

@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 // AUTOUPDATE
+// CHECK:STDERR: e.carbon: ERROR: Semantics TODO: `TODO: ResolveIfImportRefUnused for FunctionDecl`.
 
 // --- a.carbon
 
@@ -49,7 +50,7 @@ var e: () = A.B.C.D();
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.A = %.2} [template]
-// CHECK:STDOUT:   %import_ref = import_ref ir1, inst+1, unused
+// CHECK:STDOUT:   %import_ref: <namespace> = import_ref ir1, inst+1, used
 // CHECK:STDOUT:   %.2: <namespace> = namespace {.B = %.loc5}, %import_ref [template]
 // CHECK:STDOUT:   %.loc5: <namespace> = namespace {} [template]
 // CHECK:STDOUT: }
@@ -58,9 +59,9 @@ var e: () = A.B.C.D();
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.A = %.2} [template]
-// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+2, unused
+// CHECK:STDOUT:   %import_ref.1: <namespace> = import_ref ir1, inst+2, used
 // CHECK:STDOUT:   %.2: <namespace> = namespace {.B = %.3}, %import_ref.1 [template]
-// CHECK:STDOUT:   %import_ref.2 = import_ref ir1, inst+3, unused
+// CHECK:STDOUT:   %import_ref.2: <namespace> = import_ref ir1, inst+3, used
 // CHECK:STDOUT:   %.3: <namespace> = namespace {.C = %.loc5}, %import_ref.2 [template]
 // CHECK:STDOUT:   %.loc5: <namespace> = namespace {} [template]
 // CHECK:STDOUT: }
@@ -69,11 +70,11 @@ var e: () = A.B.C.D();
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.A = %.2} [template]
-// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+2, unused
+// CHECK:STDOUT:   %import_ref.1: <namespace> = import_ref ir1, inst+2, used
 // CHECK:STDOUT:   %.2: <namespace> = namespace {.B = %.3}, %import_ref.1 [template]
-// CHECK:STDOUT:   %import_ref.2 = import_ref ir1, inst+4, unused
+// CHECK:STDOUT:   %import_ref.2: <namespace> = import_ref ir1, inst+4, used
 // CHECK:STDOUT:   %.3: <namespace> = namespace {.C = %.4}, %import_ref.2 [template]
-// CHECK:STDOUT:   %import_ref.3 = import_ref ir1, inst+5, unused
+// CHECK:STDOUT:   %import_ref.3: <namespace> = import_ref ir1, inst+5, used
 // CHECK:STDOUT:   %.4: <namespace> = namespace {.D = %D}, %import_ref.3 [template]
 // CHECK:STDOUT:   %D: <function> = fn_decl @D [template]
 // CHECK:STDOUT: }
@@ -91,13 +92,13 @@ var e: () = A.B.C.D();
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.A = %.2, .e = %e} [template]
-// CHECK:STDOUT:   %import_ref.1 = import_ref ir1, inst+2, unused
+// CHECK:STDOUT:   %import_ref.1: <namespace> = import_ref ir1, inst+2, used
 // CHECK:STDOUT:   %.2: <namespace> = namespace {.B = %.3}, %import_ref.1 [template]
-// CHECK:STDOUT:   %import_ref.2 = import_ref ir1, inst+4, unused
+// CHECK:STDOUT:   %import_ref.2: <namespace> = import_ref ir1, inst+4, used
 // CHECK:STDOUT:   %.3: <namespace> = namespace {.C = %.4}, %import_ref.2 [template]
-// CHECK:STDOUT:   %import_ref.3 = import_ref ir1, inst+6, unused
-// CHECK:STDOUT:   %.4: <namespace> = namespace {.D = %.5}, %import_ref.3 [template]
-// CHECK:STDOUT:   %.5: <function> = fn_decl @.1 [template]
+// CHECK:STDOUT:   %import_ref.3: <namespace> = import_ref ir1, inst+6, used
+// CHECK:STDOUT:   %.4: <namespace> = namespace {.D = %import_ref.4}, %import_ref.3 [template]
+// CHECK:STDOUT:   %import_ref.4: <error> = import_ref ir1, inst+7, used
 // CHECK:STDOUT:   %.loc5_9.1: () = tuple_literal ()
 // CHECK:STDOUT:   %.loc5_9.2: type = converted %.loc5_9.1, constants.%.1 [template = constants.%.1]
 // CHECK:STDOUT:   %e.var: ref () = var e
@@ -105,10 +106,7 @@ var e: () = A.B.C.D();
 // CHECK:STDOUT:   %A.ref: <namespace> = name_ref A, %.2 [template = %.2]
 // CHECK:STDOUT:   %B.ref: <namespace> = name_ref B, %.3 [template = %.3]
 // CHECK:STDOUT:   %C.ref: <namespace> = name_ref C, %.4 [template = %.4]
-// CHECK:STDOUT:   %D.ref: <function> = name_ref D, %.5 [template = %.5]
-// CHECK:STDOUT:   %.loc5_20: init () = call %D.ref()
-// CHECK:STDOUT:   assign %e.var, %.loc5_20
+// CHECK:STDOUT:   %D.ref: <error> = name_ref D, %import_ref.4
+// CHECK:STDOUT:   assign %e.var, <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1();
-// CHECK:STDOUT:

+ 11 - 17
toolchain/check/testdata/namespace/merging.carbon → toolchain/check/testdata/namespace/fail_todo_merging.carbon

@@ -3,6 +3,9 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 // AUTOUPDATE
+// CHECK:STDERR: c.carbon: ERROR: Semantics TODO: `TODO: ResolveIfImportRefUnused for FunctionDecl`.
+// CHECK:STDERR: c.carbon: ERROR: Semantics TODO: `TODO: ResolveIfImportRefUnused for FunctionDecl`.
+// CHECK:STDERR: c.carbon: ERROR: Semantics TODO: `TODO: ResolveIfImportRefUnused for FunctionDecl`.
 
 // --- a.carbon
 
@@ -83,11 +86,11 @@ fn Run() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.NS = %.loc7_13.1, .Run = %Run} [template]
-// CHECK:STDOUT:   %import_ref = import_ref ir1, inst+1, unused
-// CHECK:STDOUT:   %.loc7_13.1: <namespace> = namespace {.A = %.2, .B1 = %.3, .B2 = %.4, .C = %C}, %import_ref [template]
-// CHECK:STDOUT:   %.2: <function> = fn_decl @.1 [template]
-// CHECK:STDOUT:   %.3: <function> = fn_decl @.2 [template]
-// CHECK:STDOUT:   %.4: <function> = fn_decl @.3 [template]
+// CHECK:STDOUT:   %import_ref.1: <namespace> = import_ref ir1, inst+1, used
+// CHECK:STDOUT:   %.loc7_13.1: <namespace> = namespace {.A = %import_ref.2, .B1 = %import_ref.3, .B2 = %import_ref.4, .C = %C}, %import_ref.1 [template]
+// CHECK:STDOUT:   %import_ref.2: <error> = import_ref ir1, inst+2, used
+// CHECK:STDOUT:   %import_ref.3: <error> = import_ref ir2, inst+2, used
+// CHECK:STDOUT:   %import_ref.4: <error> = import_ref ir2, inst+5, used
 // CHECK:STDOUT:   %.loc7_13.2: <namespace> = namespace {} [template]
 // CHECK:STDOUT:   %C: <function> = fn_decl @C [template]
 // CHECK:STDOUT:   %Run: <function> = fn_decl @Run [template]
@@ -101,23 +104,14 @@ fn Run() {
 // CHECK:STDOUT: fn @Run() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %NS.ref.loc12: <namespace> = name_ref NS, file.%.loc7_13.1 [template = file.%.loc7_13.1]
-// CHECK:STDOUT:   %A.ref: <function> = name_ref A, file.%.2 [template = file.%.2]
-// CHECK:STDOUT:   %.loc12: init () = call %A.ref()
+// CHECK:STDOUT:   %A.ref: <error> = name_ref A, file.%import_ref.2
 // CHECK:STDOUT:   %NS.ref.loc13: <namespace> = name_ref NS, file.%.loc7_13.1 [template = file.%.loc7_13.1]
-// CHECK:STDOUT:   %B1.ref: <function> = name_ref B1, file.%.3 [template = file.%.3]
-// CHECK:STDOUT:   %.loc13: init () = call %B1.ref()
+// CHECK:STDOUT:   %B1.ref: <error> = name_ref B1, file.%import_ref.3
 // CHECK:STDOUT:   %NS.ref.loc14: <namespace> = name_ref NS, file.%.loc7_13.1 [template = file.%.loc7_13.1]
-// CHECK:STDOUT:   %B2.ref: <function> = name_ref B2, file.%.4 [template = file.%.4]
-// CHECK:STDOUT:   %.loc14: init () = call %B2.ref()
+// CHECK:STDOUT:   %B2.ref: <error> = name_ref B2, file.%import_ref.4
 // CHECK:STDOUT:   %NS.ref.loc15: <namespace> = name_ref NS, file.%.loc7_13.1 [template = file.%.loc7_13.1]
 // CHECK:STDOUT:   %C.ref: <function> = name_ref C, file.%C [template = file.%C]
 // CHECK:STDOUT:   %.loc15: init () = call %C.ref()
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1();
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @.2();
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @.3();
-// CHECK:STDOUT:

+ 14 - 20
toolchain/check/testdata/packages/loaded_global.carbon → toolchain/check/testdata/packages/fail_todo_loaded_global.carbon

@@ -3,6 +3,8 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 // AUTOUPDATE
+// CHECK:STDERR: implicit.impl.carbon: ERROR: Semantics TODO: `TODO: ResolveIfImportRefUnused for FunctionDecl`.
+// CHECK:STDERR: same_package_importer.carbon: ERROR: Semantics TODO: `TODO: ResolveIfImportRefUnused for FunctionDecl`.
 
 // --- implicit.carbon
 
@@ -50,27 +52,23 @@ var package_b: () = package.B();
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace {.A = %.2, .a = %a, .package_a = %package_a} [template]
-// CHECK:STDOUT:   %.2: <function> = fn_decl @.1 [template]
+// CHECK:STDOUT:   package: <namespace> = namespace {.A = %import_ref, .a = %a, .package_a = %package_a} [template]
+// CHECK:STDOUT:   %import_ref: <error> = import_ref ir1, inst+1, used
 // CHECK:STDOUT:   %.loc4_9.1: () = tuple_literal ()
 // CHECK:STDOUT:   %.loc4_9.2: type = converted %.loc4_9.1, constants.%.1 [template = constants.%.1]
 // 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 [template = %.2]
-// CHECK:STDOUT:   %.loc4_14: init () = call %A.ref.loc4()
-// CHECK:STDOUT:   assign %a.var, %.loc4_14
+// CHECK:STDOUT:   %A.ref.loc4: <error> = name_ref A, %import_ref
+// CHECK:STDOUT:   assign %a.var, <error>
 // CHECK:STDOUT:   %.loc6_17.1: () = tuple_literal ()
 // CHECK:STDOUT:   %.loc6_17.2: type = converted %.loc6_17.1, constants.%.1 [template = constants.%.1]
 // 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 [template = package]
-// CHECK:STDOUT:   %A.ref.loc6: <function> = name_ref A, %.2 [template = %.2]
-// CHECK:STDOUT:   %.loc6_30: init () = call %A.ref.loc6()
-// CHECK:STDOUT:   assign %package_a.var, %.loc6_30
+// CHECK:STDOUT:   %A.ref.loc6: <error> = name_ref A, %import_ref
+// CHECK:STDOUT:   assign %package_a.var, <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1();
-// CHECK:STDOUT:
 // CHECK:STDOUT: --- same_package.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -87,24 +85,20 @@ var package_b: () = package.B();
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace {.B = %.2, .b = %b, .package_b = %package_b} [template]
-// CHECK:STDOUT:   %.2: <function> = fn_decl @.1 [template]
+// CHECK:STDOUT:   package: <namespace> = namespace {.B = %import_ref, .b = %b, .package_b = %package_b} [template]
+// CHECK:STDOUT:   %import_ref: <error> = import_ref ir1, inst+1, used
 // CHECK:STDOUT:   %.loc6_9.1: () = tuple_literal ()
 // CHECK:STDOUT:   %.loc6_9.2: type = converted %.loc6_9.1, constants.%.1 [template = constants.%.1]
 // 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 [template = %.2]
-// CHECK:STDOUT:   %.loc6_14: init () = call %B.ref.loc6()
-// CHECK:STDOUT:   assign %b.var, %.loc6_14
+// CHECK:STDOUT:   %B.ref.loc6: <error> = name_ref B, %import_ref
+// CHECK:STDOUT:   assign %b.var, <error>
 // CHECK:STDOUT:   %.loc8_17.1: () = tuple_literal ()
 // CHECK:STDOUT:   %.loc8_17.2: type = converted %.loc8_17.1, constants.%.1 [template = constants.%.1]
 // 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 [template = package]
-// CHECK:STDOUT:   %B.ref.loc8: <function> = name_ref B, %.2 [template = %.2]
-// CHECK:STDOUT:   %.loc8_30: init () = call %B.ref.loc8()
-// CHECK:STDOUT:   assign %package_b.var, %.loc8_30
+// CHECK:STDOUT:   %B.ref.loc8: <error> = name_ref B, %import_ref
+// CHECK:STDOUT:   assign %package_b.var, <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1();
-// CHECK:STDOUT:

+ 4 - 4
toolchain/check/testdata/packages/fail_todo_lazy_import_ref.carbon → toolchain/check/testdata/var/fail_todo_import.carbon

@@ -6,7 +6,7 @@
 // then, update it whenever its target is implemented.
 //
 // AUTOUPDATE
-// CHECK:STDERR: implicit.impl.carbon: ERROR: Semantics TODO: `TODO: support BindName`.
+// CHECK:STDERR: implicit.impl.carbon: ERROR: Semantics TODO: `TODO: ResolveIfImportRefUnused for BindName`.
 
 // --- implicit.carbon
 
@@ -41,13 +41,13 @@ var a: () = a_ref;
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace {.a_ref = %package.var, .a = %a} [template]
-// CHECK:STDOUT:   %package.var: ref <error> = var package
+// CHECK:STDOUT:   package: <namespace> = namespace {.a_ref = %import_ref, .a = %a} [template]
+// CHECK:STDOUT:   %import_ref: <error> = import_ref ir1, inst+2, used
 // CHECK:STDOUT:   %.loc4_9.1: () = tuple_literal ()
 // CHECK:STDOUT:   %.loc4_9.2: type = converted %.loc4_9.1, constants.%.1 [template = constants.%.1]
 // 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:   %a_ref.ref: <error> = name_ref a_ref, %import_ref
 // CHECK:STDOUT:   assign %a.var, <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 6 - 0
toolchain/lower/handle.cpp

@@ -206,6 +206,12 @@ auto HandleImportRefUnused(FunctionContext& /*context*/,
   FatalErrorIfEncountered(inst);
 }
 
+auto HandleImportRefUsed(FunctionContext& /*context*/,
+                         SemIR::InstId /*inst_id*/, SemIR::ImportRefUsed inst)
+    -> void {
+  FatalErrorIfEncountered(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();

+ 5 - 0
toolchain/sem_ir/file.cpp

@@ -206,6 +206,7 @@ static auto GetTypePrecedence(InstKind kind) -> int {
     case BindSymbolicName::Kind:
     case Builtin::Kind:
     case ClassType::Kind:
+    case ImportRefUsed::Kind:
     case NameRef::Kind:
     case StructType::Kind:
     case TupleType::Kind:
@@ -355,6 +356,9 @@ auto File::StringifyTypeExpr(InstId outer_inst_id) const -> std::string {
         }
         break;
       }
+      case ImportRefUsed::Kind:
+        out << "<TODO: ImportRefUsed " << step.inst_id << ">";
+        break;
       case NameRef::Kind: {
         out << names().GetFormatted(inst.As<NameRef>().name_id);
         break;
@@ -510,6 +514,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case FunctionDecl::Kind:
       case Import::Kind:
       case ImportRefUnused::Kind:
+      case ImportRefUsed::Kind:
       case InterfaceDecl::Kind:
       case Namespace::Kind:
       case Return::Kind:

+ 8 - 1
toolchain/sem_ir/formatter.cpp

@@ -493,7 +493,8 @@ class InstNamer {
           add_inst_name("import");
           continue;
         }
-        case ImportRefUnused::Kind: {
+        case ImportRefUnused::Kind:
+        case ImportRefUsed::Kind: {
           add_inst_name("import_ref");
           continue;
         }
@@ -953,6 +954,12 @@ class Formatter {
     out_ << " " << inst.ir_id << ", " << inst.inst_id << ", unused";
   }
 
+  auto FormatInstructionRHS(ImportRefUsed 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 << ", used";
+  }
+
   auto FormatInstructionRHS(SpliceBlock inst) -> void {
     FormatArgs(inst.result_id);
     out_ << " {";

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -47,6 +47,7 @@ CARBON_SEM_IR_INST_KIND(FieldDecl)
 CARBON_SEM_IR_INST_KIND(FunctionDecl)
 CARBON_SEM_IR_INST_KIND(Import)
 CARBON_SEM_IR_INST_KIND(ImportRefUnused)
+CARBON_SEM_IR_INST_KIND(ImportRefUsed)
 CARBON_SEM_IR_INST_KIND(InitializeFrom)
 CARBON_SEM_IR_INST_KIND(InterfaceDecl)
 CARBON_SEM_IR_INST_KIND(IntLiteral)

+ 21 - 0
toolchain/sem_ir/typed_insts.h

@@ -416,6 +416,16 @@ struct Import {
   CrossRefIRId last_cross_ref_ir_id;
 };
 
+// Common representation for all kinds of `ImportRef*` node.
+struct AnyImportRef {
+  static constexpr InstKind Kinds[] = {InstKind::ImportRefUnused,
+                                       InstKind::ImportRefUsed};
+
+  InstKind kind;
+  CrossRefIRId ir_id;
+  InstId inst_id;
+};
+
 // An imported entity that hasn't yet been referenced. If referenced, it should
 // turn into an ImportRefUsed.
 // TODO: Add ImportRefUsed.
@@ -428,6 +438,17 @@ struct ImportRefUnused {
   InstId inst_id;
 };
 
+// An imported entity that has a reference, and thus should be emitted.
+struct ImportRefUsed {
+  // No parse node: any parse node logic must use the referenced IR.
+  static constexpr auto Kind =
+      InstKind::ImportRefUsed.Define<Parse::InvalidNodeId>("import_ref");
+
+  TypeId type_id;
+  CrossRefIRId ir_id;
+  InstId inst_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.