Przeglądaj źródła

Basic name poisoning support (#4654)

https://github.com/carbon-language/carbon-lang/issues/4622
When using an unqualified name, disallow declaring that name in all
scopes that would make it ambiguous in retrospect.
Doesn't include support for poisoning in `impl library` (see new test
for that with TODO).
Implemented by introduce `InstId::PoisonedName` and entries with it to
`NameScope`.

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Co-authored-by: Geoff Romer <gromer@google.com>
Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Co-authored-by: josh11b <josh11b@users.noreply.github.com>
Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
Co-authored-by: Carbon Infra Bot <carbon-external-infra@google.com>
Boaz Brickner 1 rok temu
rodzic
commit
9c8773da1b

+ 27 - 3
toolchain/check/context.cpp

@@ -222,6 +222,18 @@ auto Context::DiagnoseDuplicateName(SemIRLoc dup_def, SemIRLoc prev_def)
       .Emit();
 }
 
+auto Context::DiagnosePoisonedName(SemIRLoc loc) -> void {
+  // TODO: Improve the diagnostic to replace NodeId::Invalid with the location
+  // where the name was poisoned. See discussion in
+  // https://github.com/carbon-language/carbon-lang/pull/4654#discussion_r1876607172
+  CARBON_DIAGNOSTIC(NameUseBeforeDecl, Error,
+                    "name used before it was declared");
+  CARBON_DIAGNOSTIC(NameUseBeforeDeclNote, Note, "declared here");
+  emitter_->Build(SemIR::LocId::Invalid, NameUseBeforeDecl)
+      .Note(loc, NameUseBeforeDeclNote)
+      .Emit();
+}
+
 auto Context::DiagnoseNameNotFound(SemIRLoc loc, SemIR::NameId name_id)
     -> void {
   CARBON_DIAGNOSTIC(NameNotFound, Error, "name `{0}` not found", SemIR::NameId);
@@ -354,6 +366,8 @@ auto Context::LookupUnqualifiedName(Parse::NodeId node_id,
       scope_stack().LookupInLexicalScopes(name_id);
 
   // Walk the non-lexical scopes and perform lookups into each of them.
+  // Collect scopes to poison this name when it's found.
+  llvm::SmallVector<LookupScope> scopes_to_poison;
   for (auto [index, lookup_scope_id, specific_id] :
        llvm::reverse(non_lexical_scopes)) {
     if (auto non_lexical_result =
@@ -361,8 +375,17 @@ auto Context::LookupUnqualifiedName(Parse::NodeId node_id,
                                 LookupScope{.name_scope_id = lookup_scope_id,
                                             .specific_id = specific_id},
                                 /*required=*/false);
-        non_lexical_result.inst_id.is_valid()) {
-      return non_lexical_result;
+        !non_lexical_result.inst_id.is_poisoned()) {
+      if (non_lexical_result.inst_id.is_valid()) {
+        // Poison the scopes for this name.
+        for (const auto [scope_id, specific_id] : scopes_to_poison) {
+          name_scopes().Get(scope_id).AddPoison(name_id);
+        }
+
+        return non_lexical_result;
+      }
+      scopes_to_poison.push_back(
+          {.name_scope_id = lookup_scope_id, .specific_id = specific_id});
     }
   }
 
@@ -616,7 +639,8 @@ auto Context::LookupQualifiedName(SemIR::LocId loc_id, SemIR::NameId name_id,
     result.specific_id = specific_id;
   }
 
-  if (required && !result.inst_id.is_valid()) {
+  if (required &&
+      (!result.inst_id.is_valid() || result.inst_id.is_poisoned())) {
     if (!has_error) {
       if (prohibited_accesses.empty()) {
         DiagnoseMemberNameNotFound(loc_id, name_id, lookup_scopes);

+ 3 - 0
toolchain/check/context.h

@@ -246,6 +246,9 @@ class Context {
   // Prints a diagnostic for a duplicate name.
   auto DiagnoseDuplicateName(SemIRLoc dup_def, SemIRLoc prev_def) -> void;
 
+  // Prints a diagnostic for a poisoned name.
+  auto DiagnosePoisonedName(SemIRLoc loc) -> void;
+
   // Prints a diagnostic for a missing name.
   auto DiagnoseNameNotFound(SemIRLoc loc, SemIR::NameId name_id) -> void;
 

+ 24 - 5
toolchain/check/decl_name_stack.cpp

@@ -33,6 +33,9 @@ auto DeclNameStack::NameContext::prev_inst_id() -> SemIR::InstId {
     case NameContext::State::Unresolved:
       return SemIR::InstId::Invalid;
 
+    case NameContext::State::Poisoned:
+      return SemIR::InstId::PoisonedName;
+
     case NameContext::State::Finished:
       CARBON_FATAL("Finished state should only be used internally");
   }
@@ -167,12 +170,15 @@ auto DeclNameStack::AddName(NameContext name_context, SemIR::InstId target_id,
   }
 }
 
-auto DeclNameStack::AddNameOrDiagnoseDuplicate(NameContext name_context,
-                                               SemIR::InstId target_id,
-                                               SemIR::AccessKind access_kind)
-    -> void {
+auto DeclNameStack::AddNameOrDiagnose(NameContext name_context,
+                                      SemIR::InstId target_id,
+                                      SemIR::AccessKind access_kind) -> void {
   if (auto id = name_context.prev_inst_id(); id.is_valid()) {
-    context_->DiagnoseDuplicateName(target_id, id);
+    if (id.is_poisoned()) {
+      context_->DiagnosePoisonedName(target_id);
+    } else {
+      context_->DiagnoseDuplicateName(target_id, id);
+    }
   } else {
     AddName(name_context, target_id, access_kind);
   }
@@ -260,6 +266,9 @@ auto DeclNameStack::ApplyAndLookupName(NameContext& name_context,
     // Invalid indicates an unresolved name. Store it and return.
     name_context.unresolved_name_id = name_id;
     name_context.state = NameContext::State::Unresolved;
+  } else if (resolved_inst_id.is_poisoned()) {
+    name_context.unresolved_name_id = name_id;
+    name_context.state = NameContext::State::Poisoned;
   } else {
     // Store the resolved instruction and continue for the target scope
     // update.
@@ -277,8 +286,14 @@ static auto CheckQualifierIsResolved(
       CARBON_FATAL("No qualifier to resolve");
 
     case DeclNameStack::NameContext::State::Resolved:
+      if (name_context.resolved_inst_id.is_poisoned()) {
+        context.DiagnoseNameNotFound(name_context.loc_id,
+                                     name_context.unresolved_name_id);
+        return false;
+      }
       return true;
 
+    case DeclNameStack::NameContext::State::Poisoned:
     case DeclNameStack::NameContext::State::Unresolved:
       // Because more qualifiers were found, we diagnose that the earlier
       // qualifier failed to resolve.
@@ -367,6 +382,10 @@ auto DeclNameStack::ResolveAsScope(const NameContext& name_context,
     return InvalidResult;
   }
 
+  if (name_context.resolved_inst_id.is_poisoned()) {
+    return InvalidResult;
+  }
+
   auto new_params = DeclParams(
       name.name_loc_id, name.first_param_node_id, name.last_param_node_id,
       name.implicit_param_patterns_id, name.param_patterns_id);

+ 5 - 3
toolchain/check/decl_name_stack.h

@@ -78,6 +78,9 @@ class DeclNameStack {
       // An identifier didn't resolve.
       Unresolved,
 
+      // An identifier was poisoned in this scope.
+      Poisoned,
+
       // The name has already been finished. This is not set in the name
       // returned by `FinishName`, but is used internally to track that
       // `FinishName` has already been called.
@@ -231,9 +234,8 @@ class DeclNameStack {
                SemIR::AccessKind access_kind) -> void;
 
   // Adds a name to name lookup. Prints a diagnostic for name conflicts.
-  auto AddNameOrDiagnoseDuplicate(NameContext name_context,
-                                  SemIR::InstId target_id,
-                                  SemIR::AccessKind access_kind) -> void;
+  auto AddNameOrDiagnose(NameContext name_context, SemIR::InstId target_id,
+                         SemIR::AccessKind access_kind) -> void;
 
   // Adds a name to name lookup, or returns the existing instruction if this
   // name has already been declared in this scope.

+ 1 - 1
toolchain/check/handle_alias.cpp

@@ -71,7 +71,7 @@ auto HandleParseNode(Context& context, Parse::AliasId /*node_id*/) -> bool {
 
   // Add the name of the binding to the current scope.
   context.decl_name_stack().PopScope();
-  context.decl_name_stack().AddNameOrDiagnoseDuplicate(
+  context.decl_name_stack().AddNameOrDiagnose(
       name_context, alias_id, introducer.modifier_set.GetAccessKind());
   return true;
 }

+ 7 - 1
toolchain/check/handle_class.cpp

@@ -113,6 +113,12 @@ static auto MergeOrAddName(Context& context, Parse::AnyClassDeclId node_id,
     return;
   }
 
+  if (prev_id.is_poisoned()) {
+    // This is a declaration of a poisoned name.
+    context.DiagnosePoisonedName(class_decl_id);
+    return;
+  }
+
   auto prev_class_id = SemIR::ClassId::Invalid;
   auto prev_import_ir_id = SemIR::ImportIRId::Invalid;
   auto prev = context.insts().Get(prev_id);
@@ -546,7 +552,7 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
   }
 
   // Bind the name `base` in the class to the base field.
-  context.decl_name_stack().AddNameOrDiagnoseDuplicate(
+  context.decl_name_stack().AddNameOrDiagnose(
       context.decl_name_stack().MakeUnqualifiedName(node_id,
                                                     SemIR::NameId::Base),
       class_info.base_id, introducer.modifier_set.GetAccessKind());

+ 5 - 0
toolchain/check/handle_function.cpp

@@ -123,6 +123,11 @@ static auto TryMergeRedecl(Context& context, Parse::AnyFunctionDeclId node_id,
     return;
   }
 
+  if (prev_id.is_poisoned()) {
+    context.DiagnosePoisonedName(function_info.latest_decl_id());
+    return;
+  }
+
   auto prev_function_id = SemIR::FunctionId::Invalid;
   auto prev_import_ir_id = SemIR::ImportIRId::Invalid;
   CARBON_KIND_SWITCH(context.insts().Get(prev_id)) {

+ 7 - 2
toolchain/check/handle_interface.cpp

@@ -66,8 +66,13 @@ static auto BuildInterfaceDecl(Context& context,
   auto existing_id = context.decl_name_stack().LookupOrAddName(
       name_context, interface_decl_id, introducer.modifier_set.GetAccessKind());
   if (existing_id.is_valid()) {
-    if (auto existing_interface_decl =
-            context.insts().Get(existing_id).TryAs<SemIR::InterfaceDecl>()) {
+    if (existing_id.is_poisoned()) {
+      // This is a declaration of a poisoned name.
+      context.DiagnosePoisonedName(interface_decl_id);
+    } else if (auto existing_interface_decl =
+                   context.insts()
+                       .Get(existing_id)
+                       .TryAs<SemIR::InterfaceDecl>()) {
       auto existing_interface =
           context.interfaces().Get(existing_interface_decl->interface_id);
       if (CheckRedeclParamsMatch(

+ 6 - 6
toolchain/check/handle_let_and_var.cpp

@@ -94,8 +94,8 @@ static auto BuildAssociatedConstantDecl(Context& context,
   auto assoc_id = BuildAssociatedEntity(context, interface_id, decl_id);
   auto name_context =
       context.decl_name_stack().MakeUnqualifiedName(pattern.loc_id, name_id);
-  context.decl_name_stack().AddNameOrDiagnoseDuplicate(name_context, assoc_id,
-                                                       access_kind);
+  context.decl_name_stack().AddNameOrDiagnose(name_context, assoc_id,
+                                              access_kind);
 }
 
 // Adds name bindings. Returns the resulting ID for the references.
@@ -109,16 +109,16 @@ static auto HandleNameBinding(Context& context, SemIR::InstId pattern_id,
     auto name_context = context.decl_name_stack().MakeUnqualifiedName(
         context.insts().GetLocId(pattern_id),
         context.entity_names().Get(bind_name->entity_name_id).name_id);
-    context.decl_name_stack().AddNameOrDiagnoseDuplicate(
-        name_context, pattern_id, access_kind);
+    context.decl_name_stack().AddNameOrDiagnose(name_context, pattern_id,
+                                                access_kind);
     return bind_name->value_id;
   } else if (auto field_decl =
                  context.insts().TryGetAs<SemIR::FieldDecl>(pattern_id)) {
     // Introduce the field name into the class.
     auto name_context = context.decl_name_stack().MakeUnqualifiedName(
         context.insts().GetLocId(pattern_id), field_decl->name_id);
-    context.decl_name_stack().AddNameOrDiagnoseDuplicate(
-        name_context, pattern_id, access_kind);
+    context.decl_name_stack().AddNameOrDiagnose(name_context, pattern_id,
+                                                access_kind);
     return pattern_id;
   } else {
     // TODO: Handle other kinds of pattern.

+ 7 - 4
toolchain/check/handle_namespace.cpp

@@ -43,10 +43,13 @@ auto HandleParseNode(Context& context, Parse::NamespaceId node_id) -> bool {
   auto existing_inst_id = context.decl_name_stack().LookupOrAddName(
       name_context, namespace_id, SemIR::AccessKind::Public);
   if (existing_inst_id.is_valid()) {
-    // If there's a name conflict with a namespace, "merge" by using the
-    // previous declaration. Otherwise, diagnose the issue.
-    if (auto existing =
-            context.insts().TryGetAs<SemIR::Namespace>(existing_inst_id)) {
+    if (existing_inst_id.is_poisoned()) {
+      context.DiagnosePoisonedName(namespace_id);
+    } else if (auto existing = context.insts().TryGetAs<SemIR::Namespace>(
+                   existing_inst_id)) {
+      // If there's a name conflict with a namespace, "merge" by using the
+      // previous declaration. Otherwise, diagnose the issue.
+
       // Point at the other namespace.
       namespace_inst.name_scope_id = existing->name_scope_id;
 

+ 4 - 0
toolchain/check/import.cpp

@@ -110,6 +110,7 @@ static auto AddNamespace(Context& context, SemIR::TypeId namespace_type_id,
       SemIR::InstId::Invalid, SemIR::AccessKind::Public);
   if (!inserted) {
     auto prev_inst_id = parent_scope->GetEntry(entry_id).inst_id;
+    CARBON_CHECK(!prev_inst_id.is_poisoned());
     if (auto namespace_inst =
             context.insts().TryGetAs<SemIR::Namespace>(prev_inst_id)) {
       if (diagnose_duplicate_namespace) {
@@ -333,6 +334,9 @@ static auto ImportScopeFromApiFile(Context& context,
   auto& impl_scope = context.name_scopes().Get(impl_scope_id);
 
   for (const auto& api_entry : api_scope.entries()) {
+    if (api_entry.inst_id.is_poisoned()) {
+      continue;
+    }
     auto impl_name_id =
         CopyNameFromImportIR(context, api_sem_ir, api_entry.name_id);
     if (auto ns =

+ 6 - 0
toolchain/check/import_ref.cpp

@@ -1194,6 +1194,9 @@ static auto AddNameScopeImportRefs(ImportContext& context,
                                    const SemIR::NameScope& import_scope,
                                    SemIR::NameScope& new_scope) -> void {
   for (auto entry : import_scope.entries()) {
+    if (entry.inst_id.is_poisoned()) {
+      continue;
+    }
     auto ref_id = AddImportRef(context, entry.inst_id);
     new_scope.AddRequired({.name_id = GetLocalNameId(context, entry.name_id),
                            .inst_id = ref_id,
@@ -2907,6 +2910,9 @@ static auto GetInstForLoad(Context& context,
 }
 
 auto LoadImportRef(Context& context, SemIR::InstId inst_id) -> void {
+  if (inst_id.is_poisoned()) {
+    return;
+  }
   auto inst = context.insts().TryGetAs<SemIR::ImportRefUnloaded>(inst_id);
   if (!inst) {
     return;

+ 812 - 59
toolchain/check/testdata/function/declaration/no_prelude/name_poisoning.carbon

@@ -12,52 +12,253 @@
 
 library "[[@TEST_NAME]]";
 
-namespace N;
 class C {};
 
 // Both N.F1 and N.F2 use N.C and not C.
+namespace N;
 class N.C {}
 fn N.F1(x: C);
 fn N.F2(x: C) { N.F1(x); }
 
-// --- poison_without_usage.carbon
+// --- poison.carbon
 
 library "[[@TEST_NAME]]";
 
+class C {};
+
 namespace N;
+// Here we use C and poison N.C.
+fn N.F1(x: C);
+
+// --- fail_poison_class_without_usage.carbon
+// CHECK:STDERR: fail_poison_class_without_usage.carbon: error: name used before it was declared [NameUseBeforeDecl]
+
+library "[[@TEST_NAME]]";
+
 class C {};
 
+namespace N;
 // Here we use C and poison N.C.
 fn N.F1(x: C);
 
-// TODO: Should fail here since C was poisoned for namespace N when it was used
-// in N context without qualification.
+// Should fail here since C was poisoned for namespace N when it was used in N
+// context without qualification.
+// CHECK:STDERR: fail_poison_class_without_usage.carbon:[[@LINE+4]]:1: note: declared here [NameUseBeforeDeclNote]
+// CHECK:STDERR: class N.C {}
+// CHECK:STDERR: ^~~~~~~~~~~
+// CHECK:STDERR:
 class N.C {}
 
-// --- fail_poison_with_usage.carbon
+// --- fail_poison_interface_without_usage.carbon
+// CHECK:STDERR: fail_poison_interface_without_usage.carbon: error: name used before it was declared [NameUseBeforeDecl]
+
+library "[[@TEST_NAME]]";
+
+interface I {};
+
+namespace N;
+// Here we use I and poison N.I.
+fn N.F1(x: I);
+
+// Should fail here since I was poisoned for namespace N when it was used in N
+// context without qualification.
+// CHECK:STDERR: fail_poison_interface_without_usage.carbon:[[@LINE+4]]:1: note: declared here [NameUseBeforeDeclNote]
+// CHECK:STDERR: interface N.I {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~
+// CHECK:STDERR:
+interface N.I {}
+
+// --- fail_poison_namespace_without_usage.carbon
+// CHECK:STDERR: fail_poison_namespace_without_usage.carbon: error: name used before it was declared [NameUseBeforeDecl]
 
 library "[[@TEST_NAME]]";
 
+class C {};
+
 namespace N;
+// Here we use C and poison N.C.
+fn N.F1(x: C);
+
+// Should fail here since C was poisoned for namespace N when it was used in N
+// context without qualification.
+// CHECK:STDERR: fail_poison_namespace_without_usage.carbon:[[@LINE+4]]:1: note: declared here [NameUseBeforeDeclNote]
+// CHECK:STDERR: namespace N.C;
+// CHECK:STDERR: ^~~~~~~~~~~~~~
+// CHECK:STDERR:
+namespace N.C;
+
+// --- fail_poison_member_without_usage.carbon
+// CHECK:STDERR: fail_poison_member_without_usage.carbon: error: name used before it was declared [NameUseBeforeDecl]
+
+library "[[@TEST_NAME]]";
+
+class C1 {};
+
+class D {
+  // Here we use C1 and poison D.C1.
+  fn F1(x: C1);
+
+  class C2 {};
+  // Should fail here since C1 was poisoned for namespace class D when it was
+  // used in D context without qualification.
+  // CHECK:STDERR: fail_poison_member_without_usage.carbon:[[@LINE+4]]:7: note: declared here [NameUseBeforeDeclNote]
+  // CHECK:STDERR:   var C1: C2;
+  // CHECK:STDERR:       ^~~~~~
+  // CHECK:STDERR:
+  var C1: C2;
+}
+
+// --- fail_poison_function_without_usage.carbon
+// CHECK:STDERR: fail_poison_function_without_usage.carbon: error: name used before it was declared [NameUseBeforeDecl]
+
+library "[[@TEST_NAME]]";
+
+class C {};
+
+namespace N;
+// Here we use C and poison N.C.
+fn N.F1(x: C);
+
+// Should fail here since C was poisoned for namespace N when it was used in N
+// context without qualification.
+// CHECK:STDERR: fail_poison_function_without_usage.carbon:[[@LINE+4]]:1: note: declared here [NameUseBeforeDeclNote]
+// CHECK:STDERR: fn N.C();
+// CHECK:STDERR: ^~~~~~~~~
+// CHECK:STDERR:
+fn N.C();
+
+// --- fail_use_undefined_poisoned_name.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {};
+
+namespace N;
+// Here we use C and poison N.C.
+fn N.F1() -> C;
+
+// Try to use N.C which was never defined and poisoned.
+// CHECK:STDERR: fail_use_undefined_poisoned_name.carbon:[[@LINE+4]]:14: error: member name `C` not found in `N` [MemberNameNotFoundInScope]
+// CHECK:STDERR: fn N.F2() -> N.C;
+// CHECK:STDERR:              ^~~
+// CHECK:STDERR:
+fn N.F2() -> N.C;
+
+// --- fail_poison_with_usage.carbon
+// CHECK:STDERR: fail_poison_with_usage.carbon: error: name used before it was declared [NameUseBeforeDecl]
+
+library "[[@TEST_NAME]]";
+
 class C {};
 
+namespace N;
 // Here we use C and poison N.C.
 fn N.F1(x: C);
 
-// TODO: Should fail here since C was poisoned for namespace N when it was used
-// in N context without qualification.
+// Should fail here since C was poisoned for namespace N when it was used in N
+// context without qualification.
+// CHECK:STDERR: fail_poison_with_usage.carbon:[[@LINE+4]]:1: note: declared here [NameUseBeforeDeclNote]
+// CHECK:STDERR: class N.C {}
+// CHECK:STDERR: ^~~~~~~~~~~
+// CHECK:STDERR:
 class N.C {}
 
-// TODO: Should not fail here since both N.F2() and N.F1() input is the class C
-// and not class N.C.
-// CHECK:STDERR: fail_poison_with_usage.carbon:[[@LINE+6]]:22: error: `Core.ImplicitAs` implicitly referenced here, but package `Core` not found [CoreNotFound]
-// CHECK:STDERR: fn N.F2(x: C) { N.F1(x); }
-// CHECK:STDERR:                      ^
-// CHECK:STDERR: fail_poison_with_usage.carbon:[[@LINE-11]]:9: note: initializing function parameter [InCallToFunctionParam]
-// CHECK:STDERR: fn N.F1(x: C);
-// CHECK:STDERR:         ^~~~
+// Should not fail here since both N.F2() and N.F1() input is the class C and
+// not class N.C.
 fn N.F2(x: C) { N.F1(x); }
 
+// --- fail_poison_multiple_scopes.carbon
+// CHECK:STDERR: fail_poison_multiple_scopes.carbon: error: name used before it was declared [NameUseBeforeDecl]
+
+library "[[@TEST_NAME]]";
+
+class C {};
+
+namespace N1;
+namespace N1.N2;
+namespace N1.N2.N3;
+class N1.N2.N3.D1 {
+  interface D2 {
+    class D3 {
+      // Here we use C and poison:
+      // * N1.C
+      // * N1.N2.C
+      // * N1.N2.N3.C
+      // * N1.N2.N3.D1.C
+      // * N1.N2.N3.D1.D2.C
+      // * N1.N2.N3.D1.D2.D3.C
+      fn F(x: C);
+
+      // CHECK:STDERR: fail_poison_multiple_scopes.carbon:[[@LINE+5]]:7: note: declared here [NameUseBeforeDeclNote]
+      // CHECK:STDERR:       class C {}
+      // CHECK:STDERR:       ^~~~~~~~~
+      // CHECK:STDERR:
+      // CHECK:STDERR: fail_poison_multiple_scopes.carbon: error: name used before it was declared [NameUseBeforeDecl]
+      class C {}
+    }
+    // CHECK:STDERR: fail_poison_multiple_scopes.carbon:[[@LINE+5]]:5: note: declared here [NameUseBeforeDeclNote]
+    // CHECK:STDERR:     class C {}
+    // CHECK:STDERR:     ^~~~~~~~~
+    // CHECK:STDERR:
+    // CHECK:STDERR: fail_poison_multiple_scopes.carbon: error: name used before it was declared [NameUseBeforeDecl]
+    class C {}
+  }
+  // CHECK:STDERR: fail_poison_multiple_scopes.carbon:[[@LINE+5]]:3: note: declared here [NameUseBeforeDeclNote]
+  // CHECK:STDERR:   class C {}
+  // CHECK:STDERR:   ^~~~~~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_poison_multiple_scopes.carbon: error: name used before it was declared [NameUseBeforeDecl]
+  class C {}
+}
+
+// CHECK:STDERR: fail_poison_multiple_scopes.carbon:[[@LINE+5]]:1: note: declared here [NameUseBeforeDeclNote]
+// CHECK:STDERR: class N1.C {}
+// CHECK:STDERR: ^~~~~~~~~~~~
+// CHECK:STDERR:
+// CHECK:STDERR: fail_poison_multiple_scopes.carbon: error: name used before it was declared [NameUseBeforeDecl]
+class N1.C {}
+
+// CHECK:STDERR: fail_poison_multiple_scopes.carbon:[[@LINE+5]]:1: note: declared here [NameUseBeforeDeclNote]
+// CHECK:STDERR: interface N1.N2.C {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+// CHECK:STDERR: fail_poison_multiple_scopes.carbon: error: name used before it was declared [NameUseBeforeDecl]
+interface N1.N2.C {}
+
+// CHECK:STDERR: fail_poison_multiple_scopes.carbon:[[@LINE+4]]:1: note: declared here [NameUseBeforeDeclNote]
+// CHECK:STDERR: class N1.N2.N3.C {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+class N1.N2.N3.C {}
+
+// --- fail_alias.carbon
+// CHECK:STDERR: fail_alias.carbon: error: name used before it was declared [NameUseBeforeDecl]
+
+library "[[@TEST_NAME]]";
+
+class C {}
+
+namespace N;
+// CHECK:STDERR: fail_alias.carbon:[[@LINE+3]]:9: note: declared here [NameUseBeforeDeclNote]
+// CHECK:STDERR: alias N.C = C;
+// CHECK:STDERR:         ^
+alias N.C = C;
+
+// --- ignored_poison_in_import.carbon
+
+library "[[@TEST_NAME]]";
+import library "poison";
+
+// This doesn't fail.
+class N.C {}
+
+// --- poison.impl.carbon
+
+impl library "[[@TEST_NAME]]";
+
+// TODO: This should fail since N.C was poisoned in the api.
+class N.C {}
+
 // CHECK:STDOUT: --- no_poison.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -74,15 +275,15 @@ fn N.F2(x: C) { N.F1(x); }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl.loc4
 // CHECK:STDOUT:     .N = %N
-// CHECK:STDOUT:     .C = %C.decl.loc5
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl.loc4: type = class_decl @C.1 [template = constants.%C.1] {} {}
 // CHECK:STDOUT:   %N: <namespace> = namespace [template] {
 // CHECK:STDOUT:     .C = %C.decl.loc8
 // CHECK:STDOUT:     .F1 = %F1.decl
 // CHECK:STDOUT:     .F2 = %F2.decl
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %C.decl.loc5: type = class_decl @C.1 [template = constants.%C.1] {} {}
 // CHECK:STDOUT:   %C.decl.loc8: type = class_decl @C.2 [template = constants.%C.2] {} {}
 // CHECK:STDOUT:   %F1.decl: %F1.type = fn_decl @F1 [template = constants.%F1] {
 // CHECK:STDOUT:     %x.patt: %C.2 = binding_pattern x
@@ -129,125 +330,677 @@ fn N.F2(x: C) { N.F1(x); }
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: --- poison_without_usage.carbon
+// CHECK:STDOUT: --- poison.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C.1: type = class_type @C.1 [template]
+// CHECK:STDOUT:   %C: type = class_type @C [template]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [template]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
 // CHECK:STDOUT:   %F1.type: type = fn_type @F1 [template]
 // CHECK:STDOUT:   %F1: %F1.type = struct_value () [template]
-// CHECK:STDOUT:   %C.2: type = class_type @C.2 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
 // CHECK:STDOUT:     .N = %N
-// CHECK:STDOUT:     .C = %C.decl.loc5
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
 // CHECK:STDOUT:   %N: <namespace> = namespace [template] {
 // CHECK:STDOUT:     .F1 = %F1.decl
-// CHECK:STDOUT:     .C = %C.decl.loc12
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %C.decl.loc5: type = class_decl @C.1 [template = constants.%C.1] {} {}
 // CHECK:STDOUT:   %F1.decl: %F1.type = fn_decl @F1 [template = constants.%F1] {
-// CHECK:STDOUT:     %x.patt: %C.1 = binding_pattern x
-// CHECK:STDOUT:     %x.param_patt: %C.1 = value_param_pattern %x.patt, runtime_param0
+// CHECK:STDOUT:     %x.patt: %C = binding_pattern x
+// CHECK:STDOUT:     %x.param_patt: %C = value_param_pattern %x.patt, runtime_param0
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %C.ref: type = name_ref C, file.%C.decl.loc5 [template = constants.%C.1]
-// CHECK:STDOUT:     %x.param: %C.1 = value_param runtime_param0
-// CHECK:STDOUT:     %x: %C.1 = bind_name x, %x.param
+// CHECK:STDOUT:     %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:     %x.param: %C = value_param runtime_param0
+// CHECK:STDOUT:     %x: %C = bind_name x, %x.param
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %C.decl.loc12: type = class_decl @C.2 [template = constants.%C.2] {} {}
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @C.1 {
+// CHECK:STDOUT: class @C {
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C.1
+// CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @C.2 {
+// CHECK:STDOUT: fn @F1(%x.param_patt: %C);
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_poison_class_without_usage.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [template]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
+// CHECK:STDOUT:   %F1.type: type = fn_type @F1 [template]
+// CHECK:STDOUT:   %F1: %F1.type = struct_value () [template]
+// CHECK:STDOUT:   %.1: type = class_type @.1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .N = %N
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
+// CHECK:STDOUT:   %N: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .F1 = %F1.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F1.decl: %F1.type = fn_decl @F1 [template = constants.%F1] {
+// CHECK:STDOUT:     %x.patt: %C = binding_pattern x
+// CHECK:STDOUT:     %x.param_patt: %C = value_param_pattern %x.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:     %x.param: %C = value_param runtime_param0
+// CHECK:STDOUT:     %x: %C = bind_name x, %x.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.decl: type = class_decl @.1 [template = constants.%.1] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C.2
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @.1 {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%.1
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F1(%x.param_patt: %C);
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_poison_interface_without_usage.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %I.type: type = facet_type <@I> [template]
+// CHECK:STDOUT:   %Self.1: %I.type = bind_symbolic_name Self, 0 [symbolic]
+// CHECK:STDOUT:   %F1.type: type = fn_type @F1 [template]
+// CHECK:STDOUT:   %F1: %F1.type = struct_value () [template]
+// CHECK:STDOUT:   %.type: type = facet_type <@.1> [template]
+// CHECK:STDOUT:   %Self.2: %.type = bind_symbolic_name Self, 0 [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .I = %I.decl
+// CHECK:STDOUT:     .N = %N
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%I.type] {} {}
+// CHECK:STDOUT:   %N: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .F1 = %F1.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F1.decl: %F1.type = fn_decl @F1 [template = constants.%F1] {
+// CHECK:STDOUT:     %x.patt: %I.type = binding_pattern x
+// CHECK:STDOUT:     %x.param_patt: %I.type = value_param_pattern %x.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %I.ref: type = name_ref I, file.%I.decl [template = constants.%I.type]
+// CHECK:STDOUT:     %x.param: %I.type = value_param runtime_param0
+// CHECK:STDOUT:     %x: %I.type = bind_name x, %x.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.decl: type = interface_decl @.1 [template = constants.%.type] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: %I.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self.1]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @.1 {
+// CHECK:STDOUT:   %Self: %.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F1(%x.param_patt: %I.type);
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_poison_namespace_without_usage.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [template]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
+// CHECK:STDOUT:   %F1.type: type = fn_type @F1 [template]
+// CHECK:STDOUT:   %F1: %F1.type = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .N = %N
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
+// CHECK:STDOUT:   %N: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .F1 = %F1.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F1.decl: %F1.type = fn_decl @F1 [template = constants.%F1] {
+// CHECK:STDOUT:     %x.patt: %C = binding_pattern x
+// CHECK:STDOUT:     %x.param_patt: %C = value_param_pattern %x.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:     %x.param: %C = value_param runtime_param0
+// CHECK:STDOUT:     %x: %C = bind_name x, %x.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc17: <namespace> = namespace [template] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F1(%x.param_patt: %C);
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_poison_member_without_usage.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C1: type = class_type @C1 [template]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [template]
+// CHECK:STDOUT:   %complete_type.1: <witness> = complete_type_witness %empty_struct_type [template]
+// CHECK:STDOUT:   %D: type = class_type @D [template]
+// CHECK:STDOUT:   %F1.type: type = fn_type @F1 [template]
+// CHECK:STDOUT:   %F1: %F1.type = struct_value () [template]
+// CHECK:STDOUT:   %C2: type = class_type @C2 [template]
+// CHECK:STDOUT:   %D.elem: type = unbound_element_type %D, %C2 [template]
+// CHECK:STDOUT:   %struct_type.C1: type = struct_type {.C1: %C2} [template]
+// CHECK:STDOUT:   %complete_type.2: <witness> = complete_type_witness %struct_type.C1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C1 = %C1.decl
+// CHECK:STDOUT:     .D = %D.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C1.decl: type = class_decl @C1 [template = constants.%C1] {} {}
+// CHECK:STDOUT:   %D.decl: type = class_decl @D [template = constants.%D] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C1 {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type.1]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C1
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @D {
+// CHECK:STDOUT:   %F1.decl: %F1.type = fn_decl @F1 [template = constants.%F1] {
+// CHECK:STDOUT:     %x.patt: %C1 = binding_pattern x
+// CHECK:STDOUT:     %x.param_patt: %C1 = value_param_pattern %x.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %C1.ref: type = name_ref C1, file.%C1.decl [template = constants.%C1]
+// CHECK:STDOUT:     %x.param: %C1 = value_param runtime_param0
+// CHECK:STDOUT:     %x: %C1 = bind_name x, %x.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C2.decl: type = class_decl @C2 [template = constants.%C2] {} {}
+// CHECK:STDOUT:   %C2.ref: type = name_ref C2, %C2.decl [template = constants.%C2]
+// CHECK:STDOUT:   %.loc18: %D.elem = field_decl C1, element0 [template]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.C1 [template = constants.%complete_type.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%D
+// CHECK:STDOUT:   .F1 = %F1.decl
+// CHECK:STDOUT:   .C2 = %C2.decl
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C2 {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type.1]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C2
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F1(%x.param_patt: %C1);
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_poison_function_without_usage.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [template]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
+// CHECK:STDOUT:   %F1.type: type = fn_type @F1 [template]
+// CHECK:STDOUT:   %F1: %F1.type = struct_value () [template]
+// CHECK:STDOUT:   %.type: type = fn_type @.1 [template]
+// CHECK:STDOUT:   %.1: %.type = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .N = %N
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
+// CHECK:STDOUT:   %N: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .F1 = %F1.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F1.decl: %F1.type = fn_decl @F1 [template = constants.%F1] {
+// CHECK:STDOUT:     %x.patt: %C = binding_pattern x
+// CHECK:STDOUT:     %x.param_patt: %C = value_param_pattern %x.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:     %x.param: %C = value_param runtime_param0
+// CHECK:STDOUT:     %x: %C = bind_name x, %x.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.decl: %.type = fn_decl @.1 [template = constants.%.1] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F1(%x.param_patt: %C);
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @.1();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_use_undefined_poisoned_name.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [template]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
+// CHECK:STDOUT:   %F1.type: type = fn_type @F1 [template]
+// CHECK:STDOUT:   %F1: %F1.type = struct_value () [template]
+// CHECK:STDOUT:   %F2.type: type = fn_type @F2 [template]
+// CHECK:STDOUT:   %F2: %F2.type = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .N = %N
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
+// CHECK:STDOUT:   %N: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .F1 = %F1.decl
+// CHECK:STDOUT:     .F2 = %F2.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F1.decl: %F1.type = fn_decl @F1 [template = constants.%F1] {
+// CHECK:STDOUT:     %return.patt: %C = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: %C = out_param_pattern %return.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:     %return.param: ref %C = out_param runtime_param0
+// CHECK:STDOUT:     %return: ref %C = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F2.decl: %F2.type = fn_decl @F2 [template = constants.%F2] {
+// CHECK:STDOUT:     %return.patt: <error> = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: <error> = out_param_pattern %return.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %N.ref: <namespace> = name_ref N, file.%N [template = file.%N]
+// CHECK:STDOUT:     %C.ref: <error> = name_ref C, <error> [template = <error>]
+// CHECK:STDOUT:     %return.param: ref <error> = out_param runtime_param0
+// CHECK:STDOUT:     %return: ref <error> = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F1(%x.param_patt: %C.1);
+// CHECK:STDOUT: fn @F1() -> %C;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F2() -> <error>;
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_poison_with_usage.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C.1: type = class_type @C.1 [template]
+// CHECK:STDOUT:   %C: type = class_type @C [template]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [template]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
 // CHECK:STDOUT:   %F1.type: type = fn_type @F1 [template]
 // CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [template]
 // CHECK:STDOUT:   %F1: %F1.type = struct_value () [template]
-// CHECK:STDOUT:   %C.2: type = class_type @C.2 [template]
+// CHECK:STDOUT:   %.1: type = class_type @.1 [template]
 // CHECK:STDOUT:   %F2.type: type = fn_type @F2 [template]
 // CHECK:STDOUT:   %F2: %F2.type = struct_value () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
 // CHECK:STDOUT:     .N = %N
-// CHECK:STDOUT:     .C = %C.decl.loc5
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
 // CHECK:STDOUT:   %N: <namespace> = namespace [template] {
 // CHECK:STDOUT:     .F1 = %F1.decl
-// CHECK:STDOUT:     .C = %C.decl.loc12
 // CHECK:STDOUT:     .F2 = %F2.decl
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %C.decl.loc5: type = class_decl @C.1 [template = constants.%C.1] {} {}
 // CHECK:STDOUT:   %F1.decl: %F1.type = fn_decl @F1 [template = constants.%F1] {
-// CHECK:STDOUT:     %x.patt: %C.1 = binding_pattern x
-// CHECK:STDOUT:     %x.param_patt: %C.1 = value_param_pattern %x.patt, runtime_param0
+// CHECK:STDOUT:     %x.patt: %C = binding_pattern x
+// CHECK:STDOUT:     %x.param_patt: %C = value_param_pattern %x.patt, runtime_param0
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %C.ref: type = name_ref C, file.%C.decl.loc5 [template = constants.%C.1]
-// CHECK:STDOUT:     %x.param: %C.1 = value_param runtime_param0
-// CHECK:STDOUT:     %x: %C.1 = bind_name x, %x.param
+// CHECK:STDOUT:     %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:     %x.param: %C = value_param runtime_param0
+// CHECK:STDOUT:     %x: %C = bind_name x, %x.param
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %C.decl.loc12: type = class_decl @C.2 [template = constants.%C.2] {} {}
+// CHECK:STDOUT:   %.decl: type = class_decl @.1 [template = constants.%.1] {} {}
 // CHECK:STDOUT:   %F2.decl: %F2.type = fn_decl @F2 [template = constants.%F2] {
-// CHECK:STDOUT:     %x.patt: %C.2 = binding_pattern x
-// CHECK:STDOUT:     %x.param_patt: %C.2 = value_param_pattern %x.patt, runtime_param0
+// CHECK:STDOUT:     %x.patt: %C = binding_pattern x
+// CHECK:STDOUT:     %x.param_patt: %C = value_param_pattern %x.patt, runtime_param0
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %C.ref: type = name_ref C, file.%C.decl.loc12 [template = constants.%C.2]
-// CHECK:STDOUT:     %x.param: %C.2 = value_param runtime_param0
-// CHECK:STDOUT:     %x: %C.2 = bind_name x, %x.param
+// CHECK:STDOUT:     %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:     %x.param: %C = value_param runtime_param0
+// CHECK:STDOUT:     %x: %C = bind_name x, %x.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @C.1 {
+// CHECK:STDOUT: class @C {
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C.1
+// CHECK:STDOUT:   .Self = constants.%C
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: class @C.2 {
+// CHECK:STDOUT: class @.1 {
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .Self = constants.%C.2
+// CHECK:STDOUT:   .Self = constants.%.1
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F1(%x.param_patt: %C.1);
+// CHECK:STDOUT: fn @F1(%x.param_patt: %C);
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F2(%x.param_patt: %C.2) {
+// CHECK:STDOUT: fn @F2(%x.param_patt: %C) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %N.ref: <namespace> = name_ref N, file.%N [template = file.%N]
 // CHECK:STDOUT:   %F1.ref: %F1.type = name_ref F1, file.%F1.decl [template = constants.%F1]
-// CHECK:STDOUT:   %x.ref: %C.2 = name_ref x, %x
-// CHECK:STDOUT:   %.loc22: %C.1 = converted %x.ref, <error> [template = <error>]
-// CHECK:STDOUT:   %F1.call: init %empty_tuple.type = call %F1.ref(<error>)
+// CHECK:STDOUT:   %x.ref: %C = name_ref x, %x
+// CHECK:STDOUT:   %F1.call: init %empty_tuple.type = call %F1.ref(%x.ref)
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_poison_multiple_scopes.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [template]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
+// CHECK:STDOUT:   %D1: type = class_type @D1 [template]
+// CHECK:STDOUT:   %D2.type: type = facet_type <@D2> [template]
+// CHECK:STDOUT:   %Self.1: %D2.type = bind_symbolic_name Self, 0 [symbolic]
+// CHECK:STDOUT:   %D3.1: type = class_type @D3 [template]
+// CHECK:STDOUT:   %D3.2: type = class_type @D3, @D3(%Self.1) [symbolic]
+// CHECK:STDOUT:   %F.type: type = fn_type @F, @D3(%Self.1) [symbolic]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [symbolic]
+// CHECK:STDOUT:   %.1: type = class_type @.1 [template]
+// CHECK:STDOUT:   %.2: type = class_type @.1, @.1(%Self.1) [symbolic]
+// CHECK:STDOUT:   %.3: type = class_type @.2 [template]
+// CHECK:STDOUT:   %.4: type = class_type @.2, @.2(%Self.1) [symbolic]
+// CHECK:STDOUT:   %.5: type = class_type @.3 [template]
+// CHECK:STDOUT:   %.6: type = class_type @.4 [template]
+// CHECK:STDOUT:   %.type: type = facet_type <@.6> [template]
+// CHECK:STDOUT:   %Self.2: %.type = bind_symbolic_name Self, 0 [symbolic]
+// CHECK:STDOUT:   %.7: type = class_type @.5 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .N1 = %N1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
+// CHECK:STDOUT:   %N1: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .N2 = %N2
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %N2: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .N3 = %N3
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %N3: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .D1 = %D1.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %D1.decl: type = class_decl @D1 [template = constants.%D1] {} {}
+// CHECK:STDOUT:   %.decl.loc49: type = class_decl @.4 [template = constants.%.6] {} {}
+// CHECK:STDOUT:   %.decl.loc56: type = interface_decl @.6 [template = constants.%.type] {} {}
+// CHECK:STDOUT:   %.decl.loc62: type = class_decl @.5 [template = constants.%.7] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @D2 {
+// CHECK:STDOUT:   %Self: %D2.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self.1]
+// CHECK:STDOUT:   %D3.decl: type = class_decl @D3 [template = constants.%D3.1] {} {}
+// CHECK:STDOUT:   %.decl: type = class_decl @.2 [template = constants.%.3] {} {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   .D3 = %D3.decl
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @.6 {
+// CHECK:STDOUT:   %Self: %.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self.2]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @D1 {
+// CHECK:STDOUT:   %D2.decl: type = interface_decl @D2 [template = constants.%D2.type] {} {}
+// CHECK:STDOUT:   %.decl: type = class_decl @.3 [template = constants.%.5] {} {}
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%D1
+// CHECK:STDOUT:   .D2 = %D2.decl
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @D3(@D2.%Self: %D2.type) {
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %Self: %D2.type = bind_symbolic_name Self, 0 [symbolic = %Self (constants.%Self.1)]
+// CHECK:STDOUT:   %F.type: type = fn_type @F, @D3(%Self) [symbolic = %F.type (constants.%F.type)]
+// CHECK:STDOUT:   %F: @D3.%F.type (%F.type) = struct_value () [symbolic = %F (constants.%F)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     %F.decl: @D3.%F.type (%F.type) = fn_decl @F [symbolic = @D3.%F (constants.%F)] {
+// CHECK:STDOUT:       %x.patt: %C = binding_pattern x
+// CHECK:STDOUT:       %x.param_patt: %C = value_param_pattern %x.patt, runtime_param0
+// CHECK:STDOUT:     } {
+// CHECK:STDOUT:       %C.ref: type = name_ref C, file.%C.decl [template = constants.%C]
+// CHECK:STDOUT:       %x.param: %C = value_param runtime_param0
+// CHECK:STDOUT:       %x: %C = bind_name x, %x.param
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %.decl: type = class_decl @.1 [template = constants.%.1] {} {}
+// CHECK:STDOUT:     %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%D3.2
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:     complete_type_witness = %complete_type
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @.1(@D2.%Self: %D2.type) {
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%.2
+// CHECK:STDOUT:     complete_type_witness = %complete_type
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @.2(@D2.%Self: %D2.type) {
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%.4
+// CHECK:STDOUT:     complete_type_witness = %complete_type
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @.3 {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%.5
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @.4 {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%.6
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @.5 {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%.7
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @F(@D2.%Self: %D2.type) {
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn(%x.param_patt: %C);
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @D3(constants.%Self.1) {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%Self.1) {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @.1(constants.%Self.1) {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @D3(%Self) {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @.2(constants.%Self.1) {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_alias.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [template]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .N = %N
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
+// CHECK:STDOUT:   %N: <namespace> = namespace [template] {}
+// CHECK:STDOUT:   %C.ref: type = name_ref C, %C.decl [template = constants.%C]
+// CHECK:STDOUT:   %.loc11: type = bind_alias <invalid>, %C.decl [template = constants.%C]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- ignored_poison_in_import.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [template]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %import_ref.1 = import_ref Main//poison, C, unloaded
+// CHECK:STDOUT:   %import_ref.2: <namespace> = import_ref Main//poison, N, loaded
+// CHECK:STDOUT:   %N: <namespace> = namespace %import_ref.2, [template] {
+// CHECK:STDOUT:     .F1 = %import_ref.3
+// CHECK:STDOUT:     .C = file.%C.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = imports.%import_ref.1
+// CHECK:STDOUT:     .N = imports.%N
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %default.import = import <invalid>
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- poison.impl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [template]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [template]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %import_ref.1 = import_ref Main//poison, C, unloaded
+// CHECK:STDOUT:   %import_ref.2: <namespace> = import_ref Main//poison, N, loaded
+// CHECK:STDOUT:   %N: <namespace> = namespace %import_ref.2, [template] {
+// CHECK:STDOUT:     .F1 = %import_ref.3
+// CHECK:STDOUT:     .C = file.%C.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .C = imports.%import_ref.1
+// CHECK:STDOUT:     .N = imports.%N
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %default.import.loc2_6.1 = import <invalid>
+// CHECK:STDOUT:   %default.import.loc2_6.2 = import <invalid>
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [template = constants.%C] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 2 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -318,6 +318,8 @@ CARBON_DIAGNOSTIC_KIND(RealMantissaTooLargeForI64)
 CARBON_DIAGNOSTIC_KIND(RealExponentTooLargeForI64)
 CARBON_DIAGNOSTIC_KIND(NameDeclDuplicate)
 CARBON_DIAGNOSTIC_KIND(NameDeclPrevious)
+CARBON_DIAGNOSTIC_KIND(NameUseBeforeDecl)
+CARBON_DIAGNOSTIC_KIND(NameUseBeforeDeclNote)
 CARBON_DIAGNOSTIC_KIND(RepeatedConst)
 CARBON_DIAGNOSTIC_KIND(IncompleteTypeInAdaptDecl)
 CARBON_DIAGNOSTIC_KIND(IncompleteTypeInBaseDecl)

+ 4 - 0
toolchain/sem_ir/formatter.cpp

@@ -626,6 +626,10 @@ class FormatterImpl {
     }
 
     for (auto [name_id, inst_id, access_kind] : scope.entries()) {
+      if (inst_id.is_poisoned()) {
+        // TODO: Add poisoned names.
+        continue;
+      }
       Indent();
       out_ << ".";
       FormatName(name_id);

+ 2 - 0
toolchain/sem_ir/ids.cpp

@@ -12,6 +12,8 @@ namespace Carbon::SemIR {
 auto InstId::Print(llvm::raw_ostream& out) const -> void {
   if (IsSingletonInstId(*this)) {
     out << Label << "(" << SingletonInstKinds[index] << ")";
+  } else if (is_poisoned()) {
+    out << "<poisoned>";
   } else {
     IdBase::Print(out);
   }

+ 7 - 0
toolchain/sem_ir/ids.h

@@ -39,12 +39,19 @@ struct InstId : public IdBase<InstId> {
   // An explicitly invalid ID.
   static const InstId Invalid;
 
+  // Represents that the name in this scope was poisoned by using it without
+  // qualifications.
+  static const InstId PoisonedName;
+
   using IdBase::IdBase;
 
+  constexpr auto is_poisoned() const -> bool { return *this == PoisonedName; }
+
   auto Print(llvm::raw_ostream& out) const -> void;
 };
 
 constexpr InstId InstId::Invalid = InstId(InvalidIndex);
+constexpr InstId InstId::PoisonedName = InstId(InvalidIndex - 1);
 
 // An ID of an instruction that is referenced absolutely by another instruction.
 // This should only be used as the type of a field within a typed instruction

+ 17 - 0
toolchain/sem_ir/name_scope.cpp

@@ -22,6 +22,9 @@ auto NameScope::Print(llvm::raw_ostream& out) const -> void {
   out << ", names: {";
   llvm::ListSeparator sep;
   for (auto entry : names_) {
+    if (entry.inst_id.is_poisoned()) {
+      continue;
+    }
     out << sep << entry.name_id << ": " << entry.inst_id;
   }
   out << "}";
@@ -30,6 +33,9 @@ auto NameScope::Print(llvm::raw_ostream& out) const -> void {
 }
 
 auto NameScope::AddRequired(Entry name_entry) -> void {
+  CARBON_CHECK(!name_entry.inst_id.is_poisoned(),
+               "Cannot add a poisoned name: {0}. Use AddPoison()",
+               name_entry.name_id);
   auto add_name = [&] {
     EntryId index(names_.size());
     names_.push_back(name_entry);
@@ -43,6 +49,8 @@ auto NameScope::AddRequired(Entry name_entry) -> void {
 auto NameScope::LookupOrAdd(SemIR::NameId name_id, InstId inst_id,
                             AccessKind access_kind)
     -> std::pair<bool, EntryId> {
+  CARBON_CHECK(!inst_id.is_poisoned(),
+               "Cannot add a poisoned name: {0}. Use AddPoison()", name_id);
   auto insert_result = name_map_.Insert(name_id, EntryId(names_.size()));
   if (!insert_result.is_inserted()) {
     return {false, EntryId(insert_result.value())};
@@ -53,6 +61,15 @@ auto NameScope::LookupOrAdd(SemIR::NameId name_id, InstId inst_id,
   return {true, EntryId(names_.size() - 1)};
 }
 
+auto NameScope::AddPoison(NameId name_id) -> void {
+  auto insert_result = name_map_.Insert(name_id, EntryId(names_.size()));
+  CARBON_CHECK(insert_result.is_inserted(),
+               "Trying to poison an existing name: {0}", name_id);
+  names_.push_back({.name_id = name_id,
+                    .inst_id = InstId::PoisonedName,
+                    .access_kind = AccessKind::Public});
+}
+
 auto NameScopeStore::GetInstIfValid(NameScopeId scope_id) const
     -> std::pair<InstId, std::optional<Inst>> {
   if (!scope_id.is_valid()) {

+ 14 - 7
toolchain/sem_ir/name_scope.h

@@ -42,6 +42,7 @@ class NameScope : public Printable<NameScope> {
   auto entries() const -> llvm::ArrayRef<Entry> { return names_; }
 
   // Get a specific Name entry based on an EntryId that return from a lookup.
+  //
   // The Entry could become invalidated if the scope object is invalidated or if
   // a name is added.
   auto GetEntry(EntryId entry_id) const -> const Entry& {
@@ -50,7 +51,7 @@ class NameScope : public Printable<NameScope> {
   auto GetEntry(EntryId entry_id) -> Entry& { return names_[entry_id.index]; }
 
   // Searches for the given name and returns an EntryId if found or nullopt if
-  // not.
+  // not. The returned entry may be poisoned.
   auto Lookup(NameId name_id) const -> std::optional<EntryId> {
     auto lookup = name_map_.Lookup(name_id);
     if (!lookup) {
@@ -59,15 +60,20 @@ class NameScope : public Printable<NameScope> {
     return lookup.value();
   }
 
-  // Adds a new name known to not exist.
+  // Adds a new name known to not exist. Must not be poisoned.
   auto AddRequired(Entry name_entry) -> void;
 
-  // If the given name already exists, return true and an EntryId.
-  // If not, adds the name using inst_id and access_kind and returns false and
-  // an EntryId.
+  // If the given name already exists, return true with the EntryId; the entry
+  // might be poisoned. Otherwise, adds the name using inst_id and access_kind
+  // and returns false with the new EntryId.
+  //
+  // This cannot be used to add poisoned entries; use AddPoison instead.
   auto LookupOrAdd(SemIR::NameId name_id, InstId inst_id,
                    AccessKind access_kind) -> std::pair<bool, EntryId>;
 
+  // Adds a new poisoned name.
+  auto AddPoison(NameId name_id) -> void;
+
   auto extended_scopes() const -> llvm::ArrayRef<InstId> {
     return extended_scopes_;
   }
@@ -111,7 +117,8 @@ class NameScope : public Printable<NameScope> {
   }
 
  private:
-  // Names in the scope.
+  // Names in the scope, including poisoned names.
+  //
   // Entries could become invalidated if the scope object is invalidated or if a
   // name is added.
   //
@@ -172,7 +179,7 @@ class NameScopeStore {
   }
 
   // Adds a name that is required to exist in a name scope, such as `Self`.
-  // These must never conflict.
+  // The name must never conflict. inst_id must not be poisoned.
   auto AddRequiredName(NameScopeId scope_id, NameId name_id, InstId inst_id)
       -> void {
     Get(scope_id).AddRequired({.name_id = name_id,

+ 37 - 0
toolchain/sem_ir/name_scope_test.cpp

@@ -146,6 +146,43 @@ TEST(NameScope, LookupOrAdd) {
   }
 }
 
+TEST(NameScope, Poison) {
+  int id = 0;
+
+  InstId scope_inst_id(++id);
+  NameId scope_name_id(++id);
+  NameScopeId parent_scope_id(++id);
+  NameScope name_scope(scope_inst_id, scope_name_id, parent_scope_id);
+
+  NameId poison1(++id);
+  name_scope.AddPoison(poison1);
+  EXPECT_THAT(name_scope.entries(),
+              ElementsAre(NameScopeEntryEquals(
+                  NameScope::Entry({.name_id = poison1,
+                                    .inst_id = InstId::PoisonedName,
+                                    .access_kind = AccessKind::Public}))));
+
+  NameId poison2(++id);
+  name_scope.AddPoison(poison2);
+  EXPECT_THAT(name_scope.entries(),
+              ElementsAre(NameScopeEntryEquals(NameScope::Entry(
+                              {.name_id = poison1,
+                               .inst_id = InstId::PoisonedName,
+                               .access_kind = AccessKind::Public})),
+                          NameScopeEntryEquals(NameScope::Entry(
+                              {.name_id = poison2,
+                               .inst_id = InstId::PoisonedName,
+                               .access_kind = AccessKind::Public}))));
+
+  auto lookup = name_scope.Lookup(poison1);
+  ASSERT_NE(lookup, std::nullopt);
+  EXPECT_THAT(name_scope.GetEntry(*lookup),
+              NameScopeEntryEquals(
+                  NameScope::Entry({.name_id = poison1,
+                                    .inst_id = InstId::PoisonedName,
+                                    .access_kind = AccessKind::Public})));
+}
+
 TEST(NameScope, ExtendedScopes) {
   int id = 0;