Просмотр исходного кода

Basic support for incomplete types. (#3302)

Incomplete types may be nested within other types; for example, a tuple
type might have an incomplete type as an element. Handle such cases by
walking through nested incomplete types when completing a type. This is
done non-recursively in case a very complex type is formed.

Types are generally no longer completed at the point where they're
formed. Instead, we attempt to complete a type when it is used in a
context that requires a complete type, and diagnose if the type cannot
be completed at that point. This will be necessary for classes, which
can become complete after their first use, and helps tease out bugs
where a type completeness check is missing.
Richard Smith 2 лет назад
Родитель
Сommit
69353ed271
57 измененных файлов с 1120 добавлено и 576 удалено
  1. 387 211
      toolchain/check/context.cpp
  2. 18 7
      toolchain/check/context.h
  3. 23 2
      toolchain/check/convert.cpp
  4. 35 10
      toolchain/check/handle_function.cpp
  5. 1 1
      toolchain/check/handle_index.cpp
  6. 4 5
      toolchain/check/handle_literal.cpp
  7. 1 1
      toolchain/check/handle_namespace.cpp
  8. 26 0
      toolchain/check/handle_pattern_binding.cpp
  9. 4 5
      toolchain/check/testdata/array/array_in_place.carbon
  10. 3 4
      toolchain/check/testdata/array/assign_var.carbon
  11. 12 14
      toolchain/check/testdata/array/base.carbon
  12. 38 0
      toolchain/check/testdata/array/fail_incomplete_element.carbon
  13. 1 2
      toolchain/check/testdata/array/fail_out_of_bound.carbon
  14. 10 12
      toolchain/check/testdata/array/fail_type_mismatch.carbon
  15. 16 16
      toolchain/check/testdata/array/function_param.carbon
  16. 30 31
      toolchain/check/testdata/array/nine_elements.carbon
  17. 1 0
      toolchain/check/testdata/basics/fail_bad_run.carbon
  18. 42 44
      toolchain/check/testdata/basics/numeric_literals.carbon
  19. 26 28
      toolchain/check/testdata/basics/raw_and_textual_ir.carbon
  20. 25 28
      toolchain/check/testdata/basics/raw_ir.carbon
  21. 1 0
      toolchain/check/testdata/basics/textual_ir.carbon
  22. 169 0
      toolchain/check/testdata/class/fail_incomplete.carbon
  23. 1 0
      toolchain/check/testdata/expression_category/in_place_tuple_initialization.carbon
  24. 1 0
      toolchain/check/testdata/function/call/empty_struct.carbon
  25. 1 0
      toolchain/check/testdata/if_expression/struct.carbon
  26. 9 10
      toolchain/check/testdata/index/array_element_access.carbon
  27. 13 13
      toolchain/check/testdata/index/expression_category.carbon
  28. 1 0
      toolchain/check/testdata/index/fail_expression_category.carbon
  29. 3 3
      toolchain/check/testdata/index/fail_invalid_base.carbon
  30. 3 4
      toolchain/check/testdata/index/fail_non_deterministic_type.carbon
  31. 3 4
      toolchain/check/testdata/index/fail_tuple_index_error.carbon
  32. 3 4
      toolchain/check/testdata/index/fail_tuple_non_int_indexing.carbon
  33. 3 4
      toolchain/check/testdata/index/fail_tuple_out_of_bound_access.carbon
  34. 3 4
      toolchain/check/testdata/let/convert.carbon
  35. 3 4
      toolchain/check/testdata/operators/assignment.carbon
  36. 21 21
      toolchain/check/testdata/operators/fail_assignment_to_non_assignable.carbon
  37. 3 4
      toolchain/check/testdata/pointer/address_of_lvalue.carbon
  38. 3 3
      toolchain/check/testdata/pointer/fail_address_of_value.carbon
  39. 1 0
      toolchain/check/testdata/return/tuple.carbon
  40. 2 2
      toolchain/check/testdata/struct/empty.carbon
  41. 1 2
      toolchain/check/testdata/struct/fail_assign_empty.carbon
  42. 3 4
      toolchain/check/testdata/struct/fail_assign_nested.carbon
  43. 2 2
      toolchain/check/testdata/struct/fail_assign_to_empty.carbon
  44. 36 0
      toolchain/check/testdata/struct/fail_nested_incomplete.carbon
  45. 5 4
      toolchain/check/testdata/struct/literal_member_access.carbon
  46. 1 0
      toolchain/check/testdata/struct/nested_struct_in_place.carbon
  47. 9 15
      toolchain/check/testdata/tuples/fail_assign_nested.carbon
  48. 6 8
      toolchain/check/testdata/tuples/fail_element_type_mismatch.carbon
  49. 39 0
      toolchain/check/testdata/tuples/fail_nested_incomplete.carbon
  50. 3 4
      toolchain/check/testdata/tuples/fail_too_few_element.carbon
  51. 7 10
      toolchain/check/testdata/tuples/nested_tuple.carbon
  52. 9 12
      toolchain/check/testdata/tuples/nested_tuple_in_place.carbon
  53. 3 4
      toolchain/check/testdata/tuples/two_elements.carbon
  54. 8 0
      toolchain/diagnostics/diagnostic_kind.def
  55. 19 0
      toolchain/lower/testdata/pointer/address_of_unused.carbon
  56. 11 7
      toolchain/sem_ir/file.h
  57. 8 3
      toolchain/sem_ir/node.h

+ 387 - 211
toolchain/check/context.cpp

@@ -21,8 +21,7 @@
 
 namespace Carbon::Check {
 
-Context::Context(const Lex::TokenizedBuffer& tokens,
-                 DiagnosticEmitter<Parse::Node>& emitter,
+Context::Context(const Lex::TokenizedBuffer& tokens, DiagnosticEmitter& emitter,
                  const Parse::Tree& parse_tree, SemIR::File& semantics_ir,
                  llvm::raw_ostream* vlog_stream)
     : tokens_(&tokens),
@@ -279,233 +278,411 @@ auto Context::ParamOrArgEnd(Parse::NodeKind start_kind) -> SemIR::NodeBlockId {
   return ParamOrArgPop();
 }
 
-// Attempts to complete the given type.
-auto Context::TryToCompleteType(SemIR::TypeId type_id) -> bool {
-  auto node_id = semantics_ir().GetTypeAllowBuiltinTypes(type_id);
-  auto node = semantics_ir().GetNode(node_id);
-
-  auto set_empty_representation = [&]() {
-    semantics_ir().CompleteType(
-        type_id, {.kind = SemIR::ValueRepresentation::None,
-                  .type_id = CanonicalizeTupleType(node.parse_node(), {})});
-    return true;
-  };
-
-  auto set_copy_representation = [&](SemIR::TypeId rep_id) {
-    semantics_ir().CompleteType(
-        type_id, {.kind = SemIR::ValueRepresentation::Copy, .type_id = rep_id});
+namespace {
+// Worklist-based type completion mechanism.
+//
+// When attempting to complete a type, we may find other types that also need to
+// be completed: types nested within that type, and the value representation of
+// the type. In order to complete a type without recursing arbitrarily deeply,
+// we use a worklist of tasks:
+//
+// - An `AddNestedIncompleteTypes` step adds a task for all incomplete types
+//   nested within a type to the work list.
+// - A `BuildValueRepresentation` step computes the value representation for a
+//   type, once all of its nested types are complete, and marks the type as
+//   complete.
+class TypeCompleter {
+ public:
+  TypeCompleter(
+      Context& context,
+      std::optional<llvm::function_ref<auto()->Context::DiagnosticBuilder>>
+          diagnoser)
+      : context_(context), diagnoser_(diagnoser) {}
+
+  // Attempts to complete the given type. Returns true if it is now complete,
+  // false if it could not be completed.
+  auto Complete(SemIR::TypeId type_id) -> bool {
+    Push(type_id);
+    while (!work_list_.empty()) {
+      if (!ProcessStep()) {
+        return false;
+      }
+    }
     return true;
-  };
+  }
 
-  auto set_pointer_representation = [&](SemIR::TypeId pointee_id) {
-    // TODO: Should we add `const` qualification to `pointee_id`?
-    semantics_ir().CompleteType(
-        type_id, {.kind = SemIR::ValueRepresentation::Pointer,
-                  .type_id = GetPointerType(node.parse_node(), pointee_id)});
-    return true;
-  };
+ private:
+  // Adds `type_id` to the work list, if it's not already complete.
+  auto Push(SemIR::TypeId type_id) -> void {
+    if (!context_.semantics_ir().IsTypeComplete(type_id)) {
+      work_list_.push_back({type_id, Phase::AddNestedIncompleteTypes});
+    }
+  }
 
-  // clang warns on unhandled enum values; clang-tidy is incorrect here.
-  // NOLINTNEXTLINE(bugprone-switch-missing-default-case)
-  switch (node.kind()) {
-    case SemIR::AddressOf::Kind:
-    case SemIR::ArrayIndex::Kind:
-    case SemIR::ArrayInit::Kind:
-    case SemIR::Assign::Kind:
-    case SemIR::BinaryOperatorAdd::Kind:
-    case SemIR::BindName::Kind:
-    case SemIR::BindValue::Kind:
-    case SemIR::BlockArg::Kind:
-    case SemIR::BoolLiteral::Kind:
-    case SemIR::Branch::Kind:
-    case SemIR::BranchIf::Kind:
-    case SemIR::BranchWithArg::Kind:
-    case SemIR::Call::Kind:
-    case SemIR::Dereference::Kind:
-    case SemIR::FunctionDeclaration::Kind:
-    case SemIR::InitializeFrom::Kind:
-    case SemIR::IntegerLiteral::Kind:
-    case SemIR::NameReference::Kind:
-    case SemIR::Namespace::Kind:
-    case SemIR::NoOp::Kind:
-    case SemIR::Parameter::Kind:
-    case SemIR::RealLiteral::Kind:
-    case SemIR::Return::Kind:
-    case SemIR::ReturnExpression::Kind:
-    case SemIR::SpliceBlock::Kind:
-    case SemIR::StringLiteral::Kind:
-    case SemIR::StructAccess::Kind:
-    case SemIR::StructTypeField::Kind:
-    case SemIR::StructLiteral::Kind:
-    case SemIR::StructInit::Kind:
-    case SemIR::StructValue::Kind:
-    case SemIR::Temporary::Kind:
-    case SemIR::TemporaryStorage::Kind:
-    case SemIR::TupleAccess::Kind:
-    case SemIR::TupleIndex::Kind:
-    case SemIR::TupleLiteral::Kind:
-    case SemIR::TupleInit::Kind:
-    case SemIR::TupleValue::Kind:
-    case SemIR::UnaryOperatorNot::Kind:
-    case SemIR::ValueAsReference::Kind:
-    case SemIR::VarStorage::Kind:
-      CARBON_FATAL() << "Type refers to non-type node " << node;
+  // Runs the next step.
+  auto ProcessStep() -> bool {
+    auto [type_id, phase] = work_list_.back();
 
-    case SemIR::CrossReference::Kind: {
-      auto xref = node.As<SemIR::CrossReference>();
-      auto xref_node =
-          semantics_ir().GetCrossReferenceIR(xref.ir_id).GetNode(xref.node_id);
-
-      // The canonical description of a type should only have cross-references
-      // for entities owned by another File, such as builtins, which are owned
-      // by the prelude, and named entities like classes and interfaces, which
-      // we don't support yet.
-      CARBON_CHECK(xref_node.kind() == SemIR::Builtin::Kind)
-          << "TODO: Handle other kinds of node cross-references";
-
-      // clang warns on unhandled enum values; clang-tidy is incorrect here.
-      // NOLINTNEXTLINE(bugprone-switch-missing-default-case)
-      switch (xref_node.As<SemIR::Builtin>().builtin_kind) {
-        case SemIR::BuiltinKind::TypeType:
-        case SemIR::BuiltinKind::Error:
-        case SemIR::BuiltinKind::Invalid:
-        case SemIR::BuiltinKind::BoolType:
-        case SemIR::BuiltinKind::IntegerType:
-        case SemIR::BuiltinKind::FloatingPointType:
-        case SemIR::BuiltinKind::NamespaceType:
-        case SemIR::BuiltinKind::FunctionType:
-          return set_copy_representation(type_id);
-
-        case SemIR::BuiltinKind::StringType:
-          // TODO: Decide on string value semantics. This should probably be a
-          // custom value representation carrying a pointer and size or
-          // similar.
-          return set_pointer_representation(type_id);
-      }
-      llvm_unreachable("All builtin kinds were handled above");
+    // We might have enqueued the same type more than once. Just skip the
+    // type if it's already complete.
+    if (context_.semantics_ir().IsTypeComplete(type_id)) {
+      work_list_.pop_back();
+      return true;
     }
 
-    case SemIR::ArrayType::Kind:
-      // For arrays, it's convenient to always use a pointer representation,
-      // even when the array has zero or one element, in order to support
-      // indexing.
-      return set_pointer_representation(type_id);
+    auto node_id = context_.semantics_ir().GetTypeAllowBuiltinTypes(type_id);
+    auto node = context_.semantics_ir().GetNode(node_id);
 
-    case SemIR::StructType::Kind: {
-      auto fields =
-          semantics_ir().GetNodeBlock(node.As<SemIR::StructType>().fields_id);
-      if (fields.empty()) {
-        return set_empty_representation();
-      }
+    auto old_work_list_size = work_list_.size();
 
-      // Find the value representation for each field, and construct a struct
-      // of value representations.
-      llvm::SmallVector<SemIR::NodeId> value_rep_fields;
-      value_rep_fields.reserve(fields.size());
-      bool same_as_object_rep = true;
-      for (auto field_id : fields) {
-        auto field = semantics_ir().GetNodeAs<SemIR::StructTypeField>(field_id);
-
-        // A struct is complete if and only if all its fields are complete.
-        auto field_value_rep =
-            semantics_ir().GetValueRepresentation(field.type_id);
-        if (field_value_rep.kind == SemIR::ValueRepresentation::Unknown) {
-          // TODO: If the field type might have become complete after we formed
-          // it, we should attempt to complete its type.
+    switch (phase) {
+      case Phase::AddNestedIncompleteTypes:
+        if (!AddNestedIncompleteTypes(node)) {
           return false;
         }
-        if (field_value_rep.type_id != field.type_id) {
-          same_as_object_rep = false;
-          field.type_id = field_value_rep.type_id;
-          field_id = AddNode(field);
+        CARBON_CHECK(work_list_.size() >= old_work_list_size)
+            << "AddNestedIncompleteTypes should not remove work items";
+        work_list_[old_work_list_size - 1].phase =
+            Phase::BuildValueRepresentation;
+        break;
+
+      case Phase::BuildValueRepresentation: {
+        auto value_rep = BuildValueRepresentation(type_id, node);
+        context_.semantics_ir().CompleteType(type_id, value_rep);
+        CARBON_CHECK(old_work_list_size == work_list_.size())
+            << "BuildValueRepresentation should not change work items";
+        work_list_.pop_back();
+
+        // Also complete the value representation type, if necessary. This
+        // should never fail: the value representation shouldn't require any
+        // additional nested types to be complete.
+        if (!context_.semantics_ir().IsTypeComplete(value_rep.type_id)) {
+          work_list_.push_back(
+              {value_rep.type_id, Phase::BuildValueRepresentation});
         }
-        value_rep_fields.push_back(field_id);
-      }
-
-      auto value_rep = same_as_object_rep
-                           ? type_id
-                           : CanonicalizeStructType(
-                                 node.parse_node(),
-                                 semantics_ir().AddNodeBlock(value_rep_fields));
-      if (fields.size() == 1) {
-        // The value representation for a struct with a single field is a struct
-        // containing the value representation of the field.
-        // TODO: Consider doing the same for structs with multiple small fields.
-        return set_copy_representation(value_rep);
+        break;
       }
-      // For a struct with multiple fields, we use a pointer representation.
-      return set_pointer_representation(value_rep);
     }
 
-    case SemIR::TupleType::Kind: {
-      // TODO: Extract and share code with structs and maybe arrays.
-      auto elements =
-          semantics_ir().GetTypeBlock(node.As<SemIR::TupleType>().elements_id);
-      if (elements.empty()) {
-        return set_empty_representation();
-      }
+    return true;
+  }
 
-      // Find the value representation for each element, and construct a tuple
-      // of value representations.
-      llvm::SmallVector<SemIR::TypeId> value_rep_elements;
-      value_rep_elements.reserve(elements.size());
-      bool same_as_object_rep = true;
-      for (auto element_type_id : elements) {
-        // A tuple is complete if and only if all its elements are complete.
-        auto element_value_rep =
-            semantics_ir().GetValueRepresentation(element_type_id);
-        if (element_value_rep.kind == SemIR::ValueRepresentation::Unknown) {
-          // TODO: If the element type might have become complete after we
-          // formed it, we should attempt to complete its type.
-          return false;
+  // Adds any types nested within `type_node` that need to be complete for
+  // `type_node` to be complete to our work list.
+  auto AddNestedIncompleteTypes(SemIR::Node type_node) -> bool {
+    switch (type_node.kind()) {
+      case SemIR::ArrayType::Kind:
+        Push(type_node.As<SemIR::ArrayType>().element_type_id);
+        break;
+
+      case SemIR::StructType::Kind:
+        for (auto field_id : context_.semantics_ir().GetNodeBlock(
+                 type_node.As<SemIR::StructType>().fields_id)) {
+          Push(context_.semantics_ir()
+                   .GetNodeAs<SemIR::StructTypeField>(field_id)
+                   .type_id);
         }
-        if (element_value_rep.type_id != element_type_id) {
-          same_as_object_rep = false;
+        break;
+
+      case SemIR::TupleType::Kind:
+        for (auto element_type_id : context_.semantics_ir().GetTypeBlock(
+                 type_node.As<SemIR::TupleType>().elements_id)) {
+          Push(element_type_id);
         }
-        value_rep_elements.push_back(element_value_rep.type_id);
-      }
+        break;
+
+      case SemIR::ClassDeclaration::Kind:
+        // TODO: Support class definitions and complete class types.
+        if (diagnoser_) {
+          CARBON_DIAGNOSTIC(ClassForwardDeclaredHere, Note,
+                            "Class was forward declared here.");
+          (*diagnoser_)()
+              .Note(type_node.parse_node(), ClassForwardDeclaredHere)
+              .Emit();
+        }
+        return false;
+
+      case SemIR::ConstType::Kind:
+        Push(type_node.As<SemIR::ConstType>().inner_id);
+        break;
+
+      default:
+        break;
+    }
+
+    return true;
+  }
+
+  // Makes an empty value representation, which is used for types that have no
+  // state, such as empty structs and tuples.
+  auto MakeEmptyRepresentation(Parse::Node parse_node) const
+      -> SemIR::ValueRepresentation {
+    return {.kind = SemIR::ValueRepresentation::None,
+            .type_id = context_.CanonicalizeTupleType(parse_node, {})};
+  }
+
+  // Makes a value representation that uses pass-by-copy, copying the given
+  // type.
+  auto MakeCopyRepresentation(SemIR::TypeId rep_id) const
+      -> SemIR::ValueRepresentation {
+    return {.kind = SemIR::ValueRepresentation::Copy, .type_id = rep_id};
+  }
+
+  // Makes a value representation that uses pass-by-address with the given
+  // pointee type.
+  auto MakePointerRepresentation(Parse::Node parse_node,
+                                 SemIR::TypeId pointee_id) const
+      -> SemIR::ValueRepresentation {
+    // TODO: Should we add `const` qualification to `pointee_id`?
+    return {.kind = SemIR::ValueRepresentation::Pointer,
+            .type_id = context_.GetPointerType(parse_node, pointee_id)};
+  }
+
+  // Gets the value representation of a nested type, which should already be
+  // complete.
+  auto GetNestedValueRepresentation(SemIR::TypeId nested_type_id) const {
+    CARBON_CHECK(context_.semantics_ir().IsTypeComplete(nested_type_id))
+        << "Nested type should already be complete";
+    auto value_rep =
+        context_.semantics_ir().GetValueRepresentation(nested_type_id);
+    CARBON_CHECK(value_rep.kind != SemIR::ValueRepresentation::Unknown)
+        << "Complete type should have a value representation";
+    return value_rep;
+  };
+
+  auto BuildCrossReferenceValueRepresentation(SemIR::TypeId type_id,
+                                              SemIR::CrossReference xref) const
+      -> SemIR::ValueRepresentation {
+    auto xref_node = context_.semantics_ir()
+                         .GetCrossReferenceIR(xref.ir_id)
+                         .GetNode(xref.node_id);
+
+    // The canonical description of a type should only have cross-references
+    // for entities owned by another File, such as builtins, which are owned
+    // by the prelude, and named entities like classes and interfaces, which
+    // we don't support yet.
+    CARBON_CHECK(xref_node.kind() == SemIR::Builtin::Kind)
+        << "TODO: Handle other kinds of node cross-references";
+
+    // clang warns on unhandled enum values; clang-tidy is incorrect here.
+    // NOLINTNEXTLINE(bugprone-switch-missing-default-case)
+    switch (xref_node.As<SemIR::Builtin>().builtin_kind) {
+      case SemIR::BuiltinKind::TypeType:
+      case SemIR::BuiltinKind::Error:
+      case SemIR::BuiltinKind::Invalid:
+      case SemIR::BuiltinKind::BoolType:
+      case SemIR::BuiltinKind::IntegerType:
+      case SemIR::BuiltinKind::FloatingPointType:
+      case SemIR::BuiltinKind::NamespaceType:
+      case SemIR::BuiltinKind::FunctionType:
+        return MakeCopyRepresentation(type_id);
+
+      case SemIR::BuiltinKind::StringType:
+        // TODO: Decide on string value semantics. This should probably be a
+        // custom value representation carrying a pointer and size or
+        // similar.
+        return MakePointerRepresentation(Parse::Node::Invalid, type_id);
+    }
+    llvm_unreachable("All builtin kinds were handled above");
+  }
+
+  auto BuildStructTypeValueRepresentation(SemIR::TypeId type_id,
+                                          SemIR::StructType struct_type) const
+      -> SemIR::ValueRepresentation {
+    // TODO: Share code with tuples.
+    auto fields = context_.semantics_ir().GetNodeBlock(struct_type.fields_id);
+    if (fields.empty()) {
+      return MakeEmptyRepresentation(struct_type.parse_node);
+    }
 
-      auto value_rep =
-          same_as_object_rep
-              ? type_id
-              : CanonicalizeTupleType(node.parse_node(), value_rep_elements);
-      if (elements.size() == 1) {
-        // The value representation for a tuple with a single element is a tuple
-        // containing the value representation of that element.
-        // TODO: Consider doing the same for tuples with multiple small
-        // elements.
-        return set_copy_representation(value_rep);
+    // Find the value representation for each field, and construct a struct
+    // of value representations.
+    llvm::SmallVector<SemIR::NodeId> value_rep_fields;
+    value_rep_fields.reserve(fields.size());
+    bool same_as_object_rep = true;
+    for (auto field_id : fields) {
+      auto field =
+          context_.semantics_ir().GetNodeAs<SemIR::StructTypeField>(field_id);
+      auto field_value_rep = GetNestedValueRepresentation(field.type_id);
+      if (field_value_rep.type_id != field.type_id) {
+        same_as_object_rep = false;
+        field.type_id = field_value_rep.type_id;
+        field_id = context_.AddNode(field);
       }
-      // For a tuple with multiple elements, we use a pointer representation.
-      return set_pointer_representation(value_rep);
+      value_rep_fields.push_back(field_id);
     }
 
-    case SemIR::ClassDeclaration::Kind: {
-      // TODO: Pick the default value representation in a smarter way.
-      // TODO: Allow the value representation for a class to be customized.
-      return set_pointer_representation(type_id);
+    auto value_rep =
+        same_as_object_rep
+            ? type_id
+            : context_.CanonicalizeStructType(
+                  struct_type.parse_node,
+                  context_.semantics_ir().AddNodeBlock(value_rep_fields));
+    if (fields.size() == 1) {
+      // The value representation for a struct with a single field is a
+      // struct containing the value representation of the field.
+      // TODO: Consider doing the same for structs with multiple small
+      // fields.
+      return MakeCopyRepresentation(value_rep);
     }
+    // For a struct with multiple fields, we use a pointer representation.
+    return MakePointerRepresentation(struct_type.parse_node, value_rep);
+  }
 
-    case SemIR::Builtin::Kind:
-      CARBON_FATAL() << "Builtins should be named as cross-references";
+  auto BuildTupleTypeValueRepresentation(SemIR::TypeId type_id,
+                                         SemIR::TupleType tuple_type) const
+      -> SemIR::ValueRepresentation {
+    // TODO: Share code with structs.
+    auto elements =
+        context_.semantics_ir().GetTypeBlock(tuple_type.elements_id);
+    if (elements.empty()) {
+      return MakeEmptyRepresentation(tuple_type.parse_node);
+    }
 
-    case SemIR::PointerType::Kind:
-      return set_copy_representation(type_id);
-
-    case SemIR::ConstType::Kind: {
-      // The value representation of `const T` is the same as that of `T`.
-      // Objects are not modifiable through their value representations.
-      auto inner_value_rep = semantics_ir().GetValueRepresentation(
-          node.As<SemIR::ConstType>().inner_id);
-      if (inner_value_rep.kind == SemIR::ValueRepresentation::Unknown) {
-        return false;
+    // Find the value representation for each element, and construct a tuple
+    // of value representations.
+    llvm::SmallVector<SemIR::TypeId> value_rep_elements;
+    value_rep_elements.reserve(elements.size());
+    bool same_as_object_rep = true;
+    for (auto element_type_id : elements) {
+      auto element_value_rep = GetNestedValueRepresentation(element_type_id);
+      if (element_value_rep.type_id != element_type_id) {
+        same_as_object_rep = false;
       }
-      semantics_ir().CompleteType(type_id, inner_value_rep);
-      return true;
+      value_rep_elements.push_back(element_value_rep.type_id);
+    }
+
+    auto value_rep = same_as_object_rep
+                         ? type_id
+                         : context_.CanonicalizeTupleType(tuple_type.parse_node,
+                                                          value_rep_elements);
+    if (elements.size() == 1) {
+      // The value representation for a tuple with a single element is a
+      // tuple containing the value representation of that element.
+      // TODO: Consider doing the same for tuples with multiple small
+      // elements.
+      return MakeCopyRepresentation(value_rep);
+    }
+    // For a tuple with multiple elements, we use a pointer representation.
+    return MakePointerRepresentation(tuple_type.parse_node, value_rep);
+  }
+
+  // Builds and returns the value representation for the given type. All nested
+  // types, as found by AddNestedIncompleteTypes, are known to be complete.
+  auto BuildValueRepresentation(SemIR::TypeId type_id, SemIR::Node node) const
+      -> SemIR::ValueRepresentation {
+    // TODO: This can emit new SemIR nodes. Consider emitting them into a
+    // dedicated file-scope node block where possible, or somewhere else that
+    // better reflects the definition of the type, rather than wherever the
+    // type happens to first be required to be complete.
+
+    // clang warns on unhandled enum values; clang-tidy is incorrect here.
+    // NOLINTNEXTLINE(bugprone-switch-missing-default-case)
+    switch (node.kind()) {
+      case SemIR::AddressOf::Kind:
+      case SemIR::ArrayIndex::Kind:
+      case SemIR::ArrayInit::Kind:
+      case SemIR::Assign::Kind:
+      case SemIR::BinaryOperatorAdd::Kind:
+      case SemIR::BindName::Kind:
+      case SemIR::BindValue::Kind:
+      case SemIR::BlockArg::Kind:
+      case SemIR::BoolLiteral::Kind:
+      case SemIR::Branch::Kind:
+      case SemIR::BranchIf::Kind:
+      case SemIR::BranchWithArg::Kind:
+      case SemIR::Call::Kind:
+      case SemIR::Dereference::Kind:
+      case SemIR::FunctionDeclaration::Kind:
+      case SemIR::InitializeFrom::Kind:
+      case SemIR::IntegerLiteral::Kind:
+      case SemIR::NameReference::Kind:
+      case SemIR::Namespace::Kind:
+      case SemIR::NoOp::Kind:
+      case SemIR::Parameter::Kind:
+      case SemIR::RealLiteral::Kind:
+      case SemIR::Return::Kind:
+      case SemIR::ReturnExpression::Kind:
+      case SemIR::SpliceBlock::Kind:
+      case SemIR::StringLiteral::Kind:
+      case SemIR::StructAccess::Kind:
+      case SemIR::StructTypeField::Kind:
+      case SemIR::StructLiteral::Kind:
+      case SemIR::StructInit::Kind:
+      case SemIR::StructValue::Kind:
+      case SemIR::Temporary::Kind:
+      case SemIR::TemporaryStorage::Kind:
+      case SemIR::TupleAccess::Kind:
+      case SemIR::TupleIndex::Kind:
+      case SemIR::TupleLiteral::Kind:
+      case SemIR::TupleInit::Kind:
+      case SemIR::TupleValue::Kind:
+      case SemIR::UnaryOperatorNot::Kind:
+      case SemIR::ValueAsReference::Kind:
+      case SemIR::VarStorage::Kind:
+        CARBON_FATAL() << "Type refers to non-type node " << node;
+
+      case SemIR::CrossReference::Kind:
+        return BuildCrossReferenceValueRepresentation(
+            type_id, node.As<SemIR::CrossReference>());
+
+      case SemIR::ArrayType::Kind: {
+        // For arrays, it's convenient to always use a pointer representation,
+        // even when the array has zero or one element, in order to support
+        // indexing.
+        return MakePointerRepresentation(node.parse_node(), type_id);
+      }
+
+      case SemIR::StructType::Kind:
+        return BuildStructTypeValueRepresentation(type_id,
+                                                  node.As<SemIR::StructType>());
+
+      case SemIR::TupleType::Kind:
+        return BuildTupleTypeValueRepresentation(type_id,
+                                                 node.As<SemIR::TupleType>());
+
+      case SemIR::ClassDeclaration::Kind:
+        // TODO: Support class definitions and complete class types.
+        CARBON_FATAL() << "Class types are currently never complete";
+
+      case SemIR::Builtin::Kind:
+        CARBON_FATAL() << "Builtins should be named as cross-references";
+
+      case SemIR::PointerType::Kind:
+        return MakeCopyRepresentation(type_id);
+
+      case SemIR::ConstType::Kind:
+        // The value representation of `const T` is the same as that of `T`.
+        // Objects are not modifiable through their value representations.
+        return GetNestedValueRepresentation(
+            node.As<SemIR::ConstType>().inner_id);
     }
   }
 
-  llvm_unreachable("All node kinds were handled above");
+  enum class Phase : int8_t {
+    // The next step is to add nested types to the list of types to complete.
+    AddNestedIncompleteTypes,
+    // The next step is to build the value representation for the type.
+    BuildValueRepresentation,
+  };
+
+  struct WorkItem {
+    SemIR::TypeId type_id;
+    Phase phase;
+  };
+
+  Context& context_;
+  llvm::SmallVector<WorkItem> work_list_;
+  std::optional<llvm::function_ref<auto()->Context::DiagnosticBuilder>>
+      diagnoser_;
+};
+}  // namespace
+
+auto Context::TryToCompleteType(
+    SemIR::TypeId type_id,
+    std::optional<llvm::function_ref<auto()->DiagnosticBuilder>> diagnoser)
+    -> bool {
+  return TypeCompleter(*this, diagnoser).Complete(type_id);
 }
 
 auto Context::CanonicalizeTypeImpl(
@@ -539,16 +716,6 @@ auto Context::CanonicalizeTypeImpl(
   }()) << "Type was created recursively during canonicalization";
 
   canonical_type_nodes_.InsertNode(type_node_storage_.back().get(), insert_pos);
-
-  // Now we've formed the type, try to complete it and build its value
-  // representation.
-  // TODO: Delay doing this until a complete type is required, and issue a
-  // diagnostic if it fails.
-  // TODO: Consider emitting this into the file's global node block
-  // (or somewhere else that better reflects the definition of the type
-  // rather than the coincidental first use).
-  bool complete = TryToCompleteType(type_id);
-  CARBON_CHECK(complete) << "Incomplete types should not exist yet";
   return type_id;
 }
 
@@ -665,6 +832,15 @@ auto Context::CanonicalizeTupleType(Parse::Node parse_node,
                               make_tuple_node);
 }
 
+auto Context::GetBuiltinType(SemIR::BuiltinKind kind) -> SemIR::TypeId {
+  CARBON_CHECK(kind != SemIR::BuiltinKind::Invalid);
+  auto type_id = CanonicalizeType(SemIR::NodeId::ForBuiltin(kind));
+  // To keep client code simpler, complete builtin types before returning them.
+  bool complete = TryToCompleteType(type_id);
+  CARBON_CHECK(complete) << "Failed to complete builtin type";
+  return type_id;
+}
+
 auto Context::GetPointerType(Parse::Node parse_node,
                              SemIR::TypeId pointee_type_id) -> SemIR::TypeId {
   return CanonicalizeTypeAndAddNodeIfNew(

+ 18 - 7
toolchain/check/context.h

@@ -21,6 +21,9 @@ namespace Carbon::Check {
 // Context and shared functionality for semantics handlers.
 class Context {
  public:
+  using DiagnosticEmitter = Carbon::DiagnosticEmitter<Parse::Node>;
+  using DiagnosticBuilder = DiagnosticEmitter::DiagnosticBuilder;
+
   // A scope in which `break` and `continue` can be used.
   struct BreakContinueScope {
     SemIR::NodeBlockId break_target;
@@ -29,9 +32,8 @@ class Context {
 
   // Stores references for work.
   explicit Context(const Lex::TokenizedBuffer& tokens,
-                   DiagnosticEmitter<Parse::Node>& emitter,
-                   const Parse::Tree& parse_tree, SemIR::File& semantics,
-                   llvm::raw_ostream* vlog_stream);
+                   DiagnosticEmitter& emitter, const Parse::Tree& parse_tree,
+                   SemIR::File& semantics, llvm::raw_ostream* vlog_stream);
 
   // Marks an implementation TODO. Always returns false.
   auto TODO(Parse::Node parse_node, std::string label) -> bool;
@@ -136,8 +138,17 @@ class Context {
   // Attempts to complete the type `type_id`. Returns `true` if the type is
   // complete, or `false` if it could not be completed. A complete type has
   // known object and value representations.
-  // TODO: For now, all types are always complete.
-  auto TryToCompleteType(SemIR::TypeId type_id) -> bool;
+  //
+  // If the type is not complete, `diagnoser` is invoked to diagnose the issue.
+  // The builder it returns will be annotated to describe the reason why the
+  // type is not complete.
+  auto TryToCompleteType(
+      SemIR::TypeId type_id,
+      std::optional<llvm::function_ref<auto()->DiagnosticBuilder>> diagnoser =
+          std::nullopt) -> bool;
+
+  // Gets a builtin type. The returned type will be complete.
+  auto GetBuiltinType(SemIR::BuiltinKind kind) -> SemIR::TypeId;
 
   // Returns a pointer type whose pointee type is `pointee_type_id`.
   auto GetPointerType(Parse::Node parse_node, SemIR::TypeId pointee_type_id)
@@ -179,7 +190,7 @@ class Context {
 
   auto tokens() -> const Lex::TokenizedBuffer& { return *tokens_; }
 
-  auto emitter() -> DiagnosticEmitter<Parse::Node>& { return *emitter_; }
+  auto emitter() -> DiagnosticEmitter& { return *emitter_; }
 
   auto parse_tree() -> const Parse::Tree& { return *parse_tree_; }
 
@@ -255,7 +266,7 @@ class Context {
   const Lex::TokenizedBuffer* tokens_;
 
   // Handles diagnostics.
-  DiagnosticEmitter<Parse::Node>* emitter_;
+  DiagnosticEmitter* emitter_;
 
   // The file's parse tree.
   const Parse::Tree* parse_tree_;

+ 23 - 2
toolchain/check/convert.cpp

@@ -133,7 +133,7 @@ static auto MakeElemAccessNode(Context& context, Parse::Node parse_node,
     // so that we don't need an integer literal node here, and remove this
     // special case.
     auto index_id = block.AddNode(SemIR::IntegerLiteral(
-        parse_node, context.CanonicalizeType(SemIR::NodeId::BuiltinIntegerType),
+        parse_node, context.GetBuiltinType(SemIR::BuiltinKind::IntegerType),
         context.semantics_ir().AddInteger(llvm::APInt(32, i))));
     return block.AddNode(
         AccessNodeT(parse_node, elem_type_id, aggregate_id, index_id));
@@ -634,6 +634,27 @@ auto Convert(Context& context, Parse::Node parse_node, SemIR::NodeId expr_id,
     return SemIR::NodeId::BuiltinError;
   }
 
+  // We can only perform initialization for complete types.
+  if (!context.TryToCompleteType(target.type_id, [&] {
+        CARBON_DIAGNOSTIC(IncompleteTypeInInitialization, Error,
+                          "Initialization of incomplete type `{0}`.",
+                          std::string);
+        CARBON_DIAGNOSTIC(IncompleteTypeInValueConversion, Error,
+                          "Forming value of incomplete type `{0}`.",
+                          std::string);
+        CARBON_DIAGNOSTIC(IncompleteTypeInConversion, Error,
+                          "Invalid use of incomplete type `{0}`.", std::string);
+        return context.emitter().Build(
+            parse_node,
+            target.is_initializer() ? IncompleteTypeInInitialization
+            : target.kind == ConversionTarget::Value
+                ? IncompleteTypeInValueConversion
+                : IncompleteTypeInConversion,
+            context.semantics_ir().StringifyType(target.type_id, true));
+      })) {
+    return SemIR::NodeId::BuiltinError;
+  }
+
   // Check whether any builtin conversion applies.
   expr_id = PerformBuiltinConversion(context, parse_node, expr_id, target);
   if (expr_id == SemIR::NodeId::BuiltinError) {
@@ -755,7 +776,7 @@ auto ConvertToBoolValue(Context& context, Parse::Node parse_node,
                         SemIR::NodeId value_id) -> SemIR::NodeId {
   return ConvertToValueOfType(
       context, parse_node, value_id,
-      context.CanonicalizeType(SemIR::NodeId::BuiltinBoolType));
+      context.GetBuiltinType(SemIR::BuiltinKind::BoolType));
 }
 
 auto ConvertCallArgs(Context& context, Parse::Node call_parse_node,

+ 35 - 10
toolchain/check/handle_function.cpp

@@ -26,14 +26,27 @@ static auto BuildFunctionDeclaration(Context& context)
   auto return_slot_id = SemIR::NodeId::Invalid;
   if (context.parse_tree().node_kind(context.node_stack().PeekParseNode()) ==
       Parse::NodeKind::ReturnType) {
-    return_slot_id = context.node_stack().Pop<Parse::NodeKind::ReturnType>();
-    return_type_id = context.semantics_ir().GetNode(return_slot_id).type_id();
-
-    // The function only has a return slot if it uses in-place initialization.
-    if (!SemIR::GetInitializingRepresentation(context.semantics_ir(),
-                                              return_type_id)
-             .has_return_slot()) {
-      return_slot_id = SemIR::NodeId::Invalid;
+    auto [return_node, return_storage_id] =
+        context.node_stack().PopWithParseNode<Parse::NodeKind::ReturnType>();
+    auto return_node_copy = return_node;
+    return_type_id =
+        context.semantics_ir().GetNode(return_storage_id).type_id();
+
+    if (!context.TryToCompleteType(return_type_id, [&] {
+          CARBON_DIAGNOSTIC(IncompleteTypeInFunctionReturnType, Error,
+                            "Function returns incomplete type `{0}`.",
+                            std::string);
+          return context.emitter().Build(
+              return_node_copy, IncompleteTypeInFunctionReturnType,
+              context.semantics_ir().StringifyType(return_type_id, true));
+        })) {
+      return_type_id = SemIR::TypeId::Error;
+    } else if (!SemIR::GetInitializingRepresentation(context.semantics_ir(),
+                                                     return_type_id)
+                    .has_return_slot()) {
+      // The function only has a return slot if it uses in-place initialization.
+    } else {
+      return_slot_id = return_storage_id;
     }
   }
 
@@ -58,7 +71,7 @@ static auto BuildFunctionDeclaration(Context& context)
        .return_slot_id = return_slot_id,
        .body_block_ids = {}});
   auto decl_id = context.AddNode(SemIR::FunctionDeclaration(
-      fn_node, context.CanonicalizeType(SemIR::NodeId::BuiltinFunctionType),
+      fn_node, context.GetBuiltinType(SemIR::BuiltinKind::FunctionType),
       function_id));
   context.declaration_name_stack().AddNameToLookup(name_context, decl_id);
 
@@ -67,7 +80,7 @@ static auto BuildFunctionDeclaration(Context& context)
     if (!context.semantics_ir().GetNodeBlock(param_refs_id).empty() ||
         (return_slot_id.is_valid() &&
          return_type_id !=
-             context.CanonicalizeType(SemIR::NodeId::BuiltinBoolType) &&
+             context.GetBuiltinType(SemIR::BuiltinKind::BoolType) &&
          return_type_id != context.CanonicalizeTupleType(fn_node, {}))) {
       CARBON_DIAGNOSTIC(InvalidMainRunSignature, Error,
                         "Invalid signature for `Main.Run` function. Expected "
@@ -127,6 +140,18 @@ auto HandleFunctionDefinitionStart(Context& context, Parse::Node parse_node)
   for (auto param_id :
        context.semantics_ir().GetNodeBlock(function.param_refs_id)) {
     auto param = context.semantics_ir().GetNodeAs<SemIR::Parameter>(param_id);
+
+    // The parameter types need to be complete.
+    context.TryToCompleteType(param.type_id, [&] {
+      CARBON_DIAGNOSTIC(
+          IncompleteTypeInFunctionParam, Error,
+          "Parameter has incomplete type `{0}` in function definition.",
+          std::string);
+      return context.emitter().Build(
+          param.parse_node, IncompleteTypeInFunctionParam,
+          context.semantics_ir().StringifyType(param.type_id, true));
+    });
+
     context.AddNameToLookup(param.parse_node, param.name_id, param_id);
   }
 

+ 1 - 1
toolchain/check/handle_index.cpp

@@ -63,7 +63,7 @@ auto HandleIndexExpression(Context& context, Parse::Node parse_node) -> bool {
       }
       auto cast_index_id = ConvertToValueOfType(
           context, index_node.parse_node(), index_node_id,
-          context.CanonicalizeType(SemIR::NodeId::BuiltinIntegerType));
+          context.GetBuiltinType(SemIR::BuiltinKind::IntegerType));
       auto array_cat =
           SemIR::GetExpressionCategory(context.semantics_ir(), operand_node_id);
       if (array_cat == SemIR::ExpressionCategory::Value) {

+ 4 - 5
toolchain/check/handle_literal.cpp

@@ -14,8 +14,7 @@ auto HandleLiteral(Context& context, Parse::Node parse_node) -> bool {
       context.AddNodeAndPush(
           parse_node,
           SemIR::BoolLiteral(
-              parse_node,
-              context.CanonicalizeType(SemIR::NodeId::BuiltinBoolType),
+              parse_node, context.GetBuiltinType(SemIR::BuiltinKind::BoolType),
               token_kind == Lex::TokenKind::True ? SemIR::BoolValue::True
                                                  : SemIR::BoolValue::False));
       break;
@@ -27,7 +26,7 @@ auto HandleLiteral(Context& context, Parse::Node parse_node) -> bool {
           parse_node,
           SemIR::IntegerLiteral(
               parse_node,
-              context.CanonicalizeType(SemIR::NodeId::BuiltinIntegerType), id));
+              context.GetBuiltinType(SemIR::BuiltinKind::IntegerType), id));
       break;
     }
     case Lex::TokenKind::RealLiteral: {
@@ -40,7 +39,7 @@ auto HandleLiteral(Context& context, Parse::Node parse_node) -> bool {
           parse_node,
           SemIR::RealLiteral(
               parse_node,
-              context.CanonicalizeType(SemIR::NodeId::BuiltinFloatingPointType),
+              context.GetBuiltinType(SemIR::BuiltinKind::FloatingPointType),
               id));
       break;
     }
@@ -51,7 +50,7 @@ auto HandleLiteral(Context& context, Parse::Node parse_node) -> bool {
           parse_node,
           SemIR::StringLiteral(
               parse_node,
-              context.CanonicalizeType(SemIR::NodeId::BuiltinStringType), id));
+              context.GetBuiltinType(SemIR::BuiltinKind::StringType), id));
       break;
     }
     case Lex::TokenKind::Type: {

+ 1 - 1
toolchain/check/handle_namespace.cpp

@@ -16,7 +16,7 @@ auto HandleNamespaceStart(Context& context, Parse::Node /*parse_node*/)
 auto HandleNamespace(Context& context, Parse::Node parse_node) -> bool {
   auto name_context = context.declaration_name_stack().Pop();
   auto namespace_id = context.AddNode(SemIR::Namespace(
-      parse_node, context.CanonicalizeType(SemIR::NodeId::BuiltinNamespaceType),
+      parse_node, context.GetBuiltinType(SemIR::BuiltinKind::NamespaceType),
       context.semantics_ir().AddNameScope()));
   context.declaration_name_stack().AddNameToLookup(name_context, namespace_id);
   return true;

+ 26 - 0
toolchain/check/handle_pattern_binding.cpp

@@ -20,6 +20,7 @@ auto HandleGenericPatternBinding(Context& context, Parse::Node parse_node)
 auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool {
   auto [type_node, parsed_type_id] =
       context.node_stack().PopExpressionWithParseNode();
+  auto type_node_copy = type_node;
   auto cast_type_id = ExpressionAsType(context, type_node, parsed_type_id);
 
   // Get the name.
@@ -32,14 +33,38 @@ auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool {
   switch (auto context_parse_node_kind = context.parse_tree().node_kind(
               context.node_stack().PeekParseNode())) {
     case Parse::NodeKind::VariableIntroducer:
+      if (!context.TryToCompleteType(cast_type_id, [&] {
+            CARBON_DIAGNOSTIC(IncompleteTypeInVarDeclaration, Error,
+                              "Variable has incomplete type `{0}`.",
+                              std::string);
+            return context.emitter().Build(
+                type_node_copy, IncompleteTypeInVarDeclaration,
+                context.semantics_ir().StringifyType(cast_type_id, true));
+          })) {
+        cast_type_id = SemIR::TypeId::Error;
+      }
       context.AddNodeAndPush(
           parse_node, SemIR::VarStorage(name_node, cast_type_id, name_id));
       break;
+
     case Parse::NodeKind::ParameterListStart:
+      // Parameters can have incomplete types in a function declaration, but not
+      // in a function definition. We don't know which kind we have here.
       context.AddNodeAndPush(
           parse_node, SemIR::Parameter(name_node, cast_type_id, name_id));
       break;
+
     case Parse::NodeKind::LetIntroducer:
+      if (!context.TryToCompleteType(cast_type_id, [&] {
+            CARBON_DIAGNOSTIC(IncompleteTypeInLetDeclaration, Error,
+                              "`let` binding has incomplete type `{0}`.",
+                              std::string);
+            return context.emitter().Build(
+                type_node_copy, IncompleteTypeInLetDeclaration,
+                context.semantics_ir().StringifyType(cast_type_id, true));
+          })) {
+        cast_type_id = SemIR::TypeId::Error;
+      }
       // Create the node, but don't add it to a block until after we've formed
       // its initializer.
       // TODO: For general pattern parsing, we'll need to create a block to hold
@@ -49,6 +74,7 @@ auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool {
           context.semantics_ir().AddNodeInNoBlock(SemIR::BindName(
               name_node, cast_type_id, name_id, SemIR::NodeId::Invalid)));
       break;
+
     default:
       CARBON_FATAL() << "Found a pattern binding in unexpected context "
                      << context_parse_node_kind;

+ 4 - 5
toolchain/check/testdata/array/array_in_place.carbon

@@ -11,6 +11,7 @@ fn G() {
 }
 
 // CHECK:STDOUT: file "array_in_place.carbon" {
+// CHECK:STDOUT:   %.loc7: type = ptr_type (i32, i32, i32)
 // CHECK:STDOUT:   %F: <function> = fn_decl @F
 // CHECK:STDOUT:   %G: <function> = fn_decl @G
 // CHECK:STDOUT: }
@@ -37,10 +38,8 @@ fn G() {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc10_40: init (i32, i32, i32) = call %F.ref.loc10_39() to %.loc10_42.6
 // CHECK:STDOUT:   %.loc10_42.7: type = tuple_type ((i32, i32, i32), (i32, i32, i32))
-// CHECK:STDOUT:   %.loc10_42.8: type = tuple_type ((i32, i32, i32)*, (i32, i32, i32)*)
-// CHECK:STDOUT:   %.loc10_42.9: type = ptr_type ((i32, i32, i32)*, (i32, i32, i32)*)
-// CHECK:STDOUT:   %.loc10_42.10: ((i32, i32, i32), (i32, i32, i32)) = tuple_literal (%.loc10_35, %.loc10_40)
-// CHECK:STDOUT:   %.loc10_42.11: init [(i32, i32, i32); 2] = array_init %.loc10_42.10, (%.loc10_35, %.loc10_40) to %v
-// CHECK:STDOUT:   assign %v, %.loc10_42.11
+// CHECK:STDOUT:   %.loc10_42.8: ((i32, i32, i32), (i32, i32, i32)) = tuple_literal (%.loc10_35, %.loc10_40)
+// CHECK:STDOUT:   %.loc10_42.9: init [(i32, i32, i32); 2] = array_init %.loc10_42.8, (%.loc10_35, %.loc10_40) to %v
+// CHECK:STDOUT:   assign %v, %.loc10_42.9
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 3 - 4
toolchain/check/testdata/array/assign_var.carbon

@@ -9,10 +9,9 @@ var b: [i32; 3] = a;
 
 // CHECK:STDOUT: file "assign_var.carbon" {
 // CHECK:STDOUT:   %.loc7_22.1: type = tuple_type (type, type, type)
-// CHECK:STDOUT:   %.loc7_22.2: type = ptr_type (type, type, type)
-// CHECK:STDOUT:   %.loc7_22.3: (type, type, type) = tuple_literal (i32, i32, i32)
-// CHECK:STDOUT:   %.loc7_22.4: type = tuple_type (i32, i32, i32)
-// CHECK:STDOUT:   %.loc7_22.5: type = ptr_type (i32, i32, i32)
+// CHECK:STDOUT:   %.loc7_22.2: (type, type, type) = tuple_literal (i32, i32, i32)
+// CHECK:STDOUT:   %.loc7_22.3: type = tuple_type (i32, i32, i32)
+// CHECK:STDOUT:   %.loc7_22.4: type = ptr_type (i32, i32, i32)
 // CHECK:STDOUT:   %a: ref (i32, i32, i32) = var "a"
 // CHECK:STDOUT:   %.loc7_27: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc7_30: i32 = int_literal 2

+ 12 - 14
toolchain/check/testdata/array/base.carbon

@@ -28,16 +28,15 @@ var c: [(); 5] = ((), (), (), (), (),);
 // CHECK:STDOUT:   %.loc8_20: f64 = real_literal 111e-1
 // CHECK:STDOUT:   %.loc8_26: f64 = real_literal 22e-1
 // CHECK:STDOUT:   %.loc8_30.1: type = tuple_type (f64, f64)
-// CHECK:STDOUT:   %.loc8_30.2: type = ptr_type (f64, f64)
-// CHECK:STDOUT:   %.loc8_30.3: (f64, f64) = tuple_literal (%.loc8_20, %.loc8_26)
-// CHECK:STDOUT:   %.loc8_30.4: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc8_30.5: ref f64 = array_index %b, %.loc8_30.4
-// CHECK:STDOUT:   %.loc8_30.6: init f64 = initialize_from %.loc8_20 to %.loc8_30.5
-// CHECK:STDOUT:   %.loc8_30.7: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc8_30.8: ref f64 = array_index %b, %.loc8_30.7
-// CHECK:STDOUT:   %.loc8_30.9: init f64 = initialize_from %.loc8_26 to %.loc8_30.8
-// CHECK:STDOUT:   %.loc8_30.10: init [f64; 2] = array_init %.loc8_30.3, (%.loc8_30.6, %.loc8_30.9) to %b
-// CHECK:STDOUT:   assign %b, %.loc8_30.10
+// CHECK:STDOUT:   %.loc8_30.2: (f64, f64) = tuple_literal (%.loc8_20, %.loc8_26)
+// CHECK:STDOUT:   %.loc8_30.3: i32 = int_literal 0
+// CHECK:STDOUT:   %.loc8_30.4: ref f64 = array_index %b, %.loc8_30.3
+// CHECK:STDOUT:   %.loc8_30.5: init f64 = initialize_from %.loc8_20 to %.loc8_30.4
+// CHECK:STDOUT:   %.loc8_30.6: i32 = int_literal 1
+// CHECK:STDOUT:   %.loc8_30.7: ref f64 = array_index %b, %.loc8_30.6
+// CHECK:STDOUT:   %.loc8_30.8: init f64 = initialize_from %.loc8_26 to %.loc8_30.7
+// CHECK:STDOUT:   %.loc8_30.9: init [f64; 2] = array_init %.loc8_30.2, (%.loc8_30.5, %.loc8_30.8) to %b
+// CHECK:STDOUT:   assign %b, %.loc8_30.9
 // CHECK:STDOUT:   %.loc9_10.1: type = tuple_type ()
 // CHECK:STDOUT:   %.loc9_10.2: () = tuple_literal ()
 // CHECK:STDOUT:   %.loc9_13: i32 = int_literal 5
@@ -50,13 +49,12 @@ var c: [(); 5] = ((), (), (), (), (),);
 // CHECK:STDOUT:   %.loc9_32.1: () = tuple_literal ()
 // CHECK:STDOUT:   %.loc9_36.1: () = tuple_literal ()
 // CHECK:STDOUT:   %.loc9_38.1: type = tuple_type ((), (), (), (), ())
-// CHECK:STDOUT:   %.loc9_38.2: type = ptr_type ((), (), (), (), ())
-// CHECK:STDOUT:   %.loc9_38.3: ((), (), (), (), ()) = tuple_literal (%.loc9_20.1, %.loc9_24.1, %.loc9_28.1, %.loc9_32.1, %.loc9_36.1)
+// CHECK:STDOUT:   %.loc9_38.2: ((), (), (), (), ()) = tuple_literal (%.loc9_20.1, %.loc9_24.1, %.loc9_28.1, %.loc9_32.1, %.loc9_36.1)
 // CHECK:STDOUT:   %.loc9_20.2: init () = tuple_init %.loc9_20.1, ()
 // CHECK:STDOUT:   %.loc9_24.2: init () = tuple_init %.loc9_24.1, ()
 // CHECK:STDOUT:   %.loc9_28.2: init () = tuple_init %.loc9_28.1, ()
 // CHECK:STDOUT:   %.loc9_32.2: init () = tuple_init %.loc9_32.1, ()
 // CHECK:STDOUT:   %.loc9_36.2: init () = tuple_init %.loc9_36.1, ()
-// CHECK:STDOUT:   %.loc9_38.4: init [(); 5] = array_init %.loc9_38.3, (%.loc9_20.2, %.loc9_24.2, %.loc9_28.2, %.loc9_32.2, %.loc9_36.2) to %c
-// CHECK:STDOUT:   assign %c, %.loc9_38.4
+// CHECK:STDOUT:   %.loc9_38.3: init [(); 5] = array_init %.loc9_38.2, (%.loc9_20.2, %.loc9_24.2, %.loc9_28.2, %.loc9_32.2, %.loc9_36.2) to %c
+// CHECK:STDOUT:   assign %c, %.loc9_38.3
 // CHECK:STDOUT: }

+ 38 - 0
toolchain/check/testdata/array/fail_incomplete_element.carbon

@@ -0,0 +1,38 @@
+// 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
+
+class Incomplete;
+
+// CHECK:STDERR: fail_incomplete_element.carbon:[[@LINE+6]]:22: ERROR: Variable has incomplete type `[Incomplete; 1]`.
+// CHECK:STDERR: var a: [Incomplete; 1];
+// CHECK:STDERR:                      ^
+// CHECK:STDERR: fail_incomplete_element.carbon:[[@LINE-5]]:1: Class was forward declared here.
+// CHECK:STDERR: class Incomplete;
+// CHECK:STDERR: ^
+var a: [Incomplete; 1];
+
+// CHECK:STDERR: fail_incomplete_element.carbon:[[@LINE+3]]:27: ERROR: Cannot implicitly convert from `<error>*` to `Incomplete*`.
+// CHECK:STDERR: var p: Incomplete* = &a[0];
+// CHECK:STDERR:                           ^
+var p: Incomplete* = &a[0];
+
+// CHECK:STDOUT: file "fail_incomplete_element.carbon" {
+// CHECK:STDOUT:   %Incomplete: type = class_declaration @Incomplete
+// CHECK:STDOUT:   %Incomplete.ref.loc15: type = name_reference "Incomplete", %Incomplete
+// CHECK:STDOUT:   %.loc15_21: i32 = int_literal 1
+// CHECK:STDOUT:   %.loc15_22: type = array_type %.loc15_21, Incomplete
+// CHECK:STDOUT:   %a: ref <error> = var "a"
+// CHECK:STDOUT:   %Incomplete.ref.loc20: type = name_reference "Incomplete", %Incomplete
+// CHECK:STDOUT:   %.loc20_18: type = ptr_type Incomplete
+// CHECK:STDOUT:   %p: ref Incomplete* = var "p"
+// CHECK:STDOUT:   %a.ref: ref <error> = name_reference "a", %a
+// CHECK:STDOUT:   %.loc20_25: i32 = int_literal 0
+// CHECK:STDOUT:   %.loc20_22.1: type = ptr_type <error>
+// CHECK:STDOUT:   %.loc20_22.2: <error>* = address_of <error>
+// CHECK:STDOUT:   assign %p, <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Incomplete;

+ 1 - 2
toolchain/check/testdata/array/fail_out_of_bound.carbon

@@ -18,7 +18,6 @@ var a: [i32; 1] = (1, 2, 3);
 // CHECK:STDOUT:   %.loc10_23: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc10_26: i32 = int_literal 3
 // CHECK:STDOUT:   %.loc10_27.1: type = tuple_type (i32, i32, i32)
-// CHECK:STDOUT:   %.loc10_27.2: type = ptr_type (i32, i32, i32)
-// CHECK:STDOUT:   %.loc10_27.3: (i32, i32, i32) = tuple_literal (%.loc10_20, %.loc10_23, %.loc10_26)
+// CHECK:STDOUT:   %.loc10_27.2: (i32, i32, i32) = tuple_literal (%.loc10_20, %.loc10_23, %.loc10_26)
 // CHECK:STDOUT:   assign %a, <error>
 // CHECK:STDOUT: }

+ 10 - 12
toolchain/check/testdata/array/fail_type_mismatch.carbon

@@ -36,16 +36,15 @@ var d: [i32; 3] = t2;
 // CHECK:STDOUT:   %.loc10_23: String = string_literal "Hello"
 // CHECK:STDOUT:   %.loc10_32: String = string_literal "World"
 // CHECK:STDOUT:   %.loc10_39.1: type = tuple_type (i32, String, String)
-// CHECK:STDOUT:   %.loc10_39.2: type = tuple_type (i32, String*, String*)
-// CHECK:STDOUT:   %.loc10_39.3: type = ptr_type (i32, String*, String*)
-// CHECK:STDOUT:   %.loc10_39.4: (i32, String, String) = tuple_literal (%.loc10_20, %.loc10_23, %.loc10_32)
-// CHECK:STDOUT:   %.loc10_39.5: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc10_39.6: ref i32 = array_index %a, %.loc10_39.5
-// CHECK:STDOUT:   %.loc10_39.7: init i32 = initialize_from %.loc10_20 to %.loc10_39.6
+// CHECK:STDOUT:   %.loc10_39.2: (i32, String, String) = tuple_literal (%.loc10_20, %.loc10_23, %.loc10_32)
+// CHECK:STDOUT:   %.loc10_39.3: i32 = int_literal 0
+// CHECK:STDOUT:   %.loc10_39.4: ref i32 = array_index %a, %.loc10_39.3
+// CHECK:STDOUT:   %.loc10_39.5: init i32 = initialize_from %.loc10_20 to %.loc10_39.4
 // CHECK:STDOUT:   assign %a, <error>
 // CHECK:STDOUT:   %.loc12_29.1: type = tuple_type (type, type, type)
-// CHECK:STDOUT:   %.loc12_29.2: type = ptr_type (type, type, type)
-// CHECK:STDOUT:   %.loc12_29.3: (type, type, type) = tuple_literal (i32, String, String)
+// CHECK:STDOUT:   %.loc12_29.2: (type, type, type) = tuple_literal (i32, String, String)
+// CHECK:STDOUT:   %.loc10_39.6: type = tuple_type (i32, String*, String*)
+// CHECK:STDOUT:   %.loc10_39.7: type = ptr_type (i32, String*, String*)
 // CHECK:STDOUT:   %t1: ref (i32, String, String) = var "t1"
 // CHECK:STDOUT:   %.loc16_14: i32 = int_literal 3
 // CHECK:STDOUT:   %.loc16_15: type = array_type %.loc16_14, i32
@@ -64,12 +63,11 @@ var d: [i32; 3] = t2;
 // CHECK:STDOUT:   %.loc21_20: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc21_23: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc21_24.1: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc21_24.2: type = ptr_type (i32, i32)
-// CHECK:STDOUT:   %.loc21_24.3: (i32, i32) = tuple_literal (%.loc21_20, %.loc21_23)
+// CHECK:STDOUT:   %.loc21_24.2: (i32, i32) = tuple_literal (%.loc21_20, %.loc21_23)
 // CHECK:STDOUT:   assign %c, <error>
 // CHECK:STDOUT:   %.loc23_18.1: type = tuple_type (type, type)
-// CHECK:STDOUT:   %.loc23_18.2: type = ptr_type (type, type)
-// CHECK:STDOUT:   %.loc23_18.3: (type, type) = tuple_literal (i32, i32)
+// CHECK:STDOUT:   %.loc23_18.2: (type, type) = tuple_literal (i32, i32)
+// CHECK:STDOUT:   %.loc21_24.3: type = ptr_type (i32, i32)
 // CHECK:STDOUT:   %t2: ref (i32, i32) = var "t2"
 // CHECK:STDOUT:   %.loc27_14: i32 = int_literal 3
 // CHECK:STDOUT:   %.loc27_15: type = array_type %.loc27_14, i32

+ 16 - 16
toolchain/check/testdata/array/function_param.carbon

@@ -19,6 +19,7 @@ fn G() -> i32 {
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F(%arr: [i32; 3], %i: i32) -> i32 {
 // CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc7: type = ptr_type [i32; 3]
 // CHECK:STDOUT:   %arr.ref: [i32; 3] = name_reference "arr", %arr
 // CHECK:STDOUT:   %i.ref: i32 = name_reference "i", %i
 // CHECK:STDOUT:   %.loc8_15.1: ref [i32; 3] = value_as_reference %arr.ref
@@ -34,23 +35,22 @@ fn G() -> i32 {
 // CHECK:STDOUT:   %.loc12_16: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc12_19: i32 = int_literal 3
 // CHECK:STDOUT:   %.loc12_20.1: type = tuple_type (i32, i32, i32)
-// CHECK:STDOUT:   %.loc12_20.2: type = ptr_type (i32, i32, i32)
-// CHECK:STDOUT:   %.loc12_20.3: (i32, i32, i32) = tuple_literal (%.loc12_13, %.loc12_16, %.loc12_19)
+// CHECK:STDOUT:   %.loc12_20.2: (i32, i32, i32) = tuple_literal (%.loc12_13, %.loc12_16, %.loc12_19)
 // CHECK:STDOUT:   %.loc12_23: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc12_20.4: ref [i32; 3] = temporary_storage
-// CHECK:STDOUT:   %.loc12_20.5: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc12_20.6: ref i32 = array_index %.loc12_20.4, %.loc12_20.5
-// CHECK:STDOUT:   %.loc12_20.7: init i32 = initialize_from %.loc12_13 to %.loc12_20.6
-// CHECK:STDOUT:   %.loc12_20.8: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc12_20.9: ref i32 = array_index %.loc12_20.4, %.loc12_20.8
-// CHECK:STDOUT:   %.loc12_20.10: init i32 = initialize_from %.loc12_16 to %.loc12_20.9
-// CHECK:STDOUT:   %.loc12_20.11: i32 = int_literal 2
-// CHECK:STDOUT:   %.loc12_20.12: ref i32 = array_index %.loc12_20.4, %.loc12_20.11
-// CHECK:STDOUT:   %.loc12_20.13: init i32 = initialize_from %.loc12_19 to %.loc12_20.12
-// CHECK:STDOUT:   %.loc12_20.14: init [i32; 3] = array_init %.loc12_20.3, (%.loc12_20.7, %.loc12_20.10, %.loc12_20.13) to %.loc12_20.4
-// CHECK:STDOUT:   %.loc12_20.15: ref [i32; 3] = temporary %.loc12_20.4, %.loc12_20.14
-// CHECK:STDOUT:   %.loc12_20.16: [i32; 3] = bind_value %.loc12_20.15
-// CHECK:STDOUT:   %.loc12_11.1: init i32 = call %F.ref(%.loc12_20.16, %.loc12_23)
+// CHECK:STDOUT:   %.loc12_20.3: ref [i32; 3] = temporary_storage
+// CHECK:STDOUT:   %.loc12_20.4: i32 = int_literal 0
+// CHECK:STDOUT:   %.loc12_20.5: ref i32 = array_index %.loc12_20.3, %.loc12_20.4
+// CHECK:STDOUT:   %.loc12_20.6: init i32 = initialize_from %.loc12_13 to %.loc12_20.5
+// CHECK:STDOUT:   %.loc12_20.7: i32 = int_literal 1
+// CHECK:STDOUT:   %.loc12_20.8: ref i32 = array_index %.loc12_20.3, %.loc12_20.7
+// CHECK:STDOUT:   %.loc12_20.9: init i32 = initialize_from %.loc12_16 to %.loc12_20.8
+// CHECK:STDOUT:   %.loc12_20.10: i32 = int_literal 2
+// CHECK:STDOUT:   %.loc12_20.11: ref i32 = array_index %.loc12_20.3, %.loc12_20.10
+// CHECK:STDOUT:   %.loc12_20.12: init i32 = initialize_from %.loc12_19 to %.loc12_20.11
+// CHECK:STDOUT:   %.loc12_20.13: init [i32; 3] = array_init %.loc12_20.2, (%.loc12_20.6, %.loc12_20.9, %.loc12_20.12) to %.loc12_20.3
+// CHECK:STDOUT:   %.loc12_20.14: ref [i32; 3] = temporary %.loc12_20.3, %.loc12_20.13
+// CHECK:STDOUT:   %.loc12_20.15: [i32; 3] = bind_value %.loc12_20.14
+// CHECK:STDOUT:   %.loc12_11.1: init i32 = call %F.ref(%.loc12_20.15, %.loc12_23)
 // CHECK:STDOUT:   %.loc12_11.2: ref i32 = temporary_storage
 // CHECK:STDOUT:   %.loc12_11.3: ref i32 = temporary %.loc12_11.2, %.loc12_11.1
 // CHECK:STDOUT:   %.loc12_11.4: i32 = bind_value %.loc12_11.3

+ 30 - 31
toolchain/check/testdata/array/nine_elements.carbon

@@ -21,35 +21,34 @@ var a: [i32; 9] = (1, 2, 3, 4, 5, 6, 7, 8, 9);
 // CHECK:STDOUT:   %.loc7_41: i32 = int_literal 8
 // CHECK:STDOUT:   %.loc7_44: i32 = int_literal 9
 // CHECK:STDOUT:   %.loc7_45.1: type = tuple_type (i32, i32, i32, i32, i32, i32, i32, i32, i32)
-// CHECK:STDOUT:   %.loc7_45.2: type = ptr_type (i32, i32, i32, i32, i32, i32, i32, i32, i32)
-// CHECK:STDOUT:   %.loc7_45.3: (i32, i32, i32, i32, i32, i32, i32, i32, i32) = tuple_literal (%.loc7_20, %.loc7_23, %.loc7_26, %.loc7_29, %.loc7_32, %.loc7_35, %.loc7_38, %.loc7_41, %.loc7_44)
-// CHECK:STDOUT:   %.loc7_45.4: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc7_45.5: ref i32 = array_index %a, %.loc7_45.4
-// CHECK:STDOUT:   %.loc7_45.6: init i32 = initialize_from %.loc7_20 to %.loc7_45.5
-// CHECK:STDOUT:   %.loc7_45.7: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc7_45.8: ref i32 = array_index %a, %.loc7_45.7
-// CHECK:STDOUT:   %.loc7_45.9: init i32 = initialize_from %.loc7_23 to %.loc7_45.8
-// CHECK:STDOUT:   %.loc7_45.10: i32 = int_literal 2
-// CHECK:STDOUT:   %.loc7_45.11: ref i32 = array_index %a, %.loc7_45.10
-// CHECK:STDOUT:   %.loc7_45.12: init i32 = initialize_from %.loc7_26 to %.loc7_45.11
-// CHECK:STDOUT:   %.loc7_45.13: i32 = int_literal 3
-// CHECK:STDOUT:   %.loc7_45.14: ref i32 = array_index %a, %.loc7_45.13
-// CHECK:STDOUT:   %.loc7_45.15: init i32 = initialize_from %.loc7_29 to %.loc7_45.14
-// CHECK:STDOUT:   %.loc7_45.16: i32 = int_literal 4
-// CHECK:STDOUT:   %.loc7_45.17: ref i32 = array_index %a, %.loc7_45.16
-// CHECK:STDOUT:   %.loc7_45.18: init i32 = initialize_from %.loc7_32 to %.loc7_45.17
-// CHECK:STDOUT:   %.loc7_45.19: i32 = int_literal 5
-// CHECK:STDOUT:   %.loc7_45.20: ref i32 = array_index %a, %.loc7_45.19
-// CHECK:STDOUT:   %.loc7_45.21: init i32 = initialize_from %.loc7_35 to %.loc7_45.20
-// CHECK:STDOUT:   %.loc7_45.22: i32 = int_literal 6
-// CHECK:STDOUT:   %.loc7_45.23: ref i32 = array_index %a, %.loc7_45.22
-// CHECK:STDOUT:   %.loc7_45.24: init i32 = initialize_from %.loc7_38 to %.loc7_45.23
-// CHECK:STDOUT:   %.loc7_45.25: i32 = int_literal 7
-// CHECK:STDOUT:   %.loc7_45.26: ref i32 = array_index %a, %.loc7_45.25
-// CHECK:STDOUT:   %.loc7_45.27: init i32 = initialize_from %.loc7_41 to %.loc7_45.26
-// CHECK:STDOUT:   %.loc7_45.28: i32 = int_literal 8
-// CHECK:STDOUT:   %.loc7_45.29: ref i32 = array_index %a, %.loc7_45.28
-// CHECK:STDOUT:   %.loc7_45.30: init i32 = initialize_from %.loc7_44 to %.loc7_45.29
-// CHECK:STDOUT:   %.loc7_45.31: init [i32; 9] = array_init %.loc7_45.3, (%.loc7_45.6, %.loc7_45.9, %.loc7_45.12, %.loc7_45.15, %.loc7_45.18, %.loc7_45.21, %.loc7_45.24, %.loc7_45.27, %.loc7_45.30) to %a
-// CHECK:STDOUT:   assign %a, %.loc7_45.31
+// CHECK:STDOUT:   %.loc7_45.2: (i32, i32, i32, i32, i32, i32, i32, i32, i32) = tuple_literal (%.loc7_20, %.loc7_23, %.loc7_26, %.loc7_29, %.loc7_32, %.loc7_35, %.loc7_38, %.loc7_41, %.loc7_44)
+// CHECK:STDOUT:   %.loc7_45.3: i32 = int_literal 0
+// CHECK:STDOUT:   %.loc7_45.4: ref i32 = array_index %a, %.loc7_45.3
+// CHECK:STDOUT:   %.loc7_45.5: init i32 = initialize_from %.loc7_20 to %.loc7_45.4
+// CHECK:STDOUT:   %.loc7_45.6: i32 = int_literal 1
+// CHECK:STDOUT:   %.loc7_45.7: ref i32 = array_index %a, %.loc7_45.6
+// CHECK:STDOUT:   %.loc7_45.8: init i32 = initialize_from %.loc7_23 to %.loc7_45.7
+// CHECK:STDOUT:   %.loc7_45.9: i32 = int_literal 2
+// CHECK:STDOUT:   %.loc7_45.10: ref i32 = array_index %a, %.loc7_45.9
+// CHECK:STDOUT:   %.loc7_45.11: init i32 = initialize_from %.loc7_26 to %.loc7_45.10
+// CHECK:STDOUT:   %.loc7_45.12: i32 = int_literal 3
+// CHECK:STDOUT:   %.loc7_45.13: ref i32 = array_index %a, %.loc7_45.12
+// CHECK:STDOUT:   %.loc7_45.14: init i32 = initialize_from %.loc7_29 to %.loc7_45.13
+// CHECK:STDOUT:   %.loc7_45.15: i32 = int_literal 4
+// CHECK:STDOUT:   %.loc7_45.16: ref i32 = array_index %a, %.loc7_45.15
+// CHECK:STDOUT:   %.loc7_45.17: init i32 = initialize_from %.loc7_32 to %.loc7_45.16
+// CHECK:STDOUT:   %.loc7_45.18: i32 = int_literal 5
+// CHECK:STDOUT:   %.loc7_45.19: ref i32 = array_index %a, %.loc7_45.18
+// CHECK:STDOUT:   %.loc7_45.20: init i32 = initialize_from %.loc7_35 to %.loc7_45.19
+// CHECK:STDOUT:   %.loc7_45.21: i32 = int_literal 6
+// CHECK:STDOUT:   %.loc7_45.22: ref i32 = array_index %a, %.loc7_45.21
+// CHECK:STDOUT:   %.loc7_45.23: init i32 = initialize_from %.loc7_38 to %.loc7_45.22
+// CHECK:STDOUT:   %.loc7_45.24: i32 = int_literal 7
+// CHECK:STDOUT:   %.loc7_45.25: ref i32 = array_index %a, %.loc7_45.24
+// CHECK:STDOUT:   %.loc7_45.26: init i32 = initialize_from %.loc7_41 to %.loc7_45.25
+// CHECK:STDOUT:   %.loc7_45.27: i32 = int_literal 8
+// CHECK:STDOUT:   %.loc7_45.28: ref i32 = array_index %a, %.loc7_45.27
+// CHECK:STDOUT:   %.loc7_45.29: init i32 = initialize_from %.loc7_44 to %.loc7_45.28
+// CHECK:STDOUT:   %.loc7_45.30: init [i32; 9] = array_init %.loc7_45.2, (%.loc7_45.5, %.loc7_45.8, %.loc7_45.11, %.loc7_45.14, %.loc7_45.17, %.loc7_45.20, %.loc7_45.23, %.loc7_45.26, %.loc7_45.29) to %a
+// CHECK:STDOUT:   assign %a, %.loc7_45.30
 // CHECK:STDOUT: }

+ 1 - 0
toolchain/check/testdata/basics/fail_bad_run.carbon

@@ -13,6 +13,7 @@
 fn Run() -> String {}
 
 // CHECK:STDOUT: file "fail_bad_run.carbon" {
+// CHECK:STDOUT:   %.1: type = ptr_type String
 // CHECK:STDOUT:   %Run: <function> = fn_decl @Run
 // CHECK:STDOUT:   %.loc13: type = tuple_type ()
 // CHECK:STDOUT: }

+ 42 - 44
toolchain/check/testdata/basics/numeric_literals.carbon

@@ -41,25 +41,24 @@ fn F() {
 // CHECK:STDOUT:   %.loc14: i32 = int_literal 8
 // CHECK:STDOUT:   %.loc15: i32 = int_literal 39999999999999999993
 // CHECK:STDOUT:   %.loc16_3.1: type = tuple_type (i32, i32, i32, i32, i32)
-// CHECK:STDOUT:   %.loc16_3.2: type = ptr_type (i32, i32, i32, i32, i32)
-// CHECK:STDOUT:   %.loc16_3.3: (i32, i32, i32, i32, i32) = tuple_literal (%.loc11, %.loc12, %.loc13, %.loc14, %.loc15)
-// CHECK:STDOUT:   %.loc16_3.4: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc16_3.5: ref i32 = array_index %ints, %.loc16_3.4
-// CHECK:STDOUT:   %.loc16_3.6: init i32 = initialize_from %.loc11 to %.loc16_3.5
-// CHECK:STDOUT:   %.loc16_3.7: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc16_3.8: ref i32 = array_index %ints, %.loc16_3.7
-// CHECK:STDOUT:   %.loc16_3.9: init i32 = initialize_from %.loc12 to %.loc16_3.8
-// CHECK:STDOUT:   %.loc16_3.10: i32 = int_literal 2
-// CHECK:STDOUT:   %.loc16_3.11: ref i32 = array_index %ints, %.loc16_3.10
-// CHECK:STDOUT:   %.loc16_3.12: init i32 = initialize_from %.loc13 to %.loc16_3.11
-// CHECK:STDOUT:   %.loc16_3.13: i32 = int_literal 3
-// CHECK:STDOUT:   %.loc16_3.14: ref i32 = array_index %ints, %.loc16_3.13
-// CHECK:STDOUT:   %.loc16_3.15: init i32 = initialize_from %.loc14 to %.loc16_3.14
-// CHECK:STDOUT:   %.loc16_3.16: i32 = int_literal 4
-// CHECK:STDOUT:   %.loc16_3.17: ref i32 = array_index %ints, %.loc16_3.16
-// CHECK:STDOUT:   %.loc16_3.18: init i32 = initialize_from %.loc15 to %.loc16_3.17
-// CHECK:STDOUT:   %.loc16_3.19: init [i32; 5] = array_init %.loc16_3.3, (%.loc16_3.6, %.loc16_3.9, %.loc16_3.12, %.loc16_3.15, %.loc16_3.18) to %ints
-// CHECK:STDOUT:   assign %ints, %.loc16_3.19
+// CHECK:STDOUT:   %.loc16_3.2: (i32, i32, i32, i32, i32) = tuple_literal (%.loc11, %.loc12, %.loc13, %.loc14, %.loc15)
+// CHECK:STDOUT:   %.loc16_3.3: i32 = int_literal 0
+// CHECK:STDOUT:   %.loc16_3.4: ref i32 = array_index %ints, %.loc16_3.3
+// CHECK:STDOUT:   %.loc16_3.5: init i32 = initialize_from %.loc11 to %.loc16_3.4
+// CHECK:STDOUT:   %.loc16_3.6: i32 = int_literal 1
+// CHECK:STDOUT:   %.loc16_3.7: ref i32 = array_index %ints, %.loc16_3.6
+// CHECK:STDOUT:   %.loc16_3.8: init i32 = initialize_from %.loc12 to %.loc16_3.7
+// CHECK:STDOUT:   %.loc16_3.9: i32 = int_literal 2
+// CHECK:STDOUT:   %.loc16_3.10: ref i32 = array_index %ints, %.loc16_3.9
+// CHECK:STDOUT:   %.loc16_3.11: init i32 = initialize_from %.loc13 to %.loc16_3.10
+// CHECK:STDOUT:   %.loc16_3.12: i32 = int_literal 3
+// CHECK:STDOUT:   %.loc16_3.13: ref i32 = array_index %ints, %.loc16_3.12
+// CHECK:STDOUT:   %.loc16_3.14: init i32 = initialize_from %.loc14 to %.loc16_3.13
+// CHECK:STDOUT:   %.loc16_3.15: i32 = int_literal 4
+// CHECK:STDOUT:   %.loc16_3.16: ref i32 = array_index %ints, %.loc16_3.15
+// CHECK:STDOUT:   %.loc16_3.17: init i32 = initialize_from %.loc15 to %.loc16_3.16
+// CHECK:STDOUT:   %.loc16_3.18: init [i32; 5] = array_init %.loc16_3.2, (%.loc16_3.5, %.loc16_3.8, %.loc16_3.11, %.loc16_3.14, %.loc16_3.17) to %ints
+// CHECK:STDOUT:   assign %ints, %.loc16_3.18
 // CHECK:STDOUT:   %.loc17_21: i32 = int_literal 7
 // CHECK:STDOUT:   %.loc17_22.1: type = array_type %.loc17_21, f64
 // CHECK:STDOUT:   %.loc17_22.2: type = ptr_type [f64; 7]
@@ -72,30 +71,29 @@ fn F() {
 // CHECK:STDOUT:   %.loc23: f64 = real_literal 10e-9
 // CHECK:STDOUT:   %.loc24: f64 = real_literal 399999999999999999930e39999999999999999992
 // CHECK:STDOUT:   %.loc25_3.1: type = tuple_type (f64, f64, f64, f64, f64, f64, f64)
-// CHECK:STDOUT:   %.loc25_3.2: type = ptr_type (f64, f64, f64, f64, f64, f64, f64)
-// CHECK:STDOUT:   %.loc25_3.3: (f64, f64, f64, f64, f64, f64, f64) = tuple_literal (%.loc18, %.loc19, %.loc20, %.loc21, %.loc22, %.loc23, %.loc24)
-// CHECK:STDOUT:   %.loc25_3.4: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc25_3.5: ref f64 = array_index %floats, %.loc25_3.4
-// CHECK:STDOUT:   %.loc25_3.6: init f64 = initialize_from %.loc18 to %.loc25_3.5
-// CHECK:STDOUT:   %.loc25_3.7: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc25_3.8: ref f64 = array_index %floats, %.loc25_3.7
-// CHECK:STDOUT:   %.loc25_3.9: init f64 = initialize_from %.loc19 to %.loc25_3.8
-// CHECK:STDOUT:   %.loc25_3.10: i32 = int_literal 2
-// CHECK:STDOUT:   %.loc25_3.11: ref f64 = array_index %floats, %.loc25_3.10
-// CHECK:STDOUT:   %.loc25_3.12: init f64 = initialize_from %.loc20 to %.loc25_3.11
-// CHECK:STDOUT:   %.loc25_3.13: i32 = int_literal 3
-// CHECK:STDOUT:   %.loc25_3.14: ref f64 = array_index %floats, %.loc25_3.13
-// CHECK:STDOUT:   %.loc25_3.15: init f64 = initialize_from %.loc21 to %.loc25_3.14
-// CHECK:STDOUT:   %.loc25_3.16: i32 = int_literal 4
-// CHECK:STDOUT:   %.loc25_3.17: ref f64 = array_index %floats, %.loc25_3.16
-// CHECK:STDOUT:   %.loc25_3.18: init f64 = initialize_from %.loc22 to %.loc25_3.17
-// CHECK:STDOUT:   %.loc25_3.19: i32 = int_literal 5
-// CHECK:STDOUT:   %.loc25_3.20: ref f64 = array_index %floats, %.loc25_3.19
-// CHECK:STDOUT:   %.loc25_3.21: init f64 = initialize_from %.loc23 to %.loc25_3.20
-// CHECK:STDOUT:   %.loc25_3.22: i32 = int_literal 6
-// CHECK:STDOUT:   %.loc25_3.23: ref f64 = array_index %floats, %.loc25_3.22
-// CHECK:STDOUT:   %.loc25_3.24: init f64 = initialize_from %.loc24 to %.loc25_3.23
-// CHECK:STDOUT:   %.loc25_3.25: init [f64; 7] = array_init %.loc25_3.3, (%.loc25_3.6, %.loc25_3.9, %.loc25_3.12, %.loc25_3.15, %.loc25_3.18, %.loc25_3.21, %.loc25_3.24) to %floats
-// CHECK:STDOUT:   assign %floats, %.loc25_3.25
+// CHECK:STDOUT:   %.loc25_3.2: (f64, f64, f64, f64, f64, f64, f64) = tuple_literal (%.loc18, %.loc19, %.loc20, %.loc21, %.loc22, %.loc23, %.loc24)
+// CHECK:STDOUT:   %.loc25_3.3: i32 = int_literal 0
+// CHECK:STDOUT:   %.loc25_3.4: ref f64 = array_index %floats, %.loc25_3.3
+// CHECK:STDOUT:   %.loc25_3.5: init f64 = initialize_from %.loc18 to %.loc25_3.4
+// CHECK:STDOUT:   %.loc25_3.6: i32 = int_literal 1
+// CHECK:STDOUT:   %.loc25_3.7: ref f64 = array_index %floats, %.loc25_3.6
+// CHECK:STDOUT:   %.loc25_3.8: init f64 = initialize_from %.loc19 to %.loc25_3.7
+// CHECK:STDOUT:   %.loc25_3.9: i32 = int_literal 2
+// CHECK:STDOUT:   %.loc25_3.10: ref f64 = array_index %floats, %.loc25_3.9
+// CHECK:STDOUT:   %.loc25_3.11: init f64 = initialize_from %.loc20 to %.loc25_3.10
+// CHECK:STDOUT:   %.loc25_3.12: i32 = int_literal 3
+// CHECK:STDOUT:   %.loc25_3.13: ref f64 = array_index %floats, %.loc25_3.12
+// CHECK:STDOUT:   %.loc25_3.14: init f64 = initialize_from %.loc21 to %.loc25_3.13
+// CHECK:STDOUT:   %.loc25_3.15: i32 = int_literal 4
+// CHECK:STDOUT:   %.loc25_3.16: ref f64 = array_index %floats, %.loc25_3.15
+// CHECK:STDOUT:   %.loc25_3.17: init f64 = initialize_from %.loc22 to %.loc25_3.16
+// CHECK:STDOUT:   %.loc25_3.18: i32 = int_literal 5
+// CHECK:STDOUT:   %.loc25_3.19: ref f64 = array_index %floats, %.loc25_3.18
+// CHECK:STDOUT:   %.loc25_3.20: init f64 = initialize_from %.loc23 to %.loc25_3.19
+// CHECK:STDOUT:   %.loc25_3.21: i32 = int_literal 6
+// CHECK:STDOUT:   %.loc25_3.22: ref f64 = array_index %floats, %.loc25_3.21
+// CHECK:STDOUT:   %.loc25_3.23: init f64 = initialize_from %.loc24 to %.loc25_3.22
+// CHECK:STDOUT:   %.loc25_3.24: init [f64; 7] = array_init %.loc25_3.2, (%.loc25_3.5, %.loc25_3.8, %.loc25_3.11, %.loc25_3.14, %.loc25_3.17, %.loc25_3.20, %.loc25_3.23) to %floats
+// CHECK:STDOUT:   assign %floats, %.loc25_3.24
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }

+ 26 - 28
toolchain/check/testdata/basics/raw_and_textual_ir.carbon

@@ -16,7 +16,7 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:   sem_ir:
 // CHECK:STDOUT:   - cross_reference_irs_size: 1
 // CHECK:STDOUT:     functions: [
-// CHECK:STDOUT:       {name: str0, param_refs: block1, return_type: type4, return_slot: node+6, body: [block4]},
+// CHECK:STDOUT:       {name: str0, param_refs: block1, return_type: type3, return_slot: node+4, body: [block4]},
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     classes: [
 // CHECK:STDOUT:     ]
@@ -33,12 +33,11 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     types: [
 // CHECK:STDOUT:       {node: nodeIntegerType, value_rep: {kind: copy, type: type0}},
-// CHECK:STDOUT:       {node: node+1, value_rep: {kind: pointer, type: type2}},
-// CHECK:STDOUT:       {node: node+2, value_rep: {kind: copy, type: type2}},
-// CHECK:STDOUT:       {node: nodeFloatingPointType, value_rep: {kind: copy, type: type3}},
-// CHECK:STDOUT:       {node: node+4, value_rep: {kind: pointer, type: type5}},
-// CHECK:STDOUT:       {node: node+5, value_rep: {kind: copy, type: type5}},
-// CHECK:STDOUT:       {node: nodeFunctionType, value_rep: {kind: copy, type: type6}},
+// CHECK:STDOUT:       {node: node+1, value_rep: {kind: unknown, type: type<invalid>}},
+// CHECK:STDOUT:       {node: nodeFloatingPointType, value_rep: {kind: copy, type: type2}},
+// CHECK:STDOUT:       {node: node+3, value_rep: {kind: pointer, type: type4}},
+// CHECK:STDOUT:       {node: node+5, value_rep: {kind: copy, type: type4}},
+// CHECK:STDOUT:       {node: nodeFunctionType, value_rep: {kind: copy, type: type5}},
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     type_blocks: [
 // CHECK:STDOUT:       [
@@ -47,29 +46,28 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
 // CHECK:STDOUT:         type0,
-// CHECK:STDOUT:         type3,
+// CHECK:STDOUT:         type2,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     nodes: [
 // CHECK:STDOUT:       {kind: Parameter, arg0: str1, type: type0},
 // CHECK:STDOUT:       {kind: TupleType, arg0: typeBlock0, type: typeTypeType},
-// CHECK:STDOUT:       {kind: PointerType, arg0: type1, type: typeTypeType},
 // CHECK:STDOUT:       {kind: TupleLiteral, arg0: block2, type: type1},
 // CHECK:STDOUT:       {kind: TupleType, arg0: typeBlock1, type: typeTypeType},
-// CHECK:STDOUT:       {kind: PointerType, arg0: type4, type: typeTypeType},
-// CHECK:STDOUT:       {kind: VarStorage, arg0: str2, type: type4},
-// CHECK:STDOUT:       {kind: FunctionDeclaration, arg0: function0, type: type6},
+// CHECK:STDOUT:       {kind: VarStorage, arg0: str2, type: type3},
+// CHECK:STDOUT:       {kind: PointerType, arg0: type3, type: typeTypeType},
+// CHECK:STDOUT:       {kind: FunctionDeclaration, arg0: function0, type: type5},
 // CHECK:STDOUT:       {kind: NameReference, arg0: str1, arg1: node+0, type: type0},
 // CHECK:STDOUT:       {kind: IntegerLiteral, arg0: int0, type: type0},
-// CHECK:STDOUT:       {kind: BinaryOperatorAdd, arg0: node+8, arg1: node+9, type: type0},
-// CHECK:STDOUT:       {kind: RealLiteral, arg0: real0, type: type3},
-// CHECK:STDOUT:       {kind: TupleLiteral, arg0: block5, type: type4},
-// CHECK:STDOUT:       {kind: TupleAccess, arg0: node+6, arg1: member0, type: type0},
-// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+10, arg1: node+13, type: type0},
-// CHECK:STDOUT:       {kind: TupleAccess, arg0: node+6, arg1: member1, type: type3},
-// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+11, arg1: node+15, type: type3},
-// CHECK:STDOUT:       {kind: TupleInit, arg0: node+12, arg1: block6, type: type4},
-// CHECK:STDOUT:       {kind: ReturnExpression, arg0: node+17},
+// CHECK:STDOUT:       {kind: BinaryOperatorAdd, arg0: node+7, arg1: node+8, type: type0},
+// CHECK:STDOUT:       {kind: RealLiteral, arg0: real0, type: type2},
+// CHECK:STDOUT:       {kind: TupleLiteral, arg0: block5, type: type3},
+// CHECK:STDOUT:       {kind: TupleAccess, arg0: node+4, arg1: member0, type: type0},
+// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+9, arg1: node+12, type: type0},
+// CHECK:STDOUT:       {kind: TupleAccess, arg0: node+4, arg1: member1, type: type2},
+// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+10, arg1: node+14, type: type2},
+// CHECK:STDOUT:       {kind: TupleInit, arg0: node+11, arg1: block6, type: type3},
+// CHECK:STDOUT:       {kind: ReturnExpression, arg0: node+16},
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     node_blocks: [
 // CHECK:STDOUT:       [
@@ -87,10 +85,9 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:         node+2,
 // CHECK:STDOUT:         node+3,
 // CHECK:STDOUT:         node+4,
-// CHECK:STDOUT:         node+5,
-// CHECK:STDOUT:         node+6,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
+// CHECK:STDOUT:         node+7,
 // CHECK:STDOUT:         node+8,
 // CHECK:STDOUT:         node+9,
 // CHECK:STDOUT:         node+10,
@@ -101,22 +98,23 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:         node+15,
 // CHECK:STDOUT:         node+16,
 // CHECK:STDOUT:         node+17,
-// CHECK:STDOUT:         node+18,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
+// CHECK:STDOUT:         node+9,
 // CHECK:STDOUT:         node+10,
-// CHECK:STDOUT:         node+11,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
-// CHECK:STDOUT:         node+14,
-// CHECK:STDOUT:         node+16,
+// CHECK:STDOUT:         node+13,
+// CHECK:STDOUT:         node+15,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
-// CHECK:STDOUT:         node+7,
+// CHECK:STDOUT:         node+5,
+// CHECK:STDOUT:         node+6,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:
 // CHECK:STDOUT: file "raw_and_textual_ir.carbon" {
+// CHECK:STDOUT:   %.loc11: type = ptr_type (i32, f64)
 // CHECK:STDOUT:   %Foo: <function> = fn_decl @Foo
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 25 - 28
toolchain/check/testdata/basics/raw_ir.carbon

@@ -16,7 +16,7 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:   sem_ir:
 // CHECK:STDOUT:   - cross_reference_irs_size: 1
 // CHECK:STDOUT:     functions: [
-// CHECK:STDOUT:       {name: str0, param_refs: block1, return_type: type4, return_slot: node+6, body: [block4]},
+// CHECK:STDOUT:       {name: str0, param_refs: block1, return_type: type3, return_slot: node+4, body: [block4]},
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     classes: [
 // CHECK:STDOUT:     ]
@@ -33,12 +33,11 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     types: [
 // CHECK:STDOUT:       {node: nodeIntegerType, value_rep: {kind: copy, type: type0}},
-// CHECK:STDOUT:       {node: node+1, value_rep: {kind: pointer, type: type2}},
-// CHECK:STDOUT:       {node: node+2, value_rep: {kind: copy, type: type2}},
-// CHECK:STDOUT:       {node: nodeFloatingPointType, value_rep: {kind: copy, type: type3}},
-// CHECK:STDOUT:       {node: node+4, value_rep: {kind: pointer, type: type5}},
-// CHECK:STDOUT:       {node: node+5, value_rep: {kind: copy, type: type5}},
-// CHECK:STDOUT:       {node: nodeFunctionType, value_rep: {kind: copy, type: type6}},
+// CHECK:STDOUT:       {node: node+1, value_rep: {kind: unknown, type: type<invalid>}},
+// CHECK:STDOUT:       {node: nodeFloatingPointType, value_rep: {kind: copy, type: type2}},
+// CHECK:STDOUT:       {node: node+3, value_rep: {kind: pointer, type: type4}},
+// CHECK:STDOUT:       {node: node+5, value_rep: {kind: copy, type: type4}},
+// CHECK:STDOUT:       {node: nodeFunctionType, value_rep: {kind: copy, type: type5}},
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     type_blocks: [
 // CHECK:STDOUT:       [
@@ -47,29 +46,28 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
 // CHECK:STDOUT:         type0,
-// CHECK:STDOUT:         type3,
+// CHECK:STDOUT:         type2,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     nodes: [
 // CHECK:STDOUT:       {kind: Parameter, arg0: str1, type: type0},
 // CHECK:STDOUT:       {kind: TupleType, arg0: typeBlock0, type: typeTypeType},
-// CHECK:STDOUT:       {kind: PointerType, arg0: type1, type: typeTypeType},
 // CHECK:STDOUT:       {kind: TupleLiteral, arg0: block2, type: type1},
 // CHECK:STDOUT:       {kind: TupleType, arg0: typeBlock1, type: typeTypeType},
-// CHECK:STDOUT:       {kind: PointerType, arg0: type4, type: typeTypeType},
-// CHECK:STDOUT:       {kind: VarStorage, arg0: str2, type: type4},
-// CHECK:STDOUT:       {kind: FunctionDeclaration, arg0: function0, type: type6},
+// CHECK:STDOUT:       {kind: VarStorage, arg0: str2, type: type3},
+// CHECK:STDOUT:       {kind: PointerType, arg0: type3, type: typeTypeType},
+// CHECK:STDOUT:       {kind: FunctionDeclaration, arg0: function0, type: type5},
 // CHECK:STDOUT:       {kind: NameReference, arg0: str1, arg1: node+0, type: type0},
 // CHECK:STDOUT:       {kind: IntegerLiteral, arg0: int0, type: type0},
-// CHECK:STDOUT:       {kind: BinaryOperatorAdd, arg0: node+8, arg1: node+9, type: type0},
-// CHECK:STDOUT:       {kind: RealLiteral, arg0: real0, type: type3},
-// CHECK:STDOUT:       {kind: TupleLiteral, arg0: block5, type: type4},
-// CHECK:STDOUT:       {kind: TupleAccess, arg0: node+6, arg1: member0, type: type0},
-// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+10, arg1: node+13, type: type0},
-// CHECK:STDOUT:       {kind: TupleAccess, arg0: node+6, arg1: member1, type: type3},
-// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+11, arg1: node+15, type: type3},
-// CHECK:STDOUT:       {kind: TupleInit, arg0: node+12, arg1: block6, type: type4},
-// CHECK:STDOUT:       {kind: ReturnExpression, arg0: node+17},
+// CHECK:STDOUT:       {kind: BinaryOperatorAdd, arg0: node+7, arg1: node+8, type: type0},
+// CHECK:STDOUT:       {kind: RealLiteral, arg0: real0, type: type2},
+// CHECK:STDOUT:       {kind: TupleLiteral, arg0: block5, type: type3},
+// CHECK:STDOUT:       {kind: TupleAccess, arg0: node+4, arg1: member0, type: type0},
+// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+9, arg1: node+12, type: type0},
+// CHECK:STDOUT:       {kind: TupleAccess, arg0: node+4, arg1: member1, type: type2},
+// CHECK:STDOUT:       {kind: InitializeFrom, arg0: node+10, arg1: node+14, type: type2},
+// CHECK:STDOUT:       {kind: TupleInit, arg0: node+11, arg1: block6, type: type3},
+// CHECK:STDOUT:       {kind: ReturnExpression, arg0: node+16},
 // CHECK:STDOUT:     ]
 // CHECK:STDOUT:     node_blocks: [
 // CHECK:STDOUT:       [
@@ -87,10 +85,9 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:         node+2,
 // CHECK:STDOUT:         node+3,
 // CHECK:STDOUT:         node+4,
-// CHECK:STDOUT:         node+5,
-// CHECK:STDOUT:         node+6,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
+// CHECK:STDOUT:         node+7,
 // CHECK:STDOUT:         node+8,
 // CHECK:STDOUT:         node+9,
 // CHECK:STDOUT:         node+10,
@@ -101,17 +98,17 @@ fn Foo(n: i32) -> (i32, f64) {
 // CHECK:STDOUT:         node+15,
 // CHECK:STDOUT:         node+16,
 // CHECK:STDOUT:         node+17,
-// CHECK:STDOUT:         node+18,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
+// CHECK:STDOUT:         node+9,
 // CHECK:STDOUT:         node+10,
-// CHECK:STDOUT:         node+11,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
-// CHECK:STDOUT:         node+14,
-// CHECK:STDOUT:         node+16,
+// CHECK:STDOUT:         node+13,
+// CHECK:STDOUT:         node+15,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:       [
-// CHECK:STDOUT:         node+7,
+// CHECK:STDOUT:         node+5,
+// CHECK:STDOUT:         node+6,
 // CHECK:STDOUT:       ],
 // CHECK:STDOUT:     ]

+ 1 - 0
toolchain/check/testdata/basics/textual_ir.carbon

@@ -13,6 +13,7 @@ fn Foo(n: i32) -> (i32, f64) {
 }
 
 // CHECK:STDOUT: file "textual_ir.carbon" {
+// CHECK:STDOUT:   %.loc11: type = ptr_type (i32, f64)
 // CHECK:STDOUT:   %Foo: <function> = fn_decl @Foo
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 169 - 0
toolchain/check/testdata/class/fail_incomplete.carbon

@@ -0,0 +1,169 @@
+// 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
+
+class Class;
+
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:17: ERROR: Variable has incomplete type `Class`.
+// CHECK:STDERR: var global_var: Class;
+// CHECK:STDERR:                 ^
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-5]]:1: Class was forward declared here.
+// CHECK:STDERR: class Class;
+// CHECK:STDERR: ^
+var global_var: Class;
+
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:24: ERROR: Function returns incomplete type `Class`.
+// CHECK:STDERR: fn ConvertFromStruct() -> Class { return {}; }
+// CHECK:STDERR:                        ^
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-13]]:1: Class was forward declared here.
+// CHECK:STDERR: class Class;
+// CHECK:STDERR: ^
+fn ConvertFromStruct() -> Class { return {}; }
+
+// TODO: Once the `->` operator is supported:
+// TODO: fn G(p: Class*) -> i32 {
+// TODO:   return p->n;
+// TODO: }
+
+fn MemberAccess(p: Class*) -> i32 {
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+9]]:11: ERROR: Invalid use of incomplete type `Class`.
+  // CHECK:STDERR:   return (*p).n;
+  // CHECK:STDERR:           ^
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-27]]:1: Class was forward declared here.
+  // CHECK:STDERR: class Class;
+  // CHECK:STDERR: ^
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+3]]:14: ERROR: Type `Class` does not support qualified expressions.
+  // CHECK:STDERR:   return (*p).n;
+  // CHECK:STDERR:              ^
+  return (*p).n;
+}
+
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:20: ERROR: Function returns incomplete type `Class`.
+// CHECK:STDERR: fn Copy(p: Class*) -> Class {
+// CHECK:STDERR:                    ^
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-39]]:1: Class was forward declared here.
+// CHECK:STDERR: class Class;
+// CHECK:STDERR: ^
+fn Copy(p: Class*) -> Class {
+  return *p;
+}
+
+fn Let(p: Class*) {
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:10: ERROR: `let` binding has incomplete type `Class`.
+  // CHECK:STDERR:   let c: Class = *p;
+  // CHECK:STDERR:          ^
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-50]]:1: Class was forward declared here.
+  // CHECK:STDERR: class Class;
+  // CHECK:STDERR: ^
+  let c: Class = *p;
+}
+
+fn TakeIncomplete(c: Class);
+
+// TODO: We should allow this, and only reject calls to the function.
+//
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE+6]]:23: ERROR: Function returns incomplete type `Class`.
+// CHECK:STDERR: fn ReturnIncomplete() -> Class;
+// CHECK:STDERR:                       ^
+// CHECK:STDERR: fail_incomplete.carbon:[[@LINE-63]]:1: Class was forward declared here.
+// CHECK:STDERR: class Class;
+// CHECK:STDERR: ^
+fn ReturnIncomplete() -> Class;
+
+fn CallTakeIncomplete(p: Class*) {
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+9]]:17: ERROR: Forming value of incomplete type `Class`.
+  // CHECK:STDERR:   TakeIncomplete(*p);
+  // CHECK:STDERR:                 ^
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-72]]:1: Class was forward declared here.
+  // CHECK:STDERR: class Class;
+  // CHECK:STDERR: ^
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-19]]:1: Initializing parameter 1 of function declared here.
+  // CHECK:STDERR: fn TakeIncomplete(c: Class);
+  // CHECK:STDERR: ^
+  TakeIncomplete(*p);
+
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE+9]]:17: ERROR: Forming value of incomplete type `Class`.
+  // CHECK:STDERR:   TakeIncomplete({});
+  // CHECK:STDERR:                 ^
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-83]]:1: Class was forward declared here.
+  // CHECK:STDERR: class Class;
+  // CHECK:STDERR: ^
+  // CHECK:STDERR: fail_incomplete.carbon:[[@LINE-30]]:1: Initializing parameter 1 of function declared here.
+  // CHECK:STDERR: fn TakeIncomplete(c: Class);
+  // CHECK:STDERR: ^
+  TakeIncomplete({});
+}
+
+fn CallReturnIncomplete() {
+  ReturnIncomplete();
+}
+
+// CHECK:STDOUT: file "fail_incomplete.carbon" {
+// CHECK:STDOUT:   %Class: type = class_declaration @Class
+// CHECK:STDOUT:   %Class.ref: type = name_reference "Class", %Class
+// CHECK:STDOUT:   %global_var: ref <error> = var "global_var"
+// CHECK:STDOUT:   %ConvertFromStruct: <function> = fn_decl @ConvertFromStruct
+// CHECK:STDOUT:   %MemberAccess: <function> = fn_decl @MemberAccess
+// CHECK:STDOUT:   %Copy: <function> = fn_decl @Copy
+// CHECK:STDOUT:   %Let: <function> = fn_decl @Let
+// CHECK:STDOUT:   %TakeIncomplete: <function> = fn_decl @TakeIncomplete
+// CHECK:STDOUT:   %ReturnIncomplete: <function> = fn_decl @ReturnIncomplete
+// CHECK:STDOUT:   %CallTakeIncomplete: <function> = fn_decl @CallTakeIncomplete
+// CHECK:STDOUT:   %CallReturnIncomplete: <function> = fn_decl @CallReturnIncomplete
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Class;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @ConvertFromStruct() -> <error> {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc23_43.1: type = struct_type {}
+// CHECK:STDOUT:   %.loc23_43.2: {} = struct_literal ()
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MemberAccess(%p: Class*) -> i32 {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %p.ref: Class* = name_reference "p", %p
+// CHECK:STDOUT:   %.loc40: ref Class = dereference %p.ref
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Copy(%p: Class*) -> <error> {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %p.ref: Class* = name_reference "p", %p
+// CHECK:STDOUT:   %.loc50: ref Class = dereference %p.ref
+// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Let(%p: Class*) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Class.ref: type = name_reference "Class", package.%Class
+// CHECK:STDOUT:   %p.ref: Class* = name_reference "p", %p
+// CHECK:STDOUT:   %.loc60: ref Class = dereference %p.ref
+// CHECK:STDOUT:   %c: <error> = bind_name "c", <error>
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @TakeIncomplete(%c: Class);
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @ReturnIncomplete() -> <error>;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @CallTakeIncomplete(%p: Class*) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %TakeIncomplete.ref.loc85: <function> = name_reference "TakeIncomplete", package.%TakeIncomplete
+// CHECK:STDOUT:   %p.ref: Class* = name_reference "p", %p
+// CHECK:STDOUT:   %.loc85_18: ref Class = dereference %p.ref
+// CHECK:STDOUT:   %.loc85_17: type = tuple_type ()
+// CHECK:STDOUT:   %TakeIncomplete.ref.loc96: <function> = name_reference "TakeIncomplete", package.%TakeIncomplete
+// CHECK:STDOUT:   %.loc96: {} = struct_literal ()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @CallReturnIncomplete() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %ReturnIncomplete.ref: <function> = name_reference "ReturnIncomplete", package.%ReturnIncomplete
+// CHECK:STDOUT:   %.loc100: init <error> = call %ReturnIncomplete.ref()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }

+ 1 - 0
toolchain/check/testdata/expression_category/in_place_tuple_initialization.carbon

@@ -17,6 +17,7 @@ fn H() -> i32 {
 }
 
 // CHECK:STDOUT: file "in_place_tuple_initialization.carbon" {
+// CHECK:STDOUT:   %.loc7: type = ptr_type (i32, i32)
 // CHECK:STDOUT:   %F: <function> = fn_decl @F
 // CHECK:STDOUT:   %G: <function> = fn_decl @G
 // CHECK:STDOUT:   %H: <function> = fn_decl @H

+ 1 - 0
toolchain/check/testdata/function/call/empty_struct.carbon

@@ -13,6 +13,7 @@ fn Main() {
 }
 
 // CHECK:STDOUT: file "empty_struct.carbon" {
+// CHECK:STDOUT:   %.loc7: type = tuple_type ()
 // CHECK:STDOUT:   %Echo: <function> = fn_decl @Echo
 // CHECK:STDOUT:   %Main: <function> = fn_decl @Main
 // CHECK:STDOUT: }

+ 1 - 0
toolchain/check/testdata/if_expression/struct.carbon

@@ -21,6 +21,7 @@ fn F(cond: bool) {
 // CHECK:STDOUT: fn @F(%cond: bool) {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %.loc10_27: type = struct_type {.a: i32, .b: i32}
+// CHECK:STDOUT:   %.loc7: type = ptr_type {.a: i32, .b: i32}
 // CHECK:STDOUT:   %a: ref {.a: i32, .b: i32} = var "a"
 // CHECK:STDOUT:   %.loc10_37: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc10_45: i32 = int_literal 2

+ 9 - 10
toolchain/check/testdata/index/array_element_access.carbon

@@ -17,16 +17,15 @@ var d: i32 = a[b];
 // CHECK:STDOUT:   %.loc7_20: i32 = int_literal 12
 // CHECK:STDOUT:   %.loc7_24: i32 = int_literal 24
 // CHECK:STDOUT:   %.loc7_26.1: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc7_26.2: type = ptr_type (i32, i32)
-// CHECK:STDOUT:   %.loc7_26.3: (i32, i32) = tuple_literal (%.loc7_20, %.loc7_24)
-// CHECK:STDOUT:   %.loc7_26.4: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc7_26.5: ref i32 = array_index %a, %.loc7_26.4
-// CHECK:STDOUT:   %.loc7_26.6: init i32 = initialize_from %.loc7_20 to %.loc7_26.5
-// CHECK:STDOUT:   %.loc7_26.7: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc7_26.8: ref i32 = array_index %a, %.loc7_26.7
-// CHECK:STDOUT:   %.loc7_26.9: init i32 = initialize_from %.loc7_24 to %.loc7_26.8
-// CHECK:STDOUT:   %.loc7_26.10: init [i32; 2] = array_init %.loc7_26.3, (%.loc7_26.6, %.loc7_26.9) to %a
-// CHECK:STDOUT:   assign %a, %.loc7_26.10
+// CHECK:STDOUT:   %.loc7_26.2: (i32, i32) = tuple_literal (%.loc7_20, %.loc7_24)
+// CHECK:STDOUT:   %.loc7_26.3: i32 = int_literal 0
+// CHECK:STDOUT:   %.loc7_26.4: ref i32 = array_index %a, %.loc7_26.3
+// CHECK:STDOUT:   %.loc7_26.5: init i32 = initialize_from %.loc7_20 to %.loc7_26.4
+// CHECK:STDOUT:   %.loc7_26.6: i32 = int_literal 1
+// CHECK:STDOUT:   %.loc7_26.7: ref i32 = array_index %a, %.loc7_26.6
+// CHECK:STDOUT:   %.loc7_26.8: init i32 = initialize_from %.loc7_24 to %.loc7_26.7
+// CHECK:STDOUT:   %.loc7_26.9: init [i32; 2] = array_init %.loc7_26.2, (%.loc7_26.5, %.loc7_26.8) to %a
+// CHECK:STDOUT:   assign %a, %.loc7_26.9
 // CHECK:STDOUT:   %b: ref i32 = var "b"
 // CHECK:STDOUT:   %.loc8: i32 = int_literal 1
 // CHECK:STDOUT:   assign %b, %.loc8

+ 13 - 13
toolchain/check/testdata/index/expression_category.carbon

@@ -25,6 +25,7 @@ fn ValueBinding(b: [i32; 3]) {
 }
 
 // CHECK:STDOUT: file "expression_category.carbon" {
+// CHECK:STDOUT:   %.loc7: type = ptr_type [i32; 3]
 // CHECK:STDOUT:   %F: <function> = fn_decl @F
 // CHECK:STDOUT:   %G: <function> = fn_decl @G
 // CHECK:STDOUT:   %ValueBinding: <function> = fn_decl @ValueBinding
@@ -41,19 +42,18 @@ fn ValueBinding(b: [i32; 3]) {
 // CHECK:STDOUT:   %.loc10_25: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc10_28: i32 = int_literal 3
 // CHECK:STDOUT:   %.loc10_29.1: type = tuple_type (i32, i32, i32)
-// CHECK:STDOUT:   %.loc10_29.2: type = ptr_type (i32, i32, i32)
-// CHECK:STDOUT:   %.loc10_29.3: (i32, i32, i32) = tuple_literal (%.loc10_22, %.loc10_25, %.loc10_28)
-// CHECK:STDOUT:   %.loc10_29.4: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc10_29.5: ref i32 = array_index %a, %.loc10_29.4
-// CHECK:STDOUT:   %.loc10_29.6: init i32 = initialize_from %.loc10_22 to %.loc10_29.5
-// CHECK:STDOUT:   %.loc10_29.7: i32 = int_literal 1
-// CHECK:STDOUT:   %.loc10_29.8: ref i32 = array_index %a, %.loc10_29.7
-// CHECK:STDOUT:   %.loc10_29.9: init i32 = initialize_from %.loc10_25 to %.loc10_29.8
-// CHECK:STDOUT:   %.loc10_29.10: i32 = int_literal 2
-// CHECK:STDOUT:   %.loc10_29.11: ref i32 = array_index %a, %.loc10_29.10
-// CHECK:STDOUT:   %.loc10_29.12: init i32 = initialize_from %.loc10_28 to %.loc10_29.11
-// CHECK:STDOUT:   %.loc10_29.13: init [i32; 3] = array_init %.loc10_29.3, (%.loc10_29.6, %.loc10_29.9, %.loc10_29.12) to %a
-// CHECK:STDOUT:   assign %a, %.loc10_29.13
+// CHECK:STDOUT:   %.loc10_29.2: (i32, i32, i32) = tuple_literal (%.loc10_22, %.loc10_25, %.loc10_28)
+// CHECK:STDOUT:   %.loc10_29.3: i32 = int_literal 0
+// CHECK:STDOUT:   %.loc10_29.4: ref i32 = array_index %a, %.loc10_29.3
+// CHECK:STDOUT:   %.loc10_29.5: init i32 = initialize_from %.loc10_22 to %.loc10_29.4
+// CHECK:STDOUT:   %.loc10_29.6: i32 = int_literal 1
+// CHECK:STDOUT:   %.loc10_29.7: ref i32 = array_index %a, %.loc10_29.6
+// CHECK:STDOUT:   %.loc10_29.8: init i32 = initialize_from %.loc10_25 to %.loc10_29.7
+// CHECK:STDOUT:   %.loc10_29.9: i32 = int_literal 2
+// CHECK:STDOUT:   %.loc10_29.10: ref i32 = array_index %a, %.loc10_29.9
+// CHECK:STDOUT:   %.loc10_29.11: init i32 = initialize_from %.loc10_28 to %.loc10_29.10
+// CHECK:STDOUT:   %.loc10_29.12: init [i32; 3] = array_init %.loc10_29.2, (%.loc10_29.5, %.loc10_29.8, %.loc10_29.11) to %a
+// CHECK:STDOUT:   assign %a, %.loc10_29.12
 // CHECK:STDOUT:   %.loc13_14: type = ptr_type i32
 // CHECK:STDOUT:   %pa: ref i32* = var "pa"
 // CHECK:STDOUT:   %a.ref.loc13: ref [i32; 3] = name_reference "a", %a

+ 1 - 0
toolchain/check/testdata/index/fail_expression_category.carbon

@@ -30,6 +30,7 @@ fn G(b: [i32; 3]) {
 }
 
 // CHECK:STDOUT: file "fail_expression_category.carbon" {
+// CHECK:STDOUT:   %.loc7: type = ptr_type [i32; 3]
 // CHECK:STDOUT:   %F: <function> = fn_decl @F
 // CHECK:STDOUT:   %G: <function> = fn_decl @G
 // CHECK:STDOUT: }

+ 3 - 3
toolchain/check/testdata/index/fail_invalid_base.carbon

@@ -45,10 +45,10 @@ var d: i32 = {.a: i32, .b: i32}[0];
 // CHECK:STDOUT:   %.loc26_20: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc26_28: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc26_29.1: type = struct_type {.a: i32, .b: i32}
-// CHECK:STDOUT:   %.loc26_29.2: type = ptr_type {.a: i32, .b: i32}
-// CHECK:STDOUT:   %.loc26_29.3: {.a: i32, .b: i32} = struct_literal (%.loc26_20, %.loc26_28)
+// CHECK:STDOUT:   %.loc26_29.2: {.a: i32, .b: i32} = struct_literal (%.loc26_20, %.loc26_28)
 // CHECK:STDOUT:   %.loc26_31: i32 = int_literal 0
-// CHECK:STDOUT:   %.loc26_29.4: {.a: i32, .b: i32} = struct_value %.loc26_29.3, (%.loc26_20, %.loc26_28)
+// CHECK:STDOUT:   %.loc26_29.3: type = ptr_type {.a: i32, .b: i32}
+// CHECK:STDOUT:   %.loc26_29.4: {.a: i32, .b: i32} = struct_value %.loc26_29.2, (%.loc26_20, %.loc26_28)
 // CHECK:STDOUT:   assign %c, <error>
 // CHECK:STDOUT:   %d: ref i32 = var "d"
 // CHECK:STDOUT:   %.loc31_31: type = struct_type {.a: i32, .b: i32}

+ 3 - 4
toolchain/check/testdata/index/fail_non_deterministic_type.carbon

@@ -13,10 +13,9 @@ var c: i32 = a[b];
 
 // CHECK:STDOUT: file "fail_non_deterministic_type.carbon" {
 // CHECK:STDOUT:   %.loc7_17.1: type = tuple_type (type, type)
-// CHECK:STDOUT:   %.loc7_17.2: type = ptr_type (type, type)
-// CHECK:STDOUT:   %.loc7_17.3: (type, type) = tuple_literal (i32, i32)
-// CHECK:STDOUT:   %.loc7_17.4: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc7_17.5: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.2: (type, type) = tuple_literal (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.3: type = tuple_type (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.4: type = ptr_type (i32, i32)
 // CHECK:STDOUT:   %a: ref (i32, i32) = var "a"
 // CHECK:STDOUT:   %.loc7_22: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc7_25: i32 = int_literal 3

+ 3 - 4
toolchain/check/testdata/index/fail_tuple_index_error.carbon

@@ -12,10 +12,9 @@ var b: i32 = a[oops];
 
 // CHECK:STDOUT: file "fail_tuple_index_error.carbon" {
 // CHECK:STDOUT:   %.loc7_17.1: type = tuple_type (type, type)
-// CHECK:STDOUT:   %.loc7_17.2: type = ptr_type (type, type)
-// CHECK:STDOUT:   %.loc7_17.3: (type, type) = tuple_literal (i32, i32)
-// CHECK:STDOUT:   %.loc7_17.4: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc7_17.5: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.2: (type, type) = tuple_literal (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.3: type = tuple_type (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.4: type = ptr_type (i32, i32)
 // CHECK:STDOUT:   %a: ref (i32, i32) = var "a"
 // CHECK:STDOUT:   %.loc7_22: i32 = int_literal 12
 // CHECK:STDOUT:   %.loc7_26: i32 = int_literal 6

+ 3 - 4
toolchain/check/testdata/index/fail_tuple_non_int_indexing.carbon

@@ -12,10 +12,9 @@ var b: i32 = a[2.6];
 
 // CHECK:STDOUT: file "fail_tuple_non_int_indexing.carbon" {
 // CHECK:STDOUT:   %.loc7_17.1: type = tuple_type (type, type)
-// CHECK:STDOUT:   %.loc7_17.2: type = ptr_type (type, type)
-// CHECK:STDOUT:   %.loc7_17.3: (type, type) = tuple_literal (i32, i32)
-// CHECK:STDOUT:   %.loc7_17.4: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc7_17.5: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.2: (type, type) = tuple_literal (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.3: type = tuple_type (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.4: type = ptr_type (i32, i32)
 // CHECK:STDOUT:   %a: ref (i32, i32) = var "a"
 // CHECK:STDOUT:   %.loc7_22: i32 = int_literal 12
 // CHECK:STDOUT:   %.loc7_26: i32 = int_literal 6

+ 3 - 4
toolchain/check/testdata/index/fail_tuple_out_of_bound_access.carbon

@@ -12,10 +12,9 @@ var b: i32 = a[2];
 
 // CHECK:STDOUT: file "fail_tuple_out_of_bound_access.carbon" {
 // CHECK:STDOUT:   %.loc7_17.1: type = tuple_type (type, type)
-// CHECK:STDOUT:   %.loc7_17.2: type = ptr_type (type, type)
-// CHECK:STDOUT:   %.loc7_17.3: (type, type) = tuple_literal (i32, i32)
-// CHECK:STDOUT:   %.loc7_17.4: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc7_17.5: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.2: (type, type) = tuple_literal (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.3: type = tuple_type (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.4: type = ptr_type (i32, i32)
 // CHECK:STDOUT:   %a: ref (i32, i32) = var "a"
 // CHECK:STDOUT:   %.loc7_22: i32 = int_literal 12
 // CHECK:STDOUT:   %.loc7_26: i32 = int_literal 6

+ 3 - 4
toolchain/check/testdata/let/convert.carbon

@@ -18,10 +18,9 @@ fn F() -> i32 {
 // CHECK:STDOUT: fn @F() -> i32 {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %.loc8_24.1: type = tuple_type (type, type, type)
-// CHECK:STDOUT:   %.loc8_24.2: type = ptr_type (type, type, type)
-// CHECK:STDOUT:   %.loc8_24.3: (type, type, type) = tuple_literal (i32, i32, i32)
-// CHECK:STDOUT:   %.loc8_24.4: type = tuple_type (i32, i32, i32)
-// CHECK:STDOUT:   %.loc8_24.5: type = ptr_type (i32, i32, i32)
+// CHECK:STDOUT:   %.loc8_24.2: (type, type, type) = tuple_literal (i32, i32, i32)
+// CHECK:STDOUT:   %.loc8_24.3: type = tuple_type (i32, i32, i32)
+// CHECK:STDOUT:   %.loc8_24.4: type = ptr_type (i32, i32, i32)
 // CHECK:STDOUT:   %v: ref (i32, i32, i32) = var "v"
 // CHECK:STDOUT:   %.loc8_29: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc8_32: i32 = int_literal 2

+ 3 - 4
toolchain/check/testdata/operators/assignment.carbon

@@ -35,10 +35,9 @@ fn Main() {
 // CHECK:STDOUT:   %.loc9: i32 = int_literal 9
 // CHECK:STDOUT:   assign %a.ref.loc9, %.loc9
 // CHECK:STDOUT:   %.loc11_19.1: type = tuple_type (type, type)
-// CHECK:STDOUT:   %.loc11_19.2: type = ptr_type (type, type)
-// CHECK:STDOUT:   %.loc11_19.3: (type, type) = tuple_literal (i32, i32)
-// CHECK:STDOUT:   %.loc11_19.4: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc11_19.5: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc11_19.2: (type, type) = tuple_literal (i32, i32)
+// CHECK:STDOUT:   %.loc11_19.3: type = tuple_type (i32, i32)
+// CHECK:STDOUT:   %.loc11_19.4: type = ptr_type (i32, i32)
 // CHECK:STDOUT:   %b: ref (i32, i32) = var "b"
 // CHECK:STDOUT:   %.loc11_24: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc11_27: i32 = int_literal 2

+ 21 - 21
toolchain/check/testdata/operators/fail_assigment_to_non_assignable.carbon → toolchain/check/testdata/operators/fail_assignment_to_non_assignable.carbon

@@ -7,45 +7,45 @@
 fn F() -> i32;
 
 fn Main() {
-  // CHECK:STDERR: fail_assigment_to_non_assignable.carbon:[[@LINE+3]]:3: ERROR: Expression is not assignable.
+  // CHECK:STDERR: fail_assignment_to_non_assignable.carbon:[[@LINE+3]]:3: ERROR: Expression is not assignable.
   // CHECK:STDERR:   1 = 2;
   // CHECK:STDERR:   ^
   1 = 2;
-  // CHECK:STDERR: fail_assigment_to_non_assignable.carbon:[[@LINE+3]]:5: ERROR: Expression is not assignable.
+  // CHECK:STDERR: fail_assignment_to_non_assignable.carbon:[[@LINE+3]]:5: ERROR: Expression is not assignable.
   // CHECK:STDERR:   F() = 1;
   // CHECK:STDERR:     ^
   F() = 1;
-  // CHECK:STDERR: fail_assigment_to_non_assignable.carbon:[[@LINE+3]]:8: ERROR: Expression is not assignable.
+  // CHECK:STDERR: fail_assignment_to_non_assignable.carbon:[[@LINE+3]]:8: ERROR: Expression is not assignable.
   // CHECK:STDERR:   (1, 2) = (3, 4);
   // CHECK:STDERR:        ^
   (1, 2) = (3, 4);
   var n: i32 = 0;
-  // CHECK:STDERR: fail_assigment_to_non_assignable.carbon:[[@LINE+3]]:8: ERROR: Expression is not assignable.
+  // CHECK:STDERR: fail_assignment_to_non_assignable.carbon:[[@LINE+3]]:8: ERROR: Expression is not assignable.
   // CHECK:STDERR:   (n, n) = (1, 2);
   // CHECK:STDERR:        ^
   (n, n) = (1, 2);
-  // CHECK:STDERR: fail_assigment_to_non_assignable.carbon:[[@LINE+3]]:3: ERROR: Expression is not assignable.
+  // CHECK:STDERR: fail_assignment_to_non_assignable.carbon:[[@LINE+3]]:3: ERROR: Expression is not assignable.
   // CHECK:STDERR:   i32 = i32*;
   // CHECK:STDERR:   ^
   i32 = i32*;
-  // CHECK:STDERR: fail_assigment_to_non_assignable.carbon:[[@LINE+3]]:18: ERROR: Expression is not assignable.
+  // CHECK:STDERR: fail_assignment_to_non_assignable.carbon:[[@LINE+3]]:18: ERROR: Expression is not assignable.
   // CHECK:STDERR:   {.x = 1, .y = 2} = {.x = 3, .y = 4};
   // CHECK:STDERR:                  ^
   {.x = 1, .y = 2} = {.x = 3, .y = 4};
-  // CHECK:STDERR: fail_assigment_to_non_assignable.carbon:[[@LINE+3]]:25: ERROR: Expression is not assignable.
+  // CHECK:STDERR: fail_assignment_to_non_assignable.carbon:[[@LINE+3]]:25: ERROR: Expression is not assignable.
   // CHECK:STDERR:   (if true then 1 else 2) = 3;
   // CHECK:STDERR:                         ^
   (if true then 1 else 2) = 3;
 
   // Under #911, if expressions are never reference expressions.
   var a: i32;
-  // CHECK:STDERR: fail_assigment_to_non_assignable.carbon:[[@LINE+3]]:25: ERROR: Expression is not assignable.
+  // CHECK:STDERR: fail_assignment_to_non_assignable.carbon:[[@LINE+3]]:25: ERROR: Expression is not assignable.
   // CHECK:STDERR:   (if true then a else a) = 10;
   // CHECK:STDERR:                         ^
   (if true then a else a) = 10;
 }
 
-// CHECK:STDOUT: file "fail_assigment_to_non_assignable.carbon" {
+// CHECK:STDOUT: file "fail_assignment_to_non_assignable.carbon" {
 // CHECK:STDOUT:   %F: <function> = fn_decl @F
 // CHECK:STDOUT:   %Main: <function> = fn_decl @Main
 // CHECK:STDOUT: }
@@ -64,18 +64,18 @@ fn Main() {
 // CHECK:STDOUT:   %.loc21_4: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc21_7: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc21_8.1: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc21_8.2: type = ptr_type (i32, i32)
-// CHECK:STDOUT:   %.loc21_8.3: (i32, i32) = tuple_literal (%.loc21_4, %.loc21_7)
+// CHECK:STDOUT:   %.loc21_8.2: (i32, i32) = tuple_literal (%.loc21_4, %.loc21_7)
 // CHECK:STDOUT:   %.loc21_13: i32 = int_literal 3
 // CHECK:STDOUT:   %.loc21_16: i32 = int_literal 4
 // CHECK:STDOUT:   %.loc21_17.1: (i32, i32) = tuple_literal (%.loc21_13, %.loc21_16)
-// CHECK:STDOUT:   %.loc21_17.2: i32 = tuple_access %.loc21_8.3, member0
+// CHECK:STDOUT:   %.loc21_8.3: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc21_17.2: i32 = tuple_access %.loc21_8.2, member0
 // CHECK:STDOUT:   %.loc21_17.3: init i32 = initialize_from %.loc21_13 to %.loc21_17.2
-// CHECK:STDOUT:   %.loc21_17.4: i32 = tuple_access %.loc21_8.3, member1
+// CHECK:STDOUT:   %.loc21_17.4: i32 = tuple_access %.loc21_8.2, member1
 // CHECK:STDOUT:   %.loc21_17.5: init i32 = initialize_from %.loc21_16 to %.loc21_17.4
 // CHECK:STDOUT:   %.loc21_17.6: init (i32, i32) = tuple_init %.loc21_17.1, (%.loc21_17.3, %.loc21_17.5)
-// CHECK:STDOUT:   assign %.loc21_8.3, %.loc21_17.6
-// CHECK:STDOUT:   %.loc21_8.4: (i32, i32) = tuple_value %.loc21_8.3, (%.loc21_4, %.loc21_7)
+// CHECK:STDOUT:   assign %.loc21_8.2, %.loc21_17.6
+// CHECK:STDOUT:   %.loc21_8.4: (i32, i32) = tuple_value %.loc21_8.2, (%.loc21_4, %.loc21_7)
 // CHECK:STDOUT:   %n: ref i32 = var "n"
 // CHECK:STDOUT:   %.loc22: i32 = int_literal 0
 // CHECK:STDOUT:   assign %n, %.loc22
@@ -99,18 +99,18 @@ fn Main() {
 // CHECK:STDOUT:   %.loc34_9: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc34_17: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc34_18.1: type = struct_type {.x: i32, .y: i32}
-// CHECK:STDOUT:   %.loc34_18.2: type = ptr_type {.x: i32, .y: i32}
-// CHECK:STDOUT:   %.loc34_18.3: {.x: i32, .y: i32} = struct_literal (%.loc34_9, %.loc34_17)
+// CHECK:STDOUT:   %.loc34_18.2: {.x: i32, .y: i32} = struct_literal (%.loc34_9, %.loc34_17)
 // CHECK:STDOUT:   %.loc34_28: i32 = int_literal 3
 // CHECK:STDOUT:   %.loc34_36: i32 = int_literal 4
 // CHECK:STDOUT:   %.loc34_37.1: {.x: i32, .y: i32} = struct_literal (%.loc34_28, %.loc34_36)
-// CHECK:STDOUT:   %.loc34_37.2: i32 = struct_access %.loc34_18.3, member0
+// CHECK:STDOUT:   %.loc34_18.3: type = ptr_type {.x: i32, .y: i32}
+// CHECK:STDOUT:   %.loc34_37.2: i32 = struct_access %.loc34_18.2, member0
 // CHECK:STDOUT:   %.loc34_37.3: init i32 = initialize_from %.loc34_28 to %.loc34_37.2
-// CHECK:STDOUT:   %.loc34_37.4: i32 = struct_access %.loc34_18.3, member1
+// CHECK:STDOUT:   %.loc34_37.4: i32 = struct_access %.loc34_18.2, member1
 // CHECK:STDOUT:   %.loc34_37.5: init i32 = initialize_from %.loc34_36 to %.loc34_37.4
 // CHECK:STDOUT:   %.loc34_37.6: init {.x: i32, .y: i32} = struct_init %.loc34_37.1, (%.loc34_37.3, %.loc34_37.5)
-// CHECK:STDOUT:   assign %.loc34_18.3, %.loc34_37.6
-// CHECK:STDOUT:   %.loc34_18.4: {.x: i32, .y: i32} = struct_value %.loc34_18.3, (%.loc34_9, %.loc34_17)
+// CHECK:STDOUT:   assign %.loc34_18.2, %.loc34_37.6
+// CHECK:STDOUT:   %.loc34_18.4: {.x: i32, .y: i32} = struct_value %.loc34_18.2, (%.loc34_9, %.loc34_17)
 // CHECK:STDOUT:   %.loc38_7: bool = bool_literal true
 // CHECK:STDOUT:   if %.loc38_7 br !if.expr.then.loc38 else br !if.expr.else.loc38
 // CHECK:STDOUT:

+ 3 - 4
toolchain/check/testdata/pointer/address_of_lvalue.carbon

@@ -53,10 +53,9 @@ fn F() {
 // CHECK:STDOUT:   %.loc12_17: i32* = address_of %.loc12_19
 // CHECK:STDOUT:   assign %r, %.loc12_17
 // CHECK:STDOUT:   %.loc14_19.1: type = tuple_type (type, type)
-// CHECK:STDOUT:   %.loc14_19.2: type = ptr_type (type, type)
-// CHECK:STDOUT:   %.loc14_19.3: (type, type) = tuple_literal (i32, i32)
-// CHECK:STDOUT:   %.loc14_19.4: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc14_19.5: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc14_19.2: (type, type) = tuple_literal (i32, i32)
+// CHECK:STDOUT:   %.loc14_19.3: type = tuple_type (i32, i32)
+// CHECK:STDOUT:   %.loc14_19.4: type = ptr_type (i32, i32)
 // CHECK:STDOUT:   %t: ref (i32, i32) = var "t"
 // CHECK:STDOUT:   %.loc14_24: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc14_27: i32 = int_literal 2

+ 3 - 3
toolchain/check/testdata/pointer/fail_address_of_value.carbon

@@ -114,9 +114,9 @@ fn AddressOfParameter(param: i32) {
 // CHECK:STDOUT:   %.loc31_5: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc31_8: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc31_9.1: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc31_9.2: type = ptr_type (i32, i32)
-// CHECK:STDOUT:   %.loc31_9.3: (i32, i32) = tuple_literal (%.loc31_5, %.loc31_8)
-// CHECK:STDOUT:   %.loc31_3: (i32, i32)* = address_of %.loc31_9.3
+// CHECK:STDOUT:   %.loc31_9.2: (i32, i32) = tuple_literal (%.loc31_5, %.loc31_8)
+// CHECK:STDOUT:   %.loc31_3.1: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc31_3.2: (i32, i32)* = address_of %.loc31_9.2
 // CHECK:STDOUT:   %.loc35_10: i32 = int_literal 5
 // CHECK:STDOUT:   %.loc35_11: {.a: i32} = struct_literal (%.loc35_10)
 // CHECK:STDOUT:   %.loc35_3.1: type = ptr_type {.a: i32}

+ 1 - 0
toolchain/check/testdata/return/tuple.carbon

@@ -10,6 +10,7 @@ fn Main() -> (i32, i32) {
 }
 
 // CHECK:STDOUT: file "tuple.carbon" {
+// CHECK:STDOUT:   %.loc8: type = ptr_type (i32, i32)
 // CHECK:STDOUT:   %Main: <function> = fn_decl @Main
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 2 - 2
toolchain/check/testdata/struct/empty.carbon

@@ -9,8 +9,8 @@ var y: {} = x;
 
 // CHECK:STDOUT: file "empty.carbon" {
 // CHECK:STDOUT:   %.loc7_9.1: type = struct_type {}
-// CHECK:STDOUT:   %.loc7_9.2: type = tuple_type ()
-// CHECK:STDOUT:   %.loc7_9.3: {} = struct_literal ()
+// CHECK:STDOUT:   %.loc7_9.2: {} = struct_literal ()
+// CHECK:STDOUT:   %.loc7_9.3: type = tuple_type ()
 // CHECK:STDOUT:   %x: ref {} = var "x"
 // CHECK:STDOUT:   %.loc7_14.1: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc7_14.2: init {} = struct_init %.loc7_14.1, ()

+ 1 - 2
toolchain/check/testdata/struct/fail_assign_empty.carbon

@@ -13,7 +13,6 @@ var x: {.a: i32} = {};
 // CHECK:STDOUT:   %.loc10_16: type = struct_type {.a: i32}
 // CHECK:STDOUT:   %x: ref {.a: i32} = var "x"
 // CHECK:STDOUT:   %.loc10_21.1: type = struct_type {}
-// CHECK:STDOUT:   %.loc10_21.2: type = tuple_type ()
-// CHECK:STDOUT:   %.loc10_21.3: {} = struct_literal ()
+// CHECK:STDOUT:   %.loc10_21.2: {} = struct_literal ()
 // CHECK:STDOUT:   assign %x, <error>
 // CHECK:STDOUT: }

+ 3 - 4
toolchain/check/testdata/struct/fail_assign_nested.carbon

@@ -11,14 +11,13 @@ var x: {.a: {}} = {.b = {}};
 
 // CHECK:STDOUT: file "fail_assign_nested.carbon" {
 // CHECK:STDOUT:   %.loc10_14.1: type = struct_type {}
-// CHECK:STDOUT:   %.loc10_14.2: type = tuple_type ()
-// CHECK:STDOUT:   %.loc10_14.3: {} = struct_literal ()
+// CHECK:STDOUT:   %.loc10_14.2: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc10_15.1: type = struct_type {.a: {}}
+// CHECK:STDOUT:   %.loc10_14.3: type = tuple_type ()
 // CHECK:STDOUT:   %.loc10_15.2: type = struct_type {.a: ()}
 // CHECK:STDOUT:   %x: ref {.a: {}} = var "x"
 // CHECK:STDOUT:   %.loc10_26: {} = struct_literal ()
 // CHECK:STDOUT:   %.loc10_27.1: type = struct_type {.b: {}}
-// CHECK:STDOUT:   %.loc10_27.2: type = struct_type {.b: ()}
-// CHECK:STDOUT:   %.loc10_27.3: {.b: {}} = struct_literal (%.loc10_26)
+// CHECK:STDOUT:   %.loc10_27.2: {.b: {}} = struct_literal (%.loc10_26)
 // CHECK:STDOUT:   assign %x, <error>
 // CHECK:STDOUT: }

+ 2 - 2
toolchain/check/testdata/struct/fail_assign_to_empty.carbon

@@ -11,8 +11,8 @@ var x: {} = {.a = 1};
 
 // CHECK:STDOUT: file "fail_assign_to_empty.carbon" {
 // CHECK:STDOUT:   %.loc10_9.1: type = struct_type {}
-// CHECK:STDOUT:   %.loc10_9.2: type = tuple_type ()
-// CHECK:STDOUT:   %.loc10_9.3: {} = struct_literal ()
+// CHECK:STDOUT:   %.loc10_9.2: {} = struct_literal ()
+// CHECK:STDOUT:   %.loc10_9.3: type = tuple_type ()
 // CHECK:STDOUT:   %x: ref {} = var "x"
 // CHECK:STDOUT:   %.loc10_19: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc10_20.1: type = struct_type {.a: i32}

+ 36 - 0
toolchain/check/testdata/struct/fail_nested_incomplete.carbon

@@ -0,0 +1,36 @@
+// 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
+
+class Incomplete;
+
+// CHECK:STDERR: fail_nested_incomplete.carbon:[[@LINE+6]]:23: ERROR: Variable has incomplete type `{.a: Incomplete}`.
+// CHECK:STDERR: var s: {.a: Incomplete};
+// CHECK:STDERR:                       ^
+// CHECK:STDERR: fail_nested_incomplete.carbon:[[@LINE-5]]:1: Class was forward declared here.
+// CHECK:STDERR: class Incomplete;
+// CHECK:STDERR: ^
+var s: {.a: Incomplete};
+
+// CHECK:STDERR: fail_nested_incomplete.carbon:[[@LINE+3]]:26: ERROR: Cannot implicitly convert from `<error>*` to `Incomplete*`.
+// CHECK:STDERR: var p: Incomplete* = &s.a;
+// CHECK:STDERR:                          ^
+var p: Incomplete* = &s.a;
+
+// CHECK:STDOUT: file "fail_nested_incomplete.carbon" {
+// CHECK:STDOUT:   %Incomplete: type = class_declaration @Incomplete
+// CHECK:STDOUT:   %Incomplete.ref.loc15: type = name_reference "Incomplete", %Incomplete
+// CHECK:STDOUT:   %.loc15: type = struct_type {.a: Incomplete}
+// CHECK:STDOUT:   %s: ref <error> = var "s"
+// CHECK:STDOUT:   %Incomplete.ref.loc20: type = name_reference "Incomplete", %Incomplete
+// CHECK:STDOUT:   %.loc20_18: type = ptr_type Incomplete
+// CHECK:STDOUT:   %p: ref Incomplete* = var "p"
+// CHECK:STDOUT:   %s.ref: ref <error> = name_reference "s", %s
+// CHECK:STDOUT:   %.loc20_22.1: type = ptr_type <error>
+// CHECK:STDOUT:   %.loc20_22.2: <error>* = address_of <error>
+// CHECK:STDOUT:   assign %p, <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Incomplete;

+ 5 - 4
toolchain/check/testdata/struct/literal_member_access.carbon

@@ -11,6 +11,7 @@ fn F() -> i32 {
 }
 
 // CHECK:STDOUT: file "literal_member_access.carbon" {
+// CHECK:STDOUT:   %.loc7: type = ptr_type {.x: i32, .y: i32, .z: i32}
 // CHECK:STDOUT:   %G: <function> = fn_decl @G
 // CHECK:STDOUT:   %F: <function> = fn_decl @F
 // CHECK:STDOUT: }
@@ -25,9 +26,9 @@ fn F() -> i32 {
 // CHECK:STDOUT:   %.loc10_25.2: init {.x: i32, .y: i32, .z: i32} = call %G.ref() to %.loc10_25.1
 // CHECK:STDOUT:   %.loc10_34: i32 = int_literal 3
 // CHECK:STDOUT:   %.loc10_35.1: type = struct_type {.a: i32, .b: {.x: i32, .y: i32, .z: i32}, .c: i32}
-// CHECK:STDOUT:   %.loc10_35.2: type = struct_type {.a: i32, .b: {.x: i32, .y: i32, .z: i32}*, .c: i32}
-// CHECK:STDOUT:   %.loc10_35.3: type = ptr_type {.a: i32, .b: {.x: i32, .y: i32, .z: i32}*, .c: i32}
-// CHECK:STDOUT:   %.loc10_35.4: {.a: i32, .b: {.x: i32, .y: i32, .z: i32}, .c: i32} = struct_literal (%.loc10_16, %.loc10_25.2, %.loc10_34)
+// CHECK:STDOUT:   %.loc10_35.2: {.a: i32, .b: {.x: i32, .y: i32, .z: i32}, .c: i32} = struct_literal (%.loc10_16, %.loc10_25.2, %.loc10_34)
+// CHECK:STDOUT:   %.loc10_35.3: type = struct_type {.a: i32, .b: {.x: i32, .y: i32, .z: i32}*, .c: i32}
+// CHECK:STDOUT:   %.loc10_35.4: type = ptr_type {.a: i32, .b: {.x: i32, .y: i32, .z: i32}*, .c: i32}
 // CHECK:STDOUT:   %.loc10_25.3: ref {.x: i32, .y: i32, .z: i32} = temporary %.loc10_25.1, %.loc10_25.2
 // CHECK:STDOUT:   %.loc10_25.4: ref i32 = struct_access %.loc10_25.3, member0
 // CHECK:STDOUT:   %.loc10_25.5: i32 = bind_value %.loc10_25.4
@@ -36,7 +37,7 @@ fn F() -> i32 {
 // CHECK:STDOUT:   %.loc10_25.8: ref i32 = struct_access %.loc10_25.3, member2
 // CHECK:STDOUT:   %.loc10_25.9: i32 = bind_value %.loc10_25.8
 // CHECK:STDOUT:   %.loc10_25.10: {.x: i32, .y: i32, .z: i32} = struct_value %.loc10_25.3, (%.loc10_25.5, %.loc10_25.7, %.loc10_25.9)
-// CHECK:STDOUT:   %.loc10_35.5: {.a: i32, .b: {.x: i32, .y: i32, .z: i32}, .c: i32} = struct_value %.loc10_35.4, (%.loc10_16, %.loc10_25.10, %.loc10_34)
+// CHECK:STDOUT:   %.loc10_35.5: {.a: i32, .b: {.x: i32, .y: i32, .z: i32}, .c: i32} = struct_value %.loc10_35.2, (%.loc10_16, %.loc10_25.10, %.loc10_34)
 // CHECK:STDOUT:   %.loc10_36: {.x: i32, .y: i32, .z: i32} = struct_access %.loc10_35.5, member1
 // CHECK:STDOUT:   %.loc10_38: i32 = struct_access %.loc10_36, member1
 // CHECK:STDOUT:   return %.loc10_38

+ 1 - 0
toolchain/check/testdata/struct/nested_struct_in_place.carbon

@@ -11,6 +11,7 @@ fn G() {
 }
 
 // CHECK:STDOUT: file "nested_struct_in_place.carbon" {
+// CHECK:STDOUT:   %.loc7: type = ptr_type (i32, i32, i32)
 // CHECK:STDOUT:   %F: <function> = fn_decl @F
 // CHECK:STDOUT:   %G: <function> = fn_decl @G
 // CHECK:STDOUT: }

+ 9 - 15
toolchain/check/testdata/tuples/fail_assign_nested.carbon

@@ -11,32 +11,26 @@ var x: ((i32, i32), (i32, i32)) = ((1, 2, 3), (4, 5, 6));
 
 // CHECK:STDOUT: file "fail_assign_nested.carbon" {
 // CHECK:STDOUT:   %.loc10_18.1: type = tuple_type (type, type)
-// CHECK:STDOUT:   %.loc10_18.2: type = ptr_type (type, type)
-// CHECK:STDOUT:   %.loc10_18.3: (type, type) = tuple_literal (i32, i32)
+// CHECK:STDOUT:   %.loc10_18.2: (type, type) = tuple_literal (i32, i32)
 // CHECK:STDOUT:   %.loc10_30: (type, type) = tuple_literal (i32, i32)
 // CHECK:STDOUT:   %.loc10_31.1: type = tuple_type ((type, type), (type, type))
-// CHECK:STDOUT:   %.loc10_31.2: type = tuple_type ((type, type)*, (type, type)*)
-// CHECK:STDOUT:   %.loc10_31.3: type = ptr_type ((type, type)*, (type, type)*)
-// CHECK:STDOUT:   %.loc10_31.4: ((type, type), (type, type)) = tuple_literal (%.loc10_18.3, %.loc10_30)
-// CHECK:STDOUT:   %.loc10_31.5: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc10_31.6: type = ptr_type (i32, i32)
-// CHECK:STDOUT:   %.loc10_31.7: type = tuple_type ((i32, i32), (i32, i32))
-// CHECK:STDOUT:   %.loc10_31.8: type = tuple_type ((i32, i32)*, (i32, i32)*)
-// CHECK:STDOUT:   %.loc10_31.9: type = ptr_type ((i32, i32)*, (i32, i32)*)
+// CHECK:STDOUT:   %.loc10_31.2: ((type, type), (type, type)) = tuple_literal (%.loc10_18.2, %.loc10_30)
+// CHECK:STDOUT:   %.loc10_31.3: type = tuple_type (i32, i32)
+// CHECK:STDOUT:   %.loc10_31.4: type = tuple_type ((i32, i32), (i32, i32))
+// CHECK:STDOUT:   %.loc10_31.5: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc10_31.6: type = tuple_type ((i32, i32)*, (i32, i32)*)
+// CHECK:STDOUT:   %.loc10_31.7: type = ptr_type ((i32, i32)*, (i32, i32)*)
 // CHECK:STDOUT:   %x: ref ((i32, i32), (i32, i32)) = var "x"
 // CHECK:STDOUT:   %.loc10_37: i32 = int_literal 1
 // CHECK:STDOUT:   %.loc10_40: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc10_43: i32 = int_literal 3
 // CHECK:STDOUT:   %.loc10_44.1: type = tuple_type (i32, i32, i32)
-// CHECK:STDOUT:   %.loc10_44.2: type = ptr_type (i32, i32, i32)
-// CHECK:STDOUT:   %.loc10_44.3: (i32, i32, i32) = tuple_literal (%.loc10_37, %.loc10_40, %.loc10_43)
+// CHECK:STDOUT:   %.loc10_44.2: (i32, i32, i32) = tuple_literal (%.loc10_37, %.loc10_40, %.loc10_43)
 // CHECK:STDOUT:   %.loc10_48: i32 = int_literal 4
 // CHECK:STDOUT:   %.loc10_51: i32 = int_literal 5
 // CHECK:STDOUT:   %.loc10_54: i32 = int_literal 6
 // CHECK:STDOUT:   %.loc10_55: (i32, i32, i32) = tuple_literal (%.loc10_48, %.loc10_51, %.loc10_54)
 // CHECK:STDOUT:   %.loc10_56.1: type = tuple_type ((i32, i32, i32), (i32, i32, i32))
-// CHECK:STDOUT:   %.loc10_56.2: type = tuple_type ((i32, i32, i32)*, (i32, i32, i32)*)
-// CHECK:STDOUT:   %.loc10_56.3: type = ptr_type ((i32, i32, i32)*, (i32, i32, i32)*)
-// CHECK:STDOUT:   %.loc10_56.4: ((i32, i32, i32), (i32, i32, i32)) = tuple_literal (%.loc10_44.3, %.loc10_55)
+// CHECK:STDOUT:   %.loc10_56.2: ((i32, i32, i32), (i32, i32, i32)) = tuple_literal (%.loc10_44.2, %.loc10_55)
 // CHECK:STDOUT:   assign %x, <error>
 // CHECK:STDOUT: }

+ 6 - 8
toolchain/check/testdata/tuples/fail_element_type_mismatch.carbon

@@ -11,17 +11,15 @@ var x: (i32, i32) = (2, 65.89);
 
 // CHECK:STDOUT: file "fail_element_type_mismatch.carbon" {
 // CHECK:STDOUT:   %.loc10_17.1: type = tuple_type (type, type)
-// CHECK:STDOUT:   %.loc10_17.2: type = ptr_type (type, type)
-// CHECK:STDOUT:   %.loc10_17.3: (type, type) = tuple_literal (i32, i32)
-// CHECK:STDOUT:   %.loc10_17.4: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc10_17.5: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc10_17.2: (type, type) = tuple_literal (i32, i32)
+// CHECK:STDOUT:   %.loc10_17.3: type = tuple_type (i32, i32)
+// CHECK:STDOUT:   %.loc10_17.4: type = ptr_type (i32, i32)
 // CHECK:STDOUT:   %x: ref (i32, i32) = var "x"
 // CHECK:STDOUT:   %.loc10_22: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc10_25: f64 = real_literal 6589e-2
 // CHECK:STDOUT:   %.loc10_30.1: type = tuple_type (i32, f64)
-// CHECK:STDOUT:   %.loc10_30.2: type = ptr_type (i32, f64)
-// CHECK:STDOUT:   %.loc10_30.3: (i32, f64) = tuple_literal (%.loc10_22, %.loc10_25)
-// CHECK:STDOUT:   %.loc10_30.4: ref i32 = tuple_access %x, member0
-// CHECK:STDOUT:   %.loc10_30.5: init i32 = initialize_from %.loc10_22 to %.loc10_30.4
+// CHECK:STDOUT:   %.loc10_30.2: (i32, f64) = tuple_literal (%.loc10_22, %.loc10_25)
+// CHECK:STDOUT:   %.loc10_30.3: ref i32 = tuple_access %x, member0
+// CHECK:STDOUT:   %.loc10_30.4: init i32 = initialize_from %.loc10_22 to %.loc10_30.3
 // CHECK:STDOUT:   assign %x, <error>
 // CHECK:STDOUT: }

+ 39 - 0
toolchain/check/testdata/tuples/fail_nested_incomplete.carbon

@@ -0,0 +1,39 @@
+// 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
+
+class Incomplete;
+
+// CHECK:STDERR: fail_nested_incomplete.carbon:[[@LINE+6]]:24: ERROR: Variable has incomplete type `(i32, Incomplete)`.
+// CHECK:STDERR: var t: (i32, Incomplete);
+// CHECK:STDERR:                        ^
+// CHECK:STDERR: fail_nested_incomplete.carbon:[[@LINE-5]]:1: Class was forward declared here.
+// CHECK:STDERR: class Incomplete;
+// CHECK:STDERR: ^
+var t: (i32, Incomplete);
+
+// CHECK:STDERR: fail_nested_incomplete.carbon:[[@LINE+3]]:27: ERROR: Cannot implicitly convert from `<error>*` to `Incomplete*`.
+// CHECK:STDERR: var p: Incomplete* = &t[1];
+// CHECK:STDERR:                           ^
+var p: Incomplete* = &t[1];
+
+// CHECK:STDOUT: file "fail_nested_incomplete.carbon" {
+// CHECK:STDOUT:   %Incomplete: type = class_declaration @Incomplete
+// CHECK:STDOUT:   %Incomplete.ref.loc15: type = name_reference "Incomplete", %Incomplete
+// CHECK:STDOUT:   %.loc15_24.1: type = tuple_type (type, type)
+// CHECK:STDOUT:   %.loc15_24.2: (type, type) = tuple_literal (i32, %Incomplete.ref.loc15)
+// CHECK:STDOUT:   %.loc15_24.3: type = tuple_type (i32, Incomplete)
+// CHECK:STDOUT:   %t: ref <error> = var "t"
+// CHECK:STDOUT:   %Incomplete.ref.loc20: type = name_reference "Incomplete", %Incomplete
+// CHECK:STDOUT:   %.loc20_18: type = ptr_type Incomplete
+// CHECK:STDOUT:   %p: ref Incomplete* = var "p"
+// CHECK:STDOUT:   %t.ref: ref <error> = name_reference "t", %t
+// CHECK:STDOUT:   %.loc20_25: i32 = int_literal 1
+// CHECK:STDOUT:   %.loc20_22.1: type = ptr_type <error>
+// CHECK:STDOUT:   %.loc20_22.2: <error>* = address_of <error>
+// CHECK:STDOUT:   assign %p, <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Incomplete;

+ 3 - 4
toolchain/check/testdata/tuples/fail_too_few_element.carbon

@@ -11,10 +11,9 @@ var x: (i32, i32) = (2, );
 
 // CHECK:STDOUT: file "fail_too_few_element.carbon" {
 // CHECK:STDOUT:   %.loc10_17.1: type = tuple_type (type, type)
-// CHECK:STDOUT:   %.loc10_17.2: type = ptr_type (type, type)
-// CHECK:STDOUT:   %.loc10_17.3: (type, type) = tuple_literal (i32, i32)
-// CHECK:STDOUT:   %.loc10_17.4: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc10_17.5: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc10_17.2: (type, type) = tuple_literal (i32, i32)
+// CHECK:STDOUT:   %.loc10_17.3: type = tuple_type (i32, i32)
+// CHECK:STDOUT:   %.loc10_17.4: type = ptr_type (i32, i32)
 // CHECK:STDOUT:   %x: ref (i32, i32) = var "x"
 // CHECK:STDOUT:   %.loc10_22: i32 = int_literal 2
 // CHECK:STDOUT:   %.loc10_25.1: type = tuple_type (i32)

+ 7 - 10
toolchain/check/testdata/tuples/nested_tuple.carbon

@@ -8,17 +8,14 @@ var x: ((i32, i32), i32) = ((12, 76), 6);
 
 // CHECK:STDOUT: file "nested_tuple.carbon" {
 // CHECK:STDOUT:   %.loc7_18.1: type = tuple_type (type, type)
-// CHECK:STDOUT:   %.loc7_18.2: type = ptr_type (type, type)
-// CHECK:STDOUT:   %.loc7_18.3: (type, type) = tuple_literal (i32, i32)
+// CHECK:STDOUT:   %.loc7_18.2: (type, type) = tuple_literal (i32, i32)
 // CHECK:STDOUT:   %.loc7_24.1: type = tuple_type ((type, type), type)
-// CHECK:STDOUT:   %.loc7_24.2: type = tuple_type ((type, type)*, type)
-// CHECK:STDOUT:   %.loc7_24.3: type = ptr_type ((type, type)*, type)
-// CHECK:STDOUT:   %.loc7_24.4: ((type, type), type) = tuple_literal (%.loc7_18.3, i32)
-// CHECK:STDOUT:   %.loc7_24.5: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc7_24.6: type = ptr_type (i32, i32)
-// CHECK:STDOUT:   %.loc7_24.7: type = tuple_type ((i32, i32), i32)
-// CHECK:STDOUT:   %.loc7_24.8: type = tuple_type ((i32, i32)*, i32)
-// CHECK:STDOUT:   %.loc7_24.9: type = ptr_type ((i32, i32)*, i32)
+// CHECK:STDOUT:   %.loc7_24.2: ((type, type), type) = tuple_literal (%.loc7_18.2, i32)
+// CHECK:STDOUT:   %.loc7_24.3: type = tuple_type (i32, i32)
+// CHECK:STDOUT:   %.loc7_24.4: type = tuple_type ((i32, i32), i32)
+// CHECK:STDOUT:   %.loc7_24.5: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc7_24.6: type = tuple_type ((i32, i32)*, i32)
+// CHECK:STDOUT:   %.loc7_24.7: type = ptr_type ((i32, i32)*, i32)
 // CHECK:STDOUT:   %x: ref ((i32, i32), i32) = var "x"
 // CHECK:STDOUT:   %.loc7_30: i32 = int_literal 12
 // CHECK:STDOUT:   %.loc7_34: i32 = int_literal 76

+ 9 - 12
toolchain/check/testdata/tuples/nested_tuple_in_place.carbon

@@ -15,6 +15,7 @@ fn H() {
 }
 
 // CHECK:STDOUT: file "nested_tuple_in_place.carbon" {
+// CHECK:STDOUT:   %.loc7: type = ptr_type (i32, i32, i32)
 // CHECK:STDOUT:   %F: <function> = fn_decl @F
 // CHECK:STDOUT:   %G: <function> = fn_decl @G
 // CHECK:STDOUT:   %H: <function> = fn_decl @H
@@ -27,12 +28,10 @@ fn H() {
 // CHECK:STDOUT:   %.loc10_25: (type, type, type) = tuple_literal (i32, i32, i32)
 // CHECK:STDOUT:   %.loc10_42: (type, type, type) = tuple_literal (i32, i32, i32)
 // CHECK:STDOUT:   %.loc10_43.1: type = tuple_type ((type, type, type), (type, type, type))
-// CHECK:STDOUT:   %.loc10_43.2: type = tuple_type ((type, type, type)*, (type, type, type)*)
-// CHECK:STDOUT:   %.loc10_43.3: type = ptr_type ((type, type, type)*, (type, type, type)*)
-// CHECK:STDOUT:   %.loc10_43.4: ((type, type, type), (type, type, type)) = tuple_literal (%.loc10_25, %.loc10_42)
-// CHECK:STDOUT:   %.loc10_43.5: type = tuple_type ((i32, i32, i32), (i32, i32, i32))
-// CHECK:STDOUT:   %.loc10_43.6: type = tuple_type ((i32, i32, i32)*, (i32, i32, i32)*)
-// CHECK:STDOUT:   %.loc10_43.7: type = ptr_type ((i32, i32, i32)*, (i32, i32, i32)*)
+// CHECK:STDOUT:   %.loc10_43.2: ((type, type, type), (type, type, type)) = tuple_literal (%.loc10_25, %.loc10_42)
+// CHECK:STDOUT:   %.loc10_43.3: type = tuple_type ((i32, i32, i32), (i32, i32, i32))
+// CHECK:STDOUT:   %.loc10_43.4: type = tuple_type ((i32, i32, i32)*, (i32, i32, i32)*)
+// CHECK:STDOUT:   %.loc10_43.5: type = ptr_type ((i32, i32, i32)*, (i32, i32, i32)*)
 // CHECK:STDOUT:   %v: ref ((i32, i32, i32), (i32, i32, i32)) = var "v"
 // CHECK:STDOUT:   %F.ref.loc10_48: <function> = name_reference "F", package.%F
 // CHECK:STDOUT:   %.loc10_56.1: ref (i32, i32, i32) = tuple_access %v, member0
@@ -50,12 +49,10 @@ fn H() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %.loc14_30: (type, type, type) = tuple_literal (i32, i32, i32)
 // CHECK:STDOUT:   %.loc14_36.1: type = tuple_type (type, (type, type, type), type)
-// CHECK:STDOUT:   %.loc14_36.2: type = tuple_type (type, (type, type, type)*, type)
-// CHECK:STDOUT:   %.loc14_36.3: type = ptr_type (type, (type, type, type)*, type)
-// CHECK:STDOUT:   %.loc14_36.4: (type, (type, type, type), type) = tuple_literal (i32, %.loc14_30, i32)
-// CHECK:STDOUT:   %.loc14_36.5: type = tuple_type (i32, (i32, i32, i32), i32)
-// CHECK:STDOUT:   %.loc14_36.6: type = tuple_type (i32, (i32, i32, i32)*, i32)
-// CHECK:STDOUT:   %.loc14_36.7: type = ptr_type (i32, (i32, i32, i32)*, i32)
+// CHECK:STDOUT:   %.loc14_36.2: (type, (type, type, type), type) = tuple_literal (i32, %.loc14_30, i32)
+// CHECK:STDOUT:   %.loc14_36.3: type = tuple_type (i32, (i32, i32, i32), i32)
+// CHECK:STDOUT:   %.loc14_36.4: type = tuple_type (i32, (i32, i32, i32)*, i32)
+// CHECK:STDOUT:   %.loc14_36.5: type = ptr_type (i32, (i32, i32, i32)*, i32)
 // CHECK:STDOUT:   %v: ref (i32, (i32, i32, i32), i32) = var "v"
 // CHECK:STDOUT:   %.loc14_41: i32 = int_literal 1
 // CHECK:STDOUT:   %F.ref: <function> = name_reference "F", package.%F

+ 3 - 4
toolchain/check/testdata/tuples/two_elements.carbon

@@ -9,10 +9,9 @@ var y: (i32, i32) = x;
 
 // CHECK:STDOUT: file "two_elements.carbon" {
 // CHECK:STDOUT:   %.loc7_17.1: type = tuple_type (type, type)
-// CHECK:STDOUT:   %.loc7_17.2: type = ptr_type (type, type)
-// CHECK:STDOUT:   %.loc7_17.3: (type, type) = tuple_literal (i32, i32)
-// CHECK:STDOUT:   %.loc7_17.4: type = tuple_type (i32, i32)
-// CHECK:STDOUT:   %.loc7_17.5: type = ptr_type (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.2: (type, type) = tuple_literal (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.3: type = tuple_type (i32, i32)
+// CHECK:STDOUT:   %.loc7_17.4: type = ptr_type (i32, i32)
 // CHECK:STDOUT:   %x: ref (i32, i32) = var "x"
 // CHECK:STDOUT:   %.loc7_22: i32 = int_literal 4
 // CHECK:STDOUT:   %.loc7_25: i32 = int_literal 102

+ 8 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -115,6 +115,7 @@ CARBON_DIAGNOSTIC_KIND(ArrayInitFromLiteralArgCountMismatch)
 CARBON_DIAGNOSTIC_KIND(ArrayInitFromExpressionArgCountMismatch)
 CARBON_DIAGNOSTIC_KIND(AssignmentToNonAssignable)
 CARBON_DIAGNOSTIC_KIND(BreakOutsideLoop)
+CARBON_DIAGNOSTIC_KIND(ClassForwardDeclaredHere)
 CARBON_DIAGNOSTIC_KIND(ContinueOutsideLoop)
 CARBON_DIAGNOSTIC_KIND(DereferenceOfNonPointer)
 CARBON_DIAGNOSTIC_KIND(DereferenceOfType)
@@ -126,6 +127,13 @@ CARBON_DIAGNOSTIC_KIND(InCallToFunction)
 CARBON_DIAGNOSTIC_KIND(InCallToFunctionParam)
 CARBON_DIAGNOSTIC_KIND(MissingReturnStatement)
 CARBON_DIAGNOSTIC_KIND(RepeatedConst)
+CARBON_DIAGNOSTIC_KIND(IncompleteTypeInConversion)
+CARBON_DIAGNOSTIC_KIND(IncompleteTypeInFunctionParam)
+CARBON_DIAGNOSTIC_KIND(IncompleteTypeInFunctionReturnType)
+CARBON_DIAGNOSTIC_KIND(IncompleteTypeInInitialization)
+CARBON_DIAGNOSTIC_KIND(IncompleteTypeInLetDeclaration)
+CARBON_DIAGNOSTIC_KIND(IncompleteTypeInValueConversion)
+CARBON_DIAGNOSTIC_KIND(IncompleteTypeInVarDeclaration)
 CARBON_DIAGNOSTIC_KIND(InvalidArrayExpression)
 CARBON_DIAGNOSTIC_KIND(TypeNotIndexable)
 CARBON_DIAGNOSTIC_KIND(IndexOutOfBounds)

+ 19 - 0
toolchain/lower/testdata/pointer/address_of_unused.carbon

@@ -0,0 +1,19 @@
+// 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
+
+fn F() {
+  var n: i32 = 0;
+  &n;
+}
+
+// CHECK:STDOUT: ; ModuleID = 'address_of_unused.carbon'
+// CHECK:STDOUT: source_filename = "address_of_unused.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @F() {
+// CHECK:STDOUT:   %n = alloca i32, align 4
+// CHECK:STDOUT:   store i32 0, ptr %n, align 4
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }

+ 11 - 7
toolchain/sem_ir/file.h

@@ -85,8 +85,7 @@ struct ValueRepresentation : public Printable<ValueRepresentation> {
 
   enum Kind : int8_t {
     // The value representation is not yet known. This is used for incomplete
-    // types, in cases where the incompleteness means the value representation
-    // can't be determined.
+    // types.
     Unknown,
     // The type has no value representation. This is used for empty types, such
     // as `()`, where there is no value.
@@ -115,8 +114,9 @@ struct TypeInfo : public Printable<TypeInfo> {
 
   // The node that defines this type.
   NodeId node_id;
-  // The value representation for this type.
-  ValueRepresentation value_representation;
+  // The value representation for this type. Will be `Unknown` if the type is
+  // not complete.
+  ValueRepresentation value_representation = ValueRepresentation();
 };
 
 // Provides semantic analysis on a Parse::Tree.
@@ -342,9 +342,7 @@ class File : public Printable<File> {
     TypeId type_id(types_.size());
     // Should never happen, will always overflow node_ids first.
     CARBON_DCHECK(type_id.index >= 0);
-    types_.push_back(
-        {.node_id = node_id,
-         .value_representation = {.kind = ValueRepresentation::Unknown}});
+    types_.push_back({.node_id = node_id});
     return type_id;
   }
 
@@ -393,6 +391,12 @@ class File : public Printable<File> {
     return types_[type_id.index].value_representation;
   }
 
+  // Determines whether the given type is known to be complete. This does not
+  // determine whether the type could be completed, only whether it has been.
+  auto IsTypeComplete(TypeId type_id) const -> bool {
+    return GetValueRepresentation(type_id).kind != ValueRepresentation::Unknown;
+  }
+
   // Gets the pointee type of the given type, which must be a pointer type.
   auto GetPointeeType(TypeId pointer_id) const -> TypeId {
     return GetNodeAs<PointerType>(GetType(pointer_id)).pointee_id;

+ 8 - 3
toolchain/sem_ir/node.h

@@ -27,6 +27,12 @@ struct NodeId : public IndexBase, public Printable<NodeId> {
   static const NodeId Builtin##Name;
 #include "toolchain/sem_ir/builtin_kind.def"
 
+  // Returns the cross-reference node ID for a builtin. This relies on File
+  // guarantees for builtin cross-reference placement.
+  static constexpr auto ForBuiltin(BuiltinKind kind) -> NodeId {
+    return NodeId(kind.AsInt());
+  }
+
   using IndexBase::IndexBase;
   auto Print(llvm::raw_ostream& out) const -> void {
     out << "node";
@@ -44,10 +50,9 @@ struct NodeId : public IndexBase, public Printable<NodeId> {
 
 constexpr NodeId NodeId::Invalid = NodeId(NodeId::InvalidIndex);
 
-// Uses the cross-reference node ID for a builtin. This relies on File
-// guarantees for builtin cross-reference placement.
 #define CARBON_SEMANTICS_BUILTIN_KIND_NAME(Name) \
-  constexpr NodeId NodeId::Builtin##Name = NodeId(BuiltinKind::Name.AsInt());
+  constexpr NodeId NodeId::Builtin##Name =       \
+      NodeId::ForBuiltin(BuiltinKind::Name);
 #include "toolchain/sem_ir/builtin_kind.def"
 
 // The ID of a function.