Browse Source

Fix introduction of class and interface names in local scopes. (#4793)

When declaring a class (or interface), we create a scope that covers the
entire class declaration. If the class was declared in a lexical scope,
we would declare the class name in the innermost scope, which was the
class's own scope instead of the enclosing lexical scope.

Fix this by instead adding the name to the lexical scope at the start of
the class declaration, not the lexical scope created to hold the class.
For now, we reject if the class name would have been shadowed by a name
that has already been declared within its scope, such as a generic
parameter, so we only ever need to modify the end of the list of lexical
lookup results for the class name.

This appears to be sufficient to make local declarations and definitions
of classes and interfaces work properly throughout check, though testing
is pretty minimal so far.
Richard Smith 1 year ago
parent
commit
0d70091bda

+ 21 - 7
toolchain/check/context.cpp

@@ -304,20 +304,22 @@ auto Context::NoteUndefinedInterface(SemIR::InterfaceId interface_id,
   }
 }
 
-auto Context::AddNameToLookup(SemIR::NameId name_id, SemIR::InstId target_id)
-    -> void {
-  if (auto existing = scope_stack().LookupOrAddName(name_id, target_id);
+auto Context::AddNameToLookup(SemIR::NameId name_id, SemIR::InstId target_id,
+                              ScopeIndex scope_index) -> void {
+  if (auto existing =
+          scope_stack().LookupOrAddName(name_id, target_id, scope_index);
       existing.is_valid()) {
     DiagnoseDuplicateName(target_id, existing);
   }
 }
 
 auto Context::LookupNameInDecl(SemIR::LocId loc_id, SemIR::NameId name_id,
-                               SemIR::NameScopeId scope_id)
+                               SemIR::NameScopeId scope_id,
+                               ScopeIndex scope_index)
     -> std::pair<SemIR::InstId, bool> {
   if (!scope_id.is_valid()) {
-    // Look for a name in the current scope only. There are two cases where the
-    // name would be in an outer scope:
+    // Look for a name in the specified scope or a scope nested within it only.
+    // There are two cases where the name would be in an outer scope:
     //
     //  - The name is the sole component of the declared name:
     //
@@ -339,7 +341,19 @@ auto Context::LookupNameInDecl(SemIR::LocId loc_id, SemIR::NameId name_id,
     //    In this case, we're not in the correct scope to define a member of
     //    class A, so we should reject, and we achieve this by not finding the
     //    name A from the outer scope.
-    return {scope_stack().LookupInCurrentScope(name_id), false};
+    //
+    // There is also one case where the name would be in an inner scope:
+    //
+    //  - The name is redeclared by a parameter of the same entity:
+    //
+    //    fn F() {
+    //      class C(C:! type);
+    //    }
+    //
+    //    In this case, the class C is not a redeclaration of its parameter, but
+    //    we find the parameter in order to diagnose a redeclaration error.
+    return {scope_stack().LookupInLexicalScopesWithin(name_id, scope_index),
+            false};
   } else {
     // We do not look into `extend`ed scopes here. A qualified name in a
     // declaration must specify the exact scope in which the name was originally

+ 11 - 6
toolchain/check/context.h

@@ -16,6 +16,7 @@
 #include "toolchain/check/inst_block_stack.h"
 #include "toolchain/check/node_stack.h"
 #include "toolchain/check/param_and_arg_refs_stack.h"
+#include "toolchain/check/scope_index.h"
 #include "toolchain/check/scope_stack.h"
 #include "toolchain/parse/node_ids.h"
 #include "toolchain/parse/tree.h"
@@ -219,16 +220,20 @@ class Context {
     sem_ir().insts().SetLocId(inst_id, SemIR::LocId(node_id));
   }
 
-  // Adds a name to name lookup. Prints a diagnostic for name conflicts.
-  auto AddNameToLookup(SemIR::NameId name_id, SemIR::InstId target_id) -> void;
+  // Adds a name to name lookup. Prints a diagnostic for name conflicts. If
+  // specified, `scope_index` specifies which lexical scope the name is inserted
+  // into, otherwise the name is inserted into the current scope.
+  auto AddNameToLookup(SemIR::NameId name_id, SemIR::InstId target_id,
+                       ScopeIndex scope_index = ScopeIndex::Invalid) -> void;
 
   // Performs name lookup in a specified scope for a name appearing in a
-  // declaration. If scope_id is invalid, uses the current contextual scope. If
-  // found, returns the referenced instruction and false. If poisoned, returns
-  // an invalid instruction and true.
+  // declaration. If scope_id is invalid, performs lookup into the lexical scope
+  // specified by scope_index instead. If found, returns the referenced
+  // instruction and false. If poisoned, returns an invalid instruction and
+  // true.
   // TODO: For poisoned names, return the poisoning instruction.
   auto LookupNameInDecl(SemIR::LocId loc_id, SemIR::NameId name_id,
-                        SemIR::NameScopeId scope_id)
+                        SemIR::NameScopeId scope_id, ScopeIndex scope_index)
       -> std::pair<SemIR::InstId, bool>;
 
   // Performs an unqualified name lookup, returning the referenced instruction.

+ 4 - 2
toolchain/check/decl_name_stack.cpp

@@ -134,7 +134,8 @@ auto DeclNameStack::AddName(NameContext name_context, SemIR::InstId target_id,
 
     case NameContext::State::Unresolved:
       if (!name_context.parent_scope_id.is_valid()) {
-        context_->AddNameToLookup(name_context.unresolved_name_id, target_id);
+        context_->AddNameToLookup(name_context.unresolved_name_id, target_id,
+                                  name_context.initial_scope_index);
       } else {
         auto& name_scope =
             context_->name_scopes().Get(name_context.parent_scope_id);
@@ -262,7 +263,8 @@ auto DeclNameStack::ApplyAndLookupName(NameContext& name_context,
 
   // For identifier nodes, we need to perform a lookup on the identifier.
   auto [resolved_inst_id, is_poisoned] = context_->LookupNameInDecl(
-      name_context.loc_id, name_id, name_context.parent_scope_id);
+      name_context.loc_id, name_id, name_context.parent_scope_id,
+      name_context.initial_scope_index);
   if (is_poisoned) {
     name_context.unresolved_name_id = name_id;
     name_context.state = NameContext::State::Poisoned;

+ 2 - 0
toolchain/check/scope_index.h

@@ -20,11 +20,13 @@ namespace Carbon::Check {
 struct ScopeIndex : public IndexBase<ScopeIndex> {
   static constexpr llvm::StringLiteral Label = "scope";
   static const ScopeIndex Package;
+  static const ScopeIndex Invalid;
 
   using IndexBase::IndexBase;
 };
 
 constexpr ScopeIndex ScopeIndex::Package = ScopeIndex(0);
+constexpr ScopeIndex ScopeIndex::Invalid = ScopeIndex(InvalidIndex);
 
 }  // namespace Carbon::Check
 

+ 38 - 17
toolchain/check/scope_stack.cpp

@@ -99,14 +99,16 @@ auto ScopeStack::PopTo(ScopeIndex index) -> void {
                PeekIndex());
 }
 
-auto ScopeStack::LookupInCurrentScope(SemIR::NameId name_id) -> SemIR::InstId {
+auto ScopeStack::LookupInLexicalScopesWithin(SemIR::NameId name_id,
+                                             ScopeIndex scope_index)
+    -> SemIR::InstId {
   auto& lexical_results = lexical_lookup_.Get(name_id);
   if (lexical_results.empty()) {
     return SemIR::InstId::Invalid;
   }
 
   auto result = lexical_results.back();
-  if (result.scope_index != PeekIndex()) {
+  if (result.scope_index < scope_index) {
     return SemIR::InstId::Invalid;
   }
 
@@ -139,24 +141,43 @@ auto ScopeStack::LookupInLexicalScopes(SemIR::NameId name_id)
       llvm::ArrayRef(first_non_lexical_scope, non_lexical_scope_stack_.end())};
 }
 
-auto ScopeStack::LookupOrAddName(SemIR::NameId name_id, SemIR::InstId target_id)
-    -> SemIR::InstId {
-  if (!scope_stack_.back().names.Insert(name_id).is_inserted()) {
-    auto existing = lexical_lookup_.Get(name_id).back().inst_id;
-    CARBON_CHECK(existing.is_valid(),
-                 "Name in scope but not in lexical lookups");
-    return existing;
+auto ScopeStack::LookupOrAddName(SemIR::NameId name_id, SemIR::InstId target_id,
+                                 ScopeIndex scope_index) -> SemIR::InstId {
+  // Find the corresponding scope depth.
+  //
+  // TODO: Consider passing in the depth rather than performing a scan for it.
+  // We only do this scan when declaring an entity such as a class within a
+  // function, so it should be relatively rare, but it's still not necesasry to
+  // recompute this.
+  int scope_depth = scope_stack_.size() - 1;
+  if (scope_index.is_valid()) {
+    scope_depth =
+        std::lower_bound(scope_stack_.begin(), scope_stack_.end(), scope_index,
+                         [](const ScopeStackEntry& entry, ScopeIndex index) {
+                           return entry.index < index;
+                         }) -
+        scope_stack_.begin();
+    CARBON_CHECK(scope_stack_[scope_depth].index == scope_index,
+                 "Declaring name in scope that has already ended");
+  } else {
+    scope_index = scope_stack_[scope_depth].index;
   }
-  ++scope_stack_.back().num_names;
 
-  // TODO: Reject if we previously performed a failed lookup for this name
-  // in this scope or a scope nested within it.
+  // If this name has already been declared in this scope or an inner scope,
+  // return the existing result.
   auto& lexical_results = lexical_lookup_.Get(name_id);
-  CARBON_CHECK(
-      lexical_results.empty() ||
-          lexical_results.back().scope_index < PeekIndex(),
-      "Failed to clean up after scope nested within the current scope");
-  lexical_results.push_back({.inst_id = target_id, .scope_index = PeekIndex()});
+  if (!lexical_results.empty() &&
+      lexical_results.back().scope_index >= scope_index) {
+    return lexical_results.back().inst_id;
+  }
+
+  // Add the name into the scope.
+  bool inserted = scope_stack_[scope_depth].names.Insert(name_id).is_inserted();
+  CARBON_CHECK(inserted, "Name in scope but not in lexical lookups");
+  ++scope_stack_[scope_depth].num_names;
+
+  // Add a corresponding lexical lookup result.
+  lexical_results.push_back({.inst_id = target_id, .scope_index = scope_index});
   return SemIR::InstId::Invalid;
 }
 

+ 10 - 7
toolchain/check/scope_stack.h

@@ -111,9 +111,10 @@ class ScopeStack {
   // is already a `returned var`, returns it instead.
   auto SetReturnedVarOrGetExisting(SemIR::InstId inst_id) -> SemIR::InstId;
 
-  // Looks up the name `name_id` in the current scope. Returns the existing
-  // lookup result, if any.
-  auto LookupInCurrentScope(SemIR::NameId name_id) -> SemIR::InstId;
+  // Looks up the name `name_id` in the current scope and enclosing scopes, but
+  // do not look past `scope_index`. Returns the existing lookup result, if any.
+  auto LookupInLexicalScopesWithin(SemIR::NameId name_id,
+                                   ScopeIndex scope_index) -> SemIR::InstId;
 
   // Looks up the name `name_id` in the current scope and related lexical
   // scopes. Returns the innermost lexical lookup result, if any, along with a
@@ -122,10 +123,12 @@ class ScopeStack {
   auto LookupInLexicalScopes(SemIR::NameId name_id)
       -> std::pair<SemIR::InstId, llvm::ArrayRef<NonLexicalScope>>;
 
-  // Looks up the name `name_id` in the current scope. Returns the existing
-  // instruction if any, and otherwise adds the name with the value `target_id`
-  // and returns Invalid.
-  auto LookupOrAddName(SemIR::NameId name_id, SemIR::InstId target_id)
+  // Looks up the name `name_id` in the current scope, or in `scope_index` if
+  // specified. Returns the existing instruction if the name is already declared
+  // in that scope or any unfinished scope within it, and otherwise adds the
+  // name with the value `target_id` and returns Invalid.
+  auto LookupOrAddName(SemIR::NameId name_id, SemIR::InstId target_id,
+                       ScopeIndex scope_index = ScopeIndex::Invalid)
       -> SemIR::InstId;
 
   // Prepares to add a compile-time binding in the current scope, and returns

+ 12 - 11
toolchain/check/testdata/class/fail_todo_local_class.carbon → toolchain/check/testdata/class/local.carbon

@@ -4,9 +4,9 @@
 //
 // AUTOUPDATE
 // TIP: To test this file alone, run:
-// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/fail_todo_local_class.carbon
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/local.carbon
 // TIP: To dump output, run:
-// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/fail_todo_local_class.carbon
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/local.carbon
 
 class A {
   fn F() -> i32 {
@@ -19,15 +19,11 @@ class A {
       var n: i32;
     }
 
-    // TODO: Add the name `B` to the lexical scope.
-    // CHECK:STDERR: fail_todo_local_class.carbon:[[@LINE+3]]:12: error: name `B` not found [NameNotFound]
-    // CHECK:STDERR:     return B.Make().n;
-    // CHECK:STDERR:            ^
     return B.Make().n;
   }
 }
 
-// CHECK:STDOUT: --- fail_todo_local_class.carbon
+// CHECK:STDOUT: --- local.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %A: type = class_type @A [template]
@@ -113,10 +109,15 @@ class A {
 // CHECK:STDOUT: fn @F() -> %i32 {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %B.decl: type = class_decl @B [template = constants.%B] {} {}
-// CHECK:STDOUT:   %B.ref: <error> = name_ref B, <error> [template = <error>]
-// CHECK:STDOUT:   %Make.ref: <error> = name_ref Make, <error> [template = <error>]
-// CHECK:STDOUT:   %n.ref: <error> = name_ref n, <error> [template = <error>]
-// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT:   %B.ref: type = name_ref B, %B.decl [template = constants.%B]
+// CHECK:STDOUT:   %Make.ref: %Make.type = name_ref Make, @B.%Make.decl [template = constants.%Make]
+// CHECK:STDOUT:   %.loc22_19.1: ref %B = temporary_storage
+// CHECK:STDOUT:   %Make.call: init %B = call %Make.ref() to %.loc22_19.1
+// CHECK:STDOUT:   %.loc22_19.2: ref %B = temporary %.loc22_19.1, %Make.call
+// CHECK:STDOUT:   %n.ref: %B.elem = name_ref n, @B.%.loc19 [template = @B.%.loc19]
+// CHECK:STDOUT:   %.loc22_20.1: ref %i32 = class_element_access %.loc22_19.2, element0
+// CHECK:STDOUT:   %.loc22_20.2: %i32 = bind_value %.loc22_20.1
+// CHECK:STDOUT:   return %.loc22_20.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @Make() -> %return.param_patt: %B {

+ 279 - 0
toolchain/check/testdata/generic/local.carbon

@@ -0,0 +1,279 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/generic/local.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/generic/local.carbon
+
+// --- class.carbon
+
+library "[[@TEST_NAME]]";
+
+fn F() {
+  class C(T:! type) {
+    var x: T;
+  }
+  var v: C(i32) = {.x = 1};
+}
+
+// --- fail_param_shadows_class.carbon
+
+library "[[@TEST_NAME]]";
+
+fn F() {
+  // TODO: Decide on what behavior we want here. We don't reject the corresponding case outside of a function.
+  // CHECK:STDERR: fail_param_shadows_class.carbon:[[@LINE+6]]:3: error: duplicate name being declared in the same scope [NameDeclDuplicate]
+  // CHECK:STDERR:   class C(C:! type) {
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_param_shadows_class.carbon:[[@LINE+3]]:11: note: name is previously declared here [NameDeclPrevious]
+  // CHECK:STDERR:   class C(C:! type) {
+  // CHECK:STDERR:           ^
+  class C(C:! type) {
+  }
+}
+
+// --- nonlocal_param_shadows_class.carbon
+
+library "[[@TEST_NAME]]";
+
+class C(C:! type) {
+}
+
+// CHECK:STDOUT: --- class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [template]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [template]
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic]
+// CHECK:STDOUT:   %C.type: type = generic_class_type @C [template]
+// CHECK:STDOUT:   %C.generic: %C.type = struct_value () [template]
+// CHECK:STDOUT:   %C.f06: type = class_type @C, @C(%T) [symbolic]
+// CHECK:STDOUT:   %require_complete.4ae: <witness> = require_complete_type %T [symbolic]
+// CHECK:STDOUT:   %C.elem.cca: type = unbound_element_type %C.f06, %T [symbolic]
+// CHECK:STDOUT:   %struct_type.x.2ac: type = struct_type {.x: %T} [symbolic]
+// CHECK:STDOUT:   %complete_type.4339: <witness> = complete_type_witness %struct_type.x.2ac [symbolic]
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [template]
+// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [template]
+// CHECK:STDOUT:   %C.d45: type = class_type @C, @C(%i32) [template]
+// CHECK:STDOUT:   %i32.builtin: type = int_type signed, %int_32 [template]
+// CHECK:STDOUT:   %complete_type.f8a: <witness> = complete_type_witness %i32.builtin [template]
+// CHECK:STDOUT:   %C.elem.f74: type = unbound_element_type %C.d45, %i32 [template]
+// CHECK:STDOUT:   %struct_type.x.ed6: type = struct_type {.x: %i32} [template]
+// CHECK:STDOUT:   %complete_type.1ec: <witness> = complete_type_witness %struct_type.x.ed6 [template]
+// CHECK:STDOUT:   %int_1.5b8: Core.IntLiteral = int_value 1 [template]
+// CHECK:STDOUT:   %struct_type.x.c96: type = struct_type {.x: Core.IntLiteral} [template]
+// CHECK:STDOUT:   %Convert.type.1b6: type = fn_type @Convert.1, @ImplicitAs(%i32) [template]
+// CHECK:STDOUT:   %impl_witness.d39: <witness> = impl_witness (imports.%import_ref.a5b), @impl.1(%int_32) [template]
+// CHECK:STDOUT:   %Convert.type.035: type = fn_type @Convert.2, @impl.1(%int_32) [template]
+// CHECK:STDOUT:   %Convert.956: %Convert.type.035 = struct_value () [template]
+// CHECK:STDOUT:   %Convert.bound: <bound method> = bound_method %int_1.5b8, %Convert.956 [template]
+// CHECK:STDOUT:   %Convert.specific_fn: <specific function> = specific_function %Convert.bound, @Convert.2(%int_32) [template]
+// CHECK:STDOUT:   %int_1.5d2: %i32 = int_value 1 [template]
+// CHECK:STDOUT:   %C.val: %C.d45 = struct_value (%int_1.5d2) [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     .Int = %import_ref.485
+// CHECK:STDOUT:     .ImplicitAs = %import_ref.d44
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @C(%T.loc5_11.1: type) {
+// CHECK:STDOUT:   %T.loc5_11.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc5_11.2 (constants.%T)]
+// CHECK:STDOUT:   %T.patt.loc5_11.2: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc5_11.2 (constants.%T.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete: <witness> = require_complete_type @C.%T.loc5_11.2 (%T) [symbolic = %require_complete (constants.%require_complete.4ae)]
+// CHECK:STDOUT:   %C: type = class_type @C, @C(%T.loc5_11.2) [symbolic = %C (constants.%C.f06)]
+// CHECK:STDOUT:   %C.elem: type = unbound_element_type @C.%C (%C.f06), @C.%T.loc5_11.2 (%T) [symbolic = %C.elem (constants.%C.elem.cca)]
+// CHECK:STDOUT:   %struct_type.x: type = struct_type {.x: @C.%T.loc5_11.2 (%T)} [symbolic = %struct_type.x (constants.%struct_type.x.2ac)]
+// CHECK:STDOUT:   %complete_type.loc7_3.2: <witness> = complete_type_witness @C.%struct_type.x (%struct_type.x.2ac) [symbolic = %complete_type.loc7_3.2 (constants.%complete_type.4339)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// CHECK:STDOUT:     %.loc6: @C.%C.elem (%C.elem.cca) = field_decl x, element0 [template]
+// CHECK:STDOUT:     %complete_type.loc7_3.1: <witness> = complete_type_witness %struct_type.x.2ac [symbolic = %complete_type.loc7_3.2 (constants.%complete_type.4339)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = constants.%C.f06
+// CHECK:STDOUT:     .x = %.loc6
+// CHECK:STDOUT:     complete_type_witness = %complete_type.loc7_3.1
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %C.decl: %C.type = class_decl @C [template = constants.%C.generic] {
+// CHECK:STDOUT:     %T.patt.loc5_11.1: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc5_11.2 (constants.%T.patt)]
+// CHECK:STDOUT:     %T.param_patt: type = value_param_pattern %T.patt.loc5_11.1, runtime_param<invalid> [symbolic = %T.patt.loc5_11.2 (constants.%T.patt)]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %T.param: type = value_param runtime_param<invalid>
+// CHECK:STDOUT:     %T.loc5_11.1: type = bind_symbolic_name T, 0, %T.param [symbolic = %T.loc5_11.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %v.var: ref %C.d45 = var v
+// CHECK:STDOUT:   %v: ref %C.d45 = bind_name v, %v.var
+// CHECK:STDOUT:   %int_1: Core.IntLiteral = int_value 1 [template = constants.%int_1.5b8]
+// CHECK:STDOUT:   %.loc8_26.1: %struct_type.x.c96 = struct_literal (%int_1)
+// CHECK:STDOUT:   %impl.elem0: %Convert.type.1b6 = impl_witness_access constants.%impl_witness.d39, element0 [template = constants.%Convert.956]
+// CHECK:STDOUT:   %Convert.bound: <bound method> = bound_method %int_1, %impl.elem0 [template = constants.%Convert.bound]
+// CHECK:STDOUT:   %Convert.specific_fn: <specific function> = specific_function %Convert.bound, @Convert.2(constants.%int_32) [template = constants.%Convert.specific_fn]
+// CHECK:STDOUT:   %int.convert_checked: init %i32 = call %Convert.specific_fn(%int_1) [template = constants.%int_1.5d2]
+// CHECK:STDOUT:   %.loc8_26.2: init %i32 = converted %int_1, %int.convert_checked [template = constants.%int_1.5d2]
+// CHECK:STDOUT:   %.loc8_26.3: ref %i32 = class_element_access %v.var, element0
+// CHECK:STDOUT:   %.loc8_26.4: init %i32 = initialize_from %.loc8_26.2 to %.loc8_26.3 [template = constants.%int_1.5d2]
+// CHECK:STDOUT:   %.loc8_26.5: init %C.d45 = class_init (%.loc8_26.4), %v.var [template = constants.%C.val]
+// CHECK:STDOUT:   %.loc8_27: init %C.d45 = converted %.loc8_26.1, %.loc8_26.5 [template = constants.%C.val]
+// CHECK:STDOUT:   assign %v.var, %.loc8_27
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%T) {
+// CHECK:STDOUT:   %T.loc5_11.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc5_11.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(%T.loc5_11.2) {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%i32) {
+// CHECK:STDOUT:   %T.loc5_11.2 => constants.%i32
+// CHECK:STDOUT:   %T.patt.loc5_11.2 => constants.%i32
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %require_complete => constants.%complete_type.f8a
+// CHECK:STDOUT:   %C => constants.%C.d45
+// CHECK:STDOUT:   %C.elem => constants.%C.elem.f74
+// CHECK:STDOUT:   %struct_type.x => constants.%struct_type.x.ed6
+// CHECK:STDOUT:   %complete_type.loc7_3.2 => constants.%complete_type.1ec
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_param_shadows_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [template]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [template]
+// CHECK:STDOUT:   %C: type = bind_symbolic_name C, 0 [symbolic]
+// CHECK:STDOUT:   %C.patt: type = symbolic_binding_pattern C, 0 [symbolic]
+// CHECK:STDOUT:   %.type: type = generic_class_type @.1 [template]
+// CHECK:STDOUT:   %.generic: %.type = struct_value () [template]
+// CHECK:STDOUT:   %.de1: type = class_type @.1, @.1(%C) [symbolic]
+// 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:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @.1(%C.loc12_11.1: type) {
+// CHECK:STDOUT:   %C.loc12_11.2: type = bind_symbolic_name C, 0 [symbolic = %C.loc12_11.2 (constants.%C)]
+// CHECK:STDOUT:   %C.patt.loc12_11.2: type = symbolic_binding_pattern C, 0 [symbolic = %C.patt.loc12_11.2 (constants.%C.patt)]
+// CHECK:STDOUT:
+// 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.%.de1
+// CHECK:STDOUT:     complete_type_witness = %complete_type
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.decl: %.type = class_decl @.1 [template = constants.%.generic] {
+// CHECK:STDOUT:     %C.patt.loc12_11.1: type = symbolic_binding_pattern C, 0 [symbolic = %C.patt.loc12_11.2 (constants.%C.patt)]
+// CHECK:STDOUT:     %C.param_patt: type = value_param_pattern %C.patt.loc12_11.1, runtime_param<invalid> [symbolic = %C.patt.loc12_11.2 (constants.%C.patt)]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %C.param: type = value_param runtime_param<invalid>
+// CHECK:STDOUT:     %C.loc12_11.1: type = bind_symbolic_name C, 0, %C.param [symbolic = %C.loc12_11.2 (constants.%C)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @.1(constants.%C) {
+// CHECK:STDOUT:   %C.loc12_11.2 => constants.%C
+// CHECK:STDOUT:   %C.patt.loc12_11.2 => constants.%C
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- nonlocal_param_shadows_class.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C.8b3: type = bind_symbolic_name C, 0 [symbolic]
+// CHECK:STDOUT:   %C.patt: type = symbolic_binding_pattern C, 0 [symbolic]
+// CHECK:STDOUT:   %C.type: type = generic_class_type @C [template]
+// CHECK:STDOUT:   %C.generic: %C.type = struct_value () [template]
+// CHECK:STDOUT:   %C.f2e: type = class_type @C, @C(%C.8b3) [symbolic]
+// 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:   %Core: <namespace> = namespace file.%Core.import, [template] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %C.decl: %C.type = class_decl @C [template = constants.%C.generic] {
+// CHECK:STDOUT:     %C.patt.loc4_9.1: type = symbolic_binding_pattern C, 0 [symbolic = %C.patt.loc4_9.2 (constants.%C.patt)]
+// CHECK:STDOUT:     %C.param_patt: type = value_param_pattern %C.patt.loc4_9.1, runtime_param<invalid> [symbolic = %C.patt.loc4_9.2 (constants.%C.patt)]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %C.param: type = value_param runtime_param<invalid>
+// CHECK:STDOUT:     %C.loc4_9.1: type = bind_symbolic_name C, 0, %C.param [symbolic = %C.loc4_9.2 (constants.%C.8b3)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @C(%C.loc4_9.1: type) {
+// CHECK:STDOUT:   %C.loc4_9.2: type = bind_symbolic_name C, 0 [symbolic = %C.loc4_9.2 (constants.%C.8b3)]
+// CHECK:STDOUT:   %C.patt.loc4_9.2: type = symbolic_binding_pattern C, 0 [symbolic = %C.patt.loc4_9.2 (constants.%C.patt)]
+// CHECK:STDOUT:
+// 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.%C.f2e
+// CHECK:STDOUT:     complete_type_witness = %complete_type
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%C.8b3) {
+// CHECK:STDOUT:   %C.loc4_9.2 => constants.%C.8b3
+// CHECK:STDOUT:   %C.patt.loc4_9.2 => constants.%C.8b3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 95 - 0
toolchain/check/testdata/interface/no_prelude/local.carbon

@@ -0,0 +1,95 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interface/no_prelude/local.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interface/no_prelude/local.carbon
+
+fn F() {
+  interface I {
+    fn G();
+  }
+  impl () as I {
+    fn G() {}
+  }
+  ().(I.G)();
+}
+
+// CHECK:STDOUT: --- local.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [template]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [template]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [template]
+// CHECK:STDOUT:   %I.type: type = facet_type <@I> [template]
+// CHECK:STDOUT:   %Self: %I.type = bind_symbolic_name Self, 0 [symbolic]
+// CHECK:STDOUT:   %G.type.bff: type = fn_type @G.1 [template]
+// CHECK:STDOUT:   %G.f0a: %G.type.bff = struct_value () [template]
+// CHECK:STDOUT:   %G.assoc_type: type = assoc_entity_type %I.type, %G.type.bff [template]
+// CHECK:STDOUT:   %assoc0: %G.assoc_type = assoc_entity element0, @I.%G.decl [template]
+// CHECK:STDOUT:   %impl_witness: <witness> = impl_witness (@impl.%G.decl) [template]
+// CHECK:STDOUT:   %G.type.c84: type = fn_type @G.2 [template]
+// CHECK:STDOUT:   %G.5a2: %G.type.c84 = struct_value () [template]
+// CHECK:STDOUT:   %I.facet: %I.type = facet_value %empty_tuple.type, %impl_witness [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: interface @I {
+// CHECK:STDOUT:   %Self: %I.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self]
+// CHECK:STDOUT:   %G.decl: %G.type.bff = fn_decl @G.1 [template = constants.%G.f0a] {} {}
+// CHECK:STDOUT:   %assoc0: %G.assoc_type = assoc_entity element0, %G.decl [template = constants.%assoc0]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   .G = %assoc0
+// CHECK:STDOUT:   witness = (%G.decl)
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: %.loc15_9.2 as %I.ref {
+// CHECK:STDOUT:   %G.decl: %G.type.c84 = fn_decl @G.2 [template = constants.%G.5a2] {} {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .G = %G.decl
+// CHECK:STDOUT:   witness = @F.%impl_witness
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%I.type] {} {}
+// CHECK:STDOUT:   impl_decl @impl [template] {} {
+// CHECK:STDOUT:     %.loc15_9.1: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:     %.loc15_9.2: type = converted %.loc15_9.1, constants.%empty_tuple.type [template = constants.%empty_tuple.type]
+// CHECK:STDOUT:     %I.ref: type = name_ref I, @F.%I.decl [template = constants.%I.type]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %impl_witness: <witness> = impl_witness (@impl.%G.decl) [template = constants.%impl_witness]
+// CHECK:STDOUT:   %.loc18: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:   %I.ref: type = name_ref I, %I.decl [template = constants.%I.type]
+// CHECK:STDOUT:   %G.ref: %G.assoc_type = name_ref G, @I.%assoc0 [template = constants.%assoc0]
+// CHECK:STDOUT:   %impl.elem0: %G.type.bff = impl_witness_access constants.%impl_witness, element0 [template = constants.%G.5a2]
+// CHECK:STDOUT:   %G.call: init %empty_tuple.type = call %impl.elem0()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @G.1(@I.%Self: %I.type) {
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn();
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G.2() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @G.1(constants.%Self) {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @G.1(constants.%I.facet) {}
+// CHECK:STDOUT:

+ 10 - 12
toolchain/check/testdata/namespace/fail_not_top_level.carbon

@@ -14,10 +14,6 @@ fn F() {
   // CHECK:STDERR:   ^~~~~~~~~~~~
   // CHECK:STDERR:
   namespace N;
-  // CHECK:STDERR: fail_not_top_level.carbon:[[@LINE+4]]:6: error: name `N` not found [NameNotFound]
-  // CHECK:STDERR:   fn N.F() {}
-  // CHECK:STDERR:      ^
-  // CHECK:STDERR:
   fn N.F() {}
 }
 
@@ -43,10 +39,10 @@ interface I {
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %F.type.b25: type = fn_type @F.1 [template]
 // CHECK:STDOUT:   %F.c41: %F.type.b25 = struct_value () [template]
-// CHECK:STDOUT:   %.type: type = fn_type @.1 [template]
-// CHECK:STDOUT:   %.3ad: %.type = struct_value () [template]
+// CHECK:STDOUT:   %F.type.bee: type = fn_type @F.2 [template]
+// CHECK:STDOUT:   %F.39e: %F.type.bee = struct_value () [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
-// CHECK:STDOUT:   %F.type.1cc: type = fn_type @F.2 [template]
+// CHECK:STDOUT:   %F.type.1cc: type = fn_type @F.3 [template]
 // CHECK:STDOUT:   %F.f49: %F.type.1cc = struct_value () [template]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [template]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
@@ -93,7 +89,7 @@ interface I {
 // CHECK:STDOUT:   %N: <namespace> = namespace [template] {
 // CHECK:STDOUT:     .F = %F.decl
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %F.decl: %F.type.1cc = fn_decl @F.2 [template = constants.%F.f49] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type.1cc = fn_decl @F.3 [template = constants.%F.f49] {} {}
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
@@ -104,17 +100,19 @@ interface I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.1() {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %N: <namespace> = namespace [template] {}
-// CHECK:STDOUT:   %.decl: %.type = fn_decl @.1 [template = constants.%.3ad] {} {}
+// CHECK:STDOUT:   %N: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type.bee = fn_decl @F.2 [template = constants.%F.39e] {} {}
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @.1() {
+// CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.2() {
+// CHECK:STDOUT: fn @F.3() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }