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

Basic semantic checking for pointer types and const-qualified types. (#3038)

Semantic handling for use of `T*` and `const T` as types.

There's no way to form values of these types yet, and no conversions for
them are supported.

Factor out the common code to canonicalize types using a folding set,
and switch to using the same folding set for all kinds of type by adding
the kind as part of the folding set key.

Improve type printing to not include the `as type` portion when the type
is printed in a context within another type where a conversion to `type`
is implied, as in `{}*` and pre-existing cases like `({}, {}) as type`
(which we used to print as `({} as type, {} as type}) as type`.
Richard Smith 2 лет назад
Родитель
Сommit
c8b42379a4

+ 1 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -103,6 +103,7 @@ CARBON_DIAGNOSTIC_KIND(NoMatchingCall)
 CARBON_DIAGNOSTIC_KIND(CallArgCountMismatch)
 CARBON_DIAGNOSTIC_KIND(CallArgTypeMismatch)
 CARBON_DIAGNOSTIC_KIND(MissingReturnStatement)
+CARBON_DIAGNOSTIC_KIND(RepeatedConst)
 CARBON_DIAGNOSTIC_KIND(ReturnStatementDisallowExpression)
 CARBON_DIAGNOSTIC_KIND(ReturnStatementImplicitNote)
 CARBON_DIAGNOSTIC_KIND(ReturnStatementMissingExpression)

+ 12 - 0
toolchain/lowering/lowering_handle_type.cpp

@@ -6,6 +6,18 @@
 
 namespace Carbon {
 
+auto LoweringHandleConstType(LoweringFunctionContext& context,
+                             SemanticsNodeId node_id, SemanticsNode /*node*/)
+    -> void {
+  context.SetLocal(node_id, context.GetTypeAsValue());
+}
+
+auto LoweringHandlePointerType(LoweringFunctionContext& context,
+                               SemanticsNodeId node_id, SemanticsNode /*node*/)
+    -> void {
+  context.SetLocal(node_id, context.GetTypeAsValue());
+}
+
 auto LoweringHandleStructType(LoweringFunctionContext& context,
                               SemanticsNodeId node_id, SemanticsNode /*node*/)
     -> void {

+ 4 - 1
toolchain/semantics/BUILD

@@ -21,7 +21,10 @@ cc_library(
     srcs = ["semantics_node_kind.cpp"],
     hdrs = ["semantics_node_kind.h"],
     textual_hdrs = ["semantics_node_kind.def"],
-    deps = ["//common:enum_base"],
+    deps = [
+        "//common:enum_base",
+        "@llvm-project//llvm:Support",
+    ],
 )
 
 cc_library(

+ 102 - 54
toolchain/semantics/semantics_context.cpp

@@ -449,6 +449,40 @@ auto SemanticsContext::ParamOrArgSave(bool for_args) -> void {
   params_or_args.push_back(entry_node_id);
 }
 
+auto SemanticsContext::CanonicalizeTypeImpl(
+    SemanticsNodeKind kind,
+    llvm::function_ref<void(llvm::FoldingSetNodeID& canonical_id)> profile_type,
+    llvm::function_ref<SemanticsNodeId()> make_node) -> SemanticsTypeId {
+  llvm::FoldingSetNodeID canonical_id;
+  kind.Profile(canonical_id);
+  profile_type(canonical_id);
+
+  void* insert_pos;
+  auto* node =
+      canonical_type_nodes_.FindNodeOrInsertPos(canonical_id, insert_pos);
+  if (node != nullptr) {
+    return node->type_id();
+  }
+
+  auto node_id = make_node();
+  auto type_id = semantics_ir_->AddType(node_id);
+  CARBON_CHECK(canonical_types_.insert({node_id, type_id}).second);
+  type_node_storage_.push_back(
+      std::make_unique<TypeNode>(canonical_id, type_id));
+
+  // In a debug build, check that our insertion position is still valid. It
+  // could have been invalidated by a misbehaving `make_node`.
+  CARBON_DCHECK([&] {
+    void* check_insert_pos;
+    auto* check_node = canonical_type_nodes_.FindNodeOrInsertPos(
+        canonical_id, check_insert_pos);
+    return !check_node && insert_pos == check_insert_pos;
+  }()) << "Type was created recursively during canonicalization";
+
+  canonical_type_nodes_.InsertNode(type_node_storage_.back().get(), insert_pos);
+  return type_id;
+}
+
 auto SemanticsContext::CanonicalizeType(SemanticsNodeId node_id)
     -> SemanticsTypeId {
   auto node = semantics_ir_->GetNode(node_id);
@@ -464,70 +498,84 @@ auto SemanticsContext::CanonicalizeType(SemanticsNodeId node_id)
     return it->second;
   }
 
-  auto type_id = semantics_ir_->AddType(node_id);
-  CARBON_CHECK(canonical_types_.insert({node_id, type_id}).second);
-  return type_id;
+  switch (node.kind()) {
+    case SemanticsNodeKind::Builtin:
+    case SemanticsNodeKind::CrossReference: {
+      // TODO: Cross-references should be canonicalized by looking at their
+      // target rather than treating them as new unique types.
+      auto type_id = semantics_ir_->AddType(node_id);
+      CARBON_CHECK(canonical_types_.insert({node_id, type_id}).second);
+      return type_id;
+    }
+    case SemanticsNodeKind::ConstType: {
+      return CanonicalizeTypeImpl(
+          node.kind(), node_id, [&](llvm::FoldingSetNodeID& canonical_id) {
+            canonical_id.AddInteger(
+                GetUnqualifiedType(node.GetAsConstType()).index);
+          });
+    }
+    case SemanticsNodeKind::PointerType: {
+      return CanonicalizeTypeImpl(
+          node.kind(), node_id, [&](llvm::FoldingSetNodeID& canonical_id) {
+            canonical_id.AddInteger(node.GetAsPointerType().index);
+          });
+    }
+    case SemanticsNodeKind::StructType:
+    case SemanticsNodeKind::TupleType: {
+      CARBON_FATAL() << "Type should have been canonizalized when created: "
+                     << node;
+    }
+    default: {
+      CARBON_FATAL() << "Unexpected non-canonical type node " << node;
+    }
+  }
 }
 
 auto SemanticsContext::CanonicalizeStructType(ParseTree::Node parse_node,
                                               SemanticsNodeBlockId refs_id)
     -> SemanticsTypeId {
-  // Construct the field structure for lookup.
-  auto refs = semantics_ir_->GetNodeBlock(refs_id);
-  llvm::FoldingSetNodeID canonical_id;
-  for (const auto& ref_id : refs) {
-    auto ref = semantics_ir_->GetNode(ref_id);
-    auto [name_id, type_id] = ref.GetAsStructTypeField();
-    canonical_id.AddInteger(name_id.index);
-    canonical_id.AddInteger(type_id.index);
-  }
-
-  // If a struct with matching fields was already created, reuse it.
-  void* insert_pos;
-  auto* node =
-      canonical_struct_types_.FindNodeOrInsertPos(canonical_id, insert_pos);
-  if (node != nullptr) {
-    return node->type_id();
-  }
-
-  // The struct doesn't already exist, so create and store it as canonical.
-  auto node_id = AddNode(SemanticsNode::StructType::Make(
-      parse_node, SemanticsTypeId::TypeType, refs_id));
-  auto type_id = semantics_ir_->AddType(node_id);
-  CARBON_CHECK(canonical_types_.insert({node_id, type_id}).second);
-  canonical_types_nodes_.push_back(
-      std::make_unique<TypeNode>(canonical_id, type_id));
-  canonical_struct_types_.InsertNode(canonical_types_nodes_.back().get(),
-                                     insert_pos);
-  return type_id;
+  auto profile_struct = [&](llvm::FoldingSetNodeID& canonical_id) {
+    auto refs = semantics_ir_->GetNodeBlock(refs_id);
+    for (const auto& ref_id : refs) {
+      auto ref = semantics_ir_->GetNode(ref_id);
+      auto [name_id, type_id] = ref.GetAsStructTypeField();
+      canonical_id.AddInteger(name_id.index);
+      canonical_id.AddInteger(type_id.index);
+    }
+  };
+  auto make_struct_node = [&] {
+    return AddNode(SemanticsNode::StructType::Make(
+        parse_node, SemanticsTypeId::TypeType, refs_id));
+  };
+  return CanonicalizeTypeImpl(SemanticsNodeKind::StructType, profile_struct,
+                              make_struct_node);
 }
 
 auto SemanticsContext::CanonicalizeTupleType(
     ParseTree::Node parse_node, llvm::SmallVector<SemanticsTypeId>&& type_ids)
     -> SemanticsTypeId {
-  llvm::FoldingSetNodeID canonical_id;
-  for (const auto& type_id : type_ids) {
-    canonical_id.AddInteger(type_id.index);
-  }
-  // If a tuple with matching fields was already created, reuse it.
-  void* insert_pos;
-  auto* node =
-      canonical_tuple_types_.FindNodeOrInsertPos(canonical_id, insert_pos);
-  if (node != nullptr) {
-    return node->type_id();
-  }
-  // The tuple type doesn't already exist, so create and store it as canonical.
-  auto type_block_id = semantics_ir_->AddTypeBlock();
-  auto& type_block = semantics_ir_->GetTypeBlock(type_block_id);
-  type_block = std::move(type_ids);
-  auto node_id = AddNode(SemanticsNode::TupleType::Make(
-      parse_node, SemanticsTypeId::TypeType, type_block_id));
-  auto type_id = semantics_ir_->AddType(node_id);
-  CARBON_CHECK(canonical_types_.insert({node_id, type_id}).second);
-  canonical_types_nodes_.push_back(
-      std::make_unique<TypeNode>(canonical_id, type_id));
-  canonical_tuple_types_.InsertNode(canonical_types_nodes_.back().get(),
-                                    insert_pos);
+  auto profile_tuple = [&](llvm::FoldingSetNodeID& canonical_id) {
+    for (const auto& type_id : type_ids) {
+      canonical_id.AddInteger(type_id.index);
+    }
+  };
+  auto make_tuple_node = [&] {
+    auto type_block_id = semantics_ir_->AddTypeBlock();
+    auto& type_block = semantics_ir_->GetTypeBlock(type_block_id);
+    type_block = std::move(type_ids);
+    return AddNode(SemanticsNode::TupleType::Make(
+        parse_node, SemanticsTypeId::TypeType, type_block_id));
+  };
+  return CanonicalizeTypeImpl(SemanticsNodeKind::TupleType, profile_tuple,
+                              make_tuple_node);
+}
+
+auto SemanticsContext::GetUnqualifiedType(SemanticsTypeId type_id)
+    -> SemanticsTypeId {
+  SemanticsNode type_node =
+      semantics_ir_->GetNode(semantics_ir_->GetType(type_id));
+  if (type_node.kind() == SemanticsNodeKind::ConstType)
+    return type_node.GetAsConstType();
   return type_id;
 }
 

+ 35 - 14
toolchain/semantics/semantics_context.h

@@ -169,6 +169,9 @@ class SemanticsContext {
         ImplicitAsRequired(parse_node, value_id, SemanticsTypeId::TypeType));
   }
 
+  // Removes any top-level `const` qualifiers from a type.
+  auto GetUnqualifiedType(SemanticsTypeId type_id) -> SemanticsTypeId;
+
   // Starts handling parameters or arguments.
   auto ParamOrArgStart() -> void;
 
@@ -229,7 +232,7 @@ class SemanticsContext {
     Compatible,
   };
 
-  // A FoldingSet node for a struct or tuple type.
+  // A FoldingSet node for a type.
   class TypeNode : public llvm::FastFoldingSetNode {
    public:
     explicit TypeNode(const llvm::FoldingSetNodeID& node_id,
@@ -262,6 +265,30 @@ class SemanticsContext {
   auto ImplicitAsImpl(SemanticsNodeId value_id, SemanticsTypeId as_type_id,
                       SemanticsNodeId* output_value_id) -> ImplicitAsKind;
 
+  // Forms a canonical type ID for a type. This function is given two
+  // callbacks:
+  //
+  // `profile_type(canonical_id)` is called to build a fingerprint for this
+  // type. The ID should be distinct for all distinct type values with the same
+  // `kind`.
+  //
+  // `make_node()` is called to obtain a `SemanticsNodeId` that describes the
+  // type. It is only called if the type does not already exist, so can be used
+  // to lazily build the `SemanticsNode`. `make_node()` is not permitted to
+  // directly or indirectly canonicalize any types.
+  auto CanonicalizeTypeImpl(
+      SemanticsNodeKind kind,
+      llvm::function_ref<void(llvm::FoldingSetNodeID& canonical_id)>
+          profile_type,
+      llvm::function_ref<SemanticsNodeId()> make_node) -> SemanticsTypeId;
+
+  // Forms a canonical type ID for an already-existing node describing a type.
+  template <typename ProfileType>
+  auto CanonicalizeTypeImpl(SemanticsNodeKind kind, SemanticsNodeId node_id,
+                            ProfileType profile_type) -> SemanticsTypeId {
+    return CanonicalizeTypeImpl(kind, profile_type, [&] { return node_id; });
+  }
+
   auto current_scope() -> ScopeStackEntry& { return scope_stack_.back(); }
 
   // Tokens for getting data on literals.
@@ -316,22 +343,16 @@ class SemanticsContext {
   llvm::DenseMap<SemanticsStringId, llvm::SmallVector<SemanticsNodeId>>
       name_lookup_;
 
-  // Tracks types which have been used, so that they aren't repeatedly added to
-  // SemanticsIR.
+  // Cache of the mapping from nodes to types, to avoid recomputing the folding
+  // set ID.
   llvm::DenseMap<SemanticsNodeId, SemanticsTypeId> canonical_types_;
 
-  // Tracks struct type literals which have been defined, so that they aren't
-  // repeatedly redefined.
-  llvm::FoldingSet<TypeNode> canonical_struct_types_;
-
-  // Tracks tuple type literals which have been defined, so that they aren't
-  // repeatedly redefined.
-  llvm::FoldingSet<TypeNode> canonical_tuple_types_;
+  // Tracks the canonical representation of types that have been defined.
+  llvm::FoldingSet<TypeNode> canonical_type_nodes_;
 
-  // Storage for the nodes in canonical_struct_types_ and
-  // canonical_tuple_types_. This stores in pointers so that FoldingSet can have
-  // stable pointers.
-  llvm::SmallVector<std::unique_ptr<TypeNode>> canonical_types_nodes_;
+  // Storage for the nodes in canonical_type_nodes_. This stores in pointers so
+  // that FoldingSet can have stable pointers.
+  llvm::SmallVector<std::unique_ptr<TypeNode>> type_node_storage_;
 };
 
 // Parse node handlers. Returns false for unrecoverable errors.

+ 40 - 9
toolchain/semantics/semantics_handle_operator.cpp

@@ -25,7 +25,7 @@ auto SemanticsHandleInfixOperator(SemanticsContext& context,
           SemanticsNode::BinaryOperatorAdd::Make(
               parse_node, context.semantics_ir().GetNode(lhs_id).type_id(),
               lhs_id, rhs_id));
-      break;
+      return true;
 
     case TokenKind::And:
     case TokenKind::Or: {
@@ -49,19 +49,33 @@ auto SemanticsHandleInfixOperator(SemanticsContext& context,
           SemanticsNode::BlockArg::Make(
               parse_node, context.semantics_ir().GetNode(rhs_id).type_id(),
               resume_block_id));
-      break;
+      return true;
     }
 
     default:
       return context.TODO(parse_node, llvm::formatv("Handle {0}", token_kind));
   }
-
-  return true;
 }
 
 auto SemanticsHandlePostfixOperator(SemanticsContext& context,
                                     ParseTree::Node parse_node) -> bool {
-  return context.TODO(parse_node, "HandlePostfixOperator");
+  auto value_id = context.node_stack().PopExpression();
+
+  // Figure out the operator for the token.
+  auto token = context.parse_tree().node_token(parse_node);
+  switch (auto token_kind = context.tokens().GetKind(token)) {
+    case TokenKind::Star: {
+      auto inner_type_id = context.ExpressionAsType(parse_node, value_id);
+      context.AddNodeAndPush(
+          parse_node,
+          SemanticsNode::PointerType::Make(
+              parse_node, SemanticsTypeId::TypeType, inner_type_id));
+      return true;
+    }
+
+    default:
+      CARBON_FATAL() << "Unexpected postfix operator " << token_kind;
+  }
 }
 
 auto SemanticsHandlePrefixOperator(SemanticsContext& context,
@@ -78,13 +92,30 @@ auto SemanticsHandlePrefixOperator(SemanticsContext& context,
           SemanticsNode::UnaryOperatorNot::Make(
               parse_node, context.semantics_ir().GetNode(value_id).type_id(),
               value_id));
-      break;
+      return true;
+
+    case TokenKind::Const: {
+      // `const (const T)` is probably not what the developer intended.
+      // TODO: Detect `const (const T)*` and suggest moving the `*` inside the
+      // parentheses.
+      if (context.semantics_ir().GetNode(value_id).kind() ==
+          SemanticsNodeKind::ConstType) {
+        CARBON_DIAGNOSTIC(RepeatedConst, Warning,
+                          "`const` applied repeatedly to the same type has no "
+                          "additional effect.");
+        context.emitter().Emit(parse_node, RepeatedConst);
+      }
+      auto inner_type_id = context.ExpressionAsType(parse_node, value_id);
+      context.AddNodeAndPush(
+          parse_node,
+          SemanticsNode::ConstType::Make(parse_node, SemanticsTypeId::TypeType,
+                                         inner_type_id));
+      return true;
+    }
 
     default:
       return context.TODO(parse_node, llvm::formatv("Handle {0}", token_kind));
   }
-
-  return true;
 }
 
 auto SemanticsHandleShortCircuitOperand(SemanticsContext& context,
@@ -115,7 +146,7 @@ auto SemanticsHandleShortCircuitOperand(SemanticsContext& context,
       break;
 
     default:
-      CARBON_FATAL() << "Unexpected short-circuiting operator " << parse_node;
+      CARBON_FATAL() << "Unexpected short-circuiting operator " << token_kind;
   }
 
   // Create a block for the right-hand side and for the continuation.

+ 95 - 7
toolchain/semantics/semantics_ir.cpp

@@ -197,6 +197,54 @@ auto SemanticsIR::Print(llvm::raw_ostream& out, bool include_builtins) const
   PrintBlock(out, "node_blocks", node_blocks_);
 }
 
+// Map a node kind representing a type into an integer describing the
+// precedence of that type's syntax. Higher numbers correspond to higher
+// precedence.
+static auto GetTypePrecedence(SemanticsNodeKind kind) -> int {
+  switch (kind) {
+    case SemanticsNodeKind::Builtin:
+    case SemanticsNodeKind::StructType:
+    case SemanticsNodeKind::TupleType:
+      return 0;
+    case SemanticsNodeKind::ConstType:
+      return -1;
+    case SemanticsNodeKind::PointerType:
+      return -2;
+
+    case SemanticsNodeKind::CrossReference:
+      // TODO: Once we support stringification of cross-references, we'll need
+      // to determine the precedence of the target of the cross-reference. For
+      // now, all cross-references refer to builtin types from the prelude.
+      return 0;
+
+    case SemanticsNodeKind::Assign:
+    case SemanticsNodeKind::BinaryOperatorAdd:
+    case SemanticsNodeKind::BindName:
+    case SemanticsNodeKind::BlockArg:
+    case SemanticsNodeKind::BoolLiteral:
+    case SemanticsNodeKind::Branch:
+    case SemanticsNodeKind::BranchIf:
+    case SemanticsNodeKind::BranchWithArg:
+    case SemanticsNodeKind::Call:
+    case SemanticsNodeKind::FunctionDeclaration:
+    case SemanticsNodeKind::IntegerLiteral:
+    case SemanticsNodeKind::Invalid:
+    case SemanticsNodeKind::Namespace:
+    case SemanticsNodeKind::RealLiteral:
+    case SemanticsNodeKind::Return:
+    case SemanticsNodeKind::ReturnExpression:
+    case SemanticsNodeKind::StringLiteral:
+    case SemanticsNodeKind::StructMemberAccess:
+    case SemanticsNodeKind::StructTypeField:
+    case SemanticsNodeKind::StructValue:
+    case SemanticsNodeKind::StubReference:
+    case SemanticsNodeKind::TupleValue:
+    case SemanticsNodeKind::UnaryOperatorNot:
+    case SemanticsNodeKind::VarStorage:
+      CARBON_FATAL() << "GetTypePrecedence for non-type node kind " << kind;
+  }
+}
+
 auto SemanticsIR::StringifyType(SemanticsTypeId type_id) -> std::string {
   std::string str;
   llvm::raw_string_ostream out(str);
@@ -206,9 +254,13 @@ auto SemanticsIR::StringifyType(SemanticsTypeId type_id) -> std::string {
     SemanticsNodeId node_id;
     // The index into node_id to print. Not used by all types.
     int index = 0;
+
+    auto Next() const -> Step {
+      return {.node_id = node_id, .index = index + 1};
+    }
   };
-  llvm::SmallVector<Step> steps = {
-      {.node_id = GetTypeAllowBuiltinTypes(type_id)}};
+  auto outer_node_id = GetTypeAllowBuiltinTypes(type_id);
+  llvm::SmallVector<Step> steps = {{.node_id = outer_node_id}};
 
   while (!steps.empty()) {
     auto step = steps.pop_back_val();
@@ -227,10 +279,37 @@ auto SemanticsIR::StringifyType(SemanticsTypeId type_id) -> std::string {
 
     auto node = GetNode(step.node_id);
     switch (node.kind()) {
+      case SemanticsNodeKind::ConstType: {
+        if (step.index == 0) {
+          out << "const ";
+
+          // Add parentheses if required.
+          auto inner_type_node_id = GetType(node.GetAsConstType());
+          if (GetTypePrecedence(GetNode(inner_type_node_id).kind()) <
+              GetTypePrecedence(node.kind())) {
+            out << "(";
+            steps.push_back(step.Next());
+          }
+
+          steps.push_back({.node_id = inner_type_node_id});
+        } else if (step.index == 1) {
+          out << ")";
+        }
+        break;
+      }
+      case SemanticsNodeKind::PointerType: {
+        if (step.index == 0) {
+          steps.push_back(step.Next());
+          steps.push_back({.node_id = GetType(node.GetAsPointerType())});
+        } else if (step.index == 1) {
+          out << "*";
+        }
+        break;
+      }
       case SemanticsNodeKind::StructType: {
         auto refs = GetNodeBlock(node.GetAsStructType());
         if (refs.empty()) {
-          out << "{} as Type";
+          out << "{}";
           break;
         } else if (step.index == 0) {
           out << "{";
@@ -241,7 +320,7 @@ auto SemanticsIR::StringifyType(SemanticsTypeId type_id) -> std::string {
           break;
         }
 
-        steps.push_back({.node_id = step.node_id, .index = step.index + 1});
+        steps.push_back(step.Next());
         steps.push_back({.node_id = refs[step.index]});
         break;
       }
@@ -254,7 +333,7 @@ auto SemanticsIR::StringifyType(SemanticsTypeId type_id) -> std::string {
       case SemanticsNodeKind::TupleType: {
         auto refs = GetTypeBlock(node.GetAsTupleType());
         if (refs.empty()) {
-          out << "() as type";
+          out << "()";
           break;
         } else if (step.index == 0) {
           out << "(";
@@ -266,10 +345,10 @@ auto SemanticsIR::StringifyType(SemanticsTypeId type_id) -> std::string {
           if (step.index == 1) {
             out << ",";
           }
-          out << ") as type";
+          out << ")";
           break;
         }
-        steps.push_back({.node_id = step.node_id, .index = step.index + 1});
+        steps.push_back(step.Next());
         steps.push_back(
             {.node_id = GetTypeAllowBuiltinTypes(refs[step.index])});
         break;
@@ -308,6 +387,15 @@ auto SemanticsIR::StringifyType(SemanticsTypeId type_id) -> std::string {
     }
   }
 
+  // For `{}` or any tuple type, we've printed a non-type expression, so add a
+  // conversion to type `type`.
+  auto outer_node = GetNode(outer_node_id);
+  if (outer_node.kind() == SemanticsNodeKind::TupleType ||
+      (outer_node.kind() == SemanticsNodeKind::StructType &&
+       GetNodeBlock(outer_node.GetAsStructType()).empty())) {
+    out << " as type";
+  }
+
   return str;
 }
 

+ 6 - 0
toolchain/semantics/semantics_node.h

@@ -357,6 +357,9 @@ class SemanticsNode {
       Factory<SemanticsNodeKind::Call, SemanticsNodeBlockId /*refs_id*/,
               SemanticsFunctionId /*function_id*/>;
 
+  using ConstType =
+      Factory<SemanticsNodeKind::ConstType, SemanticsTypeId /*inner_id*/>;
+
   class CrossReference
       : public FactoryBase<SemanticsNodeKind::CrossReference,
                            SemanticsCrossReferenceIRId /*ir_id*/,
@@ -383,6 +386,9 @@ class SemanticsNode {
   using Namespace = FactoryNoType<SemanticsNodeKind::Namespace,
                                   SemanticsNameScopeId /*name_scope_id*/>;
 
+  using PointerType =
+      Factory<SemanticsNodeKind::PointerType, SemanticsTypeId /*pointee_id*/>;
+
   using RealLiteral = Factory<SemanticsNodeKind::RealLiteral,
                               SemanticsRealLiteralId /*real_id*/>;
 

+ 2 - 0
toolchain/semantics/semantics_node_kind.def

@@ -37,9 +37,11 @@ CARBON_SEMANTICS_NODE_KIND_WITH_TERMINATOR_KIND(BranchIf, TerminatorSequence)
 CARBON_SEMANTICS_NODE_KIND_WITH_TERMINATOR_KIND(BranchWithArg, Terminator)
 CARBON_SEMANTICS_NODE_KIND(Builtin)
 CARBON_SEMANTICS_NODE_KIND(Call)
+CARBON_SEMANTICS_NODE_KIND(ConstType)
 CARBON_SEMANTICS_NODE_KIND(FunctionDeclaration)
 CARBON_SEMANTICS_NODE_KIND(IntegerLiteral)
 CARBON_SEMANTICS_NODE_KIND(Namespace)
+CARBON_SEMANTICS_NODE_KIND(PointerType)
 CARBON_SEMANTICS_NODE_KIND(RealLiteral)
 CARBON_SEMANTICS_NODE_KIND_WITH_TERMINATOR_KIND(Return, Terminator)
 CARBON_SEMANTICS_NODE_KIND_WITH_TERMINATOR_KIND(ReturnExpression, Terminator)

+ 5 - 0
toolchain/semantics/semantics_node_kind.h

@@ -8,6 +8,7 @@
 #include <cstdint>
 
 #include "common/enum_base.h"
+#include "llvm/ADT/FoldingSet.h"
 
 namespace Carbon {
 
@@ -42,6 +43,10 @@ class SemanticsNodeKind : public CARBON_ENUM_BASE(SemanticsNodeKind) {
   // code block appears after all other instructions, and ends with a
   // terminator instruction.
   [[nodiscard]] auto terminator_kind() const -> SemanticsTerminatorKind;
+
+  // Compute a fingerprint for this node kind, allowing its use as part of the
+  // key in a `FoldingSet`.
+  void Profile(llvm::FoldingSetNodeID& id) { id.AddInteger(AsInt()); }
 };
 
 #define CARBON_SEMANTICS_NODE_KIND(Name) \

+ 1 - 0
toolchain/semantics/semantics_node_stack.h

@@ -250,6 +250,7 @@ class SemanticsNodeStack {
       case ParseNodeKind::NameExpression:
       case ParseNodeKind::ParenExpression:
       case ParseNodeKind::PatternBinding:
+      case ParseNodeKind::PostfixOperator:
       case ParseNodeKind::PrefixOperator:
       case ParseNodeKind::ShortCircuitOperand:
       case ParseNodeKind::StructFieldValue:

+ 70 - 0
toolchain/semantics/testdata/const/collapse.carbon

@@ -0,0 +1,70 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str0, param_refs: block2, return_type: type3, body: {block4}}},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   F,
+// CHECK:STDOUT:   p,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeIntegerType,
+// CHECK:STDOUT:   node+0,
+// CHECK:STDOUT:   node+1,
+// CHECK:STDOUT:   node+2,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: type_blocks: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: ConstType, arg0: type0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: PointerType, arg0: type1, type: typeTypeType},
+// CHECK:STDOUT:   {kind: PointerType, arg0: type2, type: typeTypeType},
+// CHECK:STDOUT:   {kind: VarStorage, type: type3},
+// CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node+3, type: type3},
+// CHECK:STDOUT:   {kind: ConstType, arg0: type0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: ConstType, arg0: type1, type: typeTypeType},
+// CHECK:STDOUT:   {kind: PointerType, arg0: type1, type: typeTypeType},
+// CHECK:STDOUT:   {kind: PointerType, arg0: type2, type: typeTypeType},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+3},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+10,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+// OK, `const (const i32)` is the same type as `const i32`.
+// CHECK:STDERR: collapse.carbon:[[@LINE+3]]:25: `const` applied repeatedly to the same type has no additional effect.
+// CHECK:STDERR: fn F(p: const i32**) -> const (const i32)** {
+// CHECK:STDERR:                         ^
+fn F(p: const i32**) -> const (const i32)** {
+  return p;
+}

+ 72 - 0
toolchain/semantics/testdata/const/fail_collapse.carbon

@@ -0,0 +1,72 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str0, param_refs: block2, return_type: type5, body: {block4}}},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   G,
+// CHECK:STDOUT:   p,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeIntegerType,
+// CHECK:STDOUT:   node+0,
+// CHECK:STDOUT:   node+2,
+// CHECK:STDOUT:   node+3,
+// CHECK:STDOUT:   node+6,
+// CHECK:STDOUT:   node+7,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: type_blocks: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: ConstType, arg0: type0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: ConstType, arg0: type1, type: typeTypeType},
+// CHECK:STDOUT:   {kind: PointerType, arg0: type1, type: typeTypeType},
+// CHECK:STDOUT:   {kind: PointerType, arg0: type2, type: typeTypeType},
+// CHECK:STDOUT:   {kind: VarStorage, type: type3},
+// CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node+4, type: type3},
+// CHECK:STDOUT:   {kind: PointerType, arg0: type0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: PointerType, arg0: type4, type: typeTypeType},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: nodeError},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+// CHECK:STDERR: fail_collapse.carbon:[[@LINE+3]]:9: `const` applied repeatedly to the same type has no additional effect.
+// CHECK:STDERR: fn G(p: const (const i32)**) -> i32** {
+// CHECK:STDERR:         ^
+fn G(p: const (const i32)**) -> i32** {
+  // CHECK:STDERR: fail_collapse.carbon:[[@LINE+3]]:11: Cannot implicitly convert from `const i32**` to `i32**`.
+  // CHECK:STDERR:   return p;
+  // CHECK:STDERR:           ^
+  return p;
+}

+ 68 - 0
toolchain/semantics/testdata/pointer/basic.carbon

@@ -0,0 +1,68 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str0, param_refs: block0, return_type: type0, body: {block2}}},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT:   0,
+// CHECK:STDOUT:   0,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   F,
+// CHECK:STDOUT:   n,
+// CHECK:STDOUT:   p,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeIntegerType,
+// CHECK:STDOUT:   node+5,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: type_blocks: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: VarStorage, type: type0},
+// CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node+1, type: type0},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: type0},
+// CHECK:STDOUT:   {kind: Assign, arg0: node+1, arg1: node+3},
+// CHECK:STDOUT:   {kind: PointerType, arg0: type0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: VarStorage, type: type1},
+// CHECK:STDOUT:   {kind: BindName, arg0: str2, arg1: node+6, type: type1},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int1, type: type0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+8},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn F() -> i32 {
+  var n: i32 = 0;
+  var p: i32*;
+
+  // TODO: Test taking address and dereference.
+  // p = &n;
+  // *p = 1;
+  // return *p;
+
+  return 0;
+}

+ 70 - 0
toolchain/semantics/testdata/pointer/fail_type_mismatch.carbon

@@ -0,0 +1,70 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str0, param_refs: block2, return_type: type4, body: {block4}}},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   ConstMismatch,
+// CHECK:STDOUT:   p,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   node+0,
+// CHECK:STDOUT:   node+2,
+// CHECK:STDOUT:   node+3,
+// CHECK:STDOUT:   node+7,
+// CHECK:STDOUT:   node+8,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: type_blocks: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: StructType, arg0: block0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: StructValue, arg0: block0, type: type0},
+// CHECK:STDOUT:   {kind: ConstType, arg0: type0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: PointerType, arg0: type1, type: typeTypeType},
+// CHECK:STDOUT:   {kind: VarStorage, type: type2},
+// CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node+4, type: type2},
+// CHECK:STDOUT:   {kind: StructValue, arg0: block0, type: type0},
+// CHECK:STDOUT:   {kind: PointerType, arg0: type0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: ConstType, arg0: type3, type: typeTypeType},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: nodeError},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+10,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn ConstMismatch(p: const {}*) -> const ({}*) {
+  // CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+3]]:11: Cannot implicitly convert from `const {}*` to `const ({}*)`.
+  // CHECK:STDERR:   return p;
+  // CHECK:STDERR:           ^
+  return p;
+}

+ 85 - 0
toolchain/semantics/testdata/pointer/types.carbon

@@ -0,0 +1,85 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT:   {name: str0, param_refs: block2, return_type: type1, body: {block4}}},
+// CHECK:STDOUT:   {name: str2, param_refs: block6, return_type: type3, body: {block7}}},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   Ptr,
+// CHECK:STDOUT:   p,
+// CHECK:STDOUT:   ConstPtr,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   nodeIntegerType,
+// CHECK:STDOUT:   node+0,
+// CHECK:STDOUT:   node+6,
+// CHECK:STDOUT:   node+7,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: type_blocks: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: PointerType, arg0: type0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: VarStorage, type: type1},
+// CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node+1, type: type1},
+// CHECK:STDOUT:   {kind: PointerType, arg0: type0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function0},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+1},
+// CHECK:STDOUT:   {kind: ConstType, arg0: type0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: PointerType, arg0: type2, type: typeTypeType},
+// CHECK:STDOUT:   {kind: VarStorage, type: type3},
+// CHECK:STDOUT:   {kind: BindName, arg0: str1, arg1: node+8, type: type3},
+// CHECK:STDOUT:   {kind: ConstType, arg0: type0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: PointerType, arg0: type2, type: typeTypeType},
+// CHECK:STDOUT:   {kind: FunctionDeclaration, arg0: function1},
+// CHECK:STDOUT:   {kind: ReturnExpression, arg0: node+8},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:     node+10,
+// CHECK:STDOUT:     node+11,
+// CHECK:STDOUT:     node+12,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+13,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+fn Ptr(p: i32*) -> i32* {
+  return p;
+}
+
+fn ConstPtr(p: const i32*) -> (const i32)* {
+  return p;
+}

+ 1 - 1
toolchain/semantics/testdata/struct/fail_assign_empty.carbon

@@ -47,7 +47,7 @@
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]
 
-// CHECK:STDERR: fail_assign_empty.carbon:[[@LINE+3]]:22: Cannot implicitly convert from `{} as Type` to `{.a: i32}`.
+// CHECK:STDERR: fail_assign_empty.carbon:[[@LINE+3]]:22: Cannot implicitly convert from `{} as type` to `{.a: i32}`.
 // CHECK:STDERR: var x: {.a: i32} = {};
 // CHECK:STDERR:                      ^
 var x: {.a: i32} = {};

+ 69 - 0
toolchain/semantics/testdata/struct/fail_assign_nested.carbon

@@ -0,0 +1,69 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   x,
+// CHECK:STDOUT:   a,
+// CHECK:STDOUT:   b,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   node+0,
+// CHECK:STDOUT:   node+3,
+// CHECK:STDOUT:   node+9,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: type_blocks: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: StructType, arg0: block0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: StructValue, arg0: block0, type: type0},
+// CHECK:STDOUT:   {kind: StructTypeField, arg0: str1, arg1: type0},
+// CHECK:STDOUT:   {kind: StructType, arg0: block2, type: typeTypeType},
+// CHECK:STDOUT:   {kind: VarStorage, type: type1},
+// CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node+4, type: type1},
+// CHECK:STDOUT:   {kind: StructValue, arg0: block0, type: type0},
+// CHECK:STDOUT:   {kind: StructTypeField, arg0: str2, arg1: type0},
+// CHECK:STDOUT:   {kind: StubReference, arg0: node+6, type: type0},
+// CHECK:STDOUT:   {kind: StructType, arg0: block3, type: typeTypeType},
+// CHECK:STDOUT:   {kind: StructValue, arg0: block4, type: type2},
+// CHECK:STDOUT:   {kind: Assign, arg0: node+4, arg1: nodeError},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:     node+10,
+// CHECK:STDOUT:     node+11,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+// CHECK:STDERR: fail_assign_nested.carbon:[[@LINE+3]]:28: Cannot implicitly convert from `{.b: {}}` to `{.a: {}}`.
+// CHECK:STDERR: var x: {.a: {}} = {.b = {}};
+// CHECK:STDERR:                            ^
+var x: {.a: {}} = {.b = {}};

+ 1 - 1
toolchain/semantics/testdata/struct/fail_assign_to_empty.carbon

@@ -56,7 +56,7 @@
 // CHECK:STDOUT:   ],
 // CHECK:STDOUT: ]
 
-// CHECK:STDERR: fail_assign_to_empty.carbon:[[@LINE+3]]:21: Cannot implicitly convert from `{.a: i32}` to `{} as Type`.
+// CHECK:STDERR: fail_assign_to_empty.carbon:[[@LINE+3]]:21: Cannot implicitly convert from `{.a: i32}` to `{} as type`.
 // CHECK:STDERR: var x: {} = {.a = 1};
 // CHECK:STDERR:                     ^
 var x: {} = {.a = 1};

+ 166 - 0
toolchain/semantics/testdata/tuples/fail_assign_nested.carbon

@@ -0,0 +1,166 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// CHECK:STDOUT: cross_reference_irs_size: 1
+// CHECK:STDOUT: functions: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: integer_literals: [
+// CHECK:STDOUT:   1,
+// CHECK:STDOUT:   2,
+// CHECK:STDOUT:   3,
+// CHECK:STDOUT:   4,
+// CHECK:STDOUT:   5,
+// CHECK:STDOUT:   6,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: real_literals: [
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: strings: [
+// CHECK:STDOUT:   x,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: types: [
+// CHECK:STDOUT:   node+2,
+// CHECK:STDOUT:   node+9,
+// CHECK:STDOUT:   nodeIntegerType,
+// CHECK:STDOUT:   node+11,
+// CHECK:STDOUT:   node+12,
+// CHECK:STDOUT:   node+21,
+// CHECK:STDOUT:   node+32,
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: type_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     typeTypeType,
+// CHECK:STDOUT:     typeTypeType,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     type0,
+// CHECK:STDOUT:     type0,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     type2,
+// CHECK:STDOUT:     type2,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     type3,
+// CHECK:STDOUT:     type3,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     type2,
+// CHECK:STDOUT:     type2,
+// CHECK:STDOUT:     type2,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     type5,
+// CHECK:STDOUT:     type5,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: nodes: [
+// CHECK:STDOUT:   {kind: StubReference, arg0: nodeIntegerType, type: typeTypeType},
+// CHECK:STDOUT:   {kind: StubReference, arg0: nodeIntegerType, type: typeTypeType},
+// CHECK:STDOUT:   {kind: TupleType, arg0: typeBlock0, type: typeTypeType},
+// CHECK:STDOUT:   {kind: TupleValue, arg0: block2, type: type0},
+// CHECK:STDOUT:   {kind: StubReference, arg0: node+3, type: type0},
+// CHECK:STDOUT:   {kind: StubReference, arg0: nodeIntegerType, type: typeTypeType},
+// CHECK:STDOUT:   {kind: StubReference, arg0: nodeIntegerType, type: typeTypeType},
+// CHECK:STDOUT:   {kind: TupleValue, arg0: block4, type: type0},
+// CHECK:STDOUT:   {kind: StubReference, arg0: node+7, type: type0},
+// CHECK:STDOUT:   {kind: TupleType, arg0: typeBlock1, type: typeTypeType},
+// CHECK:STDOUT:   {kind: TupleValue, arg0: block3, type: type1},
+// CHECK:STDOUT:   {kind: TupleType, arg0: typeBlock2, type: typeTypeType},
+// CHECK:STDOUT:   {kind: TupleType, arg0: typeBlock3, type: typeTypeType},
+// CHECK:STDOUT:   {kind: VarStorage, type: type4},
+// CHECK:STDOUT:   {kind: BindName, arg0: str0, arg1: node+13, type: type4},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int0, type: type2},
+// CHECK:STDOUT:   {kind: StubReference, arg0: node+15, type: type2},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int1, type: type2},
+// CHECK:STDOUT:   {kind: StubReference, arg0: node+17, type: type2},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int2, type: type2},
+// CHECK:STDOUT:   {kind: StubReference, arg0: node+19, type: type2},
+// CHECK:STDOUT:   {kind: TupleType, arg0: typeBlock4, type: typeTypeType},
+// CHECK:STDOUT:   {kind: TupleValue, arg0: block5, type: type5},
+// CHECK:STDOUT:   {kind: StubReference, arg0: node+22, type: type5},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int3, type: type2},
+// CHECK:STDOUT:   {kind: StubReference, arg0: node+24, type: type2},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int4, type: type2},
+// CHECK:STDOUT:   {kind: StubReference, arg0: node+26, type: type2},
+// CHECK:STDOUT:   {kind: IntegerLiteral, arg0: int5, type: type2},
+// CHECK:STDOUT:   {kind: StubReference, arg0: node+28, type: type2},
+// CHECK:STDOUT:   {kind: TupleValue, arg0: block7, type: type5},
+// CHECK:STDOUT:   {kind: StubReference, arg0: node+30, type: type5},
+// CHECK:STDOUT:   {kind: TupleType, arg0: typeBlock5, type: typeTypeType},
+// CHECK:STDOUT:   {kind: TupleValue, arg0: block6, type: type6},
+// CHECK:STDOUT:   {kind: Assign, arg0: node+13, arg1: nodeError},
+// CHECK:STDOUT: ]
+// CHECK:STDOUT: node_blocks: [
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:     node+2,
+// CHECK:STDOUT:     node+3,
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:     node+7,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:     node+9,
+// CHECK:STDOUT:     node+10,
+// CHECK:STDOUT:     node+11,
+// CHECK:STDOUT:     node+12,
+// CHECK:STDOUT:     node+13,
+// CHECK:STDOUT:     node+14,
+// CHECK:STDOUT:     node+15,
+// CHECK:STDOUT:     node+16,
+// CHECK:STDOUT:     node+17,
+// CHECK:STDOUT:     node+18,
+// CHECK:STDOUT:     node+19,
+// CHECK:STDOUT:     node+20,
+// CHECK:STDOUT:     node+21,
+// CHECK:STDOUT:     node+22,
+// CHECK:STDOUT:     node+23,
+// CHECK:STDOUT:     node+24,
+// CHECK:STDOUT:     node+25,
+// CHECK:STDOUT:     node+26,
+// CHECK:STDOUT:     node+27,
+// CHECK:STDOUT:     node+28,
+// CHECK:STDOUT:     node+29,
+// CHECK:STDOUT:     node+30,
+// CHECK:STDOUT:     node+31,
+// CHECK:STDOUT:     node+32,
+// CHECK:STDOUT:     node+33,
+// CHECK:STDOUT:     node+34,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+0,
+// CHECK:STDOUT:     node+1,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+4,
+// CHECK:STDOUT:     node+8,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+5,
+// CHECK:STDOUT:     node+6,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+16,
+// CHECK:STDOUT:     node+18,
+// CHECK:STDOUT:     node+20,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+23,
+// CHECK:STDOUT:     node+31,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT:   [
+// CHECK:STDOUT:     node+25,
+// CHECK:STDOUT:     node+27,
+// CHECK:STDOUT:     node+29,
+// CHECK:STDOUT:   ],
+// CHECK:STDOUT: ]
+
+// CHECK:STDERR: fail_assign_nested.carbon:[[@LINE+3]]:57: Cannot implicitly convert from `((i32, i32, i32), (i32, i32, i32)) as type` to `((i32, i32), (i32, i32)) as type`.
+// CHECK:STDERR: var x: ((i32, i32), (i32, i32)) = ((1, 2, 3), (4, 5, 6));
+// CHECK:STDERR:                                                         ^
+var x: ((i32, i32), (i32, i32)) = ((1, 2, 3), (4, 5, 6));