Przeglądaj źródła

Propagate the phase of a type from its constituent types. (#3645)

To avoid bouncing through `constant_values()` to determine whether a
type is symbolic or template, store the `ConstantId` on the `TypeInfo`
not just the `InstId`.

In addition to propagating the symbolic / template phase, this also
propagates whether a type contains an error, resulting in our no longer
producing types such as `<error>*` -- these now evaluate to simply
`<error>`. While this makes our types less precise after an error, it
also removes some follow-on diagnostics, so it seems to be an
improvement on the whole.
Richard Smith 2 lat temu
rodzic
commit
439a644960
25 zmienionych plików z 243 dodań i 152 usunięć
  1. 4 2
      toolchain/check/context.cpp
  2. 52 23
      toolchain/check/eval.cpp
  3. 4 8
      toolchain/check/testdata/array/fail_incomplete_element.carbon
  4. 3 5
      toolchain/check/testdata/array/fail_invalid_type.carbon
  5. 1 1
      toolchain/check/testdata/basics/builtin_insts.carbon
  6. 4 4
      toolchain/check/testdata/basics/multifile_raw_and_textual_ir.carbon
  7. 4 4
      toolchain/check/testdata/basics/multifile_raw_ir.carbon
  8. 7 7
      toolchain/check/testdata/basics/raw_and_textual_ir.carbon
  9. 7 7
      toolchain/check/testdata/basics/raw_ir.carbon
  10. 33 41
      toolchain/check/testdata/class/fail_base_bad_type.carbon
  11. 30 0
      toolchain/check/testdata/eval/fail_symbolic.carbon
  12. 55 0
      toolchain/check/testdata/eval/symbolic.carbon
  13. 2 2
      toolchain/check/testdata/function/generic/fail_type_param_mismatch.carbon
  14. 2 2
      toolchain/check/testdata/function/generic/type_param.carbon
  15. 1 3
      toolchain/check/testdata/if_expr/fail_not_in_function.carbon
  16. 2 2
      toolchain/check/testdata/let/generic.carbon
  17. 3 8
      toolchain/check/testdata/pointer/fail_address_of_error.carbon
  18. 3 7
      toolchain/check/testdata/struct/fail_nested_incomplete.carbon
  19. 4 8
      toolchain/check/testdata/tuples/fail_nested_incomplete.carbon
  20. 3 5
      toolchain/check/testdata/tuples/fail_value_as_type.carbon
  21. 1 1
      toolchain/lower/file_context.cpp
  22. 1 1
      toolchain/sem_ir/file.cpp
  23. 2 2
      toolchain/sem_ir/type_info.h
  24. 13 7
      toolchain/sem_ir/value_stores.h
  25. 2 2
      toolchain/sem_ir/yaml_test.cpp

+ 4 - 2
toolchain/check/context.cpp

@@ -700,6 +700,9 @@ class TypeCompleter {
         }
         // For a pointer representation, the pointee also needs to be complete.
         if (value_rep.kind == SemIR::ValueRepr::Pointer) {
+          if (value_rep.type_id == SemIR::TypeId::Error) {
+            break;
+          }
           auto pointee_type_id =
               context_.sem_ir().GetPointeeType(value_rep.type_id);
           if (!context_.types().IsComplete(pointee_type_id)) {
@@ -1068,8 +1071,7 @@ auto Context::GetTypeIdForTypeConstant(SemIR::ConstantId constant_id)
   auto [it, added] = type_ids_for_type_constants_.insert(
       {constant_id, SemIR::TypeId::Invalid});
   if (added) {
-    // TODO: Store the full `constant_id` on the TypeInfo.
-    it->second = types().Add({.inst_id = constant_id.inst_id()});
+    it->second = types().Add({.constant_id = constant_id});
   }
   return it->second;
 }

+ 52 - 23
toolchain/check/eval.cpp

@@ -83,6 +83,14 @@ static auto GetConstantValue(Context& context, SemIR::InstId inst_id,
   return const_id.inst_id();
 }
 
+// A type is always constant, but we still need to extract its phase.
+static auto GetConstantValue(Context& context, SemIR::TypeId type_id,
+                             Phase* phase) -> SemIR::TypeId {
+  auto const_id = context.types().GetConstantId(type_id);
+  *phase = LatestPhase(*phase, GetPhase(const_id));
+  return type_id;
+}
+
 // If the given instruction block contains only constants, returns a
 // corresponding block of those values.
 static auto GetConstantValue(Context& context, SemIR::InstBlockId inst_block_id,
@@ -109,6 +117,17 @@ static auto GetConstantValue(Context& context, SemIR::InstBlockId inst_block_id,
   return context.inst_blocks().Add(const_insts);
 }
 
+// The constant value of a type block is that type block, but we still need to
+// extract its phase.
+static auto GetConstantValue(Context& context, SemIR::TypeBlockId type_block_id,
+                             Phase* phase) -> SemIR::TypeBlockId {
+  auto types = context.type_blocks().Get(type_block_id);
+  for (auto type_id : types) {
+    GetConstantValue(context, type_id, phase);
+  }
+  return type_block_id;
+}
+
 // Replaces the specified field of the given typed instruction with its constant
 // value, if it has constant phase. Returns true on success, false if the value
 // has runtime phase.
@@ -282,43 +301,57 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
           context, inst,
           [&](SemIR::ArrayType result) {
             auto bound_id = inst.As<SemIR::ArrayType>().bound_id;
-            if (auto int_bound = context.insts().TryGetAs<SemIR::IntLiteral>(
-                    result.bound_id)) {
-              // TODO: We should check that the size of the resulting array type
-              // fits in 64 bits, not just that the bound does. Should we use a
-              // 32-bit limit for 32-bit targets?
-              // TODO: Also check for a negative bound, once that's something we
-              // can represent.
-              const auto& bound_val = context.ints().Get(int_bound->int_id);
-              if (bound_val.getActiveBits() > 64) {
-                CARBON_DIAGNOSTIC(ArrayBoundTooLarge, Error,
-                                  "Array bound of {0} is too large.",
-                                  llvm::APInt);
-                context.emitter().Emit(bound_id, ArrayBoundTooLarge, bound_val);
-                return false;
-              }
-            } else {
+            auto int_bound =
+                context.insts().TryGetAs<SemIR::IntLiteral>(result.bound_id);
+            if (!int_bound) {
               // TODO: Permit symbolic array bounds. This will require fixing
               // callers of `GetArrayBoundValue`.
               context.TODO(context.insts().GetParseNode(bound_id),
                            "symbolic array bound");
+              return false;
+            }
+            // TODO: We should check that the size of the resulting array type
+            // fits in 64 bits, not just that the bound does. Should we use a
+            // 32-bit limit for 32-bit targets?
+            // TODO: Also check for a negative bound, once that's something we
+            // can represent.
+            const auto& bound_val = context.ints().Get(int_bound->int_id);
+            if (bound_val.getActiveBits() > 64) {
+              CARBON_DIAGNOSTIC(ArrayBoundTooLarge, Error,
+                                "Array bound of {0} is too large.",
+                                llvm::APInt);
+              context.emitter().Emit(bound_id, ArrayBoundTooLarge, bound_val);
+              return false;
             }
             return true;
           },
-          &SemIR::ArrayType::bound_id);
+          &SemIR::ArrayType::bound_id, &SemIR::ArrayType::element_type_id);
     case SemIR::BoundMethod::Kind:
       return RebuildIfFieldsAreConstant(context, inst,
                                         &SemIR::BoundMethod::object_id,
                                         &SemIR::BoundMethod::function_id);
+    case SemIR::PointerType::Kind:
+      return RebuildIfFieldsAreConstant(context, inst,
+                                        &SemIR::PointerType::pointee_id);
     case SemIR::StructType::Kind:
       return RebuildIfFieldsAreConstant(context, inst,
                                         &SemIR::StructType::fields_id);
+    case SemIR::StructTypeField::Kind:
+      return RebuildIfFieldsAreConstant(context, inst,
+                                        &SemIR::StructTypeField::field_type_id);
     case SemIR::StructValue::Kind:
       return RebuildIfFieldsAreConstant(context, inst,
                                         &SemIR::StructValue::elements_id);
+    case SemIR::TupleType::Kind:
+      return RebuildIfFieldsAreConstant(context, inst,
+                                        &SemIR::TupleType::elements_id);
     case SemIR::TupleValue::Kind:
       return RebuildIfFieldsAreConstant(context, inst,
                                         &SemIR::TupleValue::elements_id);
+    case SemIR::UnboundElementType::Kind:
+      return RebuildIfFieldsAreConstant(
+          context, inst, &SemIR::UnboundElementType::class_type_id,
+          &SemIR::UnboundElementType::element_type_id);
 
     // Initializers evaluate to a value of the object representation.
     case SemIR::ArrayInit::Kind:
@@ -334,14 +367,10 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
     case SemIR::TupleInit::Kind:
       return RebuildInitAsValue(context, inst, SemIR::TupleValue::Kind);
 
-    // These cases are always constants.
+    // These cases are always template constants.
     case SemIR::Builtin::Kind:
     case SemIR::ClassType::Kind:
-    case SemIR::PointerType::Kind:
-    case SemIR::StructTypeField::Kind:
-    case SemIR::TupleType::Kind:
-    case SemIR::UnboundElementType::Kind:
-      // TODO: Propagate symbolic / template nature from operands.
+      // TODO: Once classes have generic arguments, handle them.
       return MakeConstantResult(context, inst, Phase::Template);
 
     // These cases are treated as being the unique canonical definition of the

+ 4 - 8
toolchain/check/testdata/array/fail_incomplete_element.carbon

@@ -14,9 +14,6 @@ class Incomplete;
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~
 var a: [Incomplete; 1];
 
-// CHECK:STDERR: fail_incomplete_element.carbon:[[@LINE+3]]:1: ERROR: Cannot implicitly convert from `<error>*` to `Incomplete*`.
-// CHECK:STDERR: var p: Incomplete* = &a[0];
-// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~
 var p: Incomplete* = &a[0];
 
 // CHECK:STDOUT: --- fail_incomplete_element.carbon
@@ -27,7 +24,6 @@ var p: Incomplete* = &a[0];
 // CHECK:STDOUT:   %.2: type = array_type %.1, Incomplete [template]
 // CHECK:STDOUT:   %.3: type = ptr_type Incomplete [template]
 // CHECK:STDOUT:   %.4: i32 = int_literal 0 [template]
-// CHECK:STDOUT:   %.5: type = ptr_type <error> [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -38,13 +34,13 @@ var p: Incomplete* = &a[0];
 // CHECK:STDOUT:   %.loc15_22: type = array_type %.loc15_21, Incomplete [template = constants.%.2]
 // CHECK:STDOUT:   %a.var: ref <error> = var a
 // CHECK:STDOUT:   %a: ref <error> = bind_name a, %a.var
-// CHECK:STDOUT:   %Incomplete.ref.loc20: type = name_ref Incomplete, constants.%Incomplete [template = constants.%Incomplete]
-// CHECK:STDOUT:   %.loc20_18: type = ptr_type Incomplete [template = constants.%.3]
+// CHECK:STDOUT:   %Incomplete.ref.loc17: type = name_ref Incomplete, constants.%Incomplete [template = constants.%Incomplete]
+// CHECK:STDOUT:   %.loc17_18: type = ptr_type Incomplete [template = constants.%.3]
 // CHECK:STDOUT:   %p.var: ref Incomplete* = var p
 // CHECK:STDOUT:   %p: ref Incomplete* = bind_name p, %p.var
 // CHECK:STDOUT:   %a.ref: ref <error> = name_ref a, %a
-// CHECK:STDOUT:   %.loc20_25: i32 = int_literal 0 [template = constants.%.4]
-// CHECK:STDOUT:   %.loc20_22: <error>* = addr_of <error> [template = <error>]
+// CHECK:STDOUT:   %.loc17_25: i32 = int_literal 0 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc17_22: <error> = addr_of <error> [template = <error>]
 // CHECK:STDOUT:   assign %p.var, <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 3 - 5
toolchain/check/testdata/array/fail_invalid_type.carbon

@@ -13,16 +13,14 @@ var a: [1; 1];
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
-// CHECK:STDOUT:   %.2: type = array_type %.1, <error> [template]
-// CHECK:STDOUT:   %.3: type = ptr_type [<error>; 1] [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.a = %a} [template]
 // CHECK:STDOUT:   %.loc10_9: i32 = int_literal 1 [template = constants.%.1]
 // CHECK:STDOUT:   %.loc10_12: i32 = int_literal 1 [template = constants.%.1]
-// CHECK:STDOUT:   %.loc10_13: type = array_type %.loc10_12, <error> [template = constants.%.2]
-// CHECK:STDOUT:   %a.var: ref [<error>; 1] = var a
-// CHECK:STDOUT:   %a: ref [<error>; 1] = bind_name a, %a.var
+// CHECK:STDOUT:   %.loc10_13: type = array_type %.loc10_12, <error> [template = <error>]
+// CHECK:STDOUT:   %a.var: ref <error> = var a
+// CHECK:STDOUT:   %a: ref <error> = bind_name a, %a.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/check/testdata/basics/builtin_insts.carbon

@@ -16,7 +16,7 @@
 // CHECK:STDOUT:   functions:       {}
 // CHECK:STDOUT:   classes:         {}
 // CHECK:STDOUT:   types:
-// CHECK:STDOUT:     type0:           {inst: instNamespaceType, value_rep: {kind: copy, type: type0}}
+// CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
 // CHECK:STDOUT:   type_blocks:     {}
 // CHECK:STDOUT:   insts:
 // CHECK:STDOUT:     instTypeType:    {kind: CrossRef, arg0: ir0, arg1: instTypeType, type: typeTypeType}

+ 4 - 4
toolchain/check/testdata/basics/multifile_raw_and_textual_ir.carbon

@@ -29,8 +29,8 @@ fn B() {}
 // CHECK:STDOUT:     function0:       {name: name0, enclosing_scope: name_scope0, param_refs: empty, body: [block2]}
 // CHECK:STDOUT:   classes:         {}
 // CHECK:STDOUT:   types:
-// CHECK:STDOUT:     type0:           {inst: instNamespaceType, value_rep: {kind: copy, type: type0}}
-// CHECK:STDOUT:     type1:           {inst: instFunctionType, value_rep: {kind: copy, type: type1}}
+// CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
+// CHECK:STDOUT:     type1:           {constant: template instFunctionType, value_rep: {kind: copy, type: type1}}
 // CHECK:STDOUT:   type_blocks:     {}
 // CHECK:STDOUT:   insts:
 // CHECK:STDOUT:     inst+0:          {kind: Namespace, arg0: name_scope0, arg1: inst<invalid>, type: type0}
@@ -73,8 +73,8 @@ fn B() {}
 // CHECK:STDOUT:     function0:       {name: name0, enclosing_scope: name_scope0, param_refs: empty, body: [block2]}
 // CHECK:STDOUT:   classes:         {}
 // CHECK:STDOUT:   types:
-// CHECK:STDOUT:     type0:           {inst: instNamespaceType, value_rep: {kind: copy, type: type0}}
-// CHECK:STDOUT:     type1:           {inst: instFunctionType, value_rep: {kind: copy, type: type1}}
+// CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
+// CHECK:STDOUT:     type1:           {constant: template instFunctionType, value_rep: {kind: copy, type: type1}}
 // CHECK:STDOUT:   type_blocks:     {}
 // CHECK:STDOUT:   insts:
 // CHECK:STDOUT:     inst+0:          {kind: Namespace, arg0: name_scope0, arg1: inst<invalid>, type: type0}

+ 4 - 4
toolchain/check/testdata/basics/multifile_raw_ir.carbon

@@ -29,8 +29,8 @@ fn B() {}
 // CHECK:STDOUT:     function0:       {name: name0, enclosing_scope: name_scope0, param_refs: empty, body: [block2]}
 // CHECK:STDOUT:   classes:         {}
 // CHECK:STDOUT:   types:
-// CHECK:STDOUT:     type0:           {inst: instNamespaceType, value_rep: {kind: copy, type: type0}}
-// CHECK:STDOUT:     type1:           {inst: instFunctionType, value_rep: {kind: copy, type: type1}}
+// CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
+// CHECK:STDOUT:     type1:           {constant: template instFunctionType, value_rep: {kind: copy, type: type1}}
 // CHECK:STDOUT:   type_blocks:     {}
 // CHECK:STDOUT:   insts:
 // CHECK:STDOUT:     inst+0:          {kind: Namespace, arg0: name_scope0, arg1: inst<invalid>, type: type0}
@@ -60,8 +60,8 @@ fn B() {}
 // CHECK:STDOUT:     function0:       {name: name0, enclosing_scope: name_scope0, param_refs: empty, body: [block2]}
 // CHECK:STDOUT:   classes:         {}
 // CHECK:STDOUT:   types:
-// CHECK:STDOUT:     type0:           {inst: instNamespaceType, value_rep: {kind: copy, type: type0}}
-// CHECK:STDOUT:     type1:           {inst: instFunctionType, value_rep: {kind: copy, type: type1}}
+// CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
+// CHECK:STDOUT:     type1:           {constant: template instFunctionType, value_rep: {kind: copy, type: type1}}
 // CHECK:STDOUT:   type_blocks:     {}
 // CHECK:STDOUT:   insts:
 // CHECK:STDOUT:     inst+0:          {kind: Namespace, arg0: name_scope0, arg1: inst<invalid>, type: type0}

+ 7 - 7
toolchain/check/testdata/basics/raw_and_textual_ir.carbon

@@ -24,13 +24,13 @@ fn Foo(n: i32) -> (i32, i32, f64) {
 // CHECK:STDOUT:     function0:       {name: name0, enclosing_scope: name_scope0, param_refs: block2, return_type: type4, return_slot: inst+7, body: [block5]}
 // CHECK:STDOUT:   classes:         {}
 // CHECK:STDOUT:   types:
-// CHECK:STDOUT:     type0:           {inst: instNamespaceType, value_rep: {kind: copy, type: type0}}
-// CHECK:STDOUT:     type1:           {inst: instIntType, value_rep: {kind: copy, type: type1}}
-// CHECK:STDOUT:     type2:           {inst: inst+3, value_rep: {kind: unknown, type: type<invalid>}}
-// CHECK:STDOUT:     type3:           {inst: instFloatType, value_rep: {kind: copy, type: type3}}
-// CHECK:STDOUT:     type4:           {inst: inst+5, value_rep: {kind: pointer, type: type5}}
-// CHECK:STDOUT:     type5:           {inst: inst+8, value_rep: {kind: copy, type: type5}}
-// CHECK:STDOUT:     type6:           {inst: instFunctionType, value_rep: {kind: copy, type: type6}}
+// CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
+// CHECK:STDOUT:     type1:           {constant: template instIntType, value_rep: {kind: copy, type: type1}}
+// CHECK:STDOUT:     type2:           {constant: template inst+3, value_rep: {kind: unknown, type: type<invalid>}}
+// CHECK:STDOUT:     type3:           {constant: template instFloatType, value_rep: {kind: copy, type: type3}}
+// CHECK:STDOUT:     type4:           {constant: template inst+5, value_rep: {kind: pointer, type: type5}}
+// CHECK:STDOUT:     type5:           {constant: template inst+8, value_rep: {kind: copy, type: type5}}
+// CHECK:STDOUT:     type6:           {constant: template instFunctionType, value_rep: {kind: copy, type: type6}}
 // CHECK:STDOUT:   type_blocks:
 // CHECK:STDOUT:     typeBlock0:
 // CHECK:STDOUT:       0:               typeTypeType

+ 7 - 7
toolchain/check/testdata/basics/raw_ir.carbon

@@ -24,13 +24,13 @@ fn Foo(n: i32) -> (i32, i32, f64) {
 // CHECK:STDOUT:     function0:       {name: name0, enclosing_scope: name_scope0, param_refs: block2, return_type: type4, return_slot: inst+7, body: [block5]}
 // CHECK:STDOUT:   classes:         {}
 // CHECK:STDOUT:   types:
-// CHECK:STDOUT:     type0:           {inst: instNamespaceType, value_rep: {kind: copy, type: type0}}
-// CHECK:STDOUT:     type1:           {inst: instIntType, value_rep: {kind: copy, type: type1}}
-// CHECK:STDOUT:     type2:           {inst: inst+3, value_rep: {kind: unknown, type: type<invalid>}}
-// CHECK:STDOUT:     type3:           {inst: instFloatType, value_rep: {kind: copy, type: type3}}
-// CHECK:STDOUT:     type4:           {inst: inst+5, value_rep: {kind: pointer, type: type5}}
-// CHECK:STDOUT:     type5:           {inst: inst+8, value_rep: {kind: copy, type: type5}}
-// CHECK:STDOUT:     type6:           {inst: instFunctionType, value_rep: {kind: copy, type: type6}}
+// CHECK:STDOUT:     type0:           {constant: template instNamespaceType, value_rep: {kind: copy, type: type0}}
+// CHECK:STDOUT:     type1:           {constant: template instIntType, value_rep: {kind: copy, type: type1}}
+// CHECK:STDOUT:     type2:           {constant: template inst+3, value_rep: {kind: unknown, type: type<invalid>}}
+// CHECK:STDOUT:     type3:           {constant: template instFloatType, value_rep: {kind: copy, type: type3}}
+// CHECK:STDOUT:     type4:           {constant: template inst+5, value_rep: {kind: pointer, type: type5}}
+// CHECK:STDOUT:     type5:           {constant: template inst+8, value_rep: {kind: copy, type: type5}}
+// CHECK:STDOUT:     type6:           {constant: template instFunctionType, value_rep: {kind: copy, type: type6}}
 // CHECK:STDOUT:   type_blocks:
 // CHECK:STDOUT:     typeBlock0:
 // CHECK:STDOUT:       0:               typeTypeType

+ 33 - 41
toolchain/check/testdata/class/fail_base_bad_type.carbon

@@ -126,45 +126,37 @@ fn AccessMemberWithInvalidBaseFinal_NoMember(p: DeriveFromFinal*) -> i32 {
 // CHECK:STDOUT:   %.2: type = unbound_element_type Final, i32 [template]
 // CHECK:STDOUT:   %.3: type = struct_type {.a: i32} [template]
 // CHECK:STDOUT:   %DeriveFromError: type = class_type @DeriveFromError [template]
-// CHECK:STDOUT:   %.4: type = unbound_element_type DeriveFromError, <error> [template]
-// CHECK:STDOUT:   %.5: type = struct_type {.base: <error>} [template]
-// CHECK:STDOUT:   %.6: type = ptr_type DeriveFromError [template]
-// CHECK:STDOUT:   %.7: type = ptr_type {.base: <error>} [template]
+// CHECK:STDOUT:   %.4: type = ptr_type DeriveFromError [template]
 // CHECK:STDOUT:   %DeriveFromNonType: type = class_type @DeriveFromNonType [template]
-// CHECK:STDOUT:   %.8: i32 = int_literal 32 [template]
-// CHECK:STDOUT:   %.9: type = unbound_element_type DeriveFromNonType, <error> [template]
-// CHECK:STDOUT:   %.10: type = ptr_type DeriveFromNonType [template]
+// CHECK:STDOUT:   %.5: i32 = int_literal 32 [template]
+// CHECK:STDOUT:   %.6: type = ptr_type DeriveFromNonType [template]
 // CHECK:STDOUT:   %DeriveFromi32: type = class_type @DeriveFromi32 [template]
-// CHECK:STDOUT:   %.11: type = unbound_element_type DeriveFromi32, <error> [template]
-// CHECK:STDOUT:   %.12: type = ptr_type DeriveFromi32 [template]
-// CHECK:STDOUT:   %.13: type = ptr_type i32 [template]
+// CHECK:STDOUT:   %.7: type = ptr_type DeriveFromi32 [template]
+// CHECK:STDOUT:   %.8: type = ptr_type i32 [template]
 // CHECK:STDOUT:   %DeriveFromTuple: type = class_type @DeriveFromTuple [template]
-// CHECK:STDOUT:   %.14: type = tuple_type (type) [template]
-// CHECK:STDOUT:   %.15: type = tuple_type (Base) [template]
-// CHECK:STDOUT:   %.16: type = tuple_type () [template]
-// CHECK:STDOUT:   %.17: type = ptr_type {} [template]
-// CHECK:STDOUT:   %.18: type = tuple_type ({}*) [template]
-// CHECK:STDOUT:   %.19: type = unbound_element_type DeriveFromTuple, <error> [template]
-// CHECK:STDOUT:   %.20: type = ptr_type DeriveFromTuple [template]
-// CHECK:STDOUT:   %.21: type = ptr_type (Base,) [template]
+// CHECK:STDOUT:   %.9: type = tuple_type (type) [template]
+// CHECK:STDOUT:   %.10: type = tuple_type (Base) [template]
+// CHECK:STDOUT:   %.11: type = tuple_type () [template]
+// CHECK:STDOUT:   %.12: type = ptr_type {} [template]
+// CHECK:STDOUT:   %.13: type = tuple_type ({}*) [template]
+// CHECK:STDOUT:   %.14: type = ptr_type DeriveFromTuple [template]
+// CHECK:STDOUT:   %.15: type = ptr_type (Base,) [template]
 // CHECK:STDOUT:   %DeriveFromStruct: type = class_type @DeriveFromStruct [template]
-// CHECK:STDOUT:   %.22: type = struct_type {.a: i32, .b: i32} [template]
-// CHECK:STDOUT:   %.23: type = ptr_type {.a: i32, .b: i32} [template]
-// CHECK:STDOUT:   %.24: type = unbound_element_type DeriveFromStruct, <error> [template]
-// CHECK:STDOUT:   %.25: type = ptr_type DeriveFromStruct [template]
+// CHECK:STDOUT:   %.16: type = struct_type {.a: i32, .b: i32} [template]
+// CHECK:STDOUT:   %.17: type = ptr_type {.a: i32, .b: i32} [template]
+// CHECK:STDOUT:   %.18: type = ptr_type DeriveFromStruct [template]
 // CHECK:STDOUT:   %Incomplete: type = class_type @Incomplete [template]
 // CHECK:STDOUT:   %DeriveFromIncomplete: type = class_type @DeriveFromIncomplete [template]
-// CHECK:STDOUT:   %.26: type = unbound_element_type DeriveFromIncomplete, <error> [template]
-// CHECK:STDOUT:   %.27: type = ptr_type DeriveFromIncomplete [template]
-// CHECK:STDOUT:   %.28: type = ptr_type Incomplete [template]
+// CHECK:STDOUT:   %.19: type = ptr_type DeriveFromIncomplete [template]
+// CHECK:STDOUT:   %.20: type = ptr_type Incomplete [template]
 // CHECK:STDOUT:   %DeriveFromFinal: type = class_type @DeriveFromFinal [template]
-// CHECK:STDOUT:   %.29: type = ptr_type {.a: i32} [template]
-// CHECK:STDOUT:   %.30: type = unbound_element_type DeriveFromFinal, Final [template]
-// CHECK:STDOUT:   %.31: type = struct_type {.base: Final} [template]
-// CHECK:STDOUT:   %.32: type = ptr_type DeriveFromFinal [template]
-// CHECK:STDOUT:   %.33: type = ptr_type Final [template]
-// CHECK:STDOUT:   %.34: type = struct_type {.base: {.a: i32}*} [template]
-// CHECK:STDOUT:   %.35: type = ptr_type {.base: Final} [template]
+// CHECK:STDOUT:   %.21: type = ptr_type {.a: i32} [template]
+// CHECK:STDOUT:   %.22: type = unbound_element_type DeriveFromFinal, Final [template]
+// CHECK:STDOUT:   %.23: type = struct_type {.base: Final} [template]
+// CHECK:STDOUT:   %.24: type = ptr_type DeriveFromFinal [template]
+// CHECK:STDOUT:   %.25: type = ptr_type Final [template]
+// CHECK:STDOUT:   %.26: type = struct_type {.base: {.a: i32}*} [template]
+// CHECK:STDOUT:   %.27: type = ptr_type {.base: Final} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -208,7 +200,7 @@ fn AccessMemberWithInvalidBaseFinal_NoMember(p: DeriveFromFinal*) -> i32 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromError {
 // CHECK:STDOUT:   %error.ref: <error> = name_ref error, <error> [template = <error>]
-// CHECK:STDOUT:   %.loc16: <unbound element of class DeriveFromError> = base_decl <error>, element0 [template]
+// CHECK:STDOUT:   %.loc16: <error> = base_decl <error>, element0 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc16
@@ -216,8 +208,8 @@ fn AccessMemberWithInvalidBaseFinal_NoMember(p: DeriveFromFinal*) -> i32 {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromNonType {
-// CHECK:STDOUT:   %.loc26_16: i32 = int_literal 32 [template = constants.%.8]
-// CHECK:STDOUT:   %.loc26_18: <unbound element of class DeriveFromNonType> = base_decl <error>, element0 [template]
+// CHECK:STDOUT:   %.loc26_16: i32 = int_literal 32 [template = constants.%.5]
+// CHECK:STDOUT:   %.loc26_18: <error> = base_decl <error>, element0 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc26_18
@@ -225,7 +217,7 @@ fn AccessMemberWithInvalidBaseFinal_NoMember(p: DeriveFromFinal*) -> i32 {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromi32 {
-// CHECK:STDOUT:   %.loc35: <unbound element of class DeriveFromi32> = base_decl <error>, element0 [template]
+// CHECK:STDOUT:   %.loc35: <error> = base_decl <error>, element0 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc35
@@ -235,8 +227,8 @@ fn AccessMemberWithInvalidBaseFinal_NoMember(p: DeriveFromFinal*) -> i32 {
 // CHECK:STDOUT: class @DeriveFromTuple {
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, constants.%Base [template = constants.%Base]
 // CHECK:STDOUT:   %.loc51_22.1: (type,) = tuple_literal (%Base.ref)
-// CHECK:STDOUT:   %.loc51_22.2: type = converted %.loc51_22.1, constants.%.15 [template = constants.%.15]
-// CHECK:STDOUT:   %.loc51_23: <unbound element of class DeriveFromTuple> = base_decl <error>, element0 [template]
+// CHECK:STDOUT:   %.loc51_22.2: type = converted %.loc51_22.1, constants.%.10 [template = constants.%.10]
+// CHECK:STDOUT:   %.loc51_23: <error> = base_decl <error>, element0 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc51_23
@@ -244,8 +236,8 @@ fn AccessMemberWithInvalidBaseFinal_NoMember(p: DeriveFromFinal*) -> i32 {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromStruct {
-// CHECK:STDOUT:   %.loc67_33: type = struct_type {.a: i32, .b: i32} [template = constants.%.22]
-// CHECK:STDOUT:   %.loc67_34: <unbound element of class DeriveFromStruct> = base_decl <error>, element0 [template]
+// CHECK:STDOUT:   %.loc67_33: type = struct_type {.a: i32, .b: i32} [template = constants.%.16]
+// CHECK:STDOUT:   %.loc67_34: <error> = base_decl <error>, element0 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc67_34
@@ -256,7 +248,7 @@ fn AccessMemberWithInvalidBaseFinal_NoMember(p: DeriveFromFinal*) -> i32 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DeriveFromIncomplete {
 // CHECK:STDOUT:   %Incomplete.ref: type = name_ref Incomplete, constants.%Incomplete [template = constants.%Incomplete]
-// CHECK:STDOUT:   %.loc87: <unbound element of class DeriveFromIncomplete> = base_decl <error>, element0 [template]
+// CHECK:STDOUT:   %.loc87: <error> = base_decl <error>, element0 [template]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .base = %.loc87

+ 30 - 0
toolchain/check/testdata/eval/fail_symbolic.carbon

@@ -0,0 +1,30 @@
+// 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
+
+// TODO: This should work.
+fn G(N:! i32) {
+  // CHECK:STDERR: fail_symbolic.carbon:[[@LINE+3]]:16: ERROR: Semantics TODO: `symbolic array bound`.
+  // CHECK:STDERR:   var k: [i32; N];
+  // CHECK:STDERR:                ^
+  var k: [i32; N];
+}
+
+// CHECK:STDOUT: --- fail_symbolic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.G = %G} [template]
+// CHECK:STDOUT:   %G: <function> = fn_decl @G [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G(%N: i32) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %N.ref: i32 = name_ref N, %N [symbolic = %N]
+// CHECK:STDOUT:   %.loc12: type = array_type %N.ref, i32 [template = <error>]
+// CHECK:STDOUT:   %k.var: ref <error> = var k
+// CHECK:STDOUT:   %k: ref <error> = bind_name k, %k.var
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 55 - 0
toolchain/check/testdata/eval/symbolic.carbon

@@ -0,0 +1,55 @@
+// 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
+
+// Check that we propagate the `symbolic` tag through evaluations.
+fn F(T:! type) {
+  var u: (T*, const T);
+  var v: {.a: T};
+  var w: [T; 5];
+}
+
+// CHECK:STDOUT: --- symbolic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: type = ptr_type T [symbolic]
+// CHECK:STDOUT:   %.2: type = const_type T [symbolic]
+// CHECK:STDOUT:   %.3: type = tuple_type (type, type) [template]
+// CHECK:STDOUT:   %.4: type = tuple_type (T*, const T) [symbolic]
+// CHECK:STDOUT:   %.5: type = tuple_type (T*, T) [symbolic]
+// CHECK:STDOUT:   %.6: type = ptr_type (T*, T) [symbolic]
+// CHECK:STDOUT:   %.7: type = struct_type {.a: T} [symbolic]
+// CHECK:STDOUT:   %.8: i32 = int_literal 5 [template]
+// CHECK:STDOUT:   %.9: type = array_type %.8, T [symbolic]
+// CHECK:STDOUT:   %.10: type = ptr_type [T; 5] [symbolic]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace {.F = %F} [template]
+// CHECK:STDOUT:   %F: <function> = fn_decl @F [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%T: type) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %T.ref.loc9_11: type = name_ref T, %T [symbolic = %T]
+// CHECK:STDOUT:   %.loc9_12: type = ptr_type T [symbolic = constants.%.1]
+// CHECK:STDOUT:   %T.ref.loc9_21: type = name_ref T, %T [symbolic = %T]
+// CHECK:STDOUT:   %.loc9_15: type = const_type T [symbolic = constants.%.2]
+// CHECK:STDOUT:   %.loc9_22.1: (type, type) = tuple_literal (%.loc9_12, %.loc9_15)
+// CHECK:STDOUT:   %.loc9_22.2: type = converted %.loc9_22.1, constants.%.4 [symbolic = constants.%.4]
+// CHECK:STDOUT:   %u.var: ref (T*, const T) = var u
+// CHECK:STDOUT:   %u: ref (T*, const T) = bind_name u, %u.var
+// CHECK:STDOUT:   %T.ref.loc10: type = name_ref T, %T [symbolic = %T]
+// CHECK:STDOUT:   %.loc10: type = struct_type {.a: T} [symbolic = constants.%.7]
+// CHECK:STDOUT:   %v.var: ref {.a: T} = var v
+// CHECK:STDOUT:   %v: ref {.a: T} = bind_name v, %v.var
+// CHECK:STDOUT:   %T.ref.loc11: type = name_ref T, %T [symbolic = %T]
+// CHECK:STDOUT:   %.loc11_14: i32 = int_literal 5 [template = constants.%.8]
+// CHECK:STDOUT:   %.loc11_15: type = array_type %.loc11_14, T [symbolic = constants.%.9]
+// CHECK:STDOUT:   %w.var: ref [T; 5] = var w
+// CHECK:STDOUT:   %w: ref [T; 5] = bind_name w, %w.var
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/function/generic/fail_type_param_mismatch.carbon

@@ -15,7 +15,7 @@ fn F(T:! type, U:! type) {
 // CHECK:STDOUT: --- fail_type_param_mismatch.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %.1: type = ptr_type T [template]
+// CHECK:STDOUT:   %.1: type = ptr_type T [symbolic]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -26,7 +26,7 @@ fn F(T:! type, U:! type) {
 // CHECK:STDOUT: fn @F(%T: type, %U: type) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %T.ref: type = name_ref T, %T [symbolic = %T]
-// CHECK:STDOUT:   %.loc8: type = ptr_type T [template = constants.%.1]
+// CHECK:STDOUT:   %.loc8: type = ptr_type T [symbolic = constants.%.1]
 // CHECK:STDOUT:   %p.var: ref T* = var p
 // CHECK:STDOUT:   %p: ref T* = bind_name p, %p.var
 // CHECK:STDOUT:   %U.ref: type = name_ref U, %U [symbolic = %U]

+ 2 - 2
toolchain/check/testdata/function/generic/type_param.carbon

@@ -12,7 +12,7 @@ fn F(T:! type) {
 // CHECK:STDOUT: --- type_param.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %.1: type = ptr_type T [template]
+// CHECK:STDOUT:   %.1: type = ptr_type T [symbolic]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -23,7 +23,7 @@ fn F(T:! type) {
 // CHECK:STDOUT: fn @F(%T: type) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %T.ref.loc8: type = name_ref T, %T [symbolic = %T]
-// CHECK:STDOUT:   %.loc8: type = ptr_type T [template = constants.%.1]
+// CHECK:STDOUT:   %.loc8: type = ptr_type T [symbolic = constants.%.1]
 // CHECK:STDOUT:   %p.var: ref T* = var p
 // CHECK:STDOUT:   %p: ref T* = bind_name p, %p.var
 // CHECK:STDOUT:   %T.ref.loc9: type = name_ref T, %T [symbolic = %T]

+ 1 - 3
toolchain/check/testdata/if_expr/fail_not_in_function.carbon

@@ -40,8 +40,6 @@ class C {
 // CHECK:STDOUT:   %.2: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.3: i32 = int_literal 0 [template]
 // CHECK:STDOUT:   %C: type = class_type @C [template]
-// CHECK:STDOUT:   %.4: type = unbound_element_type C, <error> [template]
-// CHECK:STDOUT:   %.5: type = struct_type {.n: <error>} [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -55,6 +53,6 @@ class C {
 // CHECK:STDOUT:   if %.loc33 br !if.expr.then else br !if.expr.else
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .n = <unexpected instref inst+22>
+// CHECK:STDOUT:   .n = <unexpected instref inst+21>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/let/generic.carbon

@@ -13,7 +13,7 @@ fn F() {
 // CHECK:STDOUT: --- generic.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %.1: type = ptr_type T [template]
+// CHECK:STDOUT:   %.1: type = ptr_type T [symbolic]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -25,7 +25,7 @@ fn F() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T, i32 [symbolic]
 // CHECK:STDOUT:   %T.ref.loc9: type = name_ref T, %T [symbolic = %T]
-// CHECK:STDOUT:   %.loc9: type = ptr_type T [template = constants.%.1]
+// CHECK:STDOUT:   %.loc9: type = ptr_type T [symbolic = constants.%.1]
 // CHECK:STDOUT:   %p.var: ref T* = var p
 // CHECK:STDOUT:   %p: ref T* = bind_name p, %p.var
 // CHECK:STDOUT:   %T.ref.loc10: type = name_ref T, %T [symbolic = %T]

+ 3 - 8
toolchain/check/testdata/pointer/fail_address_of_error.carbon

@@ -20,11 +20,6 @@ fn Test() {
 
 // CHECK:STDOUT: --- fail_address_of_error.carbon
 // CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %.1: type = ptr_type <error> [template]
-// CHECK:STDOUT:   %.2: type = ptr_type <error>* [template]
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.Test = %Test} [template]
 // CHECK:STDOUT:   %Test: <function> = fn_decl @Test [template]
@@ -33,10 +28,10 @@ fn Test() {
 // CHECK:STDOUT: fn @Test() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %undeclared.ref.loc11: <error> = name_ref undeclared, <error> [template = <error>]
-// CHECK:STDOUT:   %.loc11: <error>* = addr_of %undeclared.ref.loc11 [template = <error>]
+// CHECK:STDOUT:   %.loc11: <error> = addr_of %undeclared.ref.loc11 [template = <error>]
 // CHECK:STDOUT:   %undeclared.ref.loc18: <error> = name_ref undeclared, <error> [template = <error>]
-// CHECK:STDOUT:   %.loc18_5: <error>* = addr_of %undeclared.ref.loc18 [template = <error>]
-// CHECK:STDOUT:   %.loc18_3: <error>** = addr_of <error> [template = <error>]
+// CHECK:STDOUT:   %.loc18_5: <error> = addr_of %undeclared.ref.loc18 [template = <error>]
+// CHECK:STDOUT:   %.loc18_3: <error> = addr_of <error> [template = <error>]
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 3 - 7
toolchain/check/testdata/struct/fail_nested_incomplete.carbon

@@ -14,9 +14,6 @@ class Incomplete;
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~
 var s: {.a: Incomplete};
 
-// CHECK:STDERR: fail_nested_incomplete.carbon:[[@LINE+3]]:1: ERROR: Cannot implicitly convert from `<error>*` to `Incomplete*`.
-// CHECK:STDERR: var p: Incomplete* = &s.a;
-// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~
 var p: Incomplete* = &s.a;
 
 // CHECK:STDOUT: --- fail_nested_incomplete.carbon
@@ -25,7 +22,6 @@ var p: Incomplete* = &s.a;
 // CHECK:STDOUT:   %Incomplete: type = class_type @Incomplete [template]
 // CHECK:STDOUT:   %.1: type = struct_type {.a: Incomplete} [template]
 // CHECK:STDOUT:   %.2: type = ptr_type Incomplete [template]
-// CHECK:STDOUT:   %.3: type = ptr_type <error> [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -35,12 +31,12 @@ var p: Incomplete* = &s.a;
 // CHECK:STDOUT:   %.loc15: type = struct_type {.a: Incomplete} [template = constants.%.1]
 // CHECK:STDOUT:   %s.var: ref <error> = var s
 // CHECK:STDOUT:   %s: ref <error> = bind_name s, %s.var
-// CHECK:STDOUT:   %Incomplete.ref.loc20: type = name_ref Incomplete, constants.%Incomplete [template = constants.%Incomplete]
-// CHECK:STDOUT:   %.loc20_18: type = ptr_type Incomplete [template = constants.%.2]
+// CHECK:STDOUT:   %Incomplete.ref.loc17: type = name_ref Incomplete, constants.%Incomplete [template = constants.%Incomplete]
+// CHECK:STDOUT:   %.loc17_18: type = ptr_type Incomplete [template = constants.%.2]
 // CHECK:STDOUT:   %p.var: ref Incomplete* = var p
 // CHECK:STDOUT:   %p: ref Incomplete* = bind_name p, %p.var
 // CHECK:STDOUT:   %s.ref: ref <error> = name_ref s, %s
-// CHECK:STDOUT:   %.loc20_22: <error>* = addr_of <error> [template = <error>]
+// CHECK:STDOUT:   %.loc17_22: <error> = addr_of <error> [template = <error>]
 // CHECK:STDOUT:   assign %p.var, <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 4 - 8
toolchain/check/testdata/tuples/fail_nested_incomplete.carbon

@@ -14,9 +14,6 @@ class Incomplete;
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~
 var t: (i32, Incomplete);
 
-// CHECK:STDERR: fail_nested_incomplete.carbon:[[@LINE+3]]:1: ERROR: Cannot implicitly convert from `<error>*` to `Incomplete*`.
-// CHECK:STDERR: var p: Incomplete* = &t[1];
-// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~
 var p: Incomplete* = &t[1];
 
 // CHECK:STDOUT: --- fail_nested_incomplete.carbon
@@ -27,7 +24,6 @@ var p: Incomplete* = &t[1];
 // CHECK:STDOUT:   %.2: type = tuple_type (i32, Incomplete) [template]
 // CHECK:STDOUT:   %.3: type = ptr_type Incomplete [template]
 // CHECK:STDOUT:   %.4: i32 = int_literal 1 [template]
-// CHECK:STDOUT:   %.5: type = ptr_type <error> [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -38,13 +34,13 @@ var p: Incomplete* = &t[1];
 // CHECK:STDOUT:   %.loc15_24.2: type = converted %.loc15_24.1, constants.%.2 [template = constants.%.2]
 // CHECK:STDOUT:   %t.var: ref <error> = var t
 // CHECK:STDOUT:   %t: ref <error> = bind_name t, %t.var
-// CHECK:STDOUT:   %Incomplete.ref.loc20: type = name_ref Incomplete, constants.%Incomplete [template = constants.%Incomplete]
-// CHECK:STDOUT:   %.loc20_18: type = ptr_type Incomplete [template = constants.%.3]
+// CHECK:STDOUT:   %Incomplete.ref.loc17: type = name_ref Incomplete, constants.%Incomplete [template = constants.%Incomplete]
+// CHECK:STDOUT:   %.loc17_18: type = ptr_type Incomplete [template = constants.%.3]
 // CHECK:STDOUT:   %p.var: ref Incomplete* = var p
 // CHECK:STDOUT:   %p: ref Incomplete* = bind_name p, %p.var
 // CHECK:STDOUT:   %t.ref: ref <error> = name_ref t, %t
-// CHECK:STDOUT:   %.loc20_25: i32 = int_literal 1 [template = constants.%.4]
-// CHECK:STDOUT:   %.loc20_22: <error>* = addr_of <error> [template = <error>]
+// CHECK:STDOUT:   %.loc17_25: i32 = int_literal 1 [template = constants.%.4]
+// CHECK:STDOUT:   %.loc17_22: <error> = addr_of <error> [template = <error>]
 // CHECK:STDOUT:   assign %p.var, <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 3 - 5
toolchain/check/testdata/tuples/fail_value_as_type.carbon

@@ -14,15 +14,13 @@ var x: (1, );
 // CHECK:STDOUT: constants {
 // CHECK:STDOUT:   %.1: i32 = int_literal 1 [template]
 // CHECK:STDOUT:   %.2: type = tuple_type (i32) [template]
-// CHECK:STDOUT:   %.3: type = tuple_type (<error>) [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace {.x = %x} [template]
 // CHECK:STDOUT:   %.loc10_9: i32 = int_literal 1 [template = constants.%.1]
-// CHECK:STDOUT:   %.loc10_12.1: (i32,) = tuple_literal (%.loc10_9)
-// CHECK:STDOUT:   %.loc10_12.2: type = converted %.loc10_12.1, constants.%.3 [template = constants.%.3]
-// CHECK:STDOUT:   %x.var: ref (<error>,) = var x
-// CHECK:STDOUT:   %x: ref (<error>,) = bind_name x, %x.var
+// CHECK:STDOUT:   %.loc10_12: (i32,) = tuple_literal (%.loc10_9)
+// CHECK:STDOUT:   %x.var: ref <error> = var x
+// CHECK:STDOUT:   %x: ref <error> = bind_name x, %x.var
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/lower/file_context.cpp

@@ -34,7 +34,7 @@ auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
   // used.
   types_.resize(sem_ir_->types().size());
   for (auto type_id : sem_ir_->complete_types()) {
-    types_[type_id.index] = BuildType(sem_ir_->types().Get(type_id).inst_id);
+    types_[type_id.index] = BuildType(sem_ir_->types().GetInstId(type_id));
   }
 
   // Lower function declarations.

+ 1 - 1
toolchain/sem_ir/file.cpp

@@ -57,7 +57,7 @@ auto ValueRepr::Print(llvm::raw_ostream& out) const -> void {
 }
 
 auto TypeInfo::Print(llvm::raw_ostream& out) const -> void {
-  out << "{inst: " << inst_id << ", value_rep: " << value_repr << "}";
+  out << "{constant: " << constant_id << ", value_rep: " << value_repr << "}";
 }
 
 File::File(SharedValueStores& value_stores)

+ 2 - 2
toolchain/sem_ir/type_info.h

@@ -66,8 +66,8 @@ struct ValueRepr : public Printable<ValueRepr> {
 struct TypeInfo : public Printable<TypeInfo> {
   auto Print(llvm::raw_ostream& out) const -> void;
 
-  // The instruction that defines this type.
-  InstId inst_id;
+  // The constant type value that defines this type.
+  ConstantId constant_id;
   // The value representation for this type. Will be `Unknown` if the type is
   // not complete.
   ValueRepr value_repr = ValueRepr();

+ 13 - 7
toolchain/sem_ir/value_stores.h

@@ -188,19 +188,25 @@ class TypeStore : public ValueStore<TypeId> {
  public:
   explicit TypeStore(InstStore* insts) : insts_(insts) {}
 
-  // Returns the ID of the instruction used to define the specified type.
-  auto GetInstId(TypeId type_id) const -> InstId {
+  // Returns the ID of the constant used to define the specified type.
+  auto GetConstantId(TypeId type_id) const -> ConstantId {
     if (type_id == TypeId::TypeType) {
-      return InstId::BuiltinTypeType;
+      return ConstantId::ForTemplateConstant(InstId::BuiltinTypeType);
     } else if (type_id == TypeId::Error) {
-      return InstId::BuiltinError;
+      return ConstantId::Error;
     } else if (!type_id.is_valid()) {
-      return InstId::Invalid;
+      // TODO: Can we CHECK-fail on this?
+      return ConstantId::NotConstant;
     } else {
-      return Get(type_id).inst_id;
+      return Get(type_id).constant_id;
     }
   }
 
+  // Returns the ID of the instruction used to define the specified type.
+  auto GetInstId(TypeId type_id) const -> InstId {
+    return GetConstantId(type_id).inst_id();
+  }
+
   // Returns the instruction used to define the specified type.
   auto GetAsInst(TypeId type_id) const -> Inst {
     return insts_->Get(GetInstId(type_id));
@@ -214,7 +220,7 @@ class TypeStore : public ValueStore<TypeId> {
       return GetAsInst(type_id).As<InstT>();
     } else {
       // The type is not a builtin, so no need to check for special values.
-      return insts_->Get(Get(type_id).inst_id).As<InstT>();
+      return insts_->Get(Get(type_id).constant_id.inst_id()).As<InstT>();
     }
   }
 

+ 2 - 2
toolchain/sem_ir/yaml_test.cpp

@@ -43,11 +43,11 @@ TEST(SemIRTest, YAML) {
   auto int_id = Yaml::Scalar(MatchesRegex(R"(int\d+)"));
   auto inst_id = Yaml::Scalar(MatchesRegex(R"(inst\+\d+)"));
   auto constant_id =
-      Yaml::Scalar(MatchesRegex(R"((template|symbolic) inst\+\d+)"));
+      Yaml::Scalar(MatchesRegex(R"((template|symbolic) inst(\w+|\+\d+))"));
   auto inst_builtin = Yaml::Scalar(MatchesRegex(R"(inst\w+)"));
   auto type_id = Yaml::Scalar(MatchesRegex(R"(type\d+)"));
   auto type_builtin = Pair(
-      type_id, Yaml::Mapping(ElementsAre(Pair("inst", inst_builtin),
+      type_id, Yaml::Mapping(ElementsAre(Pair("constant", constant_id),
                                          Pair("value_rep", Yaml::Mapping(_)))));
 
   auto file = Yaml::Mapping(ElementsAre(