From 0219218b01a722273b5dfa81412a2384b661fecc Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 28 Oct 2022 08:20:49 -0700 Subject: [PATCH] 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. --- explorer/ast/ast_rtti.txt | 10 +- explorer/ast/expression.cpp | 7 + explorer/ast/expression.h | 105 ++-- explorer/fuzzing/ast_to_proto.cpp | 6 + explorer/interpreter/interpreter.cpp | 124 ++--- explorer/interpreter/pattern_analysis.cpp | 4 +- explorer/interpreter/resolve_names.cpp | 4 +- explorer/interpreter/resolve_unformed.cpp | 5 + explorer/interpreter/type_checker.cpp | 462 ++++++++++++------ explorer/interpreter/type_checker.h | 9 +- explorer/interpreter/value.cpp | 33 +- explorer/interpreter/value.h | 69 ++- explorer/syntax/parser.ypp | 8 +- .../array/element_convertible_to_type.carbon | 22 + .../as/fail_destination_not_type.carbon | 2 +- .../fail_alternative_not_type.carbon | 2 +- .../basic_syntax/fail_nested_binding.carbon | 2 +- .../basic_syntax/fail_var_type.carbon | 2 +- .../class/fail_extends_non_class.carbon | 2 +- .../testdata/class/fail_method_deduced.carbon | 2 +- .../testdata/class/fail_method_in_var.carbon | 2 +- .../testdata/class/fail_return_method.carbon | 2 +- ...mpty_struct.carbon => empty_struct.carbon} | 7 +- .../constraint/fail_where_non_type_is.carbon | 2 +- .../function/fail_parameter_type.carbon | 2 +- .../fail_return_call_has_invalid_body.carbon | 2 +- .../function/fail_var_type_is_call.carbon | 2 +- .../return_convertible_to_type.carbon | 21 + .../fail_bad_parameter_type.carbon | 2 +- .../fail_generic_in_pattern.carbon | 2 +- .../generic_class/fail_no_args.carbon | 2 +- .../fail_return_type_is_type.carbon | 2 +- .../generic_function/fail_not_type.carbon | 2 +- .../impl/fail_impl_as_parameterized.carbon | 2 +- .../interface/fail_impl_as_not_type.carbon | 2 +- .../interface/fail_impl_not_type.carbon | 2 +- .../mixin/fail_mix_as_type_expr.carbon | 2 +- .../struct/field_convertible_to_type.carbon | 22 + .../tuple/fail_nontype_tuple_as_type.carbon | 14 + .../tuple/fail_type_tuple_as_type.carbon | 19 + 40 files changed, 674 insertions(+), 319 deletions(-) create mode 100644 explorer/testdata/array/element_convertible_to_type.carbon rename explorer/testdata/comparison/{fail_empty_struct.carbon => empty_struct.carbon} (65%) create mode 100644 explorer/testdata/function/return_convertible_to_type.carbon create mode 100644 explorer/testdata/struct/field_convertible_to_type.carbon create mode 100644 explorer/testdata/tuple/fail_nontype_tuple_as_type.carbon create mode 100644 explorer/testdata/tuple/fail_type_tuple_as_type.carbon diff --git a/explorer/ast/ast_rtti.txt b/explorer/ast/ast_rtti.txt index 646550d382f1..70c0c50640dc 100644 --- a/explorer/ast/ast_rtti.txt +++ b/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; diff --git a/explorer/ast/expression.cpp b/explorer/ast/expression.cpp index e6cee83a6b14..8af2674f5e85 100644 --- a/explorer/ast/expression.cpp +++ b/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(this)->source_expression(); + break; + } case ExpressionKind::UnimplementedExpression: { const auto& unimplemented = cast(*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: diff --git a/explorer/ast/expression.h b/explorer/ast/expression.h index 0698f8b3f644..ee9ddae7b631 100644 --- a/explorer/ast/expression.h +++ b/explorer/ast/expression.h @@ -504,21 +504,15 @@ class TupleLiteral : public Expression { std::vector> 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 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 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> 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 value) { + CARBON_CHECK(!constant_value_.has_value()); + constant_value_ = value; + } + + private: + std::optional> 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 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 parameter, Nonnull 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 value, Nonnull 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 value_; }; class IntrinsicExpression : public Expression { @@ -936,6 +955,36 @@ class WhereExpression : public RewritableMixin { std::optional> 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 source_expression, + Nonnull 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 { + return source_expression_; + } + auto source_expression() const -> Nonnull { + return source_expression_; + } + + private: + Nonnull 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 element_type_expression, Nonnull size_expression) - : Expression(AstNodeKind::ArrayTypeLiteral, source_loc), + : ConstantValueLiteral(AstNodeKind::ArrayTypeLiteral, source_loc), element_type_expression_(element_type_expression), size_expression_(size_expression) {} diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index 351a2036d306..5a92e7b38919 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/explorer/fuzzing/ast_to_proto.cpp @@ -115,6 +115,12 @@ static auto ExpressionToProto(const Expression& expression) break; } + case ExpressionKind::BuiltinConvertExpression: { + expression_proto = ExpressionToProto( + *cast(expression).source_expression()); + break; + } + case ExpressionKind::CallExpression: { const auto& call = cast(expression); auto* call_proto = expression_proto.mutable_call(); diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 8950575ed88a..2e2473400c5f 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -289,11 +289,13 @@ auto PatternMatch(Nonnull p, Nonnull 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(*p); - const auto& v_tup = cast(*v); + const auto& p_tup = cast(*p); + const auto& v_tup = cast(*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 p, Nonnull v, return true; } case Value::Kind::UninitializedValue: { - const auto& p_tup = cast(*p); + const auto& p_tup = cast(*p); for (const auto& ele : p_tup.elements()) { if (!PatternMatch(ele, arena->New(ele), source_loc, bindings, generic_args, trace_stream, @@ -501,6 +503,7 @@ auto Interpreter::StepLvalue() -> ErrorOr { 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 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 value, InstantiateType(destination_type, source_loc)); return arena_->New(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(); + } default: { CARBON_CHECK(IsValueKindDependent(destination_type) || isa(destination_type)) @@ -731,26 +743,13 @@ auto Interpreter::Convert(Nonnull 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(destination_type)) { - CARBON_CHECK(cast(*value).fields().empty()) - << "only an empty struct type value converts to class type"; - CARBON_ASSIGN_OR_RETURN(Nonnull inst_dest, - InstantiateType(destination_type, source_loc)); - return arena_->New(inst_dest, value); - } - return value; - } case Value::Kind::TupleValue: { - const auto& tuple = cast(value); + const auto* tuple = cast(value); std::vector> destination_element_types; switch (destination_type->kind()) { - case Value::Kind::TupleValue: + case Value::Kind::TupleType: destination_element_types = - cast(destination_type)->elements(); + cast(destination_type)->elements(); break; case Value::Kind::StaticArrayType: { const auto& array_type = cast(*destination_type); @@ -758,6 +757,18 @@ auto Interpreter::Convert(Nonnull value, &array_type.element_type()); break; } + case Value::Kind::TypeType: + case Value::Kind::ConstraintType: + case Value::Kind::InterfaceType: { + std::vector> new_elements; + Nonnull type_type = arena_->New(); + for (Nonnull value : tuple->elements()) { + CARBON_ASSIGN_OR_RETURN(Nonnull value_as_type, + Convert(value, type_type, source_loc)); + new_elements.push_back(value_as_type); + } + return arena_->New(std::move(new_elements)); + } default: { CARBON_CHECK(IsValueKindDependent(destination_type) || isa(destination_type)) @@ -782,7 +793,16 @@ auto Interpreter::Convert(Nonnull value, CARBON_ASSIGN_OR_RETURN( Nonnull value, EvalAssociatedConstant(cast(value), source_loc)); - if (isa(value)) { + if (auto* new_const = dyn_cast(value)) { + // TODO: Detect whether conversions are required in type-checking. + if (isa(destination_type) && + isa( + 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 { CreateStruct(literal.fields(), act.results())); } } - case ExpressionKind::StructTypeLiteral: { - const auto& struct_type = cast(exp); - if (act.pos() < static_cast(struct_type.fields().size())) { - return todo_.Spawn(std::make_unique( - &struct_type.fields()[act.pos()].expression())); - } else { - std::vector 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(std::move(fields))); - } - } case ExpressionKind::SimpleMemberAccessExpression: { const auto& access = cast(exp); bool forming_member_name = isa(&access.static_type()); @@ -1407,22 +1414,6 @@ auto Interpreter::StepExp() -> ErrorOr { CARBON_CHECK(act.pos() == 0); return todo_.FinishAction(arena_->New()); } - case ExpressionKind::FunctionTypeLiteral: { - if (act.pos() == 0) { - return todo_.Spawn(std::make_unique( - &cast(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( - &cast(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(act.results()[0], act.results()[1])); - } - } case ExpressionKind::ContinuationTypeLiteral: { CARBON_CHECK(act.pos() == 0); return todo_.FinishAction(arena_->New()); @@ -1436,9 +1427,18 @@ auto Interpreter::StepExp() -> ErrorOr { CARBON_CHECK(act.pos() == 0); return todo_.FinishAction(arena_->New()); } + case ExpressionKind::FunctionTypeLiteral: + case ExpressionKind::StructTypeLiteral: + case ExpressionKind::ArrayTypeLiteral: case ExpressionKind::ValueLiteral: { CARBON_CHECK(act.pos() == 0); - return todo_.FinishAction(&cast(exp).value()); + auto* value = &cast(exp).constant_value(); + CARBON_ASSIGN_OR_RETURN( + Nonnull destination, + InstantiateType(&exp.static_type(), exp.source_loc())); + CARBON_ASSIGN_OR_RETURN(Nonnull result, + Convert(value, destination, exp.source_loc())); + return todo_.FinishAction(result); } case ExpressionKind::IfExpression: { const auto& if_expr = cast(exp); @@ -1460,21 +1460,25 @@ auto Interpreter::StepExp() -> ErrorOr { CARBON_CHECK(rewrite) << "where expression should be rewritten"; return todo_.ReplaceWith(std::make_unique(*rewrite)); } - case ExpressionKind::UnimplementedExpression: - CARBON_FATAL() << "Unimplemented: " << exp; - case ExpressionKind::ArrayTypeLiteral: { - const auto& array_literal = cast(exp); + case ExpressionKind::BuiltinConvertExpression: { + const auto& convert_expr = cast(exp); if (act.pos() == 0) { return todo_.Spawn(std::make_unique( - &array_literal.element_type_expression())); - } else if (act.pos() == 1) { - return todo_.Spawn(std::make_unique( - &array_literal.size_expression())); + convert_expr.source_expression())); } else { - return todo_.FinishAction(arena_->New( - act.results()[0], cast(act.results()[1])->value())); + CARBON_ASSIGN_OR_RETURN(Nonnull 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 result, + Convert(act.results()[0], destination, convert_expr.source_loc())); + return todo_.FinishAction(result); } } + case ExpressionKind::UnimplementedExpression: + CARBON_FATAL() << "Unimplemented: " << exp; } // switch (exp->kind) } diff --git a/explorer/interpreter/pattern_analysis.cpp b/explorer/interpreter/pattern_analysis.cpp index 8dade645188e..0bfb058e2d68 100644 --- a/explorer/interpreter/pattern_analysis.cpp +++ b/explorer/interpreter/pattern_analysis.cpp @@ -71,7 +71,7 @@ void AbstractPattern::AppendElementsTo( } } else if (const auto* value = value_.dyn_cast()) { if (const auto* tuple = dyn_cast(value)) { - const auto* tuple_type = cast(type_); + const auto* tuple_type = cast(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(&type)) { + if (const auto* tuple = dyn_cast(&type)) { // If we find a tuple match, we've found all constructors (there's // only one!) and none were missing. return { diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index 05240ab6f7c6..c8eebbb8d0ef 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/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"; } diff --git a/explorer/interpreter/resolve_unformed.cpp b/explorer/interpreter/resolve_unformed.cpp index 0591a27cc623..2a4a118f1f2b 100644 --- a/explorer/interpreter/resolve_unformed.cpp +++ b/explorer/interpreter/resolve_unformed.cpp @@ -121,6 +121,11 @@ static auto ResolveUnformed(Nonnull expression, &cast(*expression).object(), flow_facts, FlowFacts::ActionType::Check)); break; + case ExpressionKind::BuiltinConvertExpression: + CARBON_RETURN_IF_ERROR(ResolveUnformed( + cast(*expression).source_expression(), + flow_facts, FlowFacts::ActionType::Check)); + break; case ExpressionKind::DotSelfExpression: case ExpressionKind::IntLiteral: case ExpressionKind::BoolLiteral: diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 13a599dad610..62d0500967c4 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/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 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 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 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 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 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 value) -> bool { switch (value->kind()) { case Value::Kind::IntValue: case Value::Kind::FunctionValue: @@ -154,6 +156,7 @@ static auto IsType(Nonnull 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 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 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 field : cast(*value).elements()) { - if (!IsType(field, concrete)) { - return false; - } - } return true; - } - case Value::Kind::PointerType: { - return IsType(&cast(*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 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 value) - -> ErrorOr { - 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 type) -> ErrorOr { - 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,18 +306,83 @@ 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 value) -> bool { - return IsType(value, /*concrete=*/true); +static auto TypeContainsAuto(Nonnull type) -> bool { + CARBON_CHECK(IsType(type)) << "expected a type, but found " << *type; + + 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(type)->fields(), + [](const NamedValue& v) { return v.value; }), + TypeContainsAuto); + case Value::Kind::TupleType: + return llvm::any_of(cast(type)->elements(), TypeContainsAuto); + case Value::Kind::PointerType: + return TypeContainsAuto(&cast(type)->type()); + case Value::Kind::StaticArrayType: + return TypeContainsAuto(&cast(type)->element_type()); + } } -auto TypeChecker::ExpectIsConcreteType(SourceLocation source_loc, - Nonnull value) - -> ErrorOr { - if (!IsConcreteType(value)) { - return ProgramError(source_loc) << "Expected a type, but got " << *value; - } else { - return Success(); - } +// 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 type) -> bool { + CARBON_CHECK(IsType(type)) << "expected a type, but found " << *type; + return isa(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 value) -> bool { + return IsType(value) && !TypeContainsAuto(value); } // Returns the named field, or None if not found. @@ -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(*source).fields().empty()) { + return true; + } + break; default: break; } break; - case Value::Kind::TupleValue: { - const auto& source_tuple = cast(*source); + case Value::Kind::TupleType: { + const auto& source_tuple = cast(*source); switch (destination->kind()) { - case Value::Kind::TupleValue: { - const auto& destination_tuple = cast(*destination); + case Value::Kind::TupleType: { + const auto& destination_tuple = cast(*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 source_element : source_tuple.elements()) { if (!IsImplicitlyConvertible( @@ -541,38 +600,59 @@ auto TypeChecker::ImplicitlyConvert(std::string_view context, -> ErrorOr> { Nonnull 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(destination)) { - CARBON_ASSIGN_OR_RETURN( - Nonnull destination_constraint, - ConvertToConstraintType(source->source_loc(), "implicit conversion", - destination)); - CARBON_ASSIGN_OR_RETURN(Nonnull 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(destination)) { + // First convert the source expression to type `Type`. + CARBON_ASSIGN_OR_RETURN(Nonnull source_as_type, + ImplicitlyConvert(context, impl_scope, source, + arena_->New())); + CARBON_ASSIGN_OR_RETURN(Nonnull converted_value, + InterpExp(source_as_type, arena_, trace_stream_)); + CARBON_ASSIGN_OR_RETURN( + Nonnull 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(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(source, destination); } + ErrorOr> 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 type) + -> ErrorOr { + if (!IsPlaceholderType(type)) { + return Success(); + } + if (auto* member_name = dyn_cast(type)) { + return ProgramError(source_loc) + << *member_name << " can only be used in a member access or alias"; + } + if (auto* param_entity = dyn_cast(type)) { + return ProgramError(source_loc) + << "'" << param_entity->name() << "' must be given an argument list"; + } + if (auto* mixin_type = dyn_cast(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 expected, @@ -786,12 +889,12 @@ auto TypeChecker::ArgumentDeduction::Deduce(Nonnull 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(*param); - const auto& arg_tup = cast(*arg); + const auto& param_tup = cast(*param); + const auto& arg_tup = cast(*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 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 value, @@ -1715,12 +1817,17 @@ auto TypeChecker::SubstituteImpl(const Bindings& bindings, return arena_->New(base, interface, &assoc.constant(), witness); } + case Value::Kind::TupleType: case Value::Kind::TupleValue: { std::vector> elts; - for (const auto& elt : cast(*type).elements()) { + for (const auto& elt : cast(*type).elements()) { elts.push_back(SubstituteImpl(bindings, elt)); } - return arena_->New(elts); + if (isa(type)) { + return arena_->New(std::move(elts)); + } else { + return arena_->New(std::move(elts)); + } } case Value::Kind::StructType: { std::vector 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> deduced_bindings, const ImplScope& impl_scope) -> ErrorOr { llvm::ArrayRef> params = - cast(*params_type).elements(); + cast(*params_type).elements(); llvm::ArrayRef> args = cast(call.argument()).fields(); if (params.size() != args.size()) { @@ -2303,6 +2415,7 @@ auto TypeChecker::TypeCheckExp(Nonnull 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 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(object_type); + case Value::Kind::TupleType: { + const auto& tuple_type = cast(object_type); CARBON_RETURN_IF_ERROR( ExpectExactType(index.offset().source_loc(), "tuple index", arena_->New(), @@ -2341,7 +2454,9 @@ auto TypeChecker::TypeCheckExp(Nonnull 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 e, for (auto* arg : cast(*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(std::move(arg_types))); + e->set_static_type(arena_->New(std::move(arg_types))); e->set_value_category(ValueCategory::Let); return Success(); } @@ -2360,7 +2475,7 @@ auto TypeChecker::TypeCheckExp(Nonnull e, std::vector arg_types; for (auto& arg : cast(*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 e, } case ExpressionKind::StructTypeLiteral: { auto& struct_type = cast(*e); + std::vector fields; for (auto& arg : struct_type.fields()) { - CARBON_RETURN_IF_ERROR(TypeCheckTypeExp(&arg.expression(), impl_scope)); + CARBON_ASSIGN_OR_RETURN( + Nonnull type, + TypeCheckTypeExp(&arg.expression(), impl_scope)); + fields.push_back({.name = arg.name(), .value = type}); } - 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()); - } else { - struct_type.set_static_type(arena_->New()); - } - e->set_value_category(ValueCategory::Let); + struct_type.set_static_type(arena_->New()); + struct_type.set_value_category(ValueCategory::Let); + struct_type.set_constant_value( + arena_->New(std::move(fields))); return Success(); } case ExpressionKind::SimpleMemberAccessExpression: { @@ -3040,9 +3153,7 @@ auto TypeChecker::TypeCheckExp(Nonnull e, case Operator::As: { CARBON_ASSIGN_OR_RETURN( Nonnull 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> converted = BuildBuiltinMethodCall(impl_scope, op.arguments()[0], BuiltinInterfaceName{Builtins::As, type}, @@ -3129,10 +3240,13 @@ auto TypeChecker::TypeCheckExp(Nonnull e, } case ExpressionKind::FunctionTypeLiteral: { auto& fn = cast(*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 param, + TypeCheckTypeExp(&fn.parameter(), impl_scope)); + CARBON_ASSIGN_OR_RETURN(Nonnull ret, + TypeCheckTypeExp(&fn.return_type(), impl_scope)); fn.set_static_type(arena_->New()); fn.set_value_category(ValueCategory::Let); + fn.set_constant_value(arena_->New(param, ret)); return Success(); } case ExpressionKind::StringLiteral: @@ -3160,7 +3274,7 @@ auto TypeChecker::TypeCheckExp(Nonnull e, e->source_loc(), "Print argument 1", arena_->New(), &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 e, CARBON_RETURN_IF_ERROR(ExpectType( e->source_loc(), "__intrinsic_assert argument 1", arena_->New(), &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 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 e, CARBON_FATAL() << "Unimplemented: " << *e; case ExpressionKind::ArrayTypeLiteral: { auto& array_literal = cast(*e); - CARBON_RETURN_IF_ERROR(TypeCheckTypeExp( - &array_literal.element_type_expression(), impl_scope)); - + CARBON_ASSIGN_OR_RETURN( + Nonnull 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 e, } array_literal.set_static_type(arena_->New()); array_literal.set_value_category(ValueCategory::Let); + array_literal.set_constant_value(arena_->New( + element_type, cast(size_value)->value())); return Success(); } } @@ -3571,11 +3688,24 @@ auto TypeChecker::TypeCheckTypeExp(Nonnull type_expression, const ImplScope& impl_scope, bool concrete) -> ErrorOr> { CARBON_RETURN_IF_ERROR(TypeCheckExp(type_expression, impl_scope)); + CARBON_ASSIGN_OR_RETURN( + type_expression, + ImplicitlyConvert("type expression", impl_scope, type_expression, + arena_->New())); CARBON_ASSIGN_OR_RETURN(Nonnull 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(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 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(binding.type().static_type())) { + auto* literal = arena_->New(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())); + 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 binding_value, InterpPattern(&binding, arena_, trace_stream_)); @@ -3685,8 +3837,8 @@ auto TypeChecker::TypeCheckPattern( auto& binding = cast(*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(*p); std::vector> 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(**expected).elements().size()) { + cast(**expected).elements().size()) { return ProgramError(tuple.source_loc()) << "tuples of different length"; } for (size_t i = 0; i < tuple.fields().size(); ++i) { Nonnull field = tuple.fields()[i]; std::optional> expected_field_type; if (expected) { - expected_field_type = cast(**expected).elements()[i]; + expected_field_type = cast(**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(std::move(field_types))); + tuple.set_static_type(arena_->New(std::move(field_types))); CARBON_ASSIGN_OR_RETURN(Nonnull tuple_value, InterpPattern(&tuple, arena_, trace_stream_)); SetValue(&tuple, tuple_value); @@ -3873,6 +4025,8 @@ auto TypeChecker::TypeCheckStmt(Nonnull s, case StatementKind::Match: { auto& match = cast(*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 new_clauses; std::optional> expected_type; PatternMatrix patterns; @@ -3976,6 +4130,8 @@ auto TypeChecker::TypeCheckStmt(Nonnull 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 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 f, if (std::optional> return_expression = f->return_term().type_expression(); return_expression.has_value()) { - CARBON_ASSIGN_OR_RETURN(Nonnull ret_type, - TypeCheckTypeExp(*return_expression, function_scope, - /*concrete=*/false)); + CARBON_ASSIGN_OR_RETURN( + Nonnull 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 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( &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 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 d, return ProgramError(var.binding().type().source_loc()) << "Expected expression for variable type"; } - Expression& type = - cast(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 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; } diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index a9d665a5655a..d3e0e762c306 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -333,11 +333,6 @@ class TypeChecker { auto ExpectReturnOnAllPaths(std::optional> opt_stmt, SourceLocation source_loc) -> ErrorOr; - // Verifies that *value represents a concrete type, as opposed to a - // type pattern or a non-type value. - auto ExpectIsConcreteType(SourceLocation source_loc, - Nonnull value) -> ErrorOr; - // Returns the field names of the class together with their types. auto FieldTypes(const NominalClassType& class_type) const -> std::vector; @@ -370,6 +365,10 @@ class TypeChecker { Nonnull destination) -> ErrorOr>; + // Checks that the given type is not a placeholder type. Diagnoses otherwise. + auto ExpectNonPlaceholderType(SourceLocation source_loc, + Nonnull type) -> ErrorOr; + // 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 diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index dc938d25d382..a5dce83235d9 100644 --- a/explorer/interpreter/value.cpp +++ b/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> { @@ -87,13 +88,8 @@ static auto GetMember(Nonnull arena, Nonnull v, case Value::Kind::NominalClassValue: { const auto& object = cast(*v); // Look for a field. - // Note that the value representation of an empty class is a - // `StructType`, not a `StructValue`. - std::optional> field; - if (const auto* struct_value = dyn_cast(&object.inits())) { - field = struct_value->FindField(f); - } - if (field.has_value()) { + if (std::optional> field = + cast(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(&object.type(), inits); } + case Value::Kind::TupleType: case Value::Kind::TupleValue: { std::vector> elements = - cast(*value).elements(); + cast(*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(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(elements); + if (isa(value)) { + return arena->New(elements); + } else { + return arena->New(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(s.type()).declaration().name() << s.inits(); break; } + case Value::Kind::TupleType: case Value::Kind::TupleValue: { out << "("; llvm::ListSeparator sep; - for (Nonnull element : cast(*this).elements()) { + for (Nonnull element : + cast(*this).elements()) { out << sep << *element; } out << ")"; @@ -705,9 +708,10 @@ auto TypeEqual(Nonnull t1, Nonnull t2, } case Value::Kind::ChoiceType: return cast(*t1).name() == cast(*t2).name(); + case Value::Kind::TupleType: case Value::Kind::TupleValue: { - const auto& tup1 = cast(*t1); - const auto& tup2 = cast(*t2); + const auto& tup1 = cast(*t1); + const auto& tup2 = cast(*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>& elements1 = - cast(*v1).elements(); + cast(*v1).elements(); const std::vector>& elements2 = - cast(*v2).elements(); + cast(*v2).elements(); if (elements1.size() != elements2.size()) { return false; } diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index 493eb761e12d..40ac282e939d 100644 --- a/explorer/interpreter/value.h +++ b/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 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 argument_; }; -// A tuple value. -class TupleValue : public Value { +// 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: - // An empty tuple, also known as the unit type. + explicit TupleValueBase(Value::Kind kind, + std::vector> elements) + : Value(kind), elements_(std::move(elements)) {} + + auto elements() const -> llvm::ArrayRef> { + return elements_; + } + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::TupleValue || + value->kind() == Kind::TupleType; + } + + private: + std::vector> elements_; +}; + +// A tuple value. +class TupleValue : public TupleValueBase { + public: + // An empty tuple. static auto Empty() -> Nonnull { static const TupleValue empty = TupleValue(std::vector>()); @@ -402,18 +418,30 @@ class TupleValue : public Value { } explicit TupleValue(std::vector> 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> { - 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 { + static const TupleType empty = + TupleType(std::vector>()); + return static_cast>(&empty); } - private: - std::vector> elements_; + explicit TupleType(std::vector> 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{}) {} diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index ab64b926d322..6d7db09249a7 100644 --- a/explorer/syntax/parser.ypp +++ b/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(context.source_loc()); } +| LEFT_CURLY_BRACE struct_literal_contents RIGHT_CURLY_BRACE { $$ = arena->New(context.source_loc(), $2); } | LEFT_CURLY_BRACE struct_literal_contents COMMA RIGHT_CURLY_BRACE { $$ = arena->New(context.source_loc(), $2); } @@ -769,9 +771,7 @@ struct_literal_contents: ; struct_type_literal: - LEFT_CURLY_BRACE RIGHT_CURLY_BRACE - { $$ = arena->New(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(context.source_loc(), $2); } | LEFT_CURLY_BRACE struct_type_literal_contents COMMA RIGHT_CURLY_BRACE { $$ = arena->New(context.source_loc(), $2); } diff --git a/explorer/testdata/array/element_convertible_to_type.carbon b/explorer/testdata/array/element_convertible_to_type.carbon new file mode 100644 index 000000000000..64e1e20bd6f3 --- /dev/null +++ b/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]; +} diff --git a/explorer/testdata/as/fail_destination_not_type.carbon b/explorer/testdata/as/fail_destination_not_type.carbon index 96a8b2cc844d..87c556952bbd 100644 --- a/explorer/testdata/as/fail_destination_not_type.carbon +++ b/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; } diff --git a/explorer/testdata/basic_syntax/fail_alternative_not_type.carbon b/explorer/testdata/basic_syntax/fail_alternative_not_type.carbon index 73a86dfca2cb..7dd7d38d0d35 100644 --- a/explorer/testdata/basic_syntax/fail_alternative_not_type.carbon +++ b/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 { diff --git a/explorer/testdata/basic_syntax/fail_nested_binding.carbon b/explorer/testdata/basic_syntax/fail_nested_binding.carbon index 8fffb37c27fb..8fb2e3167868 100644 --- a/explorer/testdata/basic_syntax/fail_nested_binding.carbon +++ b/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; } diff --git a/explorer/testdata/basic_syntax/fail_var_type.carbon b/explorer/testdata/basic_syntax/fail_var_type.carbon index 942575e2c0da..d65bc70bb074 100644 --- a/explorer/testdata/basic_syntax/fail_var_type.carbon +++ b/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; } diff --git a/explorer/testdata/class/fail_extends_non_class.carbon b/explorer/testdata/class/fail_extends_non_class.carbon index a0ecf1b263bb..f29de898f8ad 100644 --- a/explorer/testdata/class/fail_extends_non_class.carbon +++ b/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; diff --git a/explorer/testdata/class/fail_method_deduced.carbon b/explorer/testdata/class/fail_method_deduced.carbon index 3cd52bfff237..81662a4e0eb4 100644 --- a/explorer/testdata/class/fail_method_deduced.carbon +++ b/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; } diff --git a/explorer/testdata/class/fail_method_in_var.carbon b/explorer/testdata/class/fail_method_in_var.carbon index fb23f06ee48c..2e83439e4c0f 100644 --- a/explorer/testdata/class/fail_method_in_var.carbon +++ b/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; diff --git a/explorer/testdata/class/fail_return_method.carbon b/explorer/testdata/class/fail_return_method.carbon index 0dbbc3f0f1ba..47bfa41b69ff 100644 --- a/explorer/testdata/class/fail_return_method.carbon +++ b/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 { diff --git a/explorer/testdata/comparison/fail_empty_struct.carbon b/explorer/testdata/comparison/empty_struct.carbon similarity index 65% rename from explorer/testdata/comparison/fail_empty_struct.carbon rename to explorer/testdata/comparison/empty_struct.carbon index 06d39a39393e..f2b05c524661 100644 --- a/explorer/testdata/comparison/fail_empty_struct.carbon +++ b/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; diff --git a/explorer/testdata/constraint/fail_where_non_type_is.carbon b/explorer/testdata/constraint/fail_where_non_type_is.carbon index dfc9be5ec4c8..a48ac967980f 100644 --- a/explorer/testdata/constraint/fail_where_non_type_is.carbon +++ b/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; } diff --git a/explorer/testdata/function/fail_parameter_type.carbon b/explorer/testdata/function/fail_parameter_type.carbon index f8f945e36cd8..52b9d1861dd1 100644 --- a/explorer/testdata/function/fail_parameter_type.carbon +++ b/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; } diff --git a/explorer/testdata/function/fail_return_call_has_invalid_body.carbon b/explorer/testdata/function/fail_return_call_has_invalid_body.carbon index 0584173901f9..6415ecdcf1b5 100644 --- a/explorer/testdata/function/fail_return_call_has_invalid_body.carbon +++ b/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]) {} } diff --git a/explorer/testdata/function/fail_var_type_is_call.carbon b/explorer/testdata/function/fail_var_type_is_call.carbon index 021102ab8823..e98878db1c9d 100644 --- a/explorer/testdata/function/fail_var_type_is_call.carbon +++ b/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; } diff --git a/explorer/testdata/function/return_convertible_to_type.carbon b/explorer/testdata/function/return_convertible_to_type.carbon new file mode 100644 index 000000000000..15a2ddc1a41c --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/generic_class/fail_bad_parameter_type.carbon b/explorer/testdata/generic_class/fail_bad_parameter_type.carbon index 79e48fd9cc8e..26e179955679 100644 --- a/explorer/testdata/generic_class/fail_bad_parameter_type.carbon +++ b/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(); } diff --git a/explorer/testdata/generic_class/fail_generic_in_pattern.carbon b/explorer/testdata/generic_class/fail_generic_in_pattern.carbon index 19ab2e48870b..1ec56ecb4976 100644 --- a/explorer/testdata/generic_class/fail_generic_in_pattern.carbon +++ b/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; } } diff --git a/explorer/testdata/generic_class/fail_no_args.carbon b/explorer/testdata/generic_class/fail_no_args.carbon index ad19cd6d9e07..6a8d5aec5a7d 100644 --- a/explorer/testdata/generic_class/fail_no_args.carbon +++ b/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; } diff --git a/explorer/testdata/generic_class/fail_return_type_is_type.carbon b/explorer/testdata/generic_class/fail_return_type_is_type.carbon index 42c93da9eef7..df6518bc7ee3 100644 --- a/explorer/testdata/generic_class/fail_return_type_is_type.carbon +++ b/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}; } diff --git a/explorer/testdata/generic_function/fail_not_type.carbon b/explorer/testdata/generic_function/fail_not_type.carbon index c9d0febe2050..89b3f3f6cb39 100644 --- a/explorer/testdata/generic_function/fail_not_type.carbon +++ b/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 { diff --git a/explorer/testdata/impl/fail_impl_as_parameterized.carbon b/explorer/testdata/impl/fail_impl_as_parameterized.carbon index 90906b675b27..45a20e0d9a39 100644 --- a/explorer/testdata/impl/fail_impl_as_parameterized.carbon +++ b/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 { diff --git a/explorer/testdata/interface/fail_impl_as_not_type.carbon b/explorer/testdata/interface/fail_impl_as_not_type.carbon index cbc6f3e7c259..10fc8563b23b 100644 --- a/explorer/testdata/interface/fail_impl_as_not_type.carbon +++ b/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; } diff --git a/explorer/testdata/interface/fail_impl_not_type.carbon b/explorer/testdata/interface/fail_impl_not_type.carbon index a7c022942a52..27058f55ad49 100644 --- a/explorer/testdata/interface/fail_impl_not_type.carbon +++ b/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; } } diff --git a/explorer/testdata/mixin/fail_mix_as_type_expr.carbon b/explorer/testdata/mixin/fail_mix_as_type_expr.carbon index da41dfb1e085..0629a73fb947 100644 --- a/explorer/testdata/mixin/fail_mix_as_type_expr.carbon +++ b/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; } diff --git a/explorer/testdata/struct/field_convertible_to_type.carbon b/explorer/testdata/struct/field_convertible_to_type.carbon new file mode 100644 index 000000000000..3ea2ebafad7a --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/tuple/fail_nontype_tuple_as_type.carbon b/explorer/testdata/tuple/fail_nontype_tuple_as_type.carbon new file mode 100644 index 000000000000..f1948c24f0a5 --- /dev/null +++ b/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; diff --git a/explorer/testdata/tuple/fail_type_tuple_as_type.carbon b/explorer/testdata/tuple/fail_type_tuple_as_type.carbon new file mode 100644 index 000000000000..188e07114530 --- /dev/null +++ b/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; +}