Pārlūkot izejas kodu

When performing name lookup, determine the generic instance within which the lookup result was found. (#4118)

Require types into which qualified lookup is performed to be completely
defined. Eventually this will trigger substitution into the definition
for generic types.
Richard Smith 1 gadu atpakaļ
vecāks
revīzija
6d3c915bbf

+ 67 - 22
toolchain/check/context.cpp

@@ -263,7 +263,7 @@ auto Context::LookupNameInDecl(SemIR::LocId loc_id, SemIR::NameId name_id,
 }
 
 auto Context::LookupUnqualifiedName(Parse::NodeId node_id,
-                                    SemIR::NameId name_id) -> SemIR::InstId {
+                                    SemIR::NameId name_id) -> LookupResult {
   // TODO: Check for shadowed lookup results.
 
   // Find the results from ancestor lexical scopes. These will be combined with
@@ -272,22 +272,33 @@ auto Context::LookupUnqualifiedName(Parse::NodeId node_id,
       scope_stack().LookupInLexicalScopes(name_id);
 
   // Walk the non-lexical scopes and perform lookups into each of them.
-  for (auto [index, name_scope_id] : llvm::reverse(non_lexical_scopes)) {
-    if (auto non_lexical_result =
-            LookupQualifiedName(node_id, name_id, name_scope_id,
-                                /*required=*/false);
-        non_lexical_result.is_valid()) {
+  for (auto [index, lookup_scope_id] : llvm::reverse(non_lexical_scopes)) {
+    // Enclosing non-lexical scopes cannot correspond to an instance of a
+    // generic, so it's always OK to pass an invalid generic instance here.
+    // Note that the lookup result might still be found in an extended scope, so
+    // it can be in a generic instance.
+    if (auto non_lexical_result = LookupQualifiedName(
+            node_id, name_id,
+            {.name_scope_id = lookup_scope_id,
+             .instance_id = SemIR::GenericInstanceId::Invalid},
+            /*required=*/false);
+        non_lexical_result.inst_id.is_valid()) {
       return non_lexical_result;
     }
   }
 
   if (lexical_result.is_valid()) {
-    return lexical_result;
+    // A lexical scope never needs an associated generic instance. If there's a
+    // lexically enclosing generic, then it also encloses the point of use of
+    // the name.
+    return {.instance_id = SemIR::GenericInstanceId::Invalid,
+            .inst_id = lexical_result};
   }
 
   // We didn't find anything at all.
   DiagnoseNameNotFound(node_id, name_id);
-  return SemIR::InstId::BuiltinError;
+  return {.instance_id = SemIR::GenericInstanceId::Invalid,
+          .inst_id = SemIR::InstId::BuiltinError};
 }
 
 // Handles lookup through the import_ir_scopes for LookupNameInExactScope.
@@ -391,15 +402,16 @@ auto Context::LookupNameInExactScope(SemIRLoc loc, SemIR::NameId name_id,
 }
 
 auto Context::LookupQualifiedName(Parse::NodeId node_id, SemIR::NameId name_id,
-                                  SemIR::NameScopeId scope_id, bool required)
-    -> SemIR::InstId {
-  llvm::SmallVector<SemIR::NameScopeId> scope_ids = {scope_id};
-  auto result_id = SemIR::InstId::Invalid;
+                                  LookupScope scope, bool required)
+    -> LookupResult {
+  llvm::SmallVector<LookupScope> scopes = {scope};
+  LookupResult result = {.instance_id = SemIR::GenericInstanceId::Invalid,
+                         .inst_id = SemIR::InstId::Invalid};
   bool has_error = false;
 
   // Walk this scope and, if nothing is found here, the scopes it extends.
-  while (!scope_ids.empty()) {
-    auto scope_id = scope_ids.pop_back_val();
+  while (!scopes.empty()) {
+    auto [scope_id, instance_id] = scopes.pop_back_val();
     const auto& scope = name_scopes().Get(scope_id);
     has_error |= scope.has_error;
 
@@ -407,13 +419,19 @@ auto Context::LookupQualifiedName(Parse::NodeId node_id, SemIR::NameId name_id,
         LookupNameInExactScope(node_id, name_id, scope_id, scope);
     if (!scope_result_id.is_valid()) {
       // Nothing found in this scope: also look in its extended scopes.
-      auto extended = llvm::reverse(scope.extended_scopes);
-      scope_ids.append(extended.begin(), extended.end());
+      auto extended = scope.extended_scopes;
+      scopes.reserve(scopes.size() + extended.size());
+      for (auto extended_id : llvm::reverse(extended)) {
+        // TODO: Track a constant describing the extended scope, and substitute
+        // into it to determine its corresponding generic instance.
+        scopes.push_back({.name_scope_id = extended_id,
+                          .instance_id = SemIR::GenericInstanceId::Invalid});
+      }
       continue;
     }
 
     // If this is our second lookup result, diagnose an ambiguity.
-    if (result_id.is_valid()) {
+    if (result.inst_id.is_valid()) {
       // TODO: This is currently not reachable because the only scope that can
       // extend is a class scope, and it can only extend a single base class.
       // Add test coverage once this is possible.
@@ -423,20 +441,23 @@ auto Context::LookupQualifiedName(Parse::NodeId node_id, SemIR::NameId name_id,
           SemIR::NameId);
       emitter_->Emit(node_id, NameAmbiguousDueToExtend, name_id);
       // TODO: Add notes pointing to the scopes.
-      return SemIR::InstId::BuiltinError;
+      return {.instance_id = SemIR::GenericInstanceId::Invalid,
+              .inst_id = SemIR::InstId::BuiltinError};
     }
 
-    result_id = scope_result_id;
+    result.inst_id = scope_result_id;
+    result.instance_id = instance_id;
   }
 
-  if (required && !result_id.is_valid()) {
+  if (required && !result.inst_id.is_valid()) {
     if (!has_error) {
       DiagnoseNameNotFound(node_id, name_id);
     }
-    return SemIR::InstId::BuiltinError;
+    return {.instance_id = SemIR::GenericInstanceId::Invalid,
+            .inst_id = SemIR::InstId::BuiltinError};
   }
 
-  return result_id;
+  return result;
 }
 
 // Returns the scope of the Core package, or Invalid if it's not found.
@@ -779,6 +800,7 @@ class TypeCompleter {
           }
           return false;
         }
+        // TODO: Trigger generic resolution here for a generic class.
         Push(class_info.object_repr_id);
         break;
       }
@@ -1042,6 +1064,29 @@ auto Context::TryToCompleteType(
   return TypeCompleter(*this, diagnoser).Complete(type_id);
 }
 
+auto Context::TryToDefineType(
+    SemIR::TypeId type_id,
+    std::optional<llvm::function_ref<auto()->DiagnosticBuilder>> diagnoser)
+    -> bool {
+  if (!TryToCompleteType(type_id, diagnoser)) {
+    return false;
+  }
+
+  if (auto interface = types().TryGetAs<SemIR::InterfaceType>(type_id)) {
+    auto interface_id = interface->interface_id;
+    if (!interfaces().Get(interface_id).is_defined()) {
+      auto builder = (*diagnoser)();
+      NoteUndefinedInterface(interface_id, builder);
+      builder.Emit();
+      return false;
+    }
+
+    // TODO: Trigger generic resolution here for a generic instance.
+  }
+
+  return true;
+}
+
 auto Context::GetTypeIdForTypeConstant(SemIR::ConstantId constant_id)
     -> SemIR::TypeId {
   CARBON_CHECK(constant_id.is_constant())

+ 32 - 3
toolchain/check/context.h

@@ -26,6 +26,24 @@
 
 namespace Carbon::Check {
 
+// Information about a scope in which we can perform name lookup.
+struct LookupScope {
+  // The name scope in which names are searched.
+  SemIR::NameScopeId name_scope_id;
+  // The generic instance for the name scope, or `Invalid` if the name scope is
+  // not an instance of a generic.
+  SemIR::GenericInstanceId instance_id;
+};
+
+// A result produced by name lookup.
+struct LookupResult {
+  // The generic instance in which the lookup result was found. `Invalid` if the
+  // result was not found in a generic instance.
+  SemIR::GenericInstanceId instance_id;
+  // The declaration that was found by name lookup.
+  SemIR::InstId inst_id;
+};
+
 // Context and shared functionality for semantics handlers.
 class Context {
  public:
@@ -122,7 +140,7 @@ class Context {
 
   // Performs an unqualified name lookup, returning the referenced instruction.
   auto LookupUnqualifiedName(Parse::NodeId node_id, SemIR::NameId name_id)
-      -> SemIR::InstId;
+      -> LookupResult;
 
   // Performs a name lookup in a specified scope, returning the referenced
   // instruction. Does not look into extended scopes. Returns an invalid
@@ -134,8 +152,8 @@ class Context {
   // Performs a qualified name lookup in a specified scope and in scopes that
   // it extends, returning the referenced instruction.
   auto LookupQualifiedName(Parse::NodeId node_id, SemIR::NameId name_id,
-                           SemIR::NameScopeId scope_id, bool required = true)
-      -> SemIR::InstId;
+                           LookupScope scope, bool required = true)
+      -> LookupResult;
 
   // Returns the instruction corresponding to a name in the core package, or
   // BuiltinError if not found.
@@ -239,6 +257,17 @@ class Context {
       std::optional<llvm::function_ref<auto()->DiagnosticBuilder>> diagnoser =
           std::nullopt) -> bool;
 
+  // Attempts to complete and define the type `type_id`. Returns `true` if the
+  // type is defined, or `false` if no definition is available. A defined type
+  // has known members.
+  //
+  // This is the same as `TryToCompleteType` except for interfaces, which are
+  // complete before they are fully defined.
+  auto TryToDefineType(
+      SemIR::TypeId type_id,
+      std::optional<llvm::function_ref<auto()->DiagnosticBuilder>> diagnoser =
+          std::nullopt) -> bool;
+
   // Returns the type `type_id` as a complete type, or produces an incomplete
   // type error and returns an error type. This is a convenience wrapper around
   // TryToCompleteType.

+ 7 - 0
toolchain/check/generic.cpp

@@ -281,6 +281,13 @@ auto GetConstantInInstance(Context& context,
   return context.constant_values().Get(symbolic.inst_id);
 }
 
+auto GetConstantValueInInstance(Context& context,
+                                SemIR::GenericInstanceId instance_id,
+                                SemIR::InstId inst_id) -> SemIR::ConstantId {
+  return GetConstantInInstance(context, instance_id,
+                               context.constant_values().Get(inst_id));
+}
+
 auto GetTypeInInstance(Context& context, SemIR::GenericInstanceId instance_id,
                        SemIR::TypeId type_id) -> SemIR::TypeId {
   auto const_id = context.types().GetConstantId(type_id);

+ 9 - 0
toolchain/check/generic.h

@@ -54,6 +54,15 @@ auto GetConstantInInstance(Context& context,
                            SemIR::GenericInstanceId instance_id,
                            SemIR::ConstantId const_id) -> SemIR::ConstantId;
 
+// Gets the substituted constant value of an instruction within a specified
+// instance of a generic. Note that this does not perform substitution, and will
+// return `Invalid` if the substituted constant value is not yet known.
+//
+// TODO: Move this to sem_ir so that lowering can use it.
+auto GetConstantValueInInstance(Context& context,
+                                SemIR::GenericInstanceId instance_id,
+                                SemIR::InstId inst_id) -> SemIR::ConstantId;
+
 // Gets the substituted value of a type within a specified instance of a
 // generic. Note that this does not perform substitution, and will return
 // `Invalid` if the substituted type is not yet known.

+ 6 - 6
toolchain/check/handle_name.cpp

@@ -80,15 +80,15 @@ static auto GetIdentifierAsName(Context& context, Parse::NodeId node_id)
 // lookup.
 static auto HandleNameAsExpr(Context& context, Parse::NodeId node_id,
                              SemIR::NameId name_id) -> bool {
-  auto value_id = context.LookupUnqualifiedName(node_id, name_id);
-  // TODO: Lookup should produce this.
-  auto instance_id = SemIR::GenericInstanceId::Invalid;
-  auto value = context.insts().Get(value_id);
-  auto type_id = GetTypeInInstance(context, instance_id, value.type_id());
+  auto result = context.LookupUnqualifiedName(node_id, name_id);
+  auto value = context.insts().Get(result.inst_id);
+  auto type_id =
+      GetTypeInInstance(context, result.instance_id, value.type_id());
   CARBON_CHECK(type_id.is_valid()) << "Missing type for " << value;
 
   context.AddInstAndPush<SemIR::NameRef>(
-      node_id, {.type_id = type_id, .name_id = name_id, .value_id = value_id});
+      node_id,
+      {.type_id = type_id, .name_id = name_id, .value_id = result.inst_id});
   return true;
 }
 

+ 50 - 45
toolchain/check/member_access.cpp

@@ -8,54 +8,57 @@
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/convert.h"
+#include "toolchain/check/generic.h"
 #include "toolchain/check/import_ref.h"
 #include "toolchain/check/subst.h"
 #include "toolchain/diagnostics/diagnostic_emitter.h"
+#include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
 
-// Returns the name scope corresponding to base_id, or nullopt if not a scope.
+// Returns the lookup scope corresponding to base_id, or nullopt if not a scope.
 // On invalid scopes, prints a diagnostic and still returns the scope.
-static auto GetAsNameScope(Context& context, Parse::NodeId node_id,
-                           SemIR::ConstantId base_const_id)
-    -> std::optional<SemIR::NameScopeId> {
+static auto GetAsLookupScope(Context& context, Parse::NodeId node_id,
+                             SemIR::ConstantId base_const_id)
+    -> std::optional<LookupScope> {
   auto base_id = context.constant_values().GetInstId(base_const_id);
   auto base = context.insts().Get(base_id);
   if (auto base_as_namespace = base.TryAs<SemIR::Namespace>()) {
-    return base_as_namespace->name_scope_id;
+    return LookupScope{.name_scope_id = base_as_namespace->name_scope_id,
+                       .instance_id = SemIR::GenericInstanceId::Invalid};
   }
   // TODO: Consider refactoring the near-identical class and interface support
   // below.
   if (auto base_as_class = base.TryAs<SemIR::ClassType>()) {
+    context.TryToDefineType(
+        context.GetTypeIdForTypeConstant(base_const_id), [&] {
+          CARBON_DIAGNOSTIC(QualifiedExprInIncompleteClassScope, Error,
+                            "Member access into incomplete class `{0}`.",
+                            std::string);
+          return context.emitter().Build(
+              node_id, QualifiedExprInIncompleteClassScope,
+              context.sem_ir().StringifyType(base_const_id));
+        });
     auto& class_info = context.classes().Get(base_as_class->class_id);
-    if (!class_info.is_defined()) {
-      CARBON_DIAGNOSTIC(QualifiedExprInIncompleteClassScope, Error,
-                        "Member access into incomplete class `{0}`.",
-                        std::string);
-      auto builder = context.emitter().Build(
-          node_id, QualifiedExprInIncompleteClassScope,
-          context.sem_ir().StringifyType(base_const_id));
-      context.NoteIncompleteClass(base_as_class->class_id, builder);
-      builder.Emit();
-    }
-    return class_info.scope_id;
+    return LookupScope{.name_scope_id = class_info.scope_id,
+                       .instance_id = base_as_class->instance_id};
   }
   if (auto base_as_interface = base.TryAs<SemIR::InterfaceType>()) {
+    context.TryToDefineType(
+        context.GetTypeIdForTypeConstant(base_const_id), [&] {
+          CARBON_DIAGNOSTIC(QualifiedExprInUndefinedInterfaceScope, Error,
+                            "Member access into undefined interface `{0}`.",
+                            std::string);
+          return context.emitter().Build(
+              node_id, QualifiedExprInUndefinedInterfaceScope,
+              context.sem_ir().StringifyType(base_const_id));
+        });
     auto& interface_info =
         context.interfaces().Get(base_as_interface->interface_id);
-    if (!interface_info.is_defined()) {
-      CARBON_DIAGNOSTIC(QualifiedExprInUndefinedInterfaceScope, Error,
-                        "Member access into undefined interface `{0}`.",
-                        std::string);
-      auto builder = context.emitter().Build(
-          node_id, QualifiedExprInUndefinedInterfaceScope,
-          context.sem_ir().StringifyType(base_const_id));
-      context.NoteUndefinedInterface(base_as_interface->interface_id, builder);
-      builder.Emit();
-    }
-    return interface_info.scope_id;
+    return LookupScope{.name_scope_id = interface_info.scope_id,
+                       .instance_id = base_as_interface->instance_id};
   }
   // TODO: Per the design, if `base_id` is any kind of type, then lookup should
   // treat it as a name scope, even if it doesn't have members. For example,
@@ -96,11 +99,10 @@ static auto IsInstanceMethod(const SemIR::File& sem_ir,
   return false;
 }
 
-// Returns whether `name_scope_id` is a scope for which impl lookup should be
-// performed if we find an associated entity.
-static auto ScopeNeedsImplLookup(Context& context,
-                                 SemIR::NameScopeId name_scope_id) -> bool {
-  auto [_, inst] = context.name_scopes().GetInstIfValid(name_scope_id);
+// Returns whether `scope` is a scope for which impl lookup should be performed
+// if we find an associated entity.
+static auto ScopeNeedsImplLookup(Context& context, LookupScope scope) -> bool {
+  auto [_, inst] = context.name_scopes().GetInstIfValid(scope.name_scope_id);
   if (!inst) {
     return false;
   }
@@ -214,17 +216,20 @@ static auto LookupMemberNameInScope(Context& context, Parse::NodeId node_id,
                                     SemIR::InstId /*base_id*/,
                                     SemIR::NameId name_id,
                                     SemIR::ConstantId name_scope_const_id,
-                                    SemIR::NameScopeId name_scope_id)
-    -> SemIR::InstId {
-  auto inst_id = name_scope_id.is_valid() ? context.LookupQualifiedName(
-                                                node_id, name_id, name_scope_id)
-                                          : SemIR::InstId::BuiltinError;
-  auto inst = context.insts().Get(inst_id);
+                                    LookupScope lookup_scope) -> SemIR::InstId {
+  LookupResult result = {.instance_id = SemIR::GenericInstanceId::Invalid,
+                         .inst_id = SemIR::InstId::BuiltinError};
+  if (lookup_scope.name_scope_id.is_valid()) {
+    result = context.LookupQualifiedName(node_id, name_id, lookup_scope);
+  }
+
+  auto inst = context.insts().Get(result.inst_id);
+  auto type_id = GetTypeInInstance(context, result.instance_id, inst.type_id());
   // TODO: Use a different kind of instruction that also references the
   // `base_id` so that `SemIR` consumers can find it.
   auto member_id = context.AddInst<SemIR::NameRef>(
       node_id,
-      {.type_id = inst.type_id(), .name_id = name_id, .value_id = inst_id});
+      {.type_id = type_id, .name_id = name_id, .value_id = result.inst_id});
 
   // If member name lookup finds an associated entity name, and the scope is not
   // a facet type, perform impl lookup.
@@ -234,7 +239,7 @@ static auto LookupMemberNameInScope(Context& context, Parse::NodeId node_id,
   // impl member is not supposed to be treated as ambiguous.
   if (auto assoc_type = context.types().TryGetAs<SemIR::AssociatedEntityType>(
           inst.type_id())) {
-    if (ScopeNeedsImplLookup(context, name_scope_id)) {
+    if (ScopeNeedsImplLookup(context, lookup_scope)) {
       member_id = PerformImplLookup(context, node_id, name_scope_const_id,
                                     *assoc_type, member_id);
     }
@@ -303,9 +308,9 @@ auto PerformMemberAccess(Context& context, Parse::NodeId node_id,
   // into that scope.
   if (auto base_const_id = context.constant_values().Get(base_id);
       base_const_id.is_constant()) {
-    if (auto name_scope_id = GetAsNameScope(context, node_id, base_const_id)) {
+    if (auto lookup_scope = GetAsLookupScope(context, node_id, base_const_id)) {
       return LookupMemberNameInScope(context, node_id, base_id, name_id,
-                                     base_const_id, *name_scope_id);
+                                     base_const_id, *lookup_scope);
     }
   }
 
@@ -327,8 +332,8 @@ auto PerformMemberAccess(Context& context, Parse::NodeId node_id,
   auto base_type_const_id = context.types().GetConstantId(base_type_id);
 
   // Find the scope corresponding to the base type.
-  auto name_scope_id = GetAsNameScope(context, node_id, base_type_const_id);
-  if (!name_scope_id) {
+  auto lookup_scope = GetAsLookupScope(context, node_id, base_type_const_id);
+  if (!lookup_scope) {
     // The base type is not a name scope. Try some fallback options.
     if (auto struct_type = context.insts().TryGetAs<SemIR::StructType>(
             context.constant_values().GetInstId(base_type_const_id))) {
@@ -364,7 +369,7 @@ auto PerformMemberAccess(Context& context, Parse::NodeId node_id,
 
   // Perform lookup into the base type.
   auto member_id = LookupMemberNameInScope(context, node_id, base_id, name_id,
-                                           base_type_const_id, *name_scope_id);
+                                           base_type_const_id, *lookup_scope);
 
   // Perform instance binding if we found an instance member.
   member_id = PerformInstanceBinding(context, node_id, base_id, member_id);

+ 13 - 4
toolchain/check/operator.cpp

@@ -6,6 +6,7 @@
 
 #include "toolchain/check/call.h"
 #include "toolchain/check/context.h"
+#include "toolchain/check/generic.h"
 #include "toolchain/check/member_access.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/typed_insts.h"
@@ -37,17 +38,25 @@ static auto GetOperatorOpFunction(Context& context, Parse::AnyExprId node_id,
     return SemIR::InstId::Invalid;
   }
 
+  // TODO: For a parameterized interface, find the corresponding generic
+  // instance.
+  LookupScope scope = {.name_scope_id = interface_scope_id,
+                       .instance_id = SemIR::GenericInstanceId::Invalid};
+
   // Lookup `Interface.Op`.
   auto op_ident_id = context.identifiers().Add(op.op_name);
-  auto op_id = context.LookupQualifiedName(
-      node_id, SemIR::NameId::ForIdentifier(op_ident_id), interface_scope_id,
+  auto op_result = context.LookupQualifiedName(
+      node_id, SemIR::NameId::ForIdentifier(op_ident_id), scope,
       /*required=*/false);
-  if (!op_id.is_valid()) {
+  if (!op_result.inst_id.is_valid()) {
     return SemIR::InstId::Invalid;
   }
 
   // Look through import_refs and aliases.
-  op_id = context.constant_values().GetConstantInstId(op_id);
+  auto op_const_id =
+      GetConstantInInstance(context, op_result.instance_id,
+                            context.constant_values().Get(op_result.inst_id));
+  auto op_id = context.constant_values().GetInstId(op_const_id);
 
   // We expect it to be an associated function.
   if (context.insts().Is<SemIR::AssociatedEntity>(op_id)) {

+ 3 - 2
toolchain/check/testdata/class/basic.carbon

@@ -41,7 +41,8 @@ fn Run() -> i32 {
 // CHECK:STDOUT:   %.3: type = struct_type {.k: i32} [template]
 // CHECK:STDOUT:   %Run.type: type = fn_type @Run [template]
 // CHECK:STDOUT:   %Run: %Run.type = struct_value () [template]
-// CHECK:STDOUT:   %.4: i32 = int_literal 4 [template]
+// CHECK:STDOUT:   %.4: type = ptr_type %.3 [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 4 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -136,7 +137,7 @@ fn Run() -> i32 {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %Class.ref: type = name_ref Class, file.%Class.decl [template = constants.%Class]
 // CHECK:STDOUT:   %F.ref: %F.type = name_ref F, @Class.%F.decl [template = constants.%F]
-// CHECK:STDOUT:   %.loc26_18: i32 = int_literal 4 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc26_18: i32 = int_literal 4 [template = constants.%.5]
 // CHECK:STDOUT:   %F.call: init i32 = call %F.ref(%.loc26_18)
 // CHECK:STDOUT:   %.loc26_20.1: i32 = value_of_initializer %F.call
 // CHECK:STDOUT:   %.loc26_20.2: i32 = converted %F.call, %.loc26_20.1

+ 2 - 0
toolchain/check/testdata/class/fail_base_unbound.carbon

@@ -29,6 +29,8 @@ let b: B = C.base;
 // CHECK:STDOUT:   %.3: type = ptr_type %.1 [template]
 // CHECK:STDOUT:   %.4: type = unbound_element_type %C, %B [template]
 // CHECK:STDOUT:   %.5: type = struct_type {.base: %B} [template]
+// CHECK:STDOUT:   %.6: type = struct_type {.base: %.3} [template]
+// CHECK:STDOUT:   %.7: type = ptr_type %.5 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {

+ 1 - 1
toolchain/check/testdata/class/fail_method.carbon

@@ -56,9 +56,9 @@ fn F(c: Class) {
 // CHECK:STDOUT:   %WithSelf.type: type = fn_type @WithSelf [template]
 // CHECK:STDOUT:   %WithSelf: %WithSelf.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type %.2 [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
-// CHECK:STDOUT:   %.3: type = ptr_type %.2 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {

+ 1 - 0
toolchain/check/testdata/class/fail_unbound_field.carbon

@@ -39,6 +39,7 @@ fn G() -> i32 {
 // CHECK:STDOUT:   %.3: type = struct_type {.field: i32} [template]
 // CHECK:STDOUT:   %G.type: type = fn_type @G [template]
 // CHECK:STDOUT:   %G: %G.type = struct_value () [template]
+// CHECK:STDOUT:   %.4: type = ptr_type %.3 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {

+ 214 - 0
toolchain/check/testdata/class/generic/field.carbon

@@ -0,0 +1,214 @@
+// 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/class/generic/field.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/generic/field.carbon
+// CHECK:STDERR: fail_todo_field.carbon: ERROR: Main//default previously provided by `field.carbon`.
+// CHECK:STDERR:
+
+// --- field.carbon
+
+class Class(T:! type) {
+  var x: T;
+}
+
+// TODO: This case only works because the parameters of Class and G have the
+// same name and index, so canonicalize to the same type.
+fn G(T:! type, c: Class(T)) -> T {
+  return c.x;
+}
+
+// --- fail_todo_field.carbon
+
+class Class(T:! type) {
+  var x: T;
+}
+
+fn F(c: Class(i32)) -> i32 {
+  // CHECK:STDERR: fail_todo_field.carbon:[[@LINE+8]]:3: ERROR: Cannot implicitly convert from `T` to `i32`.
+  // CHECK:STDERR:   return c.x;
+  // CHECK:STDERR:   ^~~~~~~~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_field.carbon:[[@LINE+4]]:10: ERROR: Cannot implicitly convert from `Class` to `Class`.
+  // CHECK:STDERR:   return c.x;
+  // CHECK:STDERR:          ^~~
+  // CHECK:STDERR:
+  return c.x;
+}
+
+fn H(U:! type, c: Class(U)) -> U {
+  // CHECK:STDERR: fail_todo_field.carbon:[[@LINE+7]]:3: ERROR: Cannot implicitly convert from `T` to `U`.
+  // CHECK:STDERR:   return c.x;
+  // CHECK:STDERR:   ^~~~~~~~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_todo_field.carbon:[[@LINE+3]]:10: ERROR: Cannot implicitly convert from `Class` to `Class`.
+  // CHECK:STDERR:   return c.x;
+  // CHECK:STDERR:          ^~~
+  return c.x;
+}
+
+// CHECK:STDOUT: --- field.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %Class.type: type = generic_class_type @Class [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %Class.1: %Class.type = struct_value () [template]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, (%T) [symbolic]
+// CHECK:STDOUT:   %.2: type = unbound_element_type %Class.2, %T [symbolic]
+// CHECK:STDOUT:   %.3: type = struct_type {.x: %T} [symbolic]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [template]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [template]
+// CHECK:STDOUT:   %.4: type = ptr_type %.3 [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Class = %Class.decl
+// CHECK:STDOUT:     .G = %G.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Core: <namespace> = namespace %Core.import, [template] {}
+// CHECK:STDOUT:   %Class.decl: %Class.type = class_decl @Class [template = constants.%Class.1] {
+// CHECK:STDOUT:     %T.loc2_13.1: type = param T
+// CHECK:STDOUT:     %T.loc2_13.2: type = bind_symbolic_name T 0, %T.loc2_13.1 [symbolic = %T.loc2_13.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [template = constants.%G] {
+// CHECK:STDOUT:     %T.loc8_6.1: type = param T
+// CHECK:STDOUT:     @G.%T: type = bind_symbolic_name T 0, %T.loc8_6.1 [symbolic = @G.%T (constants.%T)]
+// CHECK:STDOUT:     %Class.ref: %Class.type = name_ref Class, %Class.decl [template = constants.%Class.1]
+// CHECK:STDOUT:     %T.ref.loc8_25: type = name_ref T, @G.%T [symbolic = @G.%T (constants.%T)]
+// CHECK:STDOUT:     %.loc8_24: init type = call %Class.ref(%T.ref.loc8_25) [symbolic = %.loc8_24 (constants.%Class.2)]
+// CHECK:STDOUT:     %.loc8_26.1: type = value_of_initializer %.loc8_24 [symbolic = %.loc8_24 (constants.%Class.2)]
+// CHECK:STDOUT:     %.loc8_26.2: type = converted %.loc8_24, %.loc8_26.1 [symbolic = %.loc8_24 (constants.%Class.2)]
+// CHECK:STDOUT:     %c.loc8_16.1: file.%.loc8_24 (%Class.2) = param c
+// CHECK:STDOUT:     @G.%c: file.%.loc8_24 (%Class.2) = bind_name c, %c.loc8_16.1
+// CHECK:STDOUT:     %T.ref.loc8_32: type = name_ref T, @G.%T [symbolic = @G.%T (constants.%T)]
+// CHECK:STDOUT:     @G.%return: ref %T = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Class
+// CHECK:STDOUT:     generic [file.%T.loc2_13.2: type] {
+// CHECK:STDOUT:   %T.ref: type = name_ref T, file.%T.loc2_13.2 [symbolic = constants.%T]
+// CHECK:STDOUT:   %.loc3: %.2 = field_decl x, element0 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Class.2
+// CHECK:STDOUT:   .x = %.loc3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G(%T: type, %c: file.%.loc8_24 (%Class.2)) -> %T
+// CHECK:STDOUT:     generic [%T: type] {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %c.ref: %Class.2 = name_ref c, %c
+// CHECK:STDOUT:   %x.ref: %.2 = name_ref x, @Class.%.loc3 [template = @Class.%.loc3]
+// CHECK:STDOUT:   %.loc9_11.1: ref %T = class_element_access %c.ref, element0
+// CHECK:STDOUT:   %.loc9_11.2: %T = bind_value %.loc9_11.1
+// CHECK:STDOUT:   return %.loc9_11.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_todo_field.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %Class.type: type = generic_class_type @Class [template]
+// CHECK:STDOUT:   %.1: type = tuple_type () [template]
+// CHECK:STDOUT:   %Class.1: %Class.type = struct_value () [template]
+// CHECK:STDOUT:   %Class.2: type = class_type @Class, (%T) [symbolic]
+// CHECK:STDOUT:   %.2: type = unbound_element_type %Class.2, %T [symbolic]
+// CHECK:STDOUT:   %.3: type = struct_type {.x: %T} [symbolic]
+// CHECK:STDOUT:   %Int32.type: type = fn_type @Int32 [template]
+// CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
+// CHECK:STDOUT:   %Class.3: type = class_type @Class, (i32) [template]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [template]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [template]
+// CHECK:STDOUT:   %.4: type = ptr_type %.3 [symbolic]
+// CHECK:STDOUT:   %U: type = bind_symbolic_name U 0 [symbolic]
+// CHECK:STDOUT:   %Class.4: type = class_type @Class, (%U) [symbolic]
+// CHECK:STDOUT:   %H.type: type = fn_type @H [template]
+// CHECK:STDOUT:   %H: %H.type = struct_value () [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %import_ref.1: %Int32.type = import_ref ir3, inst+4, loaded [template = constants.%Int32]
+// CHECK:STDOUT:   %import_ref.2: %Int32.type = import_ref ir3, inst+4, loaded [template = constants.%Int32]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Core = %Core
+// CHECK:STDOUT:     .Class = %Class.decl
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:     .H = %H.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %Core: <namespace> = namespace %Core.import, [template] {}
+// CHECK:STDOUT:   %Class.decl: %Class.type = class_decl @Class [template = constants.%Class.1] {
+// CHECK:STDOUT:     %T.loc2_13.1: type = param T
+// CHECK:STDOUT:     %T.loc2_13.2: type = bind_symbolic_name T 0, %T.loc2_13.1 [symbolic = %T.loc2_13.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [template = constants.%F] {
+// CHECK:STDOUT:     %Class.ref.loc6: %Class.type = name_ref Class, %Class.decl [template = constants.%Class.1]
+// CHECK:STDOUT:     %int.make_type_32.loc6_15: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:     %.loc6_14.1: type = value_of_initializer %int.make_type_32.loc6_15 [template = i32]
+// CHECK:STDOUT:     %.loc6_14.2: type = converted %int.make_type_32.loc6_15, %.loc6_14.1 [template = i32]
+// CHECK:STDOUT:     %.loc6_14.3: init type = call %Class.ref.loc6(%.loc6_14.2) [template = constants.%Class.3]
+// CHECK:STDOUT:     %.loc6_18.1: type = value_of_initializer %.loc6_14.3 [template = constants.%Class.3]
+// CHECK:STDOUT:     %.loc6_18.2: type = converted %.loc6_14.3, %.loc6_18.1 [template = constants.%Class.3]
+// CHECK:STDOUT:     %c.loc6_6.1: %Class.3 = param c
+// CHECK:STDOUT:     @F.%c: %Class.3 = bind_name c, %c.loc6_6.1
+// CHECK:STDOUT:     %int.make_type_32.loc6_24: init type = call constants.%Int32() [template = i32]
+// CHECK:STDOUT:     %.loc6_24.1: type = value_of_initializer %int.make_type_32.loc6_24 [template = i32]
+// CHECK:STDOUT:     %.loc6_24.2: type = converted %int.make_type_32.loc6_24, %.loc6_24.1 [template = i32]
+// CHECK:STDOUT:     @F.%return: ref i32 = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %H.decl: %H.type = fn_decl @H [template = constants.%H] {
+// CHECK:STDOUT:     %U.loc18_6.1: type = param U
+// CHECK:STDOUT:     @H.%U: type = bind_symbolic_name U 0, %U.loc18_6.1 [symbolic = @H.%U (constants.%U)]
+// CHECK:STDOUT:     %Class.ref.loc18: %Class.type = name_ref Class, %Class.decl [template = constants.%Class.1]
+// CHECK:STDOUT:     %U.ref.loc18_25: type = name_ref U, @H.%U [symbolic = @H.%U (constants.%U)]
+// CHECK:STDOUT:     %.loc18_24: init type = call %Class.ref.loc18(%U.ref.loc18_25) [symbolic = %.loc18_24 (constants.%Class.4)]
+// CHECK:STDOUT:     %.loc18_26.1: type = value_of_initializer %.loc18_24 [symbolic = %.loc18_24 (constants.%Class.4)]
+// CHECK:STDOUT:     %.loc18_26.2: type = converted %.loc18_24, %.loc18_26.1 [symbolic = %.loc18_24 (constants.%Class.4)]
+// CHECK:STDOUT:     %c.loc18_16.1: file.%.loc18_24 (%Class.4) = param c
+// CHECK:STDOUT:     @H.%c: file.%.loc18_24 (%Class.4) = bind_name c, %c.loc18_16.1
+// CHECK:STDOUT:     %U.ref.loc18_32: type = name_ref U, @H.%U [symbolic = @H.%U (constants.%U)]
+// CHECK:STDOUT:     @H.%return: ref %U = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Class
+// CHECK:STDOUT:     generic [file.%T.loc2_13.2: type] {
+// CHECK:STDOUT:   %T.ref: type = name_ref T, file.%T.loc2_13.2 [symbolic = constants.%T]
+// CHECK:STDOUT:   %.loc3: %.2 = field_decl x, element0 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%Class.2
+// CHECK:STDOUT:   .x = %.loc3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%c: %Class.3) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %c.ref: %Class.3 = name_ref c, %c
+// CHECK:STDOUT:   %x.ref: %.2 = name_ref x, @Class.%.loc3 [template = @Class.%.loc3]
+// CHECK:STDOUT:   %.loc15: %T = class_element_access <error>, element0 [template = <error>]
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @H(%U: type, %c: file.%.loc18_24 (%Class.4)) -> %U
+// CHECK:STDOUT:     generic [%U: type] {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %c.ref: %Class.4 = name_ref c, %c
+// CHECK:STDOUT:   %x.ref: %.2 = name_ref x, @Class.%.loc3 [template = @Class.%.loc3]
+// CHECK:STDOUT:   %.loc26: %T = class_element_access <error>, element0 [template = <error>]
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/class/nested_name.carbon

@@ -33,12 +33,12 @@ fn G(o: Outer) {
 // CHECK:STDOUT:   %.2: type = unbound_element_type %Inner, i32 [template]
 // CHECK:STDOUT:   %.3: type = struct_type {.n: i32} [template]
 // CHECK:STDOUT:   %.4: type = struct_type {} [template]
+// CHECK:STDOUT:   %.5: type = ptr_type %.4 [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
-// CHECK:STDOUT:   %.5: type = ptr_type %.3 [template]
+// CHECK:STDOUT:   %.6: type = ptr_type %.3 [template]
 // CHECK:STDOUT:   %G.type: type = fn_type @G [template]
 // CHECK:STDOUT:   %G: %G.type = struct_value () [template]
-// CHECK:STDOUT:   %.6: type = ptr_type %.4 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {

+ 5 - 0
toolchain/check/testdata/class/no_prelude/indirect_import_member.carbon

@@ -240,6 +240,7 @@ var x: () = D.C.F();
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type %.2 [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT: }
@@ -285,6 +286,7 @@ var x: () = D.C.F();
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type %.2 [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT: }
@@ -330,6 +332,7 @@ var x: () = D.C.F();
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type %.2 [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT: }
@@ -375,6 +378,7 @@ var x: () = D.C.F();
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %D: type = class_type @D [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type %.2 [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
@@ -430,6 +434,7 @@ var x: () = D.C.F();
 // CHECK:STDOUT:   %.1: type = tuple_type () [template]
 // CHECK:STDOUT:   %D: type = class_type @D [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type %.2 [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]

+ 1 - 0
toolchain/check/testdata/class/reenter_scope.carbon

@@ -30,6 +30,7 @@ fn Class.F() -> i32 {
 // CHECK:STDOUT:   %G.type: type = fn_type @G [template]
 // CHECK:STDOUT:   %G: %G.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
+// CHECK:STDOUT:   %.3: type = ptr_type %.2 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {

+ 3 - 2
toolchain/check/testdata/class/reorder.carbon

@@ -30,7 +30,8 @@ class Class {
 // CHECK:STDOUT:   %F.type: type = fn_type @F [template]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [template]
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
-// CHECK:STDOUT:   %.3: i32 = int_literal 1 [template]
+// CHECK:STDOUT:   %.3: type = ptr_type %.2 [template]
+// CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -82,7 +83,7 @@ class Class {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F() -> i32 {
 // CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   %.loc17: i32 = int_literal 1 [template = constants.%.3]
+// CHECK:STDOUT:   %.loc17: i32 = int_literal 1 [template = constants.%.4]
 // CHECK:STDOUT:   return %.loc17
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 0
toolchain/check/testdata/class/scope.carbon

@@ -45,6 +45,7 @@ fn Run() {
 // CHECK:STDOUT:   %.4: i32 = int_literal 2 [template]
 // CHECK:STDOUT:   %Run.type: type = fn_type @Run [template]
 // CHECK:STDOUT:   %Run: %Run.type = struct_value () [template]
+// CHECK:STDOUT:   %.5: type = ptr_type %.2 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {

+ 3 - 2
toolchain/check/testdata/impl/lookup/fail_todo_undefined_impl.carbon

@@ -44,9 +44,10 @@ impl C as I {
 // CHECK:STDOUT:   %Int32: %Int32.type = struct_value () [template]
 // CHECK:STDOUT:   %F.type.2: type = fn_type @F.2 [template]
 // CHECK:STDOUT:   %F.2: %F.type.2 = struct_value () [template]
+// CHECK:STDOUT:   %.6: type = ptr_type %.5 [template]
 // CHECK:STDOUT:   %F.type.3: type = fn_type @F.3 [template]
 // CHECK:STDOUT:   %F.3: %F.type.3 = struct_value () [template]
-// CHECK:STDOUT:   %.6: <witness> = interface_witness (%F.3) [template]
+// CHECK:STDOUT:   %.7: <witness> = interface_witness (%F.3) [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {
@@ -89,7 +90,7 @@ impl C as I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl: %C as %.1 {
 // CHECK:STDOUT:   %F.decl: %F.type.3 = fn_decl @F.3 [template = constants.%F.3] {}
-// CHECK:STDOUT:   %.1: <witness> = interface_witness (%F.decl) [template = constants.%.6]
+// CHECK:STDOUT:   %.1: <witness> = interface_witness (%F.decl) [template = constants.%.7]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .F = %F.decl

+ 1 - 0
toolchain/check/testdata/interface/no_prelude/fail_assoc_const_not_constant.carbon

@@ -30,6 +30,7 @@ alias UseOther = I.other;
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: type = interface_type @I [template]
 // CHECK:STDOUT:   %Self: %.1 = bind_symbolic_name Self 0 [symbolic]
+// CHECK:STDOUT:   %.2: type = tuple_type () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {

+ 1 - 0
toolchain/check/testdata/package_expr/syntax.carbon

@@ -162,6 +162,7 @@ fn Main() {
 // CHECK:STDOUT:   %.2: type = struct_type {} [template]
 // CHECK:STDOUT:   %Main.type: type = fn_type @Main [template]
 // CHECK:STDOUT:   %Main: %Main.type = struct_value () [template]
+// CHECK:STDOUT:   %.3: type = ptr_type %.2 [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {