diff --git a/explorer/ast/ast_rtti.txt b/explorer/ast/ast_rtti.txt index 80c5f73e6de31..83fa3d9a7fafe 100644 --- a/explorer/ast/ast_rtti.txt +++ b/explorer/ast/ast_rtti.txt @@ -54,6 +54,7 @@ abstract class Expression : AstNode; class StructLiteral : Expression; class StructTypeLiteral : Expression; class TypeTypeLiteral : Expression; + class ValueLiteral : Expression; class IdentifierExpression : Expression; class IntrinsicExpression : Expression; class IfExpression : Expression; diff --git a/explorer/ast/declaration.h b/explorer/ast/declaration.h index 52338c872bc0e..df8f8e2f8b574 100644 --- a/explorer/ast/declaration.h +++ b/explorer/ast/declaration.h @@ -295,6 +295,12 @@ class VariableDeclaration : public Declaration { auto has_initializer() const -> bool { return initializer_.has_value(); } + // Can only be called by type-checking, if a conversion was required. + void set_initializer(Nonnull initializer) { + CARBON_CHECK(has_initializer()) << "should not add a new initializer"; + initializer_ = initializer; + } + private: // TODO: split this into a non-optional name and a type, initialized by // a constructor that takes a BindingPattern and handles errors like a diff --git a/explorer/ast/expression.cpp b/explorer/ast/expression.cpp index aff7443e89063..713c7158f1369 100644 --- a/explorer/ast/expression.cpp +++ b/explorer/ast/expression.cpp @@ -200,6 +200,7 @@ void Expression::Print(llvm::raw_ostream& out) const { case ExpressionKind::StringTypeLiteral: case ExpressionKind::TypeTypeLiteral: case ExpressionKind::ContinuationTypeLiteral: + case ExpressionKind::ValueLiteral: PrintID(out); break; } @@ -236,6 +237,10 @@ void Expression::PrintID(llvm::raw_ostream& out) const { case ExpressionKind::ContinuationTypeLiteral: out << "Continuation"; break; + case ExpressionKind::ValueLiteral: + // FIXME: For layering reasons, we can't print out the value from here. + out << "ValueLiteral"; + break; case ExpressionKind::IndexExpression: case ExpressionKind::FieldAccessExpression: case ExpressionKind::CompoundFieldAccessExpression: diff --git a/explorer/ast/expression.h b/explorer/ast/expression.h index 90841c93094b4..3a80102ff1745 100644 --- a/explorer/ast/expression.h +++ b/explorer/ast/expression.h @@ -70,6 +70,12 @@ class Expression : public AstNode { value_category_ = value_category; } + // Determines whether the expression has already been type-checked. Should + // only be used by type-checking. + auto is_type_checked() -> bool { + return static_type_.has_value() && value_category_.has_value(); + } + protected: // Constructs an Expression representing syntax at the given line number. // `kind` must be the enumerator corresponding to the most-derived type being @@ -238,6 +244,9 @@ class CompoundFieldAccessExpression : public Expression { impl_ = impl; } + // Can only be called by type-checking, if a conversion was required. + void set_object(Nonnull object) { object_ = object; } + private: Nonnull object_; Nonnull path_; @@ -467,6 +476,9 @@ class CallExpression : public Expression { deduced_args_ = deduced_args; } + // Can only be called by type-checking, if a conversion was required. + void set_argument(Nonnull argument) { argument_ = argument; } + private: Nonnull function_; Nonnull argument_; @@ -537,6 +549,29 @@ class TypeTypeLiteral : public Expression { } }; +// A literal value. This is used in desugaring, and can't be expressed in +// source syntax. +class ValueLiteral : public Expression { + 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) { + set_static_type(type); + set_value_category(value_category); + } + + 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 { public: enum class Intrinsic { @@ -595,6 +630,9 @@ class IfExpression : public Expression { } auto else_expression() -> Expression& { return *else_expression_; } + // Can only be called by type-checking, if a conversion was required. + void set_condition(Nonnull condition) { condition_ = condition; } + private: Nonnull condition_; Nonnull then_expression_; diff --git a/explorer/ast/statement.h b/explorer/ast/statement.h index 7e960aae9e367..95370fb46864e 100644 --- a/explorer/ast/statement.h +++ b/explorer/ast/statement.h @@ -101,6 +101,9 @@ class Assign : public Statement { auto rhs() const -> const Expression& { return *rhs_; } auto rhs() -> Expression& { return *rhs_; } + // Can only be called by type-checking, if a conversion was required. + void set_rhs(Nonnull rhs) { rhs_ = rhs; } + private: Nonnull lhs_; Nonnull rhs_; @@ -125,6 +128,9 @@ class VariableDefinition : public Statement { auto init() -> Expression& { return *init_; } auto value_category() const -> ValueCategory { return value_category_; } + // Can only be called by type-checking, if a conversion was required. + void set_init(Nonnull init) { init_ = init; } + private: Nonnull pattern_; Nonnull init_; @@ -153,6 +159,9 @@ class If : public Statement { } auto else_block() -> std::optional> { return else_block_; } + // Can only be called by type-checking, if a conversion was required. + void set_condition(Nonnull condition) { condition_ = condition; } + private: Nonnull condition_; Nonnull then_block_; @@ -186,6 +195,11 @@ class Return : public Statement { auto function() const -> const FunctionDeclaration& { return **function_; } auto function() -> FunctionDeclaration& { return **function_; } + // Can only be called by type-checking, if a conversion was required. + void set_expression(Nonnull expression) { + expression_ = expression; + } + // Can only be called once, by ResolveControlFlow. void set_function(Nonnull function) { CARBON_CHECK(!function_.has_value()); @@ -215,6 +229,9 @@ class While : public Statement { auto body() const -> const Block& { return *body_; } auto body() -> Block& { return *body_; } + // Can only be called by type-checking, if a conversion was required. + void set_condition(Nonnull condition) { condition_ = condition; } + private: Nonnull condition_; Nonnull body_; @@ -306,6 +323,11 @@ class Match : public Statement { auto clauses() const -> llvm::ArrayRef { return clauses_; } auto clauses() -> llvm::MutableArrayRef { return clauses_; } + // Can only be called by type-checking, if a conversion was required. + void set_expression(Nonnull expression) { + expression_ = expression; + } + private: Nonnull expression_; std::vector clauses_; @@ -376,6 +398,9 @@ class Run : public Statement { auto argument() const -> const Expression& { return *argument_; } auto argument() -> Expression& { return *argument_; } + // Can only be called by type-checking, if a conversion was required. + void set_argument(Nonnull argument) { argument_ = argument; } + private: Nonnull argument_; }; diff --git a/explorer/data/prelude.carbon b/explorer/data/prelude.carbon index 3244c7d7d5e57..27c1c0130bf42 100644 --- a/explorer/data/prelude.carbon +++ b/explorer/data/prelude.carbon @@ -4,6 +4,36 @@ package Carbon api; +// Implicitly convert `Self` to `T`. +interface ImplicitAs(T:! Type) { + fn Convert[me: Self]() -> T; +} + +// TODO: Simplify this once we have variadics. +// TODO: Should these be final? +impl forall [U1:! Type, T1:! ImplicitAs(U1)] + (T1,) as ImplicitAs((U1,)) { + fn Convert[me: Self]() -> (U1,) { + let (v1: T1,) = me; + return (v1.Convert(),); + } +} +impl forall [U1:! Type, U2:! Type, T1:! ImplicitAs(U1), T2:! ImplicitAs(U2)] + (T1, T2) as ImplicitAs((U1, U2)) { + fn Convert[me: Self]() -> (U1, U2) { + let (v1: T1, v2: T2) = me; + return (v1.Convert(), v2.Convert()); + } +} +impl forall [U1:! Type, U2:! Type, U3:! Type, + T1:! ImplicitAs(U1), T2:! ImplicitAs(U2), T3:! ImplicitAs(U3)] + (T1, T2, T3) as ImplicitAs((U1, U2, U3)) { + fn Convert[me: Self]() -> (U1, U2, U3) { + let (v1: T1, v2: T2, v3: T3) = me; + return (v1.Convert(), v2.Convert(), v3.Convert()); + } +} + // Note that Print is experimental, and not part of an accepted proposal, but // is included here for printing state in tests. fn Print(format_str: String) { diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index 9953b0c1dc63f..4705c787c945d 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/explorer/fuzzing/ast_to_proto.cpp @@ -80,8 +80,9 @@ static auto ExpressionToProto(const Expression& expression) -> Fuzzing::Expression { Fuzzing::Expression expression_proto; switch (expression.kind()) { - case ExpressionKind::InstantiateImpl: { - // UNDER CONSTRUCTION + case ExpressionKind::InstantiateImpl: + case ExpressionKind::ValueLiteral: { + // These do not correspond to source syntax. break; } case ExpressionKind::CallExpression: { diff --git a/explorer/interpreter/BUILD b/explorer/interpreter/BUILD index 7fca73777d08e..6a7d2d738b24a 100644 --- a/explorer/interpreter/BUILD +++ b/explorer/interpreter/BUILD @@ -192,10 +192,12 @@ cc_library( cc_library( name = "type_checker", srcs = [ + "builtins.cpp", "impl_scope.cpp", "type_checker.cpp", ], hdrs = [ + "builtins.h", "impl_scope.h", "type_checker.h", ], @@ -203,6 +205,7 @@ cc_library( ":action_and_value", ":dictionary", ":interpreter", + "//common:error", "//common:ostream", "//explorer/ast", "//explorer/ast:declaration", @@ -211,6 +214,7 @@ cc_library( "//explorer/common:arena", "//explorer/common:error_builders", "//explorer/common:nonnull", + "//explorer/common:source_location", "@llvm-project//llvm:Support", ], ) diff --git a/explorer/interpreter/builtins.cpp b/explorer/interpreter/builtins.cpp new file mode 100644 index 0000000000000..034e2a6a48d75 --- /dev/null +++ b/explorer/interpreter/builtins.cpp @@ -0,0 +1,32 @@ +// 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 + +#include "explorer/interpreter/builtins.h" + +#include "explorer/common/error_builders.h" + +using llvm::dyn_cast; + +namespace Carbon { + +void Builtins::Register(Nonnull decl) { + if (auto* interface = dyn_cast(decl)) { + if (interface->name() == GetName(Builtin::ImplicitAs)) { + builtins_[static_cast(Builtin::ImplicitAs)] = interface; + } + } +} + +auto Builtins::Get(SourceLocation source_loc, Builtin builtin) const + -> ErrorOr> { + std::optional result = + builtins_[static_cast(builtin)]; + if (!result.has_value()) { + return CompilationError(source_loc) + << "missing declaration for builtin `" << GetName(builtin) << "`"; + } + return result.value(); +} + +} // namespace Carbon diff --git a/explorer/interpreter/builtins.h b/explorer/interpreter/builtins.h new file mode 100644 index 0000000000000..a837fdc4f879d --- /dev/null +++ b/explorer/interpreter/builtins.h @@ -0,0 +1,48 @@ +// 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 + +#ifndef CARBON_EXPLORER_INTERPRETER_BUILTINS_H_ +#define CARBON_EXPLORER_INTERPRETER_BUILTINS_H_ + +#include + +#include "common/error.h" +#include "explorer/ast/declaration.h" +#include "explorer/ast/expression.h" +#include "explorer/common/nonnull.h" +#include "explorer/common/source_location.h" +#include "explorer/interpreter/value.h" + +namespace Carbon { + +class Builtins { + public: + explicit Builtins() {} + + enum class Builtin { ImplicitAs, Last = ImplicitAs }; + // FIXME: In C++20, replace with `using enum Builtin;`. + static constexpr Builtin ImplicitAs = Builtin::ImplicitAs; + + // Register a declaration that might be a builtin. + void Register(Nonnull decl); + + // Get a registered builtin. + auto Get(SourceLocation source_loc, Builtin builtin) const + -> ErrorOr>; + + // Get the source name of a builtin. + static constexpr auto GetName(Builtin builtin) -> const char* { + return BuiltinNames[static_cast(builtin)]; + } + + private: + static constexpr int NumBuiltins = static_cast(Builtin::Last) + 1; + static constexpr const char* BuiltinNames[NumBuiltins] = {"ImplicitAs"}; + + std::optional> builtins_[NumBuiltins] = {}; +}; + +} // namespace Carbon + +#endif // CARBON_EXPLORER_INTERPRETER_BUILTINS_H_ diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index e8102be80bf12..ede59242d9a4c 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -401,6 +401,7 @@ auto Interpreter::StepLvalue() -> ErrorOr { case ExpressionKind::ContinuationTypeLiteral: case ExpressionKind::StringLiteral: case ExpressionKind::StringTypeLiteral: + case ExpressionKind::ValueLiteral: case ExpressionKind::IntrinsicExpression: case ExpressionKind::IfExpression: case ExpressionKind::ArrayTypeLiteral: @@ -1042,6 +1043,10 @@ auto Interpreter::StepExp() -> ErrorOr { CARBON_CHECK(act.pos() == 0); return todo_.FinishAction(arena_->New()); } + case ExpressionKind::ValueLiteral: { + CARBON_CHECK(act.pos() == 0); + return todo_.FinishAction(&cast(exp).value()); + } case ExpressionKind::IfExpression: { const auto& if_expr = cast(exp); if (act.pos() == 0) { diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index 818a12e5984fc..a08b997c8962d 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/explorer/interpreter/resolve_names.cpp @@ -191,6 +191,7 @@ static auto ResolveNames(Expression& expression, case ExpressionKind::StringLiteral: case ExpressionKind::StringTypeLiteral: case ExpressionKind::TypeTypeLiteral: + case ExpressionKind::ValueLiteral: break; case ExpressionKind::InstantiateImpl: // created after name resolution case ExpressionKind::UnimplementedExpression: diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index d200036a3f51d..271e47447a257 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -23,6 +23,7 @@ using llvm::cast; using llvm::dyn_cast; +using llvm::dyn_cast_or_null; using llvm::isa; namespace Carbon { @@ -267,7 +268,12 @@ auto TypeChecker::FieldTypesImplicitlyConvertible( FindField(destination_fields, source_field.name); if (!destination_field.has_value() || !IsImplicitlyConvertible(source_field.value, - destination_field.value().value)) { + destination_field.value().value, + // FIXME: We don't have a way to perform + // user-defined conversions of a struct field + // yet, because we can't write a suitable impl + // for ImplicitAs. + std::nullopt)) { return false; } } @@ -295,8 +301,11 @@ auto TypeChecker::FieldTypes(const NominalClassType& class_type) const } auto TypeChecker::IsImplicitlyConvertible( - Nonnull source, Nonnull destination) const - -> bool { + Nonnull source, Nonnull destination, + std::optional> impl_scope) const -> bool { + // Check for an exact match or for an implicit conversion. + // FIXME: `impl`s of `ImplicitAs` should be provided to cover these + // conversions. CARBON_CHECK(IsConcreteType(source)); CARBON_CHECK(IsConcreteType(destination)); if (TypeEqual(source, destination)) { @@ -306,16 +315,23 @@ auto TypeChecker::IsImplicitlyConvertible( case Value::Kind::StructType: switch (destination->kind()) { case Value::Kind::StructType: - return FieldTypesImplicitlyConvertible( - cast(*source).fields(), - cast(*destination).fields()); + if (FieldTypesImplicitlyConvertible( + cast(*source).fields(), + cast(*destination).fields())) { + return true; + } + break; case Value::Kind::NominalClassType: - return FieldTypesImplicitlyConvertible( - cast(*source).fields(), - FieldTypes(cast(*destination))); + if (FieldTypesImplicitlyConvertible( + cast(*source).fields(), + FieldTypes(cast(*destination)))) { + return true; + } + break; default: - return false; + break; } + break; case Value::Kind::TupleValue: { const auto& source_tuple = cast(*source); switch (destination->kind()) { @@ -323,58 +339,188 @@ auto TypeChecker::IsImplicitlyConvertible( const auto& destination_tuple = cast(*destination); if (source_tuple.elements().size() != destination_tuple.elements().size()) { - return false; + break; } + bool all_ok = true; for (size_t i = 0; i < source_tuple.elements().size(); ++i) { if (!IsImplicitlyConvertible(source_tuple.elements()[i], - destination_tuple.elements()[i])) { - return false; + destination_tuple.elements()[i], + impl_scope)) { + all_ok = false; + break; } } - return true; + if (all_ok) { + return true; + } + break; } case Value::Kind::StaticArrayType: { const auto& destination_array = cast(*destination); if (destination_array.size() != source_tuple.elements().size()) { - return false; + break; } + bool all_ok = true; for (Nonnull source_element : source_tuple.elements()) { if (!IsImplicitlyConvertible(source_element, - &destination_array.element_type())) { - return false; + &destination_array.element_type(), + impl_scope)) { + all_ok = false; + break; } } - return true; + if (all_ok) { + return true; + } + break; } case Value::Kind::TypeType: { + bool all_types = true; for (Nonnull source_element : source_tuple.elements()) { - if (!IsImplicitlyConvertible(source_element, destination)) { - return false; + if (!IsImplicitlyConvertible(source_element, destination, + impl_scope)) { + all_types = false; + break; } } - return true; + if (all_types) { + return true; + } + break; } default: - return false; + break; } + break; } case Value::Kind::TypeType: - return destination->kind() == Value::Kind::InterfaceType; + // FIXME: This seems suspicious. Shouldn't this require that the type + // implements the interface? + if (destination->kind() == Value::Kind::InterfaceType) { + return true; + } + break; case Value::Kind::InterfaceType: - return destination->kind() == Value::Kind::TypeType; case Value::Kind::TypeOfClassType: - return destination->kind() == Value::Kind::TypeType; + case Value::Kind::TypeOfChoiceType: + // FIXME: These types should presumably also convert to interface types. + if (destination->kind() == Value::Kind::TypeType) { + return true; + } + break; default: - return false; + break; + } + + // If we weren't given an impl scope, only look for builtin conversions. + if (!impl_scope.has_value()) { + return false; + } + + // We didn't find a builtin implicit conversion. Try a user-defined one. + // The source location doesn't matter, we're discarding the diagnostics. + SourceLocation source_loc("", 0); + ErrorOr> iface_type = GetBuiltinInterfaceType( + source_loc, BuiltinInterfaceName{Builtins::ImplicitAs, destination}); + return iface_type.ok() && + (*impl_scope)->Resolve(*iface_type, source, source_loc, *this).ok(); +} + +auto TypeChecker::ImplicitlyConvert(const std::string& context, + const ImplScope& impl_scope, + Nonnull source, + Nonnull destination) + -> ErrorOr> { + // FIXME: 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. + // FIXME: 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->static_type(), destination, + std::nullopt)) { + return source; + } + ErrorOr> converted = BuildBuiltinMethodCall( + impl_scope, source, + BuiltinInterfaceName{Builtins::ImplicitAs, destination}, + BuiltinMethodCall{"Convert"}); + if (!converted.ok()) { + // We couldn't find a matching `impl`. + return CompilationError(source->source_loc()) + << "type error in " << context << ": " + << "'" << source->static_type() + << "' is not implicitly convertible to '" << *destination << "'"; + } + return *converted; +} + +auto TypeChecker::GetBuiltinInterfaceType(SourceLocation source_loc, + BuiltinInterfaceName interface) const + -> ErrorOr> { + auto bad_builtin = [&]() -> Error { + return CompilationError(source_loc) + << "unsupported declaration for builtin `" + << Builtins::GetName(interface.builtin) << "`"; + }; + + // Find the builtin interface declaration. + CARBON_ASSIGN_OR_RETURN(Nonnull builtin_decl, + builtins_.Get(source_loc, interface.builtin)); + auto* iface_decl = dyn_cast(builtin_decl); + if (!iface_decl || !iface_decl->constant_value()) { + return bad_builtin(); + } + + // Match the interface arguments up with the parameters and build the + // interface type. + bool has_parameters = iface_decl->params().has_value(); + bool has_arguments = !interface.arguments.empty(); + if (has_parameters != has_arguments) { + return bad_builtin(); } + BindingMap bindings; + if (has_arguments) { + TupleValue args(interface.arguments); + if (!PatternMatch(&iface_decl->params().value()->value(), &args, source_loc, + std::nullopt, bindings, trace_stream_)) { + return bad_builtin(); + } + } + return arena_->New(iface_decl, bindings); } -auto TypeChecker::ExpectType(SourceLocation source_loc, - const std::string& context, - Nonnull expected, - Nonnull actual) const +auto TypeChecker::BuildBuiltinMethodCall(const ImplScope& impl_scope, + Nonnull source, + BuiltinInterfaceName interface, + BuiltinMethodCall method) + -> ErrorOr> { + const SourceLocation source_loc = source->source_loc(); + CARBON_ASSIGN_OR_RETURN(Nonnull iface_type, + GetBuiltinInterfaceType(source_loc, interface)); + + // Build an expression to perform the call `source.(interface.method)(args)`. + Nonnull iface_expr = arena_->New( + source_loc, iface_type, arena_->New(iface_type), + ValueCategory::Let); + Nonnull iface_member = + arena_->New(source_loc, iface_expr, method.name); + Nonnull method_access = + arena_->New(source_loc, source, + iface_member); + Nonnull call_args = + arena_->New(source_loc, method.arguments); + Nonnull call = + arena_->New(source_loc, method_access, call_args); + CARBON_RETURN_IF_ERROR(TypeCheckExp(call, impl_scope)); + return {call}; +} + +auto TypeChecker::ExpectType( + SourceLocation source_loc, const std::string& context, + Nonnull expected, Nonnull actual, + std::optional> impl_scope) const -> ErrorOr { - if (!IsImplicitlyConvertible(actual, expected)) { + if (!IsImplicitlyConvertible(actual, expected, impl_scope)) { return CompilationError(source_loc) << "type error in " << context << ": " << "'" << *actual << "' is not implicitly convertible to '" @@ -388,7 +534,8 @@ auto TypeChecker::ArgumentDeduction( SourceLocation source_loc, const std::string& context, llvm::ArrayRef> bindings_to_deduce, BindingMap& deduced, Nonnull param, Nonnull arg, - bool allow_implicit_conversion) const -> ErrorOr { + bool allow_implicit_conversion, const ImplScope& impl_scope) const + -> ErrorOr { if (trace_stream_) { **trace_stream_ << "deducing " << *param << " from " << *arg << "\n"; } @@ -407,7 +554,8 @@ auto TypeChecker::ArgumentDeduction( } const Value* subst_param_type = Substitute(deduced, param); return allow_implicit_conversion - ? ExpectType(source_loc, context, subst_param_type, arg) + ? ExpectType(source_loc, context, subst_param_type, arg, + &impl_scope) : ExpectExactType(source_loc, context, subst_param_type, arg); }; @@ -443,7 +591,7 @@ auto TypeChecker::ArgumentDeduction( CARBON_RETURN_IF_ERROR( ArgumentDeduction(source_loc, context, bindings_to_deduce, deduced, param_tup.elements()[i], arg_tup.elements()[i], - allow_implicit_conversion)); + allow_implicit_conversion, impl_scope)); } return Success(); } @@ -488,7 +636,7 @@ auto TypeChecker::ArgumentDeduction( } CARBON_RETURN_IF_ERROR(ArgumentDeduction( source_loc, context, bindings_to_deduce, deduced, param_field.value, - arg_field.value, allow_implicit_conversion)); + arg_field.value, allow_implicit_conversion, impl_scope)); } if (param_struct.fields().size() != arg_struct.fields().size()) { CARBON_CHECK(allow_implicit_conversion) @@ -513,11 +661,11 @@ auto TypeChecker::ArgumentDeduction( CARBON_RETURN_IF_ERROR( ArgumentDeduction(source_loc, context, bindings_to_deduce, deduced, ¶m_fn.parameters(), &arg_fn.parameters(), - /*allow_implicit_conversion=*/false)); + /*allow_implicit_conversion=*/false, impl_scope)); CARBON_RETURN_IF_ERROR( ArgumentDeduction(source_loc, context, bindings_to_deduce, deduced, ¶m_fn.return_type(), &arg_fn.return_type(), - /*allow_implicit_conversion=*/false)); + /*allow_implicit_conversion=*/false, impl_scope)); return Success(); } case Value::Kind::PointerType: { @@ -527,7 +675,7 @@ auto TypeChecker::ArgumentDeduction( return ArgumentDeduction(source_loc, context, bindings_to_deduce, deduced, &cast(*param).type(), &cast(*arg).type(), - /*allow_implicit_conversion=*/false); + /*allow_implicit_conversion=*/false, impl_scope); } // Nothing to do in the case for `auto`. case Value::Kind::AutoType: { @@ -549,7 +697,7 @@ auto TypeChecker::ArgumentDeduction( CARBON_RETURN_IF_ERROR( ArgumentDeduction(source_loc, context, bindings_to_deduce, deduced, param_ty, arg_class_type.type_args().at(ty), - /*allow_implicit_conversion=*/false)); + /*allow_implicit_conversion=*/false, impl_scope)); } return Success(); } @@ -567,7 +715,7 @@ auto TypeChecker::ArgumentDeduction( CARBON_RETURN_IF_ERROR( ArgumentDeduction(source_loc, context, bindings_to_deduce, deduced, param_ty, arg_iface_type.args().at(ty), - /*allow_implicit_conversion=*/false)); + /*allow_implicit_conversion=*/false, impl_scope)); } return Success(); } @@ -697,7 +845,6 @@ auto TypeChecker::Substitute( case Value::Kind::TypeOfParameterizedEntityName: case Value::Kind::TypeOfMemberName: return type; - // The rest of these cases should never happen. case Value::Kind::Witness: case Value::Kind::ParameterizedEntityName: case Value::Kind::MemberName: @@ -714,7 +861,10 @@ auto TypeChecker::Substitute( case Value::Kind::AlternativeConstructorValue: case Value::Kind::ContinuationValue: case Value::Kind::StringValue: - CARBON_FATAL() << "In Substitute: expected type, not value " << *type; + // This can happen when substituting into the arguments of a class or + // interface. + // TODO: Implement substitution for these cases. + return type; } } @@ -735,7 +885,7 @@ auto TypeChecker::MatchImpl(const InterfaceType& iface, if (ErrorOr e = ArgumentDeduction( source_loc, "match", impl.deduced, deduced_args, impl.type, impl_type, - /*allow_implicit_conversion=*/false); + /*allow_implicit_conversion=*/false, impl_scope); !e.ok()) { if (trace_stream_) { **trace_stream_ << "type does not match: " << e.error() << "\n"; @@ -745,7 +895,7 @@ auto TypeChecker::MatchImpl(const InterfaceType& iface, if (ErrorOr e = ArgumentDeduction( source_loc, "match", impl.deduced, deduced_args, impl.interface, - &iface, /*allow_implicit_conversion=*/false); + &iface, /*allow_implicit_conversion=*/false, impl_scope); !e.ok()) { if (trace_stream_) { **trace_stream_ << "interface does not match: " << e.error() << "\n"; @@ -828,7 +978,7 @@ auto TypeChecker::DeduceCallBindings( CARBON_RETURN_IF_ERROR( ArgumentDeduction(arg->source_loc(), "call", deduced_bindings, generic_bindings, param, &arg->static_type(), - /*allow_implicit_conversion=*/true)); + /*allow_implicit_conversion=*/true, impl_scope)); // If the parameter is a `:!` binding, evaluate and collect its // value for use in later parameters and in the function body. if (!generic_params.empty() && generic_params.front().index == i) { @@ -868,6 +1018,13 @@ auto TypeChecker::DeduceCallBindings( impl_bindings, impl_scope, call.source_loc(), generic_bindings, impls)); call.set_impls(impls); + // Convert the arguments to the parameter type. + Nonnull param_type = Substitute(generic_bindings, params_type); + CARBON_ASSIGN_OR_RETURN( + Nonnull converted_argument, + ImplicitlyConvert("call", impl_scope, &call.argument(), param_type)); + call.set_argument(converted_argument); + return Success(); } @@ -880,11 +1037,17 @@ auto TypeChecker::TypeCheckExp(Nonnull e, PrintConstants(**trace_stream_); **trace_stream_ << "\n"; } - switch (e->kind()) { - case ExpressionKind::InstantiateImpl: { - CARBON_FATAL() - << "instantiate impl nodes are generated during type checking"; + if (e->is_type_checked()) { + if (trace_stream_) { + **trace_stream_ << "expression has already been type-checked\n"; } + return Success(); + } + switch (e->kind()) { + case ExpressionKind::InstantiateImpl: + case ExpressionKind::ValueLiteral: + CARBON_FATAL() << "attempting to type check node " << *e + << " generated during type checking"; case ExpressionKind::IndexExpression: { auto& index = cast(*e); CARBON_RETURN_IF_ERROR(TypeCheckExp(&index.aggregate(), impl_scope)); @@ -1231,9 +1394,11 @@ auto TypeChecker::TypeCheckExp(Nonnull e, } else { // This is `value.(member_name)`, where `member_name` specifies a type. // `value` is implicitly converted to that type. - CARBON_RETURN_IF_ERROR(ExpectType(e->source_loc(), - "compound member access", *base_type, - &access.object().static_type())); + CARBON_ASSIGN_OR_RETURN( + Nonnull converted_object, + ImplicitlyConvert("compound member access", impl_scope, + &access.object(), *base_type)); + access.set_object(converted_object); } // Perform impl selection if necessary. @@ -1525,10 +1690,10 @@ auto TypeChecker::TypeCheckExp(Nonnull e, return CompilationError(e->source_loc()) << "__intrinsic_print takes 1 argument"; } - CARBON_RETURN_IF_ERROR( - ExpectType(e->source_loc(), "__intrinsic_print argument", - arena_->New(), - &intrinsic_exp.args().fields()[0]->static_type())); + CARBON_RETURN_IF_ERROR(ExpectExactType( + e->source_loc(), "__intrinsic_print argument", + arena_->New(), + &intrinsic_exp.args().fields()[0]->static_type())); e->set_static_type(TupleValue::Empty()); e->set_value_category(ValueCategory::Let); return Success(); @@ -1545,9 +1710,11 @@ auto TypeChecker::TypeCheckExp(Nonnull e, case ExpressionKind::IfExpression: { auto& if_expr = cast(*e); CARBON_RETURN_IF_ERROR(TypeCheckExp(&if_expr.condition(), impl_scope)); - CARBON_RETURN_IF_ERROR(ExpectType( - if_expr.source_loc(), "condition of `if`", arena_->New(), - &if_expr.condition().static_type())); + CARBON_ASSIGN_OR_RETURN( + Nonnull converted_condition, + ImplicitlyConvert("condition of `if`", impl_scope, + &if_expr.condition(), arena_->New())); + if_expr.set_condition(converted_condition); // TODO: Compute the common type and convert both operands to it. CARBON_RETURN_IF_ERROR( @@ -1684,8 +1851,8 @@ auto TypeChecker::TypeCheckPattern( CARBON_RETURN_IF_ERROR(ExpectIsType(binding.source_loc(), type)); if (expected) { if (IsConcreteType(type)) { - CARBON_RETURN_IF_ERROR( - ExpectType(p->source_loc(), "name binding", type, *expected)); + CARBON_RETURN_IF_ERROR(ExpectType(p->source_loc(), "name binding", + type, *expected, &impl_scope)); } else { BindingMap generic_args; if (!PatternMatch(type, *expected, binding.type().source_loc(), @@ -1779,7 +1946,7 @@ auto TypeChecker::TypeCheckPattern( if (expected) { CARBON_RETURN_IF_ERROR(ExpectType(alternative.source_loc(), "alternative pattern", &choice_type, - *expected)); + *expected, &impl_scope)); } std::optional> parameter_types = choice_type.FindAlternative(alternative.alternative_name()); @@ -1833,23 +2000,46 @@ auto TypeChecker::TypeCheckStmt(Nonnull s, auto& match = cast(*s); CARBON_RETURN_IF_ERROR(TypeCheckExp(&match.expression(), impl_scope)); std::vector new_clauses; + std::optional> expected_type; for (auto& clause : match.clauses()) { ImplScope clause_scope; clause_scope.AddParent(&impl_scope); + // FIXME: Should user-defined conversions be permitted in `match` + // statements? When would we run them? See #1283. CARBON_RETURN_IF_ERROR(TypeCheckPattern( &clause.pattern(), &match.expression().static_type(), clause_scope, ValueCategory::Let)); + if (expected_type.has_value()) { + // FIXME: For now, we require all patterns to have the same type. If + // that's not the same type as the scrutinee, we will convert the + // scrutinee. We might want to instead allow a different conversion + // to be performed for each pattern. + CARBON_RETURN_IF_ERROR(ExpectExactType( + clause.pattern().source_loc(), "`match` pattern type", + expected_type.value(), &clause.pattern().static_type())); + } else { + expected_type = &clause.pattern().static_type(); + } CARBON_RETURN_IF_ERROR( TypeCheckStmt(&clause.statement(), clause_scope)); } + if (expected_type.has_value()) { + CARBON_ASSIGN_OR_RETURN( + Nonnull converted_expression, + ImplicitlyConvert("`match` expression", impl_scope, + &match.expression(), expected_type.value())); + match.set_expression(converted_expression); + } return Success(); } case StatementKind::While: { auto& while_stmt = cast(*s); CARBON_RETURN_IF_ERROR(TypeCheckExp(&while_stmt.condition(), impl_scope)); - CARBON_RETURN_IF_ERROR(ExpectType(s->source_loc(), "condition of `while`", - arena_->New(), - &while_stmt.condition().static_type())); + CARBON_ASSIGN_OR_RETURN( + Nonnull converted_condition, + ImplicitlyConvert("condition of `while`", impl_scope, + &while_stmt.condition(), arena_->New())); + while_stmt.set_condition(converted_condition); CARBON_RETURN_IF_ERROR(TypeCheckStmt(&while_stmt.body(), impl_scope)); return Success(); } @@ -1878,19 +2068,26 @@ auto TypeChecker::TypeCheckStmt(Nonnull s, var_scope.AddParent(&impl_scope); CARBON_RETURN_IF_ERROR(TypeCheckPattern(&var.pattern(), &rhs_ty, var_scope, var.value_category())); + CARBON_ASSIGN_OR_RETURN( + Nonnull converted_init, + ImplicitlyConvert("initializer of variable", impl_scope, &var.init(), + &var.pattern().static_type())); + var.set_init(converted_init); return Success(); } case StatementKind::Assign: { auto& assign = cast(*s); CARBON_RETURN_IF_ERROR(TypeCheckExp(&assign.rhs(), impl_scope)); CARBON_RETURN_IF_ERROR(TypeCheckExp(&assign.lhs(), impl_scope)); - CARBON_RETURN_IF_ERROR(ExpectType(s->source_loc(), "assign", - &assign.lhs().static_type(), - &assign.rhs().static_type())); if (assign.lhs().value_category() != ValueCategory::Var) { return CompilationError(assign.source_loc()) << "Cannot assign to rvalue '" << assign.lhs() << "'"; } + CARBON_ASSIGN_OR_RETURN( + Nonnull converted_rhs, + ImplicitlyConvert("assignment", impl_scope, &assign.rhs(), + &assign.lhs().static_type())); + assign.set_rhs(converted_rhs); return Success(); } case StatementKind::ExpressionStatement: { @@ -1901,9 +2098,11 @@ auto TypeChecker::TypeCheckStmt(Nonnull s, case StatementKind::If: { auto& if_stmt = cast(*s); CARBON_RETURN_IF_ERROR(TypeCheckExp(&if_stmt.condition(), impl_scope)); - CARBON_RETURN_IF_ERROR(ExpectType(s->source_loc(), "condition of `if`", - arena_->New(), - &if_stmt.condition().static_type())); + CARBON_ASSIGN_OR_RETURN( + Nonnull converted_condition, + ImplicitlyConvert("condition of `if`", impl_scope, + &if_stmt.condition(), arena_->New())); + if_stmt.set_condition(converted_condition); CARBON_RETURN_IF_ERROR(TypeCheckStmt(&if_stmt.then_block(), impl_scope)); if (if_stmt.else_block()) { CARBON_RETURN_IF_ERROR( @@ -1918,9 +2117,11 @@ auto TypeChecker::TypeCheckStmt(Nonnull s, if (return_term.is_auto()) { return_term.set_static_type(&ret.expression().static_type()); } else { - CARBON_RETURN_IF_ERROR(ExpectType(s->source_loc(), "return", - &return_term.static_type(), - &ret.expression().static_type())); + CARBON_ASSIGN_OR_RETURN( + Nonnull converted_ret_val, + ImplicitlyConvert("return value", impl_scope, &ret.expression(), + &return_term.static_type())); + ret.set_expression(converted_ret_val); } return Success(); } @@ -1933,9 +2134,11 @@ auto TypeChecker::TypeCheckStmt(Nonnull s, case StatementKind::Run: { auto& run = cast(*s); CARBON_RETURN_IF_ERROR(TypeCheckExp(&run.argument(), impl_scope)); - CARBON_RETURN_IF_ERROR(ExpectType(s->source_loc(), "argument of `run`", - arena_->New(), - &run.argument().static_type())); + CARBON_ASSIGN_OR_RETURN( + Nonnull converted_argument, + ImplicitlyConvert("argument of `run`", impl_scope, &run.argument(), + arena_->New())); + run.set_argument(converted_argument); return Success(); } case StatementKind::Await: { @@ -2384,9 +2587,11 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull impl_decl, binding_map[iface_decl.self()] = impl_type_value; Nonnull iface_mem_type = Substitute(binding_map, &m->static_type()); + // FIXME: How should the signature in the implementation be permitted + // to differ from the signature in the interface? CARBON_RETURN_IF_ERROR( - ExpectType((*mem)->source_loc(), "member of implementation", - iface_mem_type, &(*mem)->static_type())); + ExpectExactType((*mem)->source_loc(), "member of implementation", + iface_mem_type, &(*mem)->static_type())); } else { return CompilationError(impl_decl->source_loc()) << "implementation missing " << *mem_name; @@ -2520,6 +2725,9 @@ auto TypeChecker::TypeCheck(AST& ast) -> ErrorOr { } for (Nonnull decl : ast.declarations) { CARBON_RETURN_IF_ERROR(TypeCheckDeclaration(decl, impl_scope)); + // Check to see if this declaration is a builtin. + // FIXME: Only do this when type-checking the prelude. + builtins_.Register(decl); } CARBON_RETURN_IF_ERROR(TypeCheckExp(*ast.main_call, impl_scope)); return Success(); @@ -2553,9 +2761,6 @@ auto TypeChecker::TypeCheckDeclaration(Nonnull d, return Success(); case DeclarationKind::VariableDeclaration: { auto& var = cast(*d); - // Signals a type error if the initializing expression does not have - // the declared type of the variable, otherwise returns this - // declaration with annotated types. if (var.has_initializer()) { CARBON_RETURN_IF_ERROR(TypeCheckExp(&var.initializer(), impl_scope)); } @@ -2567,9 +2772,11 @@ auto TypeChecker::TypeCheckDeclaration(Nonnull d, << "Type of a top-level variable must be an expression."; } if (var.has_initializer()) { - CARBON_RETURN_IF_ERROR( - ExpectType(var.source_loc(), "initializer of variable", - &var.static_type(), &var.initializer().static_type())); + CARBON_ASSIGN_OR_RETURN( + Nonnull converted_initializer, + ImplicitlyConvert("initializer of variable", impl_scope, + &var.initializer(), &var.static_type())); + var.set_initializer(converted_initializer); } return Success(); } diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index 87e2d33892790..e96493de08830 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -12,6 +12,7 @@ #include "explorer/ast/expression.h" #include "explorer/ast/statement.h" #include "explorer/common/nonnull.h" +#include "explorer/interpreter/builtins.h" #include "explorer/interpreter/dictionary.h" #include "explorer/interpreter/impl_scope.h" #include "explorer/interpreter/interpreter.h" @@ -38,12 +39,15 @@ class TypeChecker { // parameterized type. // The `deduced` parameter is an accumulator, that is, it holds the // results so-far. + // `allow_implicit_conversion` specifies whether implicit conversions are + // permitted from the argument to the parameter type. If so, an `impl_scope` + // must be provided. auto ArgumentDeduction( SourceLocation source_loc, const std::string& context, llvm::ArrayRef> bindings_to_deduce, BindingMap& deduced, Nonnull param, - Nonnull arg, bool allow_implicit_conversion) const - -> ErrorOr; + Nonnull arg, bool allow_implicit_conversion, + const ImplScope& impl_scope) const -> ErrorOr; // If `impl` can be an implementation of interface `iface` for the // given `type`, then return an expression that will produce the witness @@ -92,8 +96,8 @@ class TypeChecker { // Equivalent to TypeCheckExp, but operates on the AST rooted at `p`. // // `expected` is the type that this pattern is expected to have, if the - // surrounding context gives us that information. Otherwise, it is - // nullopt. + // surrounding context gives us that information. Otherwise, it is nullopt. + // Implicit conversions from `expected` to the pattern's type are permitted. // // `impl_scope` is extended with all impls implied by the pattern. auto TypeCheckPattern(Nonnull p, @@ -244,14 +248,56 @@ class TypeChecker { // Returns true if *source is implicitly convertible to *destination. *source // and *destination must be concrete types. - auto IsImplicitlyConvertible(Nonnull source, - Nonnull destination) const -> bool; + auto IsImplicitlyConvertible( + Nonnull source, Nonnull destination, + std::optional> impl_scope) const -> bool; + + // Attempt to implicitly convert type-checked expression `source` to the type + // `destination`. + auto ImplicitlyConvert(const std::string& context, + const ImplScope& impl_scope, + Nonnull source, + Nonnull destination) + -> ErrorOr>; // Check whether `actual` is implicitly convertible to `expected` // and halt with a fatal compilation error if it is not. + // + // If `impl_scope` is `std::nullopt`, only built-in conversions are + // considered. + // FIXME: Remove this behavior. + // + // FIXME: Does not actually perform the conversion if a user-defined + // conversion is needed. Should be used very rarely for that reason. auto ExpectType(SourceLocation source_loc, const std::string& context, - Nonnull expected, - Nonnull actual) const -> ErrorOr; + Nonnull expected, Nonnull actual, + std::optional> impl_scope) const + -> ErrorOr; + + // The name of a builtin interface, with any arguments. + struct BuiltinInterfaceName { + Builtins::Builtin builtin; + llvm::ArrayRef> arguments = {}; + }; + // The name of a method on a builtin interface, with any arguments. + struct BuiltinMethodCall { + const std::string& name; + llvm::ArrayRef> arguments = {}; + }; + + // Form a builtin method call. Ensures that the type of `source` implements + // the interface `interface`, which should be defined in the prelude, and + // forms a call to the method `method` on that interface. + auto BuildBuiltinMethodCall(const ImplScope& impl_scope, + Nonnull source, + BuiltinInterfaceName interface, + BuiltinMethodCall method) + -> ErrorOr>; + + // Get a type for a builtin interface. + auto GetBuiltinInterfaceType(SourceLocation source_loc, + BuiltinInterfaceName interface) const + -> ErrorOr>; // Construct a type that is the same as `type` except that occurrences // of type variables (aka. `GenericBinding`) are replaced by their @@ -279,6 +325,7 @@ class TypeChecker { Nonnull arena_; std::set constants_; + Builtins builtins_; std::optional> trace_stream_; }; diff --git a/explorer/testdata/assign/convert_rhs.carbon b/explorer/testdata/assign/convert_rhs.carbon new file mode 100644 index 0000000000000..2bdd5b5ef9f99 --- /dev/null +++ b/explorer/testdata/assign/convert_rhs.carbon @@ -0,0 +1,25 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 1 + +package ExplorerTest api; + +class A { + var n: i32; +} +impl i32 as ImplicitAs(A) { + fn Convert[me: Self]() -> A { return {.n = me }; } +} + +fn Main() -> i32 { + var a: A = {.n = 0}; + a = 1; + return a.n; +} diff --git a/explorer/testdata/assignment_copy/destruct_original.carbon b/explorer/testdata/assign/destruct_original.carbon similarity index 100% rename from explorer/testdata/assignment_copy/destruct_original.carbon rename to explorer/testdata/assign/destruct_original.carbon diff --git a/explorer/testdata/assignment_copy/reassign_original.carbon b/explorer/testdata/assign/reassign_original.carbon similarity index 100% rename from explorer/testdata/assignment_copy/reassign_original.carbon rename to explorer/testdata/assign/reassign_original.carbon diff --git a/explorer/testdata/basic_syntax/choice.carbon b/explorer/testdata/basic_syntax/choice.carbon index 0a0a5ab8e4843..d2e2b4ca77bf8 100644 --- a/explorer/testdata/basic_syntax/choice.carbon +++ b/explorer/testdata/basic_syntax/choice.carbon @@ -22,7 +22,7 @@ fn Main() -> i32 { var y: Ints = Ints.One(42); var n: i32 = 0; match (y) { - case Ints.None => + case Ints.None() => n = n + 2; case Ints.One(x: auto) => n = x + 1 - 42; diff --git a/explorer/testdata/basic_syntax/fail_choice_no_parens.carbon b/explorer/testdata/basic_syntax/fail_choice_no_parens.carbon new file mode 100644 index 0000000000000..81cea33cbcb80 --- /dev/null +++ b/explorer/testdata/basic_syntax/fail_choice_no_parens.carbon @@ -0,0 +1,27 @@ +// 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 +// +// RUN: %{not} %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +choice Ints { + None, + One(i32), + Two(i32, i32) +} + +fn Main() -> i32 { + match (Ints.None()) { + case Ints.None => return 0; + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_choice_no_parens.carbon:[[@LINE+3]]: type error in `match` pattern type + // CHECK: expected: fn () -> choice Ints + // CHECK: actual: choice Ints + default => return 1; + } +} diff --git a/explorer/testdata/experimental_continuation/convert_run.carbon b/explorer/testdata/experimental_continuation/convert_run.carbon new file mode 100644 index 0000000000000..c3a08f2c9fec2 --- /dev/null +++ b/explorer/testdata/experimental_continuation/convert_run.carbon @@ -0,0 +1,33 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 3 + +package ExplorerTest api; + +class Wrap(T:! Type) { + var v: T; +} +fn MakeWrap[T:! Type](x: T) -> Wrap(T) { return {.v = x}; } + +impl forall [T:! Type] Wrap(T) as ImplicitAs(T) { + fn Convert[me: Self]() -> T { + return me.v; + } +} + +fn Main() -> i32 { + var n: i32 = 1; + __continuation k { + n = n + 1; + } + n = 2; + __run MakeWrap(k); + return n; +} diff --git a/explorer/testdata/function/convert_args.carbon b/explorer/testdata/function/convert_args.carbon new file mode 100644 index 0000000000000..9b094843edfbd --- /dev/null +++ b/explorer/testdata/function/convert_args.carbon @@ -0,0 +1,42 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 123 + +package ExplorerTest api; + +class One { + impl One as ImplicitAs(i32) { + fn Convert[me: Self]() -> i32 { return 1; } + } +} + +class Two { + impl Two as ImplicitAs(i32) { + fn Convert[me: Self]() -> i32 { return 2; } + } +} + +class N { + impl N as ImplicitAs(i32) { + fn Convert[me: Self]() -> i32 { return me.n; } + } + var n: i32; +} + +fn Get(a: i32, b: i32, c: i32) -> i32 { + return 100 * a + 10 * b + c; +} + +fn Main() -> i32 { + var i: One = {}; + var ii: Two = {}; + var iii: N = {.n = 3}; + return Get(i, ii, iii); +} diff --git a/explorer/testdata/global_variable/implicit_conversion.carbon b/explorer/testdata/global_variable/implicit_conversion.carbon new file mode 100644 index 0000000000000..7114330d90588 --- /dev/null +++ b/explorer/testdata/global_variable/implicit_conversion.carbon @@ -0,0 +1,25 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 5 + +package ExplorerTest api; + +class A { + var n: i32; +} +impl i32 as ImplicitAs(A) { + fn Convert[me: Self]() -> A { return {.n = me}; } +} + +var a: A = 5; + +fn Main() -> i32 { + return a.n; +} diff --git a/explorer/testdata/if_else/convert_condition.carbon b/explorer/testdata/if_else/convert_condition.carbon new file mode 100644 index 0000000000000..44c8d7e160e80 --- /dev/null +++ b/explorer/testdata/if_else/convert_condition.carbon @@ -0,0 +1,33 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 1 + +package ExplorerTest api; + +class LazyEq { + var v1: i32; + var v2: i32; + impl as ImplicitAs(Bool) { + fn Convert[me: Self]() -> Bool { + return me.v1 == me.v2; + } + } + fn Make(v1: i32, v2: i32) -> Self { + return {.v1 = v1, .v2 = v2}; + } +} + +fn Main() -> i32 { + if (LazyEq.Make(2, 1 + 1)) { + return 1; + } else { + return 2; + } +} diff --git a/explorer/testdata/if_expression/convert_condition.carbon b/explorer/testdata/if_expression/convert_condition.carbon new file mode 100644 index 0000000000000..34c0337747bcc --- /dev/null +++ b/explorer/testdata/if_expression/convert_condition.carbon @@ -0,0 +1,29 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 1 + +package ExplorerTest api; + +class LazyEq { + var v1: i32; + var v2: i32; + impl as ImplicitAs(Bool) { + fn Convert[me: Self]() -> Bool { + return me.v1 == me.v2; + } + } + fn Make(v1: i32, v2: i32) -> Self { + return {.v1 = v1, .v2 = v2}; + } +} + +fn Main() -> i32 { + return if LazyEq.Make(2, 1 + 1) then 1 else 2; +} diff --git a/explorer/testdata/interface/fail_impl_bad_member.carbon b/explorer/testdata/interface/fail_impl_bad_member.carbon index 25386c4d3bff5..65d769f7d801b 100644 --- a/explorer/testdata/interface/fail_impl_bad_member.carbon +++ b/explorer/testdata/interface/fail_impl_bad_member.carbon @@ -24,7 +24,9 @@ class Point { } fn Scale[me: Point](v: i32) -> i32 { return 0; - // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_impl_bad_member.carbon:[[@LINE+1]]: type error in member of implementation: 'fn (i32) -> i32' is not implicitly convertible to 'fn (i32) -> class Point' + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/interface/fail_impl_bad_member.carbon:[[@LINE+3]]: type error in member of implementation + // CHECK: expected: fn (i32) -> class Point + // CHECK: actual: fn (i32) -> i32 } } } diff --git a/explorer/testdata/let/implicit_conversion.carbon b/explorer/testdata/let/implicit_conversion.carbon new file mode 100644 index 0000000000000..c6974d493109b --- /dev/null +++ b/explorer/testdata/let/implicit_conversion.carbon @@ -0,0 +1,26 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 3 + +package ExplorerTest api; + +class A { + impl as ImplicitAs(i32) { + fn Convert[me: Self]() -> i32 { return me.n; } + } + var n: i32; +} + +fn Main() -> i32 { + let a: A = {.n = 1}; + let b: A = {.n = 2}; + let (x: i32, var y: i32) = (a, b); + return x + y; +} diff --git a/explorer/testdata/let/implicit_conversion_choice.carbon b/explorer/testdata/let/implicit_conversion_choice.carbon new file mode 100644 index 0000000000000..3cc2864a8fe5f --- /dev/null +++ b/explorer/testdata/let/implicit_conversion_choice.carbon @@ -0,0 +1,25 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 3 + +package ExplorerTest api; + +choice A { + Value(i32) +} + +impl i32 as ImplicitAs(A) { + fn Convert[me: Self]() -> A { return A.Value(me + 1); } +} + +fn Main() -> i32 { + let A.Value(n: i32) = 2; + return n; +} diff --git a/explorer/testdata/match/convert.carbon b/explorer/testdata/match/convert.carbon new file mode 100644 index 0000000000000..05ba8a2db5189 --- /dev/null +++ b/explorer/testdata/match/convert.carbon @@ -0,0 +1,29 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 0 + +package ExplorerTest api; + +class A {} +impl A as ImplicitAs(i32) { + fn Convert[me: Self]() -> i32 { return 1; } +} + +fn Main() -> i32 { + var a: A = {}; + + match ((a, a)) { + case (0, n: i32) => return 1; + case (n: i32, 0) => return 1; + case (1, 1) => return 0; + } + + return 1; +} diff --git a/explorer/testdata/match/fail_pattern_type_mismatch.carbon b/explorer/testdata/match/fail_pattern_type_mismatch.carbon new file mode 100644 index 0000000000000..daf085c4ccc06 --- /dev/null +++ b/explorer/testdata/match/fail_pattern_type_mismatch.carbon @@ -0,0 +1,34 @@ +// 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 +// +// RUN: %{not} %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s + +package ExplorerTest api; + +class A {} +impl A as ImplicitAs(i32) { + fn Convert[me: Self]() -> i32 { return 1; } +} + +fn Main() -> i32 { + var a: A = {}; + + // FIXME: It's not clear whether this should be valid. The patterns here have + // different types, but we could perform different conversions on the source + // expression when checking each pattern. + match ((a, a)) { + case (0, n: A) => return 1; + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/match/fail_pattern_type_mismatch.carbon:[[@LINE+3]]: type error in `match` pattern type + // CHECK: expected: (i32, class A) + // CHECK: actual: (class A, i32) + case (n: A, 0) => return 1; + case (1, 1) => return 0; + } + + return 1; +} diff --git a/explorer/testdata/member_access/convert_lhs_class.carbon b/explorer/testdata/member_access/convert_lhs_class.carbon new file mode 100644 index 0000000000000..29c74a36e464e --- /dev/null +++ b/explorer/testdata/member_access/convert_lhs_class.carbon @@ -0,0 +1,30 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 3 + +package Foo api; + +class X { + fn F[me: Self](o: Self) -> Self { return {.n = me.n + o.n}; } + var n: i32; +} + +class Y { + var m: i32; + impl as ImplicitAs(X) { + fn Convert[me: Self]() -> X { return {.n = me.m}; } + } +} + +fn Main() -> i32 { + var y1: Y = {.m = 1}; + var y2: Y = {.m = 2}; + return y1.(X.F)(y2).n; +} diff --git a/explorer/testdata/member_access/convert_lhs_interface.carbon b/explorer/testdata/member_access/convert_lhs_interface.carbon new file mode 100644 index 0000000000000..811d3d5cf84f9 --- /dev/null +++ b/explorer/testdata/member_access/convert_lhs_interface.carbon @@ -0,0 +1,36 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 3 + +package Foo api; + +interface HasF { + fn F[me: Self](o: Self) -> Self; +} + +class X { + impl as HasF { + fn F[me: Self](o: Self) -> Self { return {.n = me.n + o.n}; } + } + var n: i32; +} + +class Y { + var m: i32; + impl as ImplicitAs(X) { + fn Convert[me: Self]() -> X { return {.n = me.m}; } + } +} + +fn Main() -> i32 { + var x: X = {.n = 1}; + var y: Y = {.m = 2}; + return y.(X.(HasF.F))(x).n; +} diff --git a/explorer/testdata/return/convert_return_value.carbon b/explorer/testdata/return/convert_return_value.carbon new file mode 100644 index 0000000000000..d18fc47720bc4 --- /dev/null +++ b/explorer/testdata/return/convert_return_value.carbon @@ -0,0 +1,23 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 42 + +package ExplorerTest api; + +class A { + impl as ImplicitAs(i32) { + fn Convert[me: Self]() -> i32 { return 42; } + } +} + +fn Main() -> i32 { + var a: A = {}; + return a; +} diff --git a/explorer/testdata/while/convert_condition.carbon b/explorer/testdata/while/convert_condition.carbon new file mode 100644 index 0000000000000..bf3b3a07bab44 --- /dev/null +++ b/explorer/testdata/while/convert_condition.carbon @@ -0,0 +1,33 @@ +// 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 +// +// RUN: %{explorer} %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s +// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \ +// RUN: %{FileCheck} --match-full-lines --allow-unused-prefixes %s +// AUTOUPDATE: %{explorer} %s +// CHECK: result: 0 + +package ExplorerTest api; + +class LazyNe { + var v1: i32; + var v2: i32; + impl as ImplicitAs(Bool) { + fn Convert[me: Self]() -> Bool { + return not (me.v1 == me.v2); + } + } + fn Make(v1: i32, v2: i32) -> Self { + return {.v1 = v1, .v2 = v2}; + } +} + +fn Main() -> i32 { + var x: auto = 2; + while (LazyNe.Make(x, 0)) { + x = x - 1; + } + return x; +}