Explorar o código

Make type contexts expect a value of type `Type`. (#2357)

In particular, this means that a type can implement `ImplicitAs(Type)` and have values of that type behave like types.

This implies that `()` and `{}` are no longer types. They are now values whose type is the result of converting `()` or `{}` to type `Type`, as has been discussed recently and seems to be the supported direction. This fixes various cases where these types were previously mishandled.
Richard Smith %!s(int64=3) %!d(string=hai) anos
pai
achega
0219218b01
Modificáronse 40 ficheiros con 673 adicións e 318 borrados
  1. 6 4
      explorer/ast/ast_rtti.txt
  2. 7 0
      explorer/ast/expression.cpp
  3. 77 28
      explorer/ast/expression.h
  4. 6 0
      explorer/fuzzing/ast_to_proto.cpp
  5. 64 60
      explorer/interpreter/interpreter.cpp
  6. 2 2
      explorer/interpreter/pattern_analysis.cpp
  7. 3 1
      explorer/interpreter/resolve_names.cpp
  8. 5 0
      explorer/interpreter/resolve_unformed.cpp
  9. 308 154
      explorer/interpreter/type_checker.cpp
  10. 4 5
      explorer/interpreter/type_checker.h
  11. 19 14
      explorer/interpreter/value.cpp
  12. 46 21
      explorer/interpreter/value.h
  13. 4 4
      explorer/syntax/parser.ypp
  14. 22 0
      explorer/testdata/array/element_convertible_to_type.carbon
  15. 1 1
      explorer/testdata/as/fail_destination_not_type.carbon
  16. 1 1
      explorer/testdata/basic_syntax/fail_alternative_not_type.carbon
  17. 1 1
      explorer/testdata/basic_syntax/fail_nested_binding.carbon
  18. 1 1
      explorer/testdata/basic_syntax/fail_var_type.carbon
  19. 1 1
      explorer/testdata/class/fail_extends_non_class.carbon
  20. 1 1
      explorer/testdata/class/fail_method_deduced.carbon
  21. 1 1
      explorer/testdata/class/fail_method_in_var.carbon
  22. 1 1
      explorer/testdata/class/fail_return_method.carbon
  23. 3 4
      explorer/testdata/comparison/empty_struct.carbon
  24. 1 1
      explorer/testdata/constraint/fail_where_non_type_is.carbon
  25. 1 1
      explorer/testdata/function/fail_parameter_type.carbon
  26. 1 1
      explorer/testdata/function/fail_return_call_has_invalid_body.carbon
  27. 1 1
      explorer/testdata/function/fail_var_type_is_call.carbon
  28. 21 0
      explorer/testdata/function/return_convertible_to_type.carbon
  29. 1 1
      explorer/testdata/generic_class/fail_bad_parameter_type.carbon
  30. 1 1
      explorer/testdata/generic_class/fail_generic_in_pattern.carbon
  31. 1 1
      explorer/testdata/generic_class/fail_no_args.carbon
  32. 1 1
      explorer/testdata/generic_class/fail_return_type_is_type.carbon
  33. 1 1
      explorer/testdata/generic_function/fail_not_type.carbon
  34. 1 1
      explorer/testdata/impl/fail_impl_as_parameterized.carbon
  35. 1 1
      explorer/testdata/interface/fail_impl_as_not_type.carbon
  36. 1 1
      explorer/testdata/interface/fail_impl_not_type.carbon
  37. 1 1
      explorer/testdata/mixin/fail_mix_as_type_expr.carbon
  38. 22 0
      explorer/testdata/struct/field_convertible_to_type.carbon
  39. 14 0
      explorer/testdata/tuple/fail_nontype_tuple_as_type.carbon
  40. 19 0
      explorer/testdata/tuple/fail_type_tuple_as_type.carbon

+ 6 - 4
explorer/ast/ast_rtti.txt

@@ -51,7 +51,11 @@ abstract class Expression : AstNode;
   class BoolTypeLiteral : Expression;
   class BoolLiteral : Expression;
   class CallExpression : Expression;
-  class FunctionTypeLiteral : Expression;
+  abstract class ConstantValueLiteral : Expression;
+    class FunctionTypeLiteral : ConstantValueLiteral;
+    class StructTypeLiteral : ConstantValueLiteral;
+    class ArrayTypeLiteral : ConstantValueLiteral;
+    class ValueLiteral : ConstantValueLiteral;
   abstract class MemberAccessExpression : Expression;
     class SimpleMemberAccessExpression : MemberAccessExpression;
     class CompoundMemberAccessExpression : MemberAccessExpression;
@@ -64,16 +68,14 @@ abstract class Expression : AstNode;
   class StringTypeLiteral : Expression;
   class TupleLiteral : Expression;
   class StructLiteral : Expression;
-  class StructTypeLiteral : Expression;
   class TypeTypeLiteral : Expression;
-  class ValueLiteral : Expression;
   class IdentifierExpression : Expression;
   class DotSelfExpression : Expression;
   class IntrinsicExpression : Expression;
   class IfExpression : Expression;
   class WhereExpression : Expression;
+  class BuiltinConvertExpression : Expression;
   class UnimplementedExpression : Expression;
-  class ArrayTypeLiteral : Expression;
 abstract class WhereClause : AstNode;
   class IsWhereClause : WhereClause;
   class EqualsWhereClause : WhereClause;

+ 7 - 0
explorer/ast/expression.cpp

@@ -257,6 +257,12 @@ void Expression::Print(llvm::raw_ostream& out) const {
       }
       break;
     }
+    case ExpressionKind::BuiltinConvertExpression: {
+      // These don't represent source syntax, so just print the original
+      // expression.
+      out << *cast<BuiltinConvertExpression>(this)->source_expression();
+      break;
+    }
     case ExpressionKind::UnimplementedExpression: {
       const auto& unimplemented = cast<UnimplementedExpression>(*this);
       out << "UnimplementedExpression<" << unimplemented.label() << ">(";
@@ -332,6 +338,7 @@ void Expression::PrintID(llvm::raw_ostream& out) const {
     case ExpressionKind::CompoundMemberAccessExpression:
     case ExpressionKind::IfExpression:
     case ExpressionKind::WhereExpression:
+    case ExpressionKind::BuiltinConvertExpression:
     case ExpressionKind::TupleLiteral:
     case ExpressionKind::StructLiteral:
     case ExpressionKind::StructTypeLiteral:

+ 77 - 28
explorer/ast/expression.h

@@ -504,21 +504,15 @@ class TupleLiteral : public Expression {
   std::vector<Nonnull<Expression*>> fields_;
 };
 
-// A non-empty literal value of a struct type.
-//
-// It can't be empty because the syntax `{}` is a struct type literal as well
-// as a literal value of that type, so for consistency we always represent it
-// as a StructTypeLiteral rather than let it oscillate unpredictably between
-// the two.
+// A literal value of a struct type.
 class StructLiteral : public Expression {
  public:
+  explicit StructLiteral(SourceLocation loc) : StructLiteral(loc, {}) {}
+
   explicit StructLiteral(SourceLocation loc,
                          std::vector<FieldInitializer> fields)
       : Expression(AstNodeKind::StructLiteral, loc),
-        fields_(std::move(fields)) {
-    CARBON_CHECK(!fields_.empty())
-        << "`{}` is represented as a StructTypeLiteral, not a StructLiteral.";
-  }
+        fields_(std::move(fields)) {}
 
   static auto classof(const AstNode* node) -> bool {
     return InheritsFromStructLiteral(node->kind());
@@ -531,18 +525,48 @@ class StructLiteral : public Expression {
   std::vector<FieldInitializer> fields_;
 };
 
+// A base class for literals with a constant value determined by type-checking.
+class ConstantValueLiteral : public Expression {
+ public:
+  explicit ConstantValueLiteral(
+      AstNodeKind kind, SourceLocation source_loc,
+      std::optional<Nonnull<const Value*>> constant_value = std::nullopt)
+      : Expression(kind, source_loc), constant_value_(constant_value) {}
+
+  static auto classof(const AstNode* node) -> bool {
+    return InheritsFromConstantValueLiteral(node->kind());
+  }
+
+  // Returns the constant value of this expression.
+  auto constant_value() const -> const Value& {
+    CARBON_CHECK(constant_value_);
+    return **constant_value_;
+  }
+
+  // Sets the value returned by constant_value(). Can only be called once,
+  // during typechecking.
+  void set_constant_value(Nonnull<const Value*> value) {
+    CARBON_CHECK(!constant_value_.has_value());
+    constant_value_ = value;
+  }
+
+ private:
+  std::optional<Nonnull<const Value*>> constant_value_;
+};
+
 // A literal representing a struct type.
 //
-// Code that handles this type may sometimes need to have special-case handling
-// for `{}`, which is a struct value in addition to being a struct type.
-class StructTypeLiteral : public Expression {
+// Note that a struct type literal can't be empty because `{}` is a struct
+// value. However, that value implicitly converts to a type.
+class StructTypeLiteral : public ConstantValueLiteral {
  public:
-  explicit StructTypeLiteral(SourceLocation loc) : StructTypeLiteral(loc, {}) {}
-
   explicit StructTypeLiteral(SourceLocation loc,
                              std::vector<FieldInitializer> fields)
-      : Expression(AstNodeKind::StructTypeLiteral, loc),
-        fields_(std::move(fields)) {}
+      : ConstantValueLiteral(AstNodeKind::StructTypeLiteral, loc),
+        fields_(std::move(fields)) {
+    CARBON_CHECK(!fields_.empty())
+        << "`{}` is represented as a StructLiteral, not a StructTypeLiteral.";
+  }
 
   static auto classof(const AstNode* node) -> bool {
     return InheritsFromStructTypeLiteral(node->kind());
@@ -623,12 +647,12 @@ class CallExpression : public Expression {
   Bindings bindings_;
 };
 
-class FunctionTypeLiteral : public Expression {
+class FunctionTypeLiteral : public ConstantValueLiteral {
  public:
   explicit FunctionTypeLiteral(SourceLocation source_loc,
                                Nonnull<TupleLiteral*> parameter,
                                Nonnull<Expression*> return_type)
-      : Expression(AstNodeKind::FunctionTypeLiteral, source_loc),
+      : ConstantValueLiteral(AstNodeKind::FunctionTypeLiteral, source_loc),
         parameter_(parameter),
         return_type_(return_type) {}
 
@@ -688,13 +712,13 @@ class TypeTypeLiteral : public Expression {
 
 // A literal value. This is used in desugaring, and can't be expressed in
 // source syntax.
-class ValueLiteral : public Expression {
+class ValueLiteral : public ConstantValueLiteral {
  public:
   // Value literals are created by type-checking, and so are created with their
   // type and value category already known.
   ValueLiteral(SourceLocation source_loc, Nonnull<const Value*> value,
                Nonnull<const Value*> type, ValueCategory value_category)
-      : Expression(AstNodeKind::ValueLiteral, source_loc), value_(value) {
+      : ConstantValueLiteral(AstNodeKind::ValueLiteral, source_loc, value) {
     set_static_type(type);
     set_value_category(value_category);
   }
@@ -702,11 +726,6 @@ class ValueLiteral : public Expression {
   static auto classof(const AstNode* node) -> bool {
     return InheritsFromValueLiteral(node->kind());
   }
-
-  auto value() const -> const Value& { return *value_; }
-
- private:
-  Nonnull<const Value*> value_;
 };
 
 class IntrinsicExpression : public Expression {
@@ -936,6 +955,36 @@ class WhereExpression : public RewritableMixin<Expression> {
   std::optional<Nonnull<const GenericBinding*>> enclosing_dot_self_;
 };
 
+// A builtin conversion to a type determined by type-checking. These are
+// created by type-checking when a type conversion is found to be necessary but
+// that conversion is implemented directly rather than by an `ImplicitAs`
+// implementation.
+class BuiltinConvertExpression : public Expression {
+ public:
+  BuiltinConvertExpression(Nonnull<Expression*> source_expression,
+                           Nonnull<const Value*> destination_type)
+      : Expression(AstNodeKind::BuiltinConvertExpression,
+                   source_expression->source_loc()),
+        source_expression_(source_expression) {
+    set_static_type(destination_type);
+    set_value_category(ValueCategory::Let);
+  }
+
+  static auto classof(const AstNode* node) -> bool {
+    return InheritsFromBuiltinConvertExpression(node->kind());
+  }
+
+  auto source_expression() -> Nonnull<Expression*> {
+    return source_expression_;
+  }
+  auto source_expression() const -> Nonnull<const Expression*> {
+    return source_expression_;
+  }
+
+ private:
+  Nonnull<Expression*> source_expression_;
+};
+
 // An expression whose semantics have not been implemented. This can be used
 // as a placeholder during development, in order to implement and test parsing
 // of a new expression syntax without having to implement its semantics.
@@ -976,14 +1025,14 @@ class UnimplementedExpression : public Expression {
 };
 
 // A literal representing a statically-sized array type.
-class ArrayTypeLiteral : public Expression {
+class ArrayTypeLiteral : public ConstantValueLiteral {
  public:
   // Constructs an array type literal which uses the given expressions to
   // represent the element type and size.
   ArrayTypeLiteral(SourceLocation source_loc,
                    Nonnull<Expression*> element_type_expression,
                    Nonnull<Expression*> size_expression)
-      : Expression(AstNodeKind::ArrayTypeLiteral, source_loc),
+      : ConstantValueLiteral(AstNodeKind::ArrayTypeLiteral, source_loc),
         element_type_expression_(element_type_expression),
         size_expression_(size_expression) {}
 

+ 6 - 0
explorer/fuzzing/ast_to_proto.cpp

@@ -115,6 +115,12 @@ static auto ExpressionToProto(const Expression& expression)
       break;
     }
 
+    case ExpressionKind::BuiltinConvertExpression: {
+      expression_proto = ExpressionToProto(
+          *cast<BuiltinConvertExpression>(expression).source_expression());
+      break;
+    }
+
     case ExpressionKind::CallExpression: {
       const auto& call = cast<CallExpression>(expression);
       auto* call_proto = expression_proto.mutable_call();

+ 64 - 60
explorer/interpreter/interpreter.cpp

@@ -289,11 +289,13 @@ auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
       generic_args[&var_type.binding()] = v;
       return true;
     }
+    case Value::Kind::TupleType:
     case Value::Kind::TupleValue:
       switch (v->kind()) {
+        case Value::Kind::TupleType:
         case Value::Kind::TupleValue: {
-          const auto& p_tup = cast<TupleValue>(*p);
-          const auto& v_tup = cast<TupleValue>(*v);
+          const auto& p_tup = cast<TupleValueBase>(*p);
+          const auto& v_tup = cast<TupleValueBase>(*v);
           CARBON_CHECK(p_tup.elements().size() == v_tup.elements().size());
           for (size_t i = 0; i < p_tup.elements().size(); ++i) {
             if (!PatternMatch(p_tup.elements()[i], v_tup.elements()[i],
@@ -305,7 +307,7 @@ auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
           return true;
         }
         case Value::Kind::UninitializedValue: {
-          const auto& p_tup = cast<TupleValue>(*p);
+          const auto& p_tup = cast<TupleValueBase>(*p);
           for (const auto& ele : p_tup.elements()) {
             if (!PatternMatch(ele, arena->New<UninitializedValue>(ele),
                               source_loc, bindings, generic_args, trace_stream,
@@ -501,6 +503,7 @@ auto Interpreter::StepLvalue() -> ErrorOr<Success> {
     case ExpressionKind::WhereExpression:
     case ExpressionKind::DotSelfExpression:
     case ExpressionKind::ArrayTypeLiteral:
+    case ExpressionKind::BuiltinConvertExpression:
       CARBON_FATAL() << "Can't treat expression as lvalue: " << exp;
     case ExpressionKind::UnimplementedExpression:
       CARBON_FATAL() << "Unimplemented: " << exp;
@@ -669,6 +672,8 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
     case Value::Kind::TypeType:
     case Value::Kind::FunctionType:
     case Value::Kind::PointerType:
+    case Value::Kind::TupleType:
+    case Value::Kind::StructType:
     case Value::Kind::AutoType:
     case Value::Kind::NominalClassType:
     case Value::Kind::MixinPseudoType:
@@ -722,6 +727,13 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
               InstantiateType(destination_type, source_loc));
           return arena_->New<NominalClassValue>(inst_dest, value);
         }
+        case Value::Kind::TypeType:
+        case Value::Kind::ConstraintType:
+        case Value::Kind::InterfaceType: {
+          CARBON_CHECK(struct_val.elements().empty())
+              << "only empty structs convert to Type";
+          return arena_->New<StructType>();
+        }
         default: {
           CARBON_CHECK(IsValueKindDependent(destination_type) ||
                        isa<TypeType, ConstraintType>(destination_type))
@@ -731,26 +743,13 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
         }
       }
     }
-    case Value::Kind::StructType: {
-      // The value `{}` has kind `StructType` not `StructValue`. This value can
-      // be converted to an empty class type.
-      if (const auto* destination_class_type =
-              dyn_cast<NominalClassType>(destination_type)) {
-        CARBON_CHECK(cast<StructType>(*value).fields().empty())
-            << "only an empty struct type value converts to class type";
-        CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> inst_dest,
-                                InstantiateType(destination_type, source_loc));
-        return arena_->New<NominalClassValue>(inst_dest, value);
-      }
-      return value;
-    }
     case Value::Kind::TupleValue: {
-      const auto& tuple = cast<TupleValue>(value);
+      const auto* tuple = cast<TupleValue>(value);
       std::vector<Nonnull<const Value*>> destination_element_types;
       switch (destination_type->kind()) {
-        case Value::Kind::TupleValue:
+        case Value::Kind::TupleType:
           destination_element_types =
-              cast<TupleValue>(destination_type)->elements();
+              cast<TupleType>(destination_type)->elements();
           break;
         case Value::Kind::StaticArrayType: {
           const auto& array_type = cast<StaticArrayType>(*destination_type);
@@ -758,6 +757,18 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
                                            &array_type.element_type());
           break;
         }
+        case Value::Kind::TypeType:
+        case Value::Kind::ConstraintType:
+        case Value::Kind::InterfaceType: {
+          std::vector<Nonnull<const Value*>> new_elements;
+          Nonnull<const Value*> type_type = arena_->New<TypeType>();
+          for (Nonnull<const Value*> value : tuple->elements()) {
+            CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> value_as_type,
+                                    Convert(value, type_type, source_loc));
+            new_elements.push_back(value_as_type);
+          }
+          return arena_->New<TupleType>(std::move(new_elements));
+        }
         default: {
           CARBON_CHECK(IsValueKindDependent(destination_type) ||
                        isa<TypeType, ConstraintType>(destination_type))
@@ -782,7 +793,16 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
       CARBON_ASSIGN_OR_RETURN(
           Nonnull<const Value*> value,
           EvalAssociatedConstant(cast<AssociatedConstant>(value), source_loc));
-      if (isa<AssociatedConstant>(value)) {
+      if (auto* new_const = dyn_cast<AssociatedConstant>(value)) {
+        // TODO: Detect whether conversions are required in type-checking.
+        if (isa<TypeType, ConstraintType, InterfaceType>(destination_type) &&
+            isa<TypeType, ConstraintType, InterfaceType>(
+                new_const->constant().static_type())) {
+          // No further conversions are required.
+          return value;
+        }
+        // We need to convert this, and we don't know how because we don't have
+        // the value yet.
         return ProgramError(source_loc)
                << "value of associated constant " << *value << " is not known";
       }
@@ -989,19 +1009,6 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
             CreateStruct(literal.fields(), act.results()));
       }
     }
-    case ExpressionKind::StructTypeLiteral: {
-      const auto& struct_type = cast<StructTypeLiteral>(exp);
-      if (act.pos() < static_cast<int>(struct_type.fields().size())) {
-        return todo_.Spawn(std::make_unique<ExpressionAction>(
-            &struct_type.fields()[act.pos()].expression()));
-      } else {
-        std::vector<NamedValue> fields;
-        for (size_t i = 0; i < struct_type.fields().size(); ++i) {
-          fields.push_back({struct_type.fields()[i].name(), act.results()[i]});
-        }
-        return todo_.FinishAction(arena_->New<StructType>(std::move(fields)));
-      }
-    }
     case ExpressionKind::SimpleMemberAccessExpression: {
       const auto& access = cast<SimpleMemberAccessExpression>(exp);
       bool forming_member_name = isa<TypeOfMemberName>(&access.static_type());
@@ -1407,22 +1414,6 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
       CARBON_CHECK(act.pos() == 0);
       return todo_.FinishAction(arena_->New<TypeType>());
     }
-    case ExpressionKind::FunctionTypeLiteral: {
-      if (act.pos() == 0) {
-        return todo_.Spawn(std::make_unique<ExpressionAction>(
-            &cast<FunctionTypeLiteral>(exp).parameter()));
-      } else if (act.pos() == 1) {
-        //    { { pt :: fn [] -> e :: C, E, F} :: S, H}
-        // -> { { e :: fn pt -> []) :: C, E, F} :: S, H}
-        return todo_.Spawn(std::make_unique<ExpressionAction>(
-            &cast<FunctionTypeLiteral>(exp).return_type()));
-      } else {
-        //    { { rt :: fn pt -> [] :: C, E, F} :: S, H}
-        // -> { fn pt -> rt :: {C, E, F} :: S, H}
-        return todo_.FinishAction(
-            arena_->New<FunctionType>(act.results()[0], act.results()[1]));
-      }
-    }
     case ExpressionKind::ContinuationTypeLiteral: {
       CARBON_CHECK(act.pos() == 0);
       return todo_.FinishAction(arena_->New<ContinuationType>());
@@ -1436,9 +1427,18 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
       CARBON_CHECK(act.pos() == 0);
       return todo_.FinishAction(arena_->New<StringType>());
     }
+    case ExpressionKind::FunctionTypeLiteral:
+    case ExpressionKind::StructTypeLiteral:
+    case ExpressionKind::ArrayTypeLiteral:
     case ExpressionKind::ValueLiteral: {
       CARBON_CHECK(act.pos() == 0);
-      return todo_.FinishAction(&cast<ValueLiteral>(exp).value());
+      auto* value = &cast<ConstantValueLiteral>(exp).constant_value();
+      CARBON_ASSIGN_OR_RETURN(
+          Nonnull<const Value*> destination,
+          InstantiateType(&exp.static_type(), exp.source_loc()));
+      CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> result,
+                              Convert(value, destination, exp.source_loc()));
+      return todo_.FinishAction(result);
     }
     case ExpressionKind::IfExpression: {
       const auto& if_expr = cast<IfExpression>(exp);
@@ -1460,21 +1460,25 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
       CARBON_CHECK(rewrite) << "where expression should be rewritten";
       return todo_.ReplaceWith(std::make_unique<ExpressionAction>(*rewrite));
     }
-    case ExpressionKind::UnimplementedExpression:
-      CARBON_FATAL() << "Unimplemented: " << exp;
-    case ExpressionKind::ArrayTypeLiteral: {
-      const auto& array_literal = cast<ArrayTypeLiteral>(exp);
+    case ExpressionKind::BuiltinConvertExpression: {
+      const auto& convert_expr = cast<BuiltinConvertExpression>(exp);
       if (act.pos() == 0) {
         return todo_.Spawn(std::make_unique<ExpressionAction>(
-            &array_literal.element_type_expression()));
-      } else if (act.pos() == 1) {
-        return todo_.Spawn(std::make_unique<ExpressionAction>(
-            &array_literal.size_expression()));
+            convert_expr.source_expression()));
       } else {
-        return todo_.FinishAction(arena_->New<StaticArrayType>(
-            act.results()[0], cast<IntValue>(act.results()[1])->value()));
+        CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> destination,
+                                InstantiateType(&convert_expr.static_type(),
+                                                convert_expr.source_loc()));
+        // TODO: Remove all calls to Convert other than this one. We shouldn't
+        // need them any more.
+        CARBON_ASSIGN_OR_RETURN(
+            Nonnull<const Value*> result,
+            Convert(act.results()[0], destination, convert_expr.source_loc()));
+        return todo_.FinishAction(result);
       }
     }
+    case ExpressionKind::UnimplementedExpression:
+      CARBON_FATAL() << "Unimplemented: " << exp;
   }  // switch (exp->kind)
 }
 

+ 2 - 2
explorer/interpreter/pattern_analysis.cpp

@@ -71,7 +71,7 @@ void AbstractPattern::AppendElementsTo(
     }
   } else if (const auto* value = value_.dyn_cast<const Value*>()) {
     if (const auto* tuple = dyn_cast<TupleValue>(value)) {
-      const auto* tuple_type = cast<TupleValue>(type_);
+      const auto* tuple_type = cast<TupleType>(type_);
       CARBON_CHECK(tuple->elements().size() == tuple_type->elements().size());
       for (size_t i = 0; i != tuple->elements().size(); ++i) {
         out.push_back(
@@ -180,7 +180,7 @@ auto PatternMatrix::FirstColumnDiscriminators() const -> DiscriminatorSet {
         continue;
       case AbstractPattern::Compound: {
         const Value& type = row[0].type();
-        if (const auto* tuple = dyn_cast<TupleValue>(&type)) {
+        if (const auto* tuple = dyn_cast<TupleType>(&type)) {
           // If we find a tuple match, we've found all constructors (there's
           // only one!) and none were missing.
           return {

+ 3 - 1
explorer/interpreter/resolve_names.cpp

@@ -271,8 +271,10 @@ static auto ResolveNames(Expression& expression,
     case ExpressionKind::StringLiteral:
     case ExpressionKind::StringTypeLiteral:
     case ExpressionKind::TypeTypeLiteral:
-    case ExpressionKind::ValueLiteral:
       break;
+    case ExpressionKind::ValueLiteral:
+    case ExpressionKind::BuiltinConvertExpression:
+      CARBON_FATAL() << "should not exist before type checking";
     case ExpressionKind::UnimplementedExpression:
       return ProgramError(expression.source_loc()) << "Unimplemented";
   }

+ 5 - 0
explorer/interpreter/resolve_unformed.cpp

@@ -121,6 +121,11 @@ static auto ResolveUnformed(Nonnull<const Expression*> expression,
           &cast<SimpleMemberAccessExpression>(*expression).object(), flow_facts,
           FlowFacts::ActionType::Check));
       break;
+    case ExpressionKind::BuiltinConvertExpression:
+      CARBON_RETURN_IF_ERROR(ResolveUnformed(
+          cast<BuiltinConvertExpression>(*expression).source_expression(),
+          flow_facts, FlowFacts::ActionType::Check));
+      break;
     case ExpressionKind::DotSelfExpression:
     case ExpressionKind::IntLiteral:
     case ExpressionKind::BoolLiteral:

+ 308 - 154
explorer/interpreter/type_checker.cpp

@@ -28,6 +28,7 @@
 #include "explorer/interpreter/pattern_analysis.h"
 #include "explorer/interpreter/value.h"
 #include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/TinyPtrVector.h"
@@ -95,6 +96,7 @@ static auto IsTypeOfType(Nonnull<const Value*> value) -> bool {
     case Value::Kind::PointerValue:
     case Value::Kind::LValue:
     case Value::Kind::BoolValue:
+    case Value::Kind::TupleValue:
     case Value::Kind::StructValue:
     case Value::Kind::NominalClassValue:
     case Value::Kind::AlternativeValue:
@@ -110,8 +112,6 @@ static auto IsTypeOfType(Nonnull<const Value*> value) -> bool {
     case Value::Kind::ConstraintImplWitness:
     case Value::Kind::ParameterizedEntityName:
     case Value::Kind::MemberName:
-    case Value::Kind::TypeOfParameterizedEntityName:
-    case Value::Kind::TypeOfMemberName:
       // These are values, not types.
       return false;
     case Value::Kind::IntType:
@@ -125,7 +125,10 @@ static auto IsTypeOfType(Nonnull<const Value*> value) -> bool {
     case Value::Kind::ContinuationType:
     case Value::Kind::StringType:
     case Value::Kind::StaticArrayType:
-    case Value::Kind::TupleValue:
+    case Value::Kind::TupleType:
+    case Value::Kind::TypeOfMixinPseudoType:
+    case Value::Kind::TypeOfParameterizedEntityName:
+    case Value::Kind::TypeOfMemberName:
       // These are types whose values are not types.
       return false;
     case Value::Kind::AutoType:
@@ -136,16 +139,15 @@ static auto IsTypeOfType(Nonnull<const Value*> value) -> bool {
     case Value::Kind::TypeType:
     case Value::Kind::InterfaceType:
     case Value::Kind::ConstraintType:
-    case Value::Kind::TypeOfMixinPseudoType:
       // A value of one of these types is itself always a type.
       return true;
   }
 }
 
-// Returns whether the value is a valid result from a type expression,
-// as opposed to a non-type value.
-// `auto` is not considered a type by the function if `concrete` is false.
-static auto IsType(Nonnull<const Value*> value, bool concrete = false) -> bool {
+// Returns whether the value is a type value, such as might be a valid type for
+// a syntactic pattern. This includes types involving `auto`. Use
+// `TypeContainsAuto` to determine if a type involves `auto`.
+static auto IsType(Nonnull<const Value*> value) -> bool {
   switch (value->kind()) {
     case Value::Kind::IntValue:
     case Value::Kind::FunctionValue:
@@ -154,6 +156,7 @@ static auto IsType(Nonnull<const Value*> value, bool concrete = false) -> bool {
     case Value::Kind::PointerValue:
     case Value::Kind::LValue:
     case Value::Kind::BoolValue:
+    case Value::Kind::TupleValue:
     case Value::Kind::StructValue:
     case Value::Kind::NominalClassValue:
     case Value::Kind::AlternativeValue:
@@ -170,16 +173,13 @@ static auto IsType(Nonnull<const Value*> value, bool concrete = false) -> bool {
     case Value::Kind::ParameterizedEntityName:
     case Value::Kind::MemberName:
       return false;
-    case Value::Kind::TypeOfParameterizedEntityName:
-    case Value::Kind::TypeOfMemberName:
-      // Names aren't first-class values, and their types aren't first-class
-      // types.
-      return false;
     case Value::Kind::IntType:
     case Value::Kind::BoolType:
     case Value::Kind::TypeType:
+    case Value::Kind::PointerType:
     case Value::Kind::FunctionType:
     case Value::Kind::StructType:
+    case Value::Kind::TupleType:
     case Value::Kind::NominalClassType:
     case Value::Kind::InterfaceType:
     case Value::Kind::ConstraintType:
@@ -188,21 +188,13 @@ static auto IsType(Nonnull<const Value*> value, bool concrete = false) -> bool {
     case Value::Kind::VariableType:
     case Value::Kind::StringType:
     case Value::Kind::StaticArrayType:
-      return true;
     case Value::Kind::AutoType:
-      // `auto` isn't a concrete type, it's a pattern that matches types.
-      return !concrete;
-    case Value::Kind::TupleValue: {
-      for (Nonnull<const Value*> field : cast<TupleValue>(*value).elements()) {
-        if (!IsType(field, concrete)) {
-          return false;
-        }
-      }
       return true;
-    }
-    case Value::Kind::PointerType: {
-      return IsType(&cast<PointerType>(*value).type(), concrete);
-    }
+    case Value::Kind::TypeOfParameterizedEntityName:
+    case Value::Kind::TypeOfMemberName:
+    case Value::Kind::TypeOfMixinPseudoType:
+      // These aren't first-class types, but they are still types.
+      return true;
     case Value::Kind::AssociatedConstant: {
       // An associated type is an associated constant whose type is a
       // type-of-type.
@@ -213,27 +205,17 @@ static auto IsType(Nonnull<const Value*> value, bool concrete = false) -> bool {
       return IsTypeOfType(&assoc.constant().static_type());
     }
     case Value::Kind::MixinPseudoType:
-    case Value::Kind::TypeOfMixinPseudoType:
       // Mixin type is a second-class type that cannot be used
       // within a type annotation expression.
       return false;
   }
 }
 
-static auto ExpectIsType(SourceLocation source_loc, Nonnull<const Value*> value)
-    -> ErrorOr<Success> {
-  if (!IsType(value)) {
-    return ProgramError(source_loc) << "Expected a type, but got " << *value;
-  } else {
-    return Success();
-  }
-}
-
 // Expect that a type is complete. Issue a diagnostic if not.
 static auto ExpectCompleteType(SourceLocation source_loc,
                                std::string_view context,
                                Nonnull<const Value*> type) -> ErrorOr<Success> {
-  CARBON_RETURN_IF_ERROR(ExpectIsType(source_loc, type));
+  CARBON_CHECK(IsType(type));
 
   switch (type->kind()) {
     case Value::Kind::IntValue:
@@ -244,6 +226,7 @@ static auto ExpectCompleteType(SourceLocation source_loc,
     case Value::Kind::LValue:
     case Value::Kind::BoolValue:
     case Value::Kind::StructValue:
+    case Value::Kind::TupleValue:
     case Value::Kind::NominalClassValue:
     case Value::Kind::AlternativeValue:
     case Value::Kind::BindingPlaceholderValue:
@@ -258,10 +241,7 @@ static auto ExpectCompleteType(SourceLocation source_loc,
     case Value::Kind::ConstraintImplWitness:
     case Value::Kind::ParameterizedEntityName:
     case Value::Kind::MemberName:
-    case Value::Kind::TypeOfParameterizedEntityName:
-    case Value::Kind::TypeOfMemberName:
     case Value::Kind::MixinPseudoType:
-    case Value::Kind::TypeOfMixinPseudoType:
       CARBON_FATAL() << "should not see non-type values";
 
     case Value::Kind::IntType:
@@ -274,7 +254,10 @@ static auto ExpectCompleteType(SourceLocation source_loc,
     case Value::Kind::ConstraintType:
     case Value::Kind::ContinuationType:
     case Value::Kind::VariableType:
-    case Value::Kind::AssociatedConstant: {
+    case Value::Kind::AssociatedConstant:
+    case Value::Kind::TypeOfParameterizedEntityName:
+    case Value::Kind::TypeOfMemberName:
+    case Value::Kind::TypeOfMixinPseudoType: {
       // These types are always complete.
       return Success();
     }
@@ -284,7 +267,7 @@ static auto ExpectCompleteType(SourceLocation source_loc,
       // complete.
       return Success();
 
-    case Value::Kind::TupleValue: {
+    case Value::Kind::TupleType: {
       // TODO: Tuple types should be complete only if all element types are
       // complete.
       return Success();
@@ -323,20 +306,85 @@ static auto ExpectCompleteType(SourceLocation source_loc,
 
 // Returns whether *value represents the type of a Carbon value, as
 // opposed to a type pattern or a non-type value.
-static auto IsConcreteType(Nonnull<const Value*> value) -> bool {
-  return IsType(value, /*concrete=*/true);
-}
+static auto TypeContainsAuto(Nonnull<const Value*> type) -> bool {
+  CARBON_CHECK(IsType(type)) << "expected a type, but found " << *type;
 
-auto TypeChecker::ExpectIsConcreteType(SourceLocation source_loc,
-                                       Nonnull<const Value*> value)
-    -> ErrorOr<Success> {
-  if (!IsConcreteType(value)) {
-    return ProgramError(source_loc) << "Expected a type, but got " << *value;
-  } else {
-    return Success();
+  switch (type->kind()) {
+    case Value::Kind::IntValue:
+    case Value::Kind::FunctionValue:
+    case Value::Kind::DestructorValue:
+    case Value::Kind::BoundMethodValue:
+    case Value::Kind::PointerValue:
+    case Value::Kind::LValue:
+    case Value::Kind::BoolValue:
+    case Value::Kind::TupleValue:
+    case Value::Kind::StructValue:
+    case Value::Kind::NominalClassValue:
+    case Value::Kind::AlternativeValue:
+    case Value::Kind::BindingPlaceholderValue:
+    case Value::Kind::AddrValue:
+    case Value::Kind::AlternativeConstructorValue:
+    case Value::Kind::ContinuationValue:
+    case Value::Kind::StringValue:
+    case Value::Kind::UninitializedValue:
+    case Value::Kind::ImplWitness:
+    case Value::Kind::BindingWitness:
+    case Value::Kind::ConstraintWitness:
+    case Value::Kind::ConstraintImplWitness:
+    case Value::Kind::ParameterizedEntityName:
+    case Value::Kind::MemberName:
+    case Value::Kind::MixinPseudoType:
+      CARBON_FATAL() << "non-type value";
+    case Value::Kind::IntType:
+    case Value::Kind::BoolType:
+    case Value::Kind::TypeType:
+    case Value::Kind::VariableType:
+    case Value::Kind::StringType:
+    case Value::Kind::TypeOfMixinPseudoType:
+    case Value::Kind::TypeOfParameterizedEntityName:
+    case Value::Kind::TypeOfMemberName:
+      // These types do not contain other types.
+      return false;
+    case Value::Kind::FunctionType:
+    case Value::Kind::NominalClassType:
+    case Value::Kind::InterfaceType:
+    case Value::Kind::ConstraintType:
+    case Value::Kind::ChoiceType:
+    case Value::Kind::ContinuationType:
+    case Value::Kind::AssociatedConstant:
+      // These types can contain other types, but those types can't involve
+      // `auto`.
+      return false;
+    case Value::Kind::AutoType:
+      return true;
+    case Value::Kind::StructType:
+      return llvm::any_of(
+          llvm::map_range(cast<StructType>(type)->fields(),
+                          [](const NamedValue& v) { return v.value; }),
+          TypeContainsAuto);
+    case Value::Kind::TupleType:
+      return llvm::any_of(cast<TupleType>(type)->elements(), TypeContainsAuto);
+    case Value::Kind::PointerType:
+      return TypeContainsAuto(&cast<PointerType>(type)->type());
+    case Value::Kind::StaticArrayType:
+      return TypeContainsAuto(&cast<StaticArrayType>(type)->element_type());
   }
 }
 
+// Returns whether `type` is a placeholder type, which is a second-class type
+// that cannot be the type of a binding but can be the type of an expression.
+static auto IsPlaceholderType(Nonnull<const Value*> type) -> bool {
+  CARBON_CHECK(IsType(type)) << "expected a type, but found " << *type;
+  return isa<TypeOfParameterizedEntityName, TypeOfMemberName,
+             TypeOfMixinPseudoType>(type);
+}
+
+// Returns whether `value` is a concrete type, which would be valid as the
+// static type of an expression. This is currently any type other than `auto`.
+static auto IsConcreteType(Nonnull<const Value*> value) -> bool {
+  return IsType(value) && !TypeContainsAuto(value);
+}
+
 // Returns the named field, or None if not found.
 static auto FindField(llvm::ArrayRef<NamedValue> fields,
                       const std::string& field_name)
@@ -444,15 +492,23 @@ auto TypeChecker::IsImplicitlyConvertible(
             return true;
           }
           break;
+        case Value::Kind::TypeType:
+        case Value::Kind::InterfaceType:
+        case Value::Kind::ConstraintType:
+          // A value of empty struct type implicitly converts to a type.
+          if (cast<StructType>(*source).fields().empty()) {
+            return true;
+          }
+          break;
         default:
           break;
       }
       break;
-    case Value::Kind::TupleValue: {
-      const auto& source_tuple = cast<TupleValue>(*source);
+    case Value::Kind::TupleType: {
+      const auto& source_tuple = cast<TupleType>(*source);
       switch (destination->kind()) {
-        case Value::Kind::TupleValue: {
-          const auto& destination_tuple = cast<TupleValue>(*destination);
+        case Value::Kind::TupleType: {
+          const auto& destination_tuple = cast<TupleType>(*destination);
           if (source_tuple.elements().size() !=
               destination_tuple.elements().size()) {
             break;
@@ -490,7 +546,10 @@ auto TypeChecker::IsImplicitlyConvertible(
           }
           break;
         }
-        case Value::Kind::TypeType: {
+        case Value::Kind::TypeType:
+        case Value::Kind::InterfaceType:
+        case Value::Kind::ConstraintType: {
+          // A tuple value converts to a type if all of its fields do.
           bool all_types = true;
           for (Nonnull<const Value*> source_element : source_tuple.elements()) {
             if (!IsImplicitlyConvertible(
@@ -541,38 +600,59 @@ auto TypeChecker::ImplicitlyConvert(std::string_view context,
     -> ErrorOr<Nonnull<Expression*>> {
   Nonnull<const Value*> source_type = &source->static_type();
 
-  // A type implicitly converts to a constraint if there is an impl of that
-  // constraint for that type in scope.
-  if (isa<InterfaceType, ConstraintType>(destination)) {
-    CARBON_ASSIGN_OR_RETURN(
-        Nonnull<const ConstraintType*> destination_constraint,
-        ConvertToConstraintType(source->source_loc(), "implicit conversion",
-                                destination));
-    CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> source_value,
-                            InterpExp(source, arena_, trace_stream_));
-    if (trace_stream_) {
-      **trace_stream_ << "converting type " << *source_value
-                      << " to constraint " << *destination_constraint << " for "
-                      << context << " in scope " << impl_scope << "\n";
-    }
-    // Note, we discard the witness. We don't actually need it in order to
-    // perform the conversion, but we do want to know it exists.
-    CARBON_RETURN_IF_ERROR(impl_scope.Resolve(
-        destination_constraint, source_value, source->source_loc(), *this));
-    // This conversion is a no-op at runtime.
-    // TODO: Should we record the change in type in the AST?
+  CARBON_RETURN_IF_ERROR(
+      ExpectNonPlaceholderType(source->source_loc(), &source->static_type()));
+
+  if (TypeEqual(&source->static_type(), destination, std::nullopt)) {
+    // No conversions are required.
     return source;
   }
 
-  // TODO: If a builtin conversion works, for now we don't create any
-  // expression to do the conversion and rely on the interpreter to know how to
-  // do it.
   // TODO: This doesn't work for cases of combined built-in and user-defined
   // conversion, such as converting a struct element via an `ImplicitAs` impl.
   if (IsImplicitlyConvertible(source_type, destination, impl_scope,
                               /*allow_user_defined_conversions=*/false)) {
-    return source;
+    // A type only implicitly converts to a constraint if there is an impl of
+    // that constraint for that type in scope.
+    if (isa<InterfaceType, ConstraintType>(destination)) {
+      // First convert the source expression to type `Type`.
+      CARBON_ASSIGN_OR_RETURN(Nonnull<Expression*> source_as_type,
+                              ImplicitlyConvert(context, impl_scope, source,
+                                                arena_->New<TypeType>()));
+      CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> converted_value,
+                              InterpExp(source_as_type, arena_, trace_stream_));
+      CARBON_ASSIGN_OR_RETURN(
+          Nonnull<const ConstraintType*> destination_constraint,
+          ConvertToConstraintType(source->source_loc(), "implicit conversion",
+                                  destination));
+      destination = destination_constraint;
+      if (trace_stream_) {
+        **trace_stream_ << "converting type " << *converted_value
+                        << " to constraint " << *destination_constraint
+                        << " for " << context << " in scope " << impl_scope
+                        << "\n";
+      }
+      // Note, we discard the witness. We don't actually need it in order to
+      // perform the conversion, but we do want to know it exists.
+      // TODO: A value of constraint type should carry both the type and the
+      // witness.
+      CARBON_RETURN_IF_ERROR(impl_scope.Resolve(destination_constraint,
+                                                converted_value,
+                                                source->source_loc(), *this));
+      return arena_->New<ValueLiteral>(source->source_loc(), converted_value,
+                                       destination_constraint,
+                                       ValueCategory::Let);
+    }
+
+    if (IsTypeOfType(source_type) && IsTypeOfType(destination)) {
+      // No conversion is required.
+      return source;
+    }
+
+    // Perform the builtin conversion.
+    return arena_->New<BuiltinConvertExpression>(source, destination);
   }
+
   ErrorOr<Nonnull<Expression*>> converted = BuildBuiltinMethodCall(
       impl_scope, source,
       BuiltinInterfaceName{Builtins::ImplicitAs, destination},
@@ -650,6 +730,29 @@ auto TypeChecker::BuildBuiltinMethodCall(const ImplScope& impl_scope,
   return {call};
 }
 
+// Checks that the given type is not a placeholder type. Diagnoses otherwise.
+auto TypeChecker::ExpectNonPlaceholderType(SourceLocation source_loc,
+                                           Nonnull<const Value*> type)
+    -> ErrorOr<Success> {
+  if (!IsPlaceholderType(type)) {
+    return Success();
+  }
+  if (auto* member_name = dyn_cast<TypeOfMemberName>(type)) {
+    return ProgramError(source_loc)
+           << *member_name << " can only be used in a member access or alias";
+  }
+  if (auto* param_entity = dyn_cast<TypeOfParameterizedEntityName>(type)) {
+    return ProgramError(source_loc)
+           << "'" << param_entity->name() << "' must be given an argument list";
+  }
+  if (auto* mixin_type = dyn_cast<TypeOfMixinPseudoType>(type)) {
+    return ProgramError(source_loc)
+           << "invalid use of mixin "
+           << mixin_type->mixin_type().declaration().name();
+  }
+  CARBON_FATAL() << "unknown kind of placeholder type " << *type;
+}
+
 auto TypeChecker::ExpectType(SourceLocation source_loc,
                              std::string_view context,
                              Nonnull<const Value*> expected,
@@ -786,12 +889,12 @@ auto TypeChecker::ArgumentDeduction::Deduce(Nonnull<const Value*> param,
       }
       return Success();
     }
-    case Value::Kind::TupleValue: {
-      if (arg->kind() != Value::Kind::TupleValue) {
+    case Value::Kind::TupleType: {
+      if (arg->kind() != Value::Kind::TupleType) {
         return handle_non_deduced_type();
       }
-      const auto& param_tup = cast<TupleValue>(*param);
-      const auto& arg_tup = cast<TupleValue>(*arg);
+      const auto& param_tup = cast<TupleType>(*param);
+      const auto& arg_tup = cast<TupleType>(*arg);
       if (param_tup.elements().size() != arg_tup.elements().size()) {
         return ProgramError(source_loc_)
                << "mismatch in tuple sizes, expected "
@@ -951,6 +1054,7 @@ auto TypeChecker::ArgumentDeduction::Deduce(Nonnull<const Value*> param,
     case Value::Kind::PointerValue:
     case Value::Kind::LValue:
     case Value::Kind::StructValue:
+    case Value::Kind::TupleValue:
     case Value::Kind::NominalClassValue:
     case Value::Kind::AlternativeValue:
     case Value::Kind::BindingPlaceholderValue:
@@ -1042,11 +1146,9 @@ auto TypeChecker::ArgumentDeduction::Finish(TypeChecker& type_checker,
     const Value* binding_type = &binding->static_type();
     const Value* substituted_type =
         type_checker.Substitute(bindings, binding_type);
-    if (!IsTypeOfType(substituted_type)) {
-      CARBON_ASSIGN_OR_RETURN(
-          arg, type_checker.ImplicitlyConvert(context_, impl_scope, arg,
-                                              substituted_type));
-    }
+    CARBON_ASSIGN_OR_RETURN(
+        arg, type_checker.ImplicitlyConvert(context_, impl_scope, arg,
+                                            substituted_type));
 
     // Evaluate the argument to get the value.
     CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> value,
@@ -1715,12 +1817,17 @@ auto TypeChecker::SubstituteImpl(const Bindings& bindings,
       return arena_->New<AssociatedConstant>(base, interface, &assoc.constant(),
                                              witness);
     }
+    case Value::Kind::TupleType:
     case Value::Kind::TupleValue: {
       std::vector<Nonnull<const Value*>> elts;
-      for (const auto& elt : cast<TupleValue>(*type).elements()) {
+      for (const auto& elt : cast<TupleValueBase>(*type).elements()) {
         elts.push_back(SubstituteImpl(bindings, elt));
       }
-      return arena_->New<TupleValue>(elts);
+      if (isa<TupleType>(type)) {
+        return arena_->New<TupleType>(std::move(elts));
+      } else {
+        return arena_->New<TupleValue>(std::move(elts));
+      }
     }
     case Value::Kind::StructType: {
       std::vector<NamedValue> fields;
@@ -1853,14 +1960,17 @@ auto TypeChecker::SubstituteImpl(const Bindings& bindings,
           witness.index());
     }
     case Value::Kind::StaticArrayType:
+    case Value::Kind::ChoiceType:
+    case Value::Kind::MixinPseudoType:
+      // TODO: These can contain bindings. We should substitute into them.
+      return type;
     case Value::Kind::AutoType:
     case Value::Kind::IntType:
     case Value::Kind::BoolType:
     case Value::Kind::TypeType:
-    case Value::Kind::ChoiceType:
     case Value::Kind::ContinuationType:
     case Value::Kind::StringType:
-    case Value::Kind::MixinPseudoType:
+      // These types cannot contain bindings or witnesses.
       return type;
     case Value::Kind::TypeOfMixinPseudoType:
     case Value::Kind::TypeOfParameterizedEntityName:
@@ -1870,13 +1980,9 @@ auto TypeChecker::SubstituteImpl(const Bindings& bindings,
       return type;
     case Value::Kind::ParameterizedEntityName:
     case Value::Kind::MemberName:
-    case Value::Kind::IntValue:
-    case Value::Kind::BoolValue:
     case Value::Kind::FunctionValue:
     case Value::Kind::DestructorValue:
     case Value::Kind::BoundMethodValue:
-    case Value::Kind::PointerValue:
-    case Value::Kind::LValue:
     case Value::Kind::StructValue:
     case Value::Kind::NominalClassValue:
     case Value::Kind::AlternativeValue:
@@ -1884,12 +1990,18 @@ auto TypeChecker::SubstituteImpl(const Bindings& bindings,
     case Value::Kind::AddrValue:
     case Value::Kind::AlternativeConstructorValue:
     case Value::Kind::ContinuationValue:
-    case Value::Kind::StringValue:
-    case Value::Kind::UninitializedValue:
       // This can happen when substituting into the arguments of a class or
       // interface.
       // TODO: Implement substitution for these cases.
       return type;
+    case Value::Kind::IntValue:
+    case Value::Kind::BoolValue:
+    case Value::Kind::PointerValue:
+    case Value::Kind::LValue:
+    case Value::Kind::StringValue:
+    case Value::Kind::UninitializedValue:
+      // These values cannot contain bindings or witnesses.
+      return type;
   }
 }
 
@@ -2058,7 +2170,7 @@ auto TypeChecker::DeduceCallBindings(
     llvm::ArrayRef<Nonnull<const GenericBinding*>> deduced_bindings,
     const ImplScope& impl_scope) -> ErrorOr<Success> {
   llvm::ArrayRef<Nonnull<const Value*>> params =
-      cast<TupleValue>(*params_type).elements();
+      cast<TupleType>(*params_type).elements();
   llvm::ArrayRef<Nonnull<Expression*>> args =
       cast<TupleLiteral>(call.argument()).fields();
   if (params.size() != args.size()) {
@@ -2303,6 +2415,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
   }
   switch (e->kind()) {
     case ExpressionKind::ValueLiteral:
+    case ExpressionKind::BuiltinConvertExpression:
       CARBON_FATAL() << "attempting to type check node " << *e
                      << " generated during type checking";
     case ExpressionKind::IndexExpression: {
@@ -2311,8 +2424,8 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
       CARBON_RETURN_IF_ERROR(TypeCheckExp(&index.offset(), impl_scope));
       const Value& object_type = index.object().static_type();
       switch (object_type.kind()) {
-        case Value::Kind::TupleValue: {
-          const auto& tuple_type = cast<TupleValue>(object_type);
+        case Value::Kind::TupleType: {
+          const auto& tuple_type = cast<TupleType>(object_type);
           CARBON_RETURN_IF_ERROR(
               ExpectExactType(index.offset().source_loc(), "tuple index",
                               arena_->New<IntType>(),
@@ -2341,7 +2454,9 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
           return Success();
         }
         default:
-          return ProgramError(e->source_loc()) << "expected a tuple";
+          return ProgramError(e->source_loc())
+                 << "only arrays and tuples can be indexed, found "
+                 << object_type;
       }
     }
     case ExpressionKind::TupleLiteral: {
@@ -2349,10 +2464,10 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
       for (auto* arg : cast<TupleLiteral>(*e).fields()) {
         CARBON_RETURN_IF_ERROR(TypeCheckExp(arg, impl_scope));
         CARBON_RETURN_IF_ERROR(
-            ExpectIsConcreteType(arg->source_loc(), &arg->static_type()));
+            ExpectNonPlaceholderType(arg->source_loc(), &arg->static_type()));
         arg_types.push_back(&arg->static_type());
       }
-      e->set_static_type(arena_->New<TupleValue>(std::move(arg_types)));
+      e->set_static_type(arena_->New<TupleType>(std::move(arg_types)));
       e->set_value_category(ValueCategory::Let);
       return Success();
     }
@@ -2360,7 +2475,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
       std::vector<NamedValue> arg_types;
       for (auto& arg : cast<StructLiteral>(*e).fields()) {
         CARBON_RETURN_IF_ERROR(TypeCheckExp(&arg.expression(), impl_scope));
-        CARBON_RETURN_IF_ERROR(ExpectIsConcreteType(
+        CARBON_RETURN_IF_ERROR(ExpectNonPlaceholderType(
             arg.expression().source_loc(), &arg.expression().static_type()));
         arg_types.push_back({arg.name(), &arg.expression().static_type()});
       }
@@ -2370,19 +2485,17 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
     }
     case ExpressionKind::StructTypeLiteral: {
       auto& struct_type = cast<StructTypeLiteral>(*e);
+      std::vector<NamedValue> fields;
       for (auto& arg : struct_type.fields()) {
-        CARBON_RETURN_IF_ERROR(TypeCheckTypeExp(&arg.expression(), impl_scope));
-      }
-      if (struct_type.fields().empty()) {
-        // `{}` is the type of `{}`, just as `()` is the type of `()`.
-        // This applies only if there are no fields, because (unlike with
-        // tuples) non-empty struct types are syntactically disjoint
-        // from non-empty struct values.
-        struct_type.set_static_type(arena_->New<StructType>());
-      } else {
-        struct_type.set_static_type(arena_->New<TypeType>());
+        CARBON_ASSIGN_OR_RETURN(
+            Nonnull<const Value*> type,
+            TypeCheckTypeExp(&arg.expression(), impl_scope));
+        fields.push_back({.name = arg.name(), .value = type});
       }
-      e->set_value_category(ValueCategory::Let);
+      struct_type.set_static_type(arena_->New<TypeType>());
+      struct_type.set_value_category(ValueCategory::Let);
+      struct_type.set_constant_value(
+          arena_->New<StructType>(std::move(fields)));
       return Success();
     }
     case ExpressionKind::SimpleMemberAccessExpression: {
@@ -3040,9 +3153,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
         case Operator::As: {
           CARBON_ASSIGN_OR_RETURN(
               Nonnull<const Value*> type,
-              InterpExp(op.arguments()[1], arena_, trace_stream_));
-          CARBON_RETURN_IF_ERROR(
-              ExpectIsConcreteType(op.arguments()[1]->source_loc(), type));
+              TypeCheckTypeExp(op.arguments()[1], impl_scope));
           ErrorOr<Nonnull<Expression*>> converted =
               BuildBuiltinMethodCall(impl_scope, op.arguments()[0],
                                      BuiltinInterfaceName{Builtins::As, type},
@@ -3129,10 +3240,13 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
     }
     case ExpressionKind::FunctionTypeLiteral: {
       auto& fn = cast<FunctionTypeLiteral>(*e);
-      CARBON_RETURN_IF_ERROR(TypeCheckTypeExp(&fn.parameter(), impl_scope));
-      CARBON_RETURN_IF_ERROR(TypeCheckTypeExp(&fn.return_type(), impl_scope));
+      CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> param,
+                              TypeCheckTypeExp(&fn.parameter(), impl_scope));
+      CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> ret,
+                              TypeCheckTypeExp(&fn.return_type(), impl_scope));
       fn.set_static_type(arena_->New<TypeType>());
       fn.set_value_category(ValueCategory::Let);
+      fn.set_constant_value(arena_->New<FunctionType>(param, ret));
       return Success();
     }
     case ExpressionKind::StringLiteral:
@@ -3160,7 +3274,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
                 e->source_loc(), "Print argument 1", arena_->New<IntType>(),
                 &args[1]->static_type(), impl_scope));
           }
-          e->set_static_type(TupleValue::Empty());
+          e->set_static_type(TupleType::Empty());
           e->set_value_category(ValueCategory::Let);
           return Success();
         case IntrinsicExpression::Intrinsic::Assert: {
@@ -3174,7 +3288,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
           CARBON_RETURN_IF_ERROR(ExpectType(
               e->source_loc(), "__intrinsic_assert argument 1",
               arena_->New<StringType>(), &args[1]->static_type(), impl_scope));
-          e->set_static_type(TupleValue::Empty());
+          e->set_static_type(TupleType::Empty());
           e->set_value_category(ValueCategory::Let);
           return Success();
         }
@@ -3196,7 +3310,7 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
           const auto* arg_type = &args[0]->static_type();
           CARBON_RETURN_IF_ERROR(
               ExpectPointerType(e->source_loc(), "*", arg_type));
-          e->set_static_type(TupleValue::Empty());
+          e->set_static_type(TupleType::Empty());
           e->set_value_category(ValueCategory::Let);
           return Success();
         }
@@ -3496,9 +3610,10 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
       CARBON_FATAL() << "Unimplemented: " << *e;
     case ExpressionKind::ArrayTypeLiteral: {
       auto& array_literal = cast<ArrayTypeLiteral>(*e);
-      CARBON_RETURN_IF_ERROR(TypeCheckTypeExp(
-          &array_literal.element_type_expression(), impl_scope));
-
+      CARBON_ASSIGN_OR_RETURN(
+          Nonnull<const Value*> element_type,
+          TypeCheckTypeExp(&array_literal.element_type_expression(),
+                           impl_scope));
       CARBON_RETURN_IF_ERROR(
           TypeCheckExp(&array_literal.size_expression(), impl_scope));
       CARBON_RETURN_IF_ERROR(ExpectExactType(
@@ -3514,6 +3629,8 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
       }
       array_literal.set_static_type(arena_->New<TypeType>());
       array_literal.set_value_category(ValueCategory::Let);
+      array_literal.set_constant_value(arena_->New<StaticArrayType>(
+          element_type, cast<IntValue>(size_value)->value()));
       return Success();
     }
   }
@@ -3571,11 +3688,24 @@ auto TypeChecker::TypeCheckTypeExp(Nonnull<Expression*> type_expression,
                                    const ImplScope& impl_scope, bool concrete)
     -> ErrorOr<Nonnull<const Value*>> {
   CARBON_RETURN_IF_ERROR(TypeCheckExp(type_expression, impl_scope));
+  CARBON_ASSIGN_OR_RETURN(
+      type_expression,
+      ImplicitlyConvert("type expression", impl_scope, type_expression,
+                        arena_->New<TypeType>()));
   CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> type,
                           InterpExp(type_expression, arena_, trace_stream_));
-  CARBON_RETURN_IF_ERROR(
-      concrete ? ExpectIsConcreteType(type_expression->source_loc(), type)
-               : ExpectIsType(type_expression->source_loc(), type));
+  CARBON_CHECK(IsType(type))
+      << "type expression did not produce a type, got " << *type;
+  if (concrete) {
+    if (TypeContainsAuto(type)) {
+      return ProgramError(type_expression->source_loc())
+             << "`auto` is not permitted in this context";
+    }
+    CARBON_CHECK(IsConcreteType(type))
+        << "unknown kind of non-concrete type " << *type;
+  }
+  CARBON_CHECK(!IsPlaceholderType(type))
+      << "should be no way to write a placeholder type";
   return type;
 }
 
@@ -3646,14 +3776,30 @@ auto TypeChecker::TypeCheckPattern(
             return !isa<BindingPattern>(pattern);
           })) {
         return ProgramError(binding.type().source_loc())
-               << "The type of a binding pattern cannot contain bindings.";
+               << "the type of a binding pattern cannot contain bindings";
       }
       CARBON_RETURN_IF_ERROR(TypeCheckPattern(
           &binding.type(), std::nullopt, impl_scope, enclosing_value_category));
       CARBON_ASSIGN_OR_RETURN(
           Nonnull<const Value*> type,
           InterpPattern(&binding.type(), arena_, trace_stream_));
-      CARBON_RETURN_IF_ERROR(ExpectIsType(binding.source_loc(), type));
+      // Convert to a type.
+      // TODO: Convert the pattern before interpreting it rather than doing
+      // this as a separate step.
+      if (!isa<TypeType>(binding.type().static_type())) {
+        auto* literal = arena_->New<ValueLiteral>(binding.source_loc(), type,
+                                                  &binding.type().static_type(),
+                                                  ValueCategory::Let);
+        CARBON_ASSIGN_OR_RETURN(
+            auto* converted,
+            ImplicitlyConvert("type of name binding", impl_scope, literal,
+                              arena_->New<TypeType>()));
+        CARBON_ASSIGN_OR_RETURN(type,
+                                InterpExp(converted, arena_, trace_stream_));
+      }
+      CARBON_CHECK(IsType(type))
+          << "conversion to type succeeded but didn't produce a type, got "
+          << *type;
       if (expected) {
         if (IsConcreteType(type)) {
           CARBON_RETURN_IF_ERROR(ExpectType(p->source_loc(), "name binding",
@@ -3664,13 +3810,19 @@ auto TypeChecker::TypeCheckPattern(
                             std::nullopt, generic_args, trace_stream_,
                             this->arena_)) {
             return ProgramError(binding.type().source_loc())
-                   << "Type pattern '" << *type
+                   << "type pattern '" << *type
                    << "' does not match actual type '" << **expected << "'";
           }
           type = *expected;
         }
+      } else if (TypeContainsAuto(type)) {
+        return ProgramError(binding.source_loc())
+               << "cannot deduce `auto` type for " << binding;
       }
-      CARBON_RETURN_IF_ERROR(ExpectIsConcreteType(binding.source_loc(), type));
+      CARBON_CHECK(IsConcreteType(type)) << "did not resolve " << binding
+                                         << " to concrete type, got " << *type;
+      CARBON_CHECK(!IsPlaceholderType(type))
+          << "should be no way to write a placeholder type";
       binding.set_static_type(type);
       CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> binding_value,
                               InterpPattern(&binding, arena_, trace_stream_));
@@ -3685,8 +3837,8 @@ auto TypeChecker::TypeCheckPattern(
       auto& binding = cast<GenericBinding>(*p);
       if (expected) {
         return ProgramError(binding.type().source_loc())
-               << "Generic binding may not occur in pattern with expected "
-                  "type: "
+               << "generic binding may not occur in pattern with expected "
+                  "type "
                << binding;
       }
 
@@ -3695,18 +3847,18 @@ auto TypeChecker::TypeCheckPattern(
     case PatternKind::TuplePattern: {
       auto& tuple = cast<TuplePattern>(*p);
       std::vector<Nonnull<const Value*>> field_types;
-      if (expected && (*expected)->kind() != Value::Kind::TupleValue) {
+      if (expected && (*expected)->kind() != Value::Kind::TupleType) {
         return ProgramError(p->source_loc()) << "didn't expect a tuple";
       }
       if (expected && tuple.fields().size() !=
-                          cast<TupleValue>(**expected).elements().size()) {
+                          cast<TupleType>(**expected).elements().size()) {
         return ProgramError(tuple.source_loc()) << "tuples of different length";
       }
       for (size_t i = 0; i < tuple.fields().size(); ++i) {
         Nonnull<Pattern*> field = tuple.fields()[i];
         std::optional<Nonnull<const Value*>> expected_field_type;
         if (expected) {
-          expected_field_type = cast<TupleValue>(**expected).elements()[i];
+          expected_field_type = cast<TupleType>(**expected).elements()[i];
         }
         CARBON_RETURN_IF_ERROR(TypeCheckPattern(
             field, expected_field_type, impl_scope, enclosing_value_category));
@@ -3716,7 +3868,7 @@ auto TypeChecker::TypeCheckPattern(
         }
         field_types.push_back(&field->static_type());
       }
-      tuple.set_static_type(arena_->New<TupleValue>(std::move(field_types)));
+      tuple.set_static_type(arena_->New<TupleType>(std::move(field_types)));
       CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> tuple_value,
                               InterpPattern(&tuple, arena_, trace_stream_));
       SetValue(&tuple, tuple_value);
@@ -3873,6 +4025,8 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
     case StatementKind::Match: {
       auto& match = cast<Match>(*s);
       CARBON_RETURN_IF_ERROR(TypeCheckExp(&match.expression(), impl_scope));
+      CARBON_RETURN_IF_ERROR(ExpectNonPlaceholderType(
+          match.expression().source_loc(), &match.expression().static_type()));
       std::vector<Match::Clause> new_clauses;
       std::optional<Nonnull<const Value*>> expected_type;
       PatternMatrix patterns;
@@ -3976,6 +4130,8 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
       // so we can use its type to deduce parts of the type of the binding.
       if (var.has_init()) {
         CARBON_RETURN_IF_ERROR(TypeCheckExp(&var.init(), impl_scope));
+        CARBON_RETURN_IF_ERROR(ExpectNonPlaceholderType(
+            var.init().source_loc(), &var.init().static_type()));
         init_type = &var.init().static_type();
       }
       CARBON_RETURN_IF_ERROR(TypeCheckPattern(&var.pattern(), init_type,
@@ -4051,6 +4207,8 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
       CARBON_RETURN_IF_ERROR(TypeCheckExp(&ret.expression(), impl_scope));
       ReturnTerm& return_term = ret.function().return_term();
       if (return_term.is_auto()) {
+        CARBON_RETURN_IF_ERROR(ExpectNonPlaceholderType(
+            ret.source_loc(), &ret.expression().static_type()));
         return_term.set_static_type(&ret.expression().static_type());
       } else {
         CARBON_ASSIGN_OR_RETURN(
@@ -4205,15 +4363,15 @@ auto TypeChecker::DeclareCallableDeclaration(Nonnull<CallableDeclaration*> f,
   if (std::optional<Nonnull<Expression*>> return_expression =
           f->return_term().type_expression();
       return_expression.has_value()) {
-    CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> ret_type,
-                            TypeCheckTypeExp(*return_expression, function_scope,
-                                             /*concrete=*/false));
+    CARBON_ASSIGN_OR_RETURN(
+        Nonnull<const Value*> ret_type,
+        TypeCheckTypeExp(*return_expression, function_scope));
     // TODO: This is setting the constant value of the return type. It would
     // make more sense if this were called `set_constant_value` rather than
     // `set_static_type`.
     f->return_term().set_static_type(ret_type);
   } else if (f->return_term().is_omitted()) {
-    f->return_term().set_static_type(TupleValue::Empty());
+    f->return_term().set_static_type(TupleType::Empty());
   } else {
     // We have to type-check the body in order to determine the return type.
     if (!f->body().has_value()) {
@@ -4226,9 +4384,8 @@ auto TypeChecker::DeclareCallableDeclaration(Nonnull<CallableDeclaration*> f,
           ExpectReturnOnAllPaths(f->body(), f->source_loc()));
     }
   }
+  CARBON_CHECK(IsConcreteType(&f->return_term().static_type()));
 
-  CARBON_RETURN_IF_ERROR(
-      ExpectIsConcreteType(f->source_loc(), &f->return_term().static_type()));
   f->set_static_type(arena_->New<FunctionType>(
       &f->param_pattern().static_type(), std::move(generic_parameters),
       &f->return_term().static_type(), std::move(deduced_bindings),
@@ -5111,6 +5268,7 @@ static auto IsValidTypeForAliasTarget(Nonnull<const Value*> type) -> bool {
     case Value::Kind::PointerType:
     case Value::Kind::StaticArrayType:
     case Value::Kind::StructType:
+    case Value::Kind::TupleType:
     case Value::Kind::NominalClassType:
     case Value::Kind::ChoiceType:
     case Value::Kind::ContinuationType:
@@ -5311,16 +5469,12 @@ auto TypeChecker::DeclareDeclaration(Nonnull<Declaration*> d,
         return ProgramError(var.binding().type().source_loc())
                << "Expected expression for variable type";
       }
-      Expression& type =
-          cast<ExpressionPattern>(var.binding().type()).expression();
       CARBON_RETURN_IF_ERROR(TypeCheckPattern(&var.binding(), std::nullopt,
                                               *scope_info.innermost_scope,
                                               var.value_category()));
-      CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> declared_type,
-                              InterpExp(&type, arena_, trace_stream_));
       CARBON_RETURN_IF_ERROR(ExpectCompleteType(
-          var.source_loc(), "type of variable", declared_type));
-      var.set_static_type(declared_type);
+          var.source_loc(), "type of variable", &var.binding().static_type()));
+      var.set_static_type(&var.binding().static_type());
       break;
     }
 

+ 4 - 5
explorer/interpreter/type_checker.h

@@ -333,11 +333,6 @@ class TypeChecker {
   auto ExpectReturnOnAllPaths(std::optional<Nonnull<Statement*>> opt_stmt,
                               SourceLocation source_loc) -> ErrorOr<Success>;
 
-  // Verifies that *value represents a concrete type, as opposed to a
-  // type pattern or a non-type value.
-  auto ExpectIsConcreteType(SourceLocation source_loc,
-                            Nonnull<const Value*> value) -> ErrorOr<Success>;
-
   // Returns the field names of the class together with their types.
   auto FieldTypes(const NominalClassType& class_type) const
       -> std::vector<NamedValue>;
@@ -370,6 +365,10 @@ class TypeChecker {
                          Nonnull<const Value*> destination)
       -> ErrorOr<Nonnull<Expression*>>;
 
+  // Checks that the given type is not a placeholder type. Diagnoses otherwise.
+  auto ExpectNonPlaceholderType(SourceLocation source_loc,
+                                Nonnull<const Value*> type) -> ErrorOr<Success>;
+
   // Determine whether `type1` and `type2` are considered to be the same type
   // in the given scope. This is true if they're structurally identical or if
   // there is an equality relation in scope that specifies that they are the

+ 19 - 14
explorer/interpreter/value.cpp

@@ -21,6 +21,7 @@ namespace Carbon {
 using llvm::cast;
 using llvm::dyn_cast;
 using llvm::dyn_cast_or_null;
+using llvm::isa;
 
 auto StructValue::FindField(std::string_view name) const
     -> std::optional<Nonnull<const Value*>> {
@@ -87,13 +88,8 @@ static auto GetMember(Nonnull<Arena*> arena, Nonnull<const Value*> v,
     case Value::Kind::NominalClassValue: {
       const auto& object = cast<NominalClassValue>(*v);
       // Look for a field.
-      // Note that the value representation of an empty class is a
-      // `StructType`, not a `StructValue`.
-      std::optional<Nonnull<const Value*>> field;
-      if (const auto* struct_value = dyn_cast<StructValue>(&object.inits())) {
-        field = struct_value->FindField(f);
-      }
-      if (field.has_value()) {
+      if (std::optional<Nonnull<const Value*>> field =
+              cast<StructValue>(object.inits()).FindField(f)) {
         return *field;
       } else {
         // Look for a method in the object's class
@@ -185,9 +181,10 @@ static auto SetFieldImpl(
                                            path_end, field_value, source_loc));
       return arena->New<NominalClassValue>(&object.type(), inits);
     }
+    case Value::Kind::TupleType:
     case Value::Kind::TupleValue: {
       std::vector<Nonnull<const Value*>> elements =
-          cast<TupleValue>(*value).elements();
+          cast<TupleValueBase>(*value).elements();
       // TODO(geoffromer): update FieldPath to hold integers as well as strings.
       int index = std::stoi(std::string((*path_begin).name()));
       if (index < 0 || static_cast<size_t>(index) >= elements.size()) {
@@ -197,7 +194,11 @@ static auto SetFieldImpl(
       CARBON_ASSIGN_OR_RETURN(
           elements[index], SetFieldImpl(arena, elements[index], path_begin + 1,
                                         path_end, field_value, source_loc));
-      return arena->New<TupleValue>(elements);
+      if (isa<TupleType>(value)) {
+        return arena->New<TupleType>(elements);
+      } else {
+        return arena->New<TupleValue>(elements);
+      }
     }
     default:
       CARBON_FATAL() << "field access not allowed for value " << *value;
@@ -272,10 +273,12 @@ void Value::Print(llvm::raw_ostream& out) const {
       out << cast<NominalClassType>(s.type()).declaration().name() << s.inits();
       break;
     }
+    case Value::Kind::TupleType:
     case Value::Kind::TupleValue: {
       out << "(";
       llvm::ListSeparator sep;
-      for (Nonnull<const Value*> element : cast<TupleValue>(*this).elements()) {
+      for (Nonnull<const Value*> element :
+           cast<TupleValueBase>(*this).elements()) {
         out << sep << *element;
       }
       out << ")";
@@ -705,9 +708,10 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2,
     }
     case Value::Kind::ChoiceType:
       return cast<ChoiceType>(*t1).name() == cast<ChoiceType>(*t2).name();
+    case Value::Kind::TupleType:
     case Value::Kind::TupleValue: {
-      const auto& tup1 = cast<TupleValue>(*t1);
-      const auto& tup2 = cast<TupleValue>(*t2);
+      const auto& tup1 = cast<TupleValueBase>(*t1);
+      const auto& tup2 = cast<TupleValueBase>(*t2);
       if (tup1.elements().size() != tup2.elements().size()) {
         return false;
       }
@@ -806,11 +810,12 @@ auto ValueStructurallyEqual(
              body1.has_value() == body2.has_value() &&
              (!body1.has_value() || *body1 == *body2);
     }
+    case Value::Kind::TupleType:
     case Value::Kind::TupleValue: {
       const std::vector<Nonnull<const Value*>>& elements1 =
-          cast<TupleValue>(*v1).elements();
+          cast<TupleValueBase>(*v1).elements();
       const std::vector<Nonnull<const Value*>>& elements2 =
-          cast<TupleValue>(*v2).elements();
+          cast<TupleValueBase>(*v2).elements();
       if (elements1.size() != elements2.size()) {
         return false;
       }

+ 46 - 21
explorer/interpreter/value.h

@@ -61,6 +61,7 @@ class Value {
     AutoType,
     StructType,
     NominalClassType,
+    TupleType,
     MixinPseudoType,
     InterfaceType,
     ConstraintType,
@@ -298,20 +299,13 @@ class BoolValue : public Value {
   bool value_;
 };
 
-// A non-empty value of a struct type.
-//
-// It can't be empty because `{}` is a struct type as well as a value of that
-// type, so for consistency we always represent it as a StructType rather than
-// let it oscillate unpredictably between the two. However, this means code
-// that handles StructValue instances may also need to be able to handle
-// StructType instances.
+// A value of a struct type. Note that the expression `{}` is a value of type
+// `{} as Type`; the former is a `StructValue` and the latter is a
+// `StructType`.
 class StructValue : public Value {
  public:
   explicit StructValue(std::vector<NamedValue> elements)
-      : Value(Kind::StructValue), elements_(std::move(elements)) {
-    CARBON_CHECK(!elements_.empty())
-        << "`{}` is represented as a StructType, not a StructValue.";
-  }
+      : Value(Kind::StructValue), elements_(std::move(elements)) {}
 
   static auto classof(const Value* value) -> bool {
     return value->kind() == Kind::StructValue;
@@ -391,10 +385,32 @@ class AlternativeValue : public Value {
   Nonnull<const Value*> argument_;
 };
 
+// Base class for tuple types and tuple values. These are the same other than
+// their type-of-type, but we separate them to make it easier to tell types and
+// values apart.
+class TupleValueBase : public Value {
+ public:
+  explicit TupleValueBase(Value::Kind kind,
+                          std::vector<Nonnull<const Value*>> elements)
+      : Value(kind), elements_(std::move(elements)) {}
+
+  auto elements() const -> llvm::ArrayRef<Nonnull<const Value*>> {
+    return elements_;
+  }
+
+  static auto classof(const Value* value) -> bool {
+    return value->kind() == Kind::TupleValue ||
+           value->kind() == Kind::TupleType;
+  }
+
+ private:
+  std::vector<Nonnull<const Value*>> elements_;
+};
+
 // A tuple value.
-class TupleValue : public Value {
+class TupleValue : public TupleValueBase {
  public:
-  // An empty tuple, also known as the unit type.
+  // An empty tuple.
   static auto Empty() -> Nonnull<const TupleValue*> {
     static const TupleValue empty =
         TupleValue(std::vector<Nonnull<const Value*>>());
@@ -402,18 +418,30 @@ class TupleValue : public Value {
   }
 
   explicit TupleValue(std::vector<Nonnull<const Value*>> elements)
-      : Value(Kind::TupleValue), elements_(std::move(elements)) {}
+      : TupleValueBase(Kind::TupleValue, std::move(elements)) {}
 
   static auto classof(const Value* value) -> bool {
     return value->kind() == Kind::TupleValue;
   }
+};
 
-  auto elements() const -> llvm::ArrayRef<Nonnull<const Value*>> {
-    return elements_;
+// A tuple type. This is the result of converting a tuple value containing
+// only types to type Type.
+class TupleType : public TupleValueBase {
+ public:
+  // The unit type.
+  static auto Empty() -> Nonnull<const TupleType*> {
+    static const TupleType empty =
+        TupleType(std::vector<Nonnull<const Value*>>());
+    return static_cast<Nonnull<const TupleType*>>(&empty);
   }
 
- private:
-  std::vector<Nonnull<const Value*>> elements_;
+  explicit TupleType(std::vector<Nonnull<const Value*>> elements)
+      : TupleValueBase(Kind::TupleType, std::move(elements)) {}
+
+  static auto classof(const Value* value) -> bool {
+    return value->kind() == Kind::TupleType;
+  }
 };
 
 // A binding placeholder value.
@@ -587,9 +615,6 @@ class AutoType : public Value {
 };
 
 // A struct type.
-//
-// Code that handles this type may sometimes need to have special-case handling
-// for `{}`, which is a struct value in addition to being a struct type.
 class StructType : public Value {
  public:
   StructType() : StructType(std::vector<NamedValue>{}) {}

+ 4 - 4
explorer/syntax/parser.ypp

@@ -753,7 +753,9 @@ paren_expression_contents:
 ;
 
 struct_literal:
-  LEFT_CURLY_BRACE struct_literal_contents RIGHT_CURLY_BRACE
+  LEFT_CURLY_BRACE RIGHT_CURLY_BRACE
+    { $$ = arena->New<StructLiteral>(context.source_loc()); }
+| LEFT_CURLY_BRACE struct_literal_contents RIGHT_CURLY_BRACE
     { $$ = arena->New<StructLiteral>(context.source_loc(), $2); }
 | LEFT_CURLY_BRACE struct_literal_contents COMMA RIGHT_CURLY_BRACE
     { $$ = arena->New<StructLiteral>(context.source_loc(), $2); }
@@ -769,9 +771,7 @@ struct_literal_contents:
 ;
 
 struct_type_literal:
-  LEFT_CURLY_BRACE RIGHT_CURLY_BRACE
-    { $$ = arena->New<StructTypeLiteral>(context.source_loc()); }
-| LEFT_CURLY_BRACE struct_type_literal_contents RIGHT_CURLY_BRACE
+  LEFT_CURLY_BRACE struct_type_literal_contents RIGHT_CURLY_BRACE
     { $$ = arena->New<StructTypeLiteral>(context.source_loc(), $2); }
 | LEFT_CURLY_BRACE struct_type_literal_contents COMMA RIGHT_CURLY_BRACE
     { $$ = arena->New<StructTypeLiteral>(context.source_loc(), $2); }

+ 22 - 0
explorer/testdata/array/element_convertible_to_type.carbon

@@ -0,0 +1,22 @@
+// 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
+// RUN: %{explorer-run}
+// RUN: %{explorer-run-trace}
+// CHECK:STDOUT: result: 3
+
+package ExplorerTest api;
+
+class TypeLike {
+  impl as ImplicitAs(Type) {
+    fn Convert[me: Self]() -> Type { return i32; }
+  }
+  fn Make() -> Self { return {}; }
+}
+
+fn Main() -> TypeLike.Make() {
+  var v: [TypeLike.Make(); 2] = (1, 2);
+  return v[0] + v[1];
+}

+ 1 - 1
explorer/testdata/as/fail_destination_not_type.carbon

@@ -9,6 +9,6 @@
 package ExplorerTest api;
 
 fn Main() -> i32 {
-  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/as/fail_destination_not_type.carbon:[[@LINE+1]]: Expected a type, but got 7
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/as/fail_destination_not_type.carbon:[[@LINE+1]]: type error in type expression: 'i32' is not implicitly convertible to 'Type'
   return 4 as 7;
 }

+ 1 - 1
explorer/testdata/basic_syntax/fail_alternative_not_type.carbon

@@ -8,7 +8,7 @@
 
 package ExplorerTest api;
 
-// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_alternative_not_type.carbon:[[@LINE+1]]: Expected a type, but got (42)
+// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_alternative_not_type.carbon:[[@LINE+1]]: type error in type expression: '(i32)' is not implicitly convertible to 'Type'
 choice C { X(42) }
 
 fn Main() -> i32 {

+ 1 - 1
explorer/testdata/basic_syntax/fail_nested_binding.carbon

@@ -9,7 +9,7 @@
 package ExplorerTest api;
 
 fn Main() -> i32 {
-  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_nested_binding.carbon:[[@LINE+1]]: The type of a binding pattern cannot contain bindings.
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_nested_binding.carbon:[[@LINE+1]]: the type of a binding pattern cannot contain bindings
   var x: (T: Type) = 1;
   return 1;
 }

+ 1 - 1
explorer/testdata/basic_syntax/fail_var_type.carbon

@@ -11,7 +11,7 @@ package ExplorerTest api;
 fn Main () -> i32
 {
   // 42 cannot be used as the type of a variable.
-  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_var_type.carbon:[[@LINE+1]]: Expected a type, but got 42
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_var_type.carbon:[[@LINE+1]]: type error in type of name binding: 'i32' is not implicitly convertible to 'Type'
   var x: 42 = 0;
   return x;
 }

+ 1 - 1
explorer/testdata/class/fail_extends_non_class.carbon

@@ -8,7 +8,7 @@
 
 package ExplorerTest api;
 
-// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_extends_non_class.carbon:[[@LINE+1]]: Expected a type, but got 3
+// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_extends_non_class.carbon:[[@LINE+1]]: type error in type expression: 'i32' is not implicitly convertible to 'Type'
 class C extends 3 {
   var x: i32;
   var y: i32;

+ 1 - 1
explorer/testdata/class/fail_method_deduced.carbon

@@ -17,7 +17,7 @@ fn H[T:! Type](x: T) {}
 
 fn Main() -> i32 {
   H(C.F);
-  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_method_deduced.carbon:[[@LINE+1]]: Expected a type, but got member name G
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_method_deduced.carbon:[[@LINE+1]]: member name G can only be used in a member access or alias
   H(C.G);
   return 0;
 }

+ 1 - 1
explorer/testdata/class/fail_method_in_var.carbon

@@ -15,7 +15,7 @@ class C {
 
 fn Main() -> i32 {
   var f: auto = C.F;
-  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_method_in_var.carbon:[[@LINE+1]]: Expected a type, but got member name G
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_method_in_var.carbon:[[@LINE+1]]: member name G can only be used in a member access or alias
   var g: auto = C.G;
 
   return 0;

+ 1 - 1
explorer/testdata/class/fail_return_method.carbon

@@ -14,7 +14,7 @@ class C {
 }
 
 fn ReturnF() -> auto { return C.F; }
-// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_return_method.carbon:[[@LINE+1]]: Expected a type, but got member name G
+// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_return_method.carbon:[[@LINE+1]]: member name G can only be used in a member access or alias
 fn ReturnG() -> auto { return C.G; }
 
 fn Main() -> i32 {

+ 3 - 4
explorer/testdata/comparison/fail_empty_struct.carbon → explorer/testdata/comparison/empty_struct.carbon

@@ -3,13 +3,12 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 // AUTOUPDATE
-// RUN: %{not} %{explorer-run}
-// RUN: %{not} %{explorer-run-trace}
+// RUN: %{explorer-run}
+// RUN: %{explorer-run-trace}
+// CHECK:STDOUT: result: 0
 
 package ExplorerTest api;
 
-// TODO: This should work
-// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/comparison/fail_empty_struct.carbon:[[@LINE+1]]: type error in call: '({})' is not implicitly convertible to '(Type)'
 external impl {} as EqWith({}) {
   fn Equal[me: Self](other: Self) -> bool {
     return true;

+ 1 - 1
explorer/testdata/constraint/fail_where_non_type_is.carbon

@@ -10,7 +10,7 @@ package ExplorerTest api;
 
 interface A {}
 
-// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_where_non_type_is.carbon:[[@LINE+1]]: Expected a type, but got 4
+// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/constraint/fail_where_non_type_is.carbon:[[@LINE+1]]: type error in type expression: 'i32' is not implicitly convertible to 'Type'
 alias B = A where 4 is A;
 
 fn Main() -> i32 { return 0; }

+ 1 - 1
explorer/testdata/function/fail_parameter_type.carbon

@@ -9,7 +9,7 @@
 package ExplorerTest api;
 
 // 42 cannot be used as the type of a parameter.
-// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/function/fail_parameter_type.carbon:[[@LINE+1]]: Expected a type, but got 42
+// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/function/fail_parameter_type.carbon:[[@LINE+1]]: type error in type of name binding: 'i32' is not implicitly convertible to 'Type'
 fn f(x: 42) -> i32 {
   return x - 1;
 }

+ 1 - 1
explorer/testdata/function/fail_return_call_has_invalid_body.carbon

@@ -9,7 +9,7 @@
 package EmptyIdentifier impl;
 
 fn apply[T:! Type, U:! Type](f: T, EmptyIdentifier: U) {
-  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/function/fail_return_call_has_invalid_body.carbon:[[@LINE+1]]: expected a tuple
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/function/fail_return_call_has_invalid_body.carbon:[[@LINE+1]]: only arrays and tuples can be indexed, found bool
   match (true[true]) {}
 }
 

+ 1 - 1
explorer/testdata/function/fail_var_type_is_call.carbon

@@ -13,7 +13,7 @@ fn test() -> i32 {
 }
 
 fn Main() -> i32 {
-  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/function/fail_var_type_is_call.carbon:[[@LINE+1]]: Expected a type, but got 1
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/function/fail_var_type_is_call.carbon:[[@LINE+1]]: type error in type of name binding: 'i32' is not implicitly convertible to 'Type'
   var x: test() = 1;
   return 0;
 }

+ 21 - 0
explorer/testdata/function/return_convertible_to_type.carbon

@@ -0,0 +1,21 @@
+// 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
+// RUN: %{explorer-run}
+// RUN: %{explorer-run-trace}
+// CHECK:STDOUT: result: 0
+
+package ExplorerTest api;
+
+class TypeLike {
+  impl as ImplicitAs(Type) {
+    fn Convert[me: Self]() -> Type { return i32; }
+  }
+  fn Make() -> Self { return {}; }
+}
+
+fn Main() -> TypeLike.Make() {
+  return 0;
+}

+ 1 - 1
explorer/testdata/generic_class/fail_bad_parameter_type.carbon

@@ -9,6 +9,7 @@
 package ExplorerTest api;
 
 class Point(T:! i32) {
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_bad_parameter_type.carbon:[[@LINE+1]]: type error in type of name binding: 'i32' is not implicitly convertible to 'Type'
   fn Origin(zero: T) -> Point(T) {
     return {.x = zero, .y = zero};
   }
@@ -22,7 +23,6 @@ class Point(T:! i32) {
 }
 
 fn Main() -> i32 {
-  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_bad_parameter_type.carbon:[[@LINE+1]]: type error in call: 'Type' is not implicitly convertible to 'i32'
   var p: Point(i32) = Point(i32).Origin(0);
   return p.GetX();
 }

+ 1 - 1
explorer/testdata/generic_class/fail_generic_in_pattern.carbon

@@ -11,7 +11,7 @@ package ExplorerTest api;
 fn Main() -> i32 {
   var t: auto = 5;
   match (t) {
-    // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_generic_in_pattern.carbon:[[@LINE+1]]: Generic binding may not occur in pattern with expected type: T:! i32
+    // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_generic_in_pattern.carbon:[[@LINE+1]]: generic binding may not occur in pattern with expected type T:! i32
     case T:! i32 => { return 0; }
     default => { return 1; }
   }

+ 1 - 1
explorer/testdata/generic_class/fail_no_args.carbon

@@ -14,7 +14,7 @@ class Point(T:! Type) {
   }
 
   // Error: wrote `Point` instead of `Point(T)`, `Point` by itself is not a type.
-  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_no_args.carbon:[[@LINE+1]]: Expected a type, but got Point
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_no_args.carbon:[[@LINE+1]]: 'Point' must be given an argument list
   fn GetX[me: Point]() -> T {
     return me.x;
   }

+ 1 - 1
explorer/testdata/generic_class/fail_return_type_is_type.carbon

@@ -10,7 +10,7 @@ package ExplorerTest api;
 
 class Point(T:! Type) {
   // The return type should be Point(T). Point by itself is not a type.
-  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_return_type_is_type.carbon:[[@LINE+1]]: Expected a type, but got Point
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_class/fail_return_type_is_type.carbon:[[@LINE+1]]: 'Point' must be given an argument list
   fn Create(x: T, y: T) -> Point {
     return {.x = x, .y = y};
   }

+ 1 - 1
explorer/testdata/generic_function/fail_not_type.carbon

@@ -8,7 +8,7 @@
 
 package ExplorerTest api;
 
-// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_function/fail_not_type.carbon:[[@LINE+1]]: Expected a type, but got 42
+// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/generic_function/fail_not_type.carbon:[[@LINE+1]]: type error in type expression: 'i32' is not implicitly convertible to 'Type'
 fn F[a:! 42]();
 
 fn Main() -> i32 {

+ 1 - 1
explorer/testdata/impl/fail_impl_as_parameterized.carbon

@@ -11,7 +11,7 @@ package ExplorerTest api;
 interface Vector(Scalar:! Type) {
 }
 
-// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/impl/fail_impl_as_parameterized.carbon:[[@LINE+1]]: Expected a type, but got Vector
+// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/impl/fail_impl_as_parameterized.carbon:[[@LINE+1]]: 'Vector' must be given an argument list
 external impl i32 as Vector {}
 
 fn Main() -> i32 {

+ 1 - 1
explorer/testdata/interface/fail_impl_as_not_type.carbon

@@ -11,7 +11,7 @@ package ExplorerTest api;
 interface A {}
 
 interface B {
-  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_impl_as_not_type.carbon:[[@LINE+1]]: Expected a type, but got 5
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_impl_as_not_type.carbon:[[@LINE+1]]: type error in type expression: 'i32' is not implicitly convertible to 'Type'
   impl 5 as A;
 }
 

+ 1 - 1
explorer/testdata/interface/fail_impl_not_type.carbon

@@ -11,7 +11,7 @@ package ExplorerTest api;
 interface Vector {
   fn Zero() -> i32;
 }
-// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_impl_not_type.carbon:[[@LINE+1]]: Expected a type, but got "hello"
+// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_impl_not_type.carbon:[[@LINE+1]]: type error in type expression: 'String' is not implicitly convertible to 'Type'
 impl "hello" as Vector {
   fn Zero() -> i32 { return 0; }
 }

+ 1 - 1
explorer/testdata/mixin/fail_mix_as_type_expr.carbon

@@ -11,7 +11,7 @@ package ExplorerTest api;
 __mixin Operations {}
 
 fn Main() -> i32 {
-   // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail_mix_as_type_expr.carbon:[[@LINE+1]]: Expected a type, but got mixin Operations
+   // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/mixin/fail_mix_as_type_expr.carbon:[[@LINE+1]]: invalid use of mixin Operations
    var a: Operations;
    return 0;
 }

+ 22 - 0
explorer/testdata/struct/field_convertible_to_type.carbon

@@ -0,0 +1,22 @@
+// 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
+// RUN: %{explorer-run}
+// RUN: %{explorer-run-trace}
+// CHECK:STDOUT: result: 3
+
+package ExplorerTest api;
+
+class TypeLike {
+  impl as ImplicitAs(Type) {
+    fn Convert[me: Self]() -> Type { return i32; }
+  }
+  fn Make() -> Self { return {}; }
+}
+
+fn Main() -> TypeLike.Make() {
+  var x: {.a: TypeLike.Make(), .b: TypeLike.Make()} = {.a = 1, .b = 2};
+  return x.a + x.b;
+}

+ 14 - 0
explorer/testdata/tuple/fail_nontype_tuple_as_type.carbon

@@ -0,0 +1,14 @@
+// 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
+// RUN: %{not} %{explorer-run}
+// RUN: %{not} %{explorer-run-trace}
+
+package ExplorerTest api;
+
+// CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/tuple/fail_nontype_tuple_as_type.carbon:[[@LINE+1]]: type error in type of name binding: '(i32, i32)' is not implicitly convertible to 'Type'
+fn F[T:! (i32, i32)](x: T);
+
+fn Main() -> i32;

+ 19 - 0
explorer/testdata/tuple/fail_type_tuple_as_type.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
+// RUN: %{not} %{explorer-run}
+// RUN: %{not} %{explorer-run-trace}
+
+package ExplorerTest api;
+
+fn F[T:! ((), ())](x: T) -> () {
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/tuple/fail_type_tuple_as_type.carbon:[[@LINE+1]]: only arrays and tuples can be indexed, found T
+  return x[0];
+}
+
+fn Main() -> i32 {
+  F(((), ()));
+  return 0;
+}