From 474b1eb24c8ac7315e6bf6633825a09855048959 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 14 Jun 2023 16:14:21 -0700 Subject: [PATCH] Support builtin conversions that internally rely on user-defined conversions (#2881) There are a few implicit conversions that are implemented by an `impl` of `ImplicitAs` that delegates to code in explorer: - Converting between tuple types - Converting from tuples of types to `type` - Converting from tuples of values to an array type - Converting between struct types - Converting from a struct type to a class type These conversions can all rely on performing more conversions for elements or subobjects, but previously those inner conversions could only be performed if they were built into explorer. This change instead uses the full implicit conversion machinery in explorer to perform these conversions, including searching for a user-defined `impl` of `ImplicitAs` when necessary. For example, this permits a conversion from `{.a: T}` to `{.a: U}`, or from `(T, T)` to `(U, U)`, or from `(T, T)` to `[U; 2]` when there is a user-defined conversion from `T` to `U`. Depends on #2878 --- explorer/ast/expression.h | 39 +- explorer/data/prelude.carbon | 77 +-- explorer/interpreter/interpreter.cpp | 38 +- explorer/interpreter/type_checker.cpp | 521 ++++++++++++------ explorer/interpreter/type_checker.h | 17 +- explorer/testdata/array/conversion.carbon | 33 ++ ...> fail_intrinsic_convert_non_value.carbon} | 4 +- explorer/testdata/class/conversion.carbon | 51 ++ .../fail_wrong_variable_substituation.carbon | 2 +- explorer/testdata/struct/conversion.carbon | 34 ++ .../tuple/fail_type_tuple_as_type.carbon | 2 +- .../testdata/tuple/indirect_to_type.carbon | 25 + 12 files changed, 594 insertions(+), 249 deletions(-) create mode 100644 explorer/testdata/array/conversion.carbon rename explorer/testdata/as/{intrinsic_convert_non_value.carbon => fail_intrinsic_convert_non_value.carbon} (63%) create mode 100644 explorer/testdata/class/conversion.carbon create mode 100644 explorer/testdata/struct/conversion.carbon create mode 100644 explorer/testdata/tuple/indirect_to_type.carbon diff --git a/explorer/ast/expression.h b/explorer/ast/expression.h index fe37ec06c461..48044d9847f8 100644 --- a/explorer/ast/expression.h +++ b/explorer/ast/expression.h @@ -852,7 +852,7 @@ class ValueLiteral : public ConstantValueLiteral { } }; -class IntrinsicExpression : public Expression { +class IntrinsicExpression : public RewritableMixin { public: enum class Intrinsic { Print, @@ -881,13 +881,13 @@ class IntrinsicExpression : public Expression { explicit IntrinsicExpression(Intrinsic intrinsic, Nonnull args, SourceLocation source_loc) - : Expression(AstNodeKind::IntrinsicExpression, source_loc), + : RewritableMixin(AstNodeKind::IntrinsicExpression, source_loc), intrinsic_(intrinsic), args_(args) {} explicit IntrinsicExpression(CloneContext& context, const IntrinsicExpression& other) - : Expression(context, other), + : RewritableMixin(context, other), intrinsic_(other.intrinsic_), args_(context.Clone(other.args_)) {} @@ -1121,22 +1121,17 @@ class WhereExpression : public RewritableMixin { // 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 { +class BuiltinConvertExpression : public RewritableMixin { 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_expression_category(ExpressionCategory::Value); - } + BuiltinConvertExpression(Nonnull source_expression) + : RewritableMixin(AstNodeKind::BuiltinConvertExpression, + source_expression->source_loc()), + source_expression_(source_expression) {} explicit BuiltinConvertExpression(CloneContext& context, const BuiltinConvertExpression& other) - : Expression(context, other), - source_expression_(context.Clone(other.source_expression_)), - rewritten_form_(context.Clone(other.rewritten_form_)) {} + : RewritableMixin(context, other), + source_expression_(context.Clone(other.source_expression_)) {} static auto classof(const AstNode* node) -> bool { return InheritsFromBuiltinConvertExpression(node->kind()); @@ -1149,22 +1144,8 @@ class BuiltinConvertExpression : public Expression { return source_expression_; } - // Set the rewritten form of this expression. Can only be called during type - // checking. - auto set_rewritten_form(Nonnull rewritten_form) -> void { - CARBON_CHECK(!rewritten_form_.has_value()) << "rewritten form set twice"; - rewritten_form_ = rewritten_form; - } - - // Get the rewritten form of this expression. A rewritten form can be used to - // prepare the conversion during type checking. - auto rewritten_form() const -> std::optional> { - return rewritten_form_; - } - private: Nonnull source_expression_; - std::optional> rewritten_form_; }; // An expression whose semantics have not been implemented. This can be used diff --git a/explorer/data/prelude.carbon b/explorer/data/prelude.carbon index 5acd6e49fadd..1cc9b0c2fea2 100644 --- a/explorer/data/prelude.carbon +++ b/explorer/data/prelude.carbon @@ -34,65 +34,42 @@ __match_first { // Pick up implicit conversions that are built into the compiler. // TODO: Split these into individual categories and implement as many as we can // in the prelude. - impl forall [U:! type, template T:! __intrinsic_implicit_as(U)] T as ImplicitAs(U) { - fn Convert[self: Self]() -> U { return __intrinsic_implicit_as_convert(self, U); } + impl forall [template U:! type, template T:! __intrinsic_implicit_as(U)] + T as ImplicitAs(U) { + fn Convert[self: Self]() -> U { + return __intrinsic_implicit_as_convert(self, U); + } } // Every type implicitly converts to single-step-equal types. impl forall [T:! type, U:! type where .Self == T] T as ImplicitAs(U) { fn Convert[self: Self]() -> U { return __EqualConvert(self, U); } } +} - // A tuple implicitly converts to another tuple if all its elements do. - // 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[self: Self]() -> (U1,) { - let (v1: T1,) = self; - return (v1.Convert(),); - } +// A tuple explicitly converts to another tuple if all its elements do. +// TODO: Simplify this once we have variadics. +// TODO: Should these be final? +impl forall [U1:! type, T1:! As(U1)] + (T1,) as As((U1,)) { + fn Convert[self: Self]() -> (U1,) { + let (v1: T1,) = self; + return (v1.Convert(),); } - impl forall [U1:! type, U2:! type, T1:! ImplicitAs(U1), T2:! ImplicitAs(U2)] - (T1, T2) as ImplicitAs((U1, U2)) { - fn Convert[self: Self]() -> (U1, U2) { - let (v1: T1, v2: T2) = self; - return (v1.Convert(), v2.Convert()); - } +} +impl forall [U1:! type, U2:! type, T1:! As(U1), T2:! As(U2)] + (T1, T2) as As((U1, U2)) { + fn Convert[self: Self]() -> (U1, U2) { + let (v1: T1, v2: T2) = self; + 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[self: Self]() -> (U1, U2, U3) { - let (v1: T1, v2: T2, v3: T3) = self; - return (v1.Convert(), v2.Convert(), v3.Convert()); - } - } - - // A tuple explicitly converts to another tuple if all its elements do. Note - // that this fully overlaps with the previous set of impl declarations for - // the case where an implicit conversion is possible. - impl forall [U1:! type, T1:! As(U1)] - (T1,) as As((U1,)) { - fn Convert[self: Self]() -> (U1,) { - let (v1: T1,) = self; - return (v1.Convert(),); - } - } - impl forall [U1:! type, U2:! type, T1:! As(U1), T2:! As(U2)] - (T1, T2) as As((U1, U2)) { - fn Convert[self: Self]() -> (U1, U2) { - let (v1: T1, v2: T2) = self; - return (v1.Convert(), v2.Convert()); - } - } - impl forall [U1:! type, U2:! type, U3:! type, - T1:! As(U1), T2:! As(U2), T3:! As(U3)] - (T1, T2, T3) as As((U1, U2, U3)) { - fn Convert[self: Self]() -> (U1, U2, U3) { - let (v1: T1, v2: T2, v3: T3) = self; - return (v1.Convert(), v2.Convert(), v3.Convert()); - } +} +impl forall [U1:! type, U2:! type, U3:! type, + T1:! As(U1), T2:! As(U2), T3:! As(U3)] + (T1, T2, T3) as As((U1, U2, U3)) { + fn Convert[self: Self]() -> (U1, U2, U3) { + let (v1: T1, v2: T2, v3: T3) = self; + return (v1.Convert(), v2.Convert(), v3.Convert()); } } diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 20d151460705..7b3552448897 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -784,7 +784,6 @@ auto Interpreter::Convert(Nonnull value, case Value::Kind::ConstraintImplWitness: case Value::Kind::ParameterizedEntityName: case Value::Kind::ChoiceType: - case Value::Kind::VariableType: case Value::Kind::BindingPlaceholderValue: case Value::Kind::AddrValue: case Value::Kind::AlternativeConstructorValue: @@ -890,6 +889,34 @@ auto Interpreter::Convert(Nonnull value, } return arena_->New(std::move(new_elements)); } + case Value::Kind::VariableType: { + std::optional> source_type; + // While type-checking a `where` expression, we can evaluate a reference + // to its self binding before we know its type. In this case, the self + // binding is always a type. + // + // TODO: Add a conversion kind to BuiltinConvertExpression so that we + // don't need to look at the types and reconstruct what kind of + // conversion is being performed from here. + if (cast(value)->binding().is_type_checked()) { + CARBON_ASSIGN_OR_RETURN( + source_type, + InstantiateType(&cast(value)->binding().static_type(), + source_loc)); + } + if (isa( + destination_type) && + (!source_type || + isa( + *source_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 generic binding " << *value << " is not known"; + } case Value::Kind::AssociatedConstant: { CARBON_ASSIGN_OR_RETURN( Nonnull value, @@ -1575,6 +1602,9 @@ auto Interpreter::StepExp() -> ErrorOr { } case ExpressionKind::IntrinsicExpression: { const auto& intrinsic = cast(exp); + if (auto rewrite = intrinsic.rewritten_form()) { + return todo_.ReplaceWith(std::make_unique(*rewrite)); + } if (act.pos() == 0) { return todo_.Spawn( std::make_unique(&intrinsic.args())); @@ -1722,10 +1752,8 @@ auto Interpreter::StepExp() -> ErrorOr { return todo_.FinishAction(result); } case IntrinsicExpression::Intrinsic::ImplicitAsConvert: { - CARBON_CHECK(args.size() == 2); - CARBON_ASSIGN_OR_RETURN(Nonnull result, - Convert(args[0], args[1], exp.source_loc())); - return todo_.FinishAction(result); + CARBON_FATAL() + << "__intrinsic_implicit_as_convert should have been rewritten"; } case IntrinsicExpression::Intrinsic::IntEq: { CARBON_CHECK(args.size() == 2); diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 85a82707ccc2..aaf10059e93d 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -506,40 +506,6 @@ static auto FindField(llvm::ArrayRef fields, return *it; } -auto TypeChecker::FieldTypesImplicitlyConvertible( - llvm::ArrayRef source_fields, - llvm::ArrayRef destination_fields, - const ImplScope& impl_scope) const -> ErrorOr { - // TODO: If default fields are implemented, the - // code must be adapted to skip them. - // Ensure every field name exists in the destination. - for (const auto& dest_field : destination_fields) { - if (!FindField(source_fields, dest_field.name)) { - return false; - } - } - for (const auto& source_field : source_fields) { - std::optional destination_field = - FindField(destination_fields, source_field.name); - if (!destination_field.has_value()) { - return false; - } - CARBON_ASSIGN_OR_RETURN( - bool convertible, - IsImplicitlyConvertible(source_field.value, - destination_field.value().value, impl_scope, - // TODO: 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. - /*allow_user_defined_conversions=*/false)); - if (!convertible) { - return false; - } - } - return true; -} - auto TypeChecker::FieldTypes(const NominalClassType& class_type) const -> ErrorOr> { std::vector field_types; @@ -564,7 +530,7 @@ auto TypeChecker::FieldTypesWithBase(const NominalClassType& class_type) const -> ErrorOr> { CARBON_ASSIGN_OR_RETURN(auto fields, FieldTypes(class_type)); if (const auto base_type = class_type.base()) { - CARBON_ASSIGN_OR_RETURN(auto base_fields, + CARBON_ASSIGN_OR_RETURN(std::vector base_fields, FieldTypesWithBase(*base_type.value())); fields.emplace_back(NamedValue{std::string(NominalClassValue::BaseField), base_type.value()}); @@ -576,32 +542,91 @@ auto TypeChecker::IsImplicitlyConvertible( Nonnull source, Nonnull destination, const ImplScope& impl_scope, bool allow_user_defined_conversions) const -> ErrorOr { - // Check for an exact match or for an implicit conversion. - // TODO: `impl` definitions of `ImplicitAs` should be provided to cover these - // conversions. + // Check for an exact match to avoid impl lookup in this common case. CARBON_CHECK(IsNonDeduceableType(source)); CARBON_CHECK(IsNonDeduceableType(destination)); if (IsSameType(source, destination, impl_scope)) { return true; } + // If the source is a type, or a type-like tuple, then it might convert to + // another type-of-type. This can't be done by `ImplicitAs` because it + // depends on the value, not only on the type. + // + // TODO: We can't tell whether the conversion to this type-of-type would + // work, because we don't have the source value, only its type. So we allow + // this conversion if the source converts to `type`, even if it won't convert + // to the actual destination type. We'll catch any problems when we actually + // come to perform the conversion. + if (isa(source) && IsTypeOfType(destination)) { + return IsBuiltinConversion(source, arena_->New(), impl_scope, + allow_user_defined_conversions); + } + if (IsTypeOfType(source) && IsTypeOfType(destination)) { + return true; + } + + // If we're not supposed to look for a user-defined conversion, check for + // builtin conversions, which are normally found by impl lookup. + if (!allow_user_defined_conversions) { + return IsBuiltinConversion(source, destination, impl_scope, + allow_user_defined_conversions); + } + + // We didn't find a builtin implicit conversion. Check if a user-defined one + // exists. + SourceLocation source_loc = SourceLocation::DiagnosticsIgnored(); + CARBON_ASSIGN_OR_RETURN( + Nonnull iface_type, + GetBuiltinInterfaceType( + source_loc, BuiltinInterfaceName{Builtin::ImplicitAs, destination})); + CARBON_ASSIGN_OR_RETURN( + std::optional> conversion_witness, + impl_scope.TryResolve(iface_type, source, source_loc, *this, + /*bindings=*/{}, /*diagnose_missing_impl=*/false)); + return conversion_witness.has_value(); +} + +auto TypeChecker::IsBuiltinConversion(Nonnull source, + Nonnull destination, + const ImplScope& impl_scope, + bool allow_user_defined_conversions) const + -> ErrorOr { switch (source->kind()) { case Value::Kind::StructType: switch (destination->kind()) { case Value::Kind::StructType: { - CARBON_ASSIGN_OR_RETURN( - bool fields_convertible, - FieldTypesImplicitlyConvertible( - cast(*source).fields(), - cast(*destination).fields(), impl_scope)); - if (fields_convertible) { - return true; + llvm::ArrayRef source_fields = + cast(*source).fields(); + llvm::ArrayRef destination_fields = + cast(*destination).fields(); + // Ensure every source field exists in the destination type. + for (const auto& source_field : source_fields) { + if (!FindField(destination_fields, source_field.name)) { + return false; + } } - break; + // Ensure every destination field is initialized. + for (const auto& destination_field : destination_fields) { + std::optional source_field = + FindField(source_fields, destination_field.name); + if (!source_field.has_value()) { + return false; + } + CARBON_ASSIGN_OR_RETURN( + bool convertible, + IsImplicitlyConvertible(source_field->value, + destination_field.value, impl_scope, + allow_user_defined_conversions)); + if (!convertible) { + return false; + } + } + return true; } case Value::Kind::NominalClassType: { CARBON_ASSIGN_OR_RETURN( - auto field_types, + std::vector field_types, FieldTypesWithBase(cast(*destination))); CARBON_ASSIGN_OR_RETURN( bool convertible, @@ -641,7 +666,7 @@ auto TypeChecker::IsImplicitlyConvertible( bool convertible, IsImplicitlyConvertible( source_tuple.elements()[i], destination_tuple.elements()[i], - impl_scope, /*allow_user_defined_conversions=*/false)); + impl_scope, allow_user_defined_conversions)); if (!convertible) { all_ok = false; break; @@ -663,7 +688,7 @@ auto TypeChecker::IsImplicitlyConvertible( bool convertible, IsImplicitlyConvertible( source_element, &destination_array.element_type(), - impl_scope, /*allow_user_defined_conversions=*/false)); + impl_scope, allow_user_defined_conversions)); if (!convertible) { all_ok = false; break; @@ -674,18 +699,14 @@ auto TypeChecker::IsImplicitlyConvertible( } break; } - case Value::Kind::TypeType: - case Value::Kind::InterfaceType: - case Value::Kind::NamedConstraintType: - case Value::Kind::ConstraintType: { - // A tuple value converts to a type if all of its fields do. + case Value::Kind::TypeType: { + // A tuple value converts to `type` if all of its fields do. bool all_types = true; for (Nonnull source_element : source_tuple.elements()) { CARBON_ASSIGN_OR_RETURN( bool convertible, - IsImplicitlyConvertible( - source_element, destination, impl_scope, - /*allow_user_defined_conversions=*/false)); + IsImplicitlyConvertible(source_element, destination, impl_scope, + allow_user_defined_conversions)); if (!convertible) { all_types = false; break; @@ -701,14 +722,6 @@ auto TypeChecker::IsImplicitlyConvertible( } break; } - case Value::Kind::TypeType: - case Value::Kind::InterfaceType: - case Value::Kind::NamedConstraintType: - case Value::Kind::ConstraintType: - // TODO: We can't tell whether the conversion to this type-of-type would - // work, because that depends on the source value, and we only have its - // type. - return IsTypeOfType(destination); case Value::Kind::PointerType: { if (destination->kind() != Value::Kind::PointerType) { break; @@ -729,24 +742,13 @@ auto TypeChecker::IsImplicitlyConvertible( break; } - // If we're not supposed to look for a user-defined conversion, we're done. - if (!allow_user_defined_conversions) { - return false; - } - - // We didn't find a builtin implicit conversion. Try a user-defined one. - SourceLocation source_loc = SourceLocation::DiagnosticsIgnored(); - ErrorOr> iface_type = GetBuiltinInterfaceType( - source_loc, BuiltinInterfaceName{Builtin::ImplicitAs, destination}); - // TODO: If the Resolve call fails with a hard error, don't swallow it. - return iface_type.ok() && - impl_scope.Resolve(*iface_type, source, source_loc, *this).ok(); + return false; } auto TypeChecker::BuildSubtypeConversion(Nonnull source, Nonnull src_ptr, Nonnull dest_ptr) - -> ErrorOr> { + -> ErrorOr> { const auto* src_class = cast(&src_ptr->pointee_type()); const auto* dest_class = cast(&dest_ptr->pointee_type()); const auto dest = dest_class->declaration().name(); @@ -766,6 +768,186 @@ auto TypeChecker::BuildSubtypeConversion(Nonnull source, return last_expr; } +auto TypeChecker::BuildBuiltinConversion(Nonnull source, + Nonnull destination, + const ImplScope& impl_scope) + -> ErrorOr> { + Nonnull source_type = &source->static_type(); + + if (trace_stream_->is_enabled()) { + *trace_stream_ << "building builtin conversion from " << *source_type + << " to " << *destination << "\n"; + } + + // Build a simple conversion that the interpreter can perform directly. + auto make_builtin_conversion = [&](Nonnull from) { + auto* result = arena_->New(from); + result->set_static_type(destination); + result->set_expression_category(ExpressionCategory::Value); + return result; + }; + + // Report that the conversion was not possible. This error should only be + // visible if __builtin_implicit_as_convert is called directly. + auto conversion_failed = [&] { + return ProgramError(source->source_loc()) + << "no builtin conversion from " << *source_type << " to " + << *destination << " is known"; + }; + + // Note that the conversion expression that we build may evaluate `source` + // more than once. This is OK because the __builtin_implicit_as_convert + // intrinsic is only intended to be called from within the prelude's impl of + // ImplicitAs, where `source` has no side effects. + + switch (source_type->kind()) { + case Value::Kind::StructType: + switch (destination->kind()) { + case Value::Kind::StructType: { + llvm::ArrayRef source_fields = + cast(*source_type).fields(); + llvm::ArrayRef destination_fields = + cast(*destination).fields(); + // Ensure every source field exists in the destination type. + for (const auto& source_field : source_fields) { + if (!FindField(destination_fields, source_field.name)) { + return conversion_failed(); + } + } + // Initialize every destination field. + std::vector result_fields; + for (const auto& destination_field : destination_fields) { + std::optional source_field = + FindField(source_fields, destination_field.name); + if (!source_field.has_value()) { + return conversion_failed(); + } + auto* elem = arena_->New( + source->source_loc(), source, source_field->name); + CARBON_RETURN_IF_ERROR(TypeCheckExp(elem, impl_scope)); + CARBON_ASSIGN_OR_RETURN( + Nonnull converted, + ImplicitlyConvert("implicit conversion", impl_scope, elem, + destination_field.value)); + result_fields.push_back( + FieldInitializer(destination_field.name, converted)); + } + auto* result = arena_->New(source->source_loc(), + std::move(result_fields)); + CARBON_RETURN_IF_ERROR(TypeCheckExp(result, impl_scope)); + return result; + } + case Value::Kind::NominalClassType: { + CARBON_ASSIGN_OR_RETURN( + std::vector field_types, + FieldTypesWithBase(cast(*destination))); + CARBON_ASSIGN_OR_RETURN( + Nonnull result, + ImplicitlyConvert("implicit conversion", impl_scope, source, + arena_->New(field_types))); + // Perform a builtin conversion from struct to class. + return make_builtin_conversion(result); + } + case Value::Kind::TypeType: + // A value of empty struct type implicitly converts to type `type`. + if (cast(*source_type).fields().empty()) { + return make_builtin_conversion(source); + } + return conversion_failed(); + default: + return conversion_failed(); + } + return conversion_failed(); + case Value::Kind::TupleType: { + const auto& source_tuple = cast(*source_type); + switch (destination->kind()) { + case Value::Kind::TupleType: { + const auto& destination_tuple = cast(*destination); + if (source_tuple.elements().size() != + destination_tuple.elements().size()) { + return conversion_failed(); + } + std::vector> converted_elements; + for (size_t i = 0; i < source_tuple.elements().size(); ++i) { + auto* elem = arena_->New( + source->source_loc(), source, + arena_->New(source->source_loc(), i)); + CARBON_RETURN_IF_ERROR(TypeCheckExp(elem, impl_scope)); + CARBON_ASSIGN_OR_RETURN( + Nonnull converted, + ImplicitlyConvert("implicit conversion", impl_scope, elem, + destination_tuple.elements()[i])); + converted_elements.push_back(converted); + } + auto* result = arena_->New( + source->source_loc(), std::move(converted_elements)); + CARBON_RETURN_IF_ERROR(TypeCheckExp(result, impl_scope)); + return result; + } + case Value::Kind::StaticArrayType: { + const auto& destination_array = cast(*destination); + // First, convert each tuple element to the array element type if + // necessary. + if (!std::all_of(source_tuple.elements().begin(), + source_tuple.elements().end(), + [&](Nonnull element_type) { + return TypeEqual(element_type, + &destination_array.element_type(), + std::nullopt); + })) { + auto* destination_tuple_type = arena_->New(std::vector( + destination_array.size(), &destination_array.element_type())); + CARBON_ASSIGN_OR_RETURN( + source, BuildBuiltinConversion(source, destination_tuple_type, + impl_scope)); + } + // Perform a builtin conversion from tuple to array. + return make_builtin_conversion(source); + } + case Value::Kind::TypeType: { + // First, convert each tuple element to 'type' if necessary. + if (!std::all_of(source_tuple.elements().begin(), + source_tuple.elements().end(), + [](Nonnull element_type) { + return isa(element_type); + })) { + auto* destination_tuple_type = arena_->New( + std::vector(source_tuple.elements().size(), destination)); + CARBON_ASSIGN_OR_RETURN( + source, BuildBuiltinConversion(source, destination_tuple_type, + impl_scope)); + } + // Perform a builtin conversion from tuple of types to type. + return make_builtin_conversion(source); + } + default: + return conversion_failed(); + } + return conversion_failed(); + } + case Value::Kind::PointerType: { + if (destination->kind() != Value::Kind::PointerType) { + return conversion_failed(); + } + const auto* src_ptr = cast(source_type); + const auto* dest_ptr = cast(destination); + if (src_ptr->pointee_type().kind() != Value::Kind::NominalClassType || + dest_ptr->pointee_type().kind() != Value::Kind::NominalClassType) { + return conversion_failed(); + } + const auto& src_class = cast(src_ptr->pointee_type()); + if (src_class.InheritsClass(&dest_ptr->pointee_type())) { + return BuildSubtypeConversion(source, src_ptr, dest_ptr); + } + return conversion_failed(); + } + default: + return conversion_failed(); + } + + CARBON_FATAL() << "unreachable"; +} + auto TypeChecker::ImplicitlyConvert(std::string_view context, const ImplScope& impl_scope, Nonnull source, @@ -774,79 +956,106 @@ auto TypeChecker::ImplicitlyConvert(std::string_view context, Nonnull source_type = &source->static_type(); CARBON_RETURN_IF_ERROR( - ExpectNonPlaceholderType(source->source_loc(), &source->static_type())); + ExpectNonPlaceholderType(source->source_loc(), source_type)); - if (TypeEqual(&source->static_type(), destination, std::nullopt)) { + if (TypeEqual(source_type, destination, std::nullopt)) { // No conversions are required. return source; } - // 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. - CARBON_ASSIGN_OR_RETURN( - bool convertible, - IsImplicitlyConvertible(source_type, destination, impl_scope, - /*allow_user_defined_conversions=*/false)); - if (convertible) { - // A type only implicitly converts to a constraint if there is an impl of - // that constraint for that type in scope. + // Conversion from a tuple of types to the type `type` is used in the prelude + // before the intrinsic impl of `ImplicitAs` is declared. We also need to do + // this as a prerequisite to the conversion of tuples to constrained types + // below. + if (isa(source_type) && IsTypeOfType(destination)) { + auto* type_type = arena_->New(); + CARBON_ASSIGN_OR_RETURN( + bool convertible, + IsBuiltinConversion(source_type, type_type, impl_scope, + /*allow_user_defined_conversions=*/true)); + if (convertible) { + CARBON_ASSIGN_OR_RETURN( + source, BuildBuiltinConversion(source, type_type, impl_scope)); + source_type = &source->static_type(); + } + } + + // A type of type can be converted to another type of type if the value of + // the former satisfies the constraints of the latter. This conversion + // depends on the value, not only the type, so isn't supported by + // `ImplicitAs`. + if (IsTypeOfType(source_type) && IsTypeOfType(destination)) { + // Don't require the source value to be constant if the destination is + // `type`. // TODO: Instead of excluding the special case where the destination is // `type`, we should check if the source type has a subset of the // constraints of the destination type. In that case, the source should not - // be required to be constant. - if (IsTypeOfType(destination) && !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)); - CARBON_ASSIGN_OR_RETURN( - Nonnull destination_constraint, - ConvertToConstraintType(source->source_loc(), "implicit conversion", - destination)); - destination = destination_constraint; - if (trace_stream_->is_enabled()) { - *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, - ExpressionCategory::Value); - } - - if (IsTypeOfType(source_type) && IsTypeOfType(destination)) { - // No conversion is required. + // be required to be constant. That case should also be supported by + // `ImplicitAs`. + if (isa(destination)) { return source; } - // Perform the builtin conversion. - auto* convert_expr = - arena_->New(source, destination); - - // For subtyping, rewrite into successive `.base` accesses. - if (isa(source_type) && isa(destination) && - cast(destination)->pointee_type().kind() == - Value::Kind::NominalClassType) { - CARBON_ASSIGN_OR_RETURN( - const auto* rewrite, - BuildSubtypeConversion(source, cast(source_type), - cast(destination))) - convert_expr->set_rewritten_form(rewrite); + // 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)); + CARBON_ASSIGN_OR_RETURN( + Nonnull destination_constraint, + ConvertToConstraintType(source->source_loc(), "implicit conversion", + destination)); + destination = destination_constraint; + if (trace_stream_->is_enabled()) { + *trace_stream_ << "converting type " << *converted_value + << " to constraint " << *destination_constraint << " for " + << context << " in scope " << impl_scope << "\n"; } - - return convert_expr; + // 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, + ExpressionCategory::Value); } + // Conversion from a tuple literal to a tuple type converts each element in + // turn, rather than converting the tuple as a whole. This is important in + // order to evaluate arguments to a function call in a reasonable order, and + // this conversion needs to be built-in because we use it while type-checking + // the prelude. + if (auto* source_tuple = dyn_cast(source)) { + if (auto* destination_tuple = dyn_cast(destination)) { + if (source_tuple->fields().size() != + destination_tuple->elements().size()) { + return ProgramError(source->source_loc()) + << "type error in " << context << ": `" << *source_type << "`" + << " is not implicitly convertible to tuple type " + << "`" << *destination << "` of different length"; + } + std::vector> converted_elements; + for (size_t i = 0; i < source_tuple->fields().size(); ++i) { + CARBON_ASSIGN_OR_RETURN( + Nonnull converted, + ImplicitlyConvert("implicit conversion", impl_scope, + source_tuple->fields()[i], + destination_tuple->elements()[i])); + converted_elements.push_back(converted); + } + auto* result = arena_->New(source->source_loc(), + std::move(converted_elements)); + // TODO: Should be ExpressionCategory::Initializing. + result->set_expression_category(ExpressionCategory::Value); + result->set_static_type(destination); + return result; + } + } + + // Build a call to the conversion function. ErrorOr> converted = BuildBuiltinMethodCall( impl_scope, source, BuiltinInterfaceName{Builtin::ImplicitAs, destination}, @@ -869,9 +1078,15 @@ auto TypeChecker::IsIntrinsicConstraintSatisfied( case IntrinsicConstraint::ImplicitAs: CARBON_CHECK(constraint.arguments.size() == 1) << "wrong number of arguments for `__intrinsic_implicit_as`"; - return IsImplicitlyConvertible(constraint.type, constraint.arguments[0], - impl_scope, - /*allow_user_defined_conversions=*/false); + CARBON_ASSIGN_OR_RETURN( + bool convertible, + IsBuiltinConversion(constraint.type, constraint.arguments[0], + impl_scope, + /*allow_user_defined_conversions=*/true)); + if (trace_stream_->is_enabled()) { + *trace_stream_ << constraint << " evaluated to " << convertible << "\n"; + } + return convertible; } } @@ -1346,20 +1561,6 @@ auto TypeChecker::ArgumentDeduction::Finish( type_checker.Substitute(bindings, &binding->static_type())); const auto* first_value = values[0]; for (const auto* value : values) { - // TODO: It's not clear that conversions are or should be possible here. - // If they are permitted, we should allow user-defined conversions, and - // actually perform the conversion. - if (!IsTypeOfType(binding_type)) { - CARBON_ASSIGN_OR_RETURN(bool convertible, - type_checker.IsImplicitlyConvertible( - value, binding_type, impl_scope, false)); - if (!convertible) { - return ProgramError(source_loc_) - << "cannot convert deduced value " << *value << " for " - << binding->name() << " to parameter type " << *binding_type; - } - } - // All deductions are required to produce the same value. Note that we // intentionally don't consider equality constraints here; we need the // same symbolic type, otherwise it would be ambiguous which spelling @@ -3745,10 +3946,10 @@ auto TypeChecker::TypeCheckExpImpl(Nonnull e, } CARBON_ASSIGN_OR_RETURN(Nonnull result, TypeCheckTypeExp(args[1], impl_scope)); - // TODO: Check that the type of args[0] implicitly converts to - // args[1]. - e->set_static_type(result); - e->set_expression_category(ExpressionCategory::Value); + CARBON_ASSIGN_OR_RETURN( + Nonnull converted, + BuildBuiltinConversion(args[0], result, impl_scope)); + cast(e)->set_rewritten_form(converted); return Success(); } case IntrinsicExpression::Intrinsic::IntEq: { @@ -5707,7 +5908,7 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull impl_decl, // processing the interface, in case the interface expression uses `Self`. Nonnull self = impl_decl->self(); self->set_constant_value(impl_type_value); - self->set_static_type(&impl_decl->impl_type()->static_type()); + self->set_static_type(arena_->New()); // Check and interpret the interface. CARBON_ASSIGN_OR_RETURN( @@ -5724,7 +5925,7 @@ auto TypeChecker::DeclareImplDeclaration(Nonnull impl_decl, { // TODO: Combine this with the SelfDeclaration. auto* self_binding = arena_->New( - self->source_loc(), "Self", impl_decl->impl_type(), + self->source_loc(), "Self", &impl_decl->interface(), GenericBinding::BindingKind::Checked); self_binding->set_symbolic_identity(impl_type_value); self_binding->set_value(impl_type_value); diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index 0ff1065a24d5..a6fb222c65a4 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -425,6 +425,14 @@ class TypeChecker { bool allow_user_defined_conversions) const -> ErrorOr; + // Returns true if the conversion from `source` to `destination` is a builtin + // conversion that `BuildBuiltinConversion` can perform. + auto IsBuiltinConversion(Nonnull source, + Nonnull destination, + const ImplScope& impl_scope, + bool allow_user_defined_conversions) const + -> ErrorOr; + // Attempt to implicitly convert type-checked expression `source` to the type // `destination`. auto ImplicitlyConvert(std::string_view context, const ImplScope& impl_scope, @@ -441,7 +449,14 @@ class TypeChecker { auto BuildSubtypeConversion(Nonnull source, Nonnull src_ptr, Nonnull dest_ptr) - -> ErrorOr>; + -> ErrorOr>; + + // Build a builtin conversion of `source` to the type `destination`, if + // possible. + auto BuildBuiltinConversion(Nonnull source, + Nonnull destination, + const ImplScope& impl_scope) + -> 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 diff --git a/explorer/testdata/array/conversion.carbon b/explorer/testdata/array/conversion.carbon new file mode 100644 index 000000000000..6b3181091864 --- /dev/null +++ b/explorer/testdata/array/conversion.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 +// +// AUTOUPDATE +// CHECK:STDOUT: 1: 1 +// CHECK:STDOUT: 2: 2 +// CHECK:STDOUT: 3: 3 +// CHECK:STDOUT: 4: 4 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class A { + var n: i32; + impl as ImplicitAs(i32) { + fn Convert[self: Self]() -> i32 { return self.n; } + } +} +impl i32 as ImplicitAs(A) { + fn Convert[self: Self]() -> A { return {.n = self}; } +} + +fn Main() -> i32 { + var arr1: [i32; 2] = (1, 2 as A); + Print("1: {0}", arr1[0]); + Print("2: {0}", arr1[1]); + + var arr2: [A; 2] = (3, 4 as A); + Print("3: {0}", arr2[0].n); + Print("4: {0}", arr2[1].n); + return 0; +} diff --git a/explorer/testdata/as/intrinsic_convert_non_value.carbon b/explorer/testdata/as/fail_intrinsic_convert_non_value.carbon similarity index 63% rename from explorer/testdata/as/intrinsic_convert_non_value.carbon rename to explorer/testdata/as/fail_intrinsic_convert_non_value.carbon index 90b5a2f4ff88..e69f9698444f 100644 --- a/explorer/testdata/as/intrinsic_convert_non_value.carbon +++ b/explorer/testdata/as/fail_intrinsic_convert_non_value.carbon @@ -3,14 +3,14 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // AUTOUPDATE -// CHECK:STDOUT: result: 0 package ExplorerTest api; fn F() {} fn Main() -> i32 { - // This should probably fail, but at least validate that it doesn't crash. + // Validate that this doesn't crash. + // CHECK:STDERR: COMPILATION ERROR: fail_intrinsic_convert_non_value.carbon:[[@LINE+1]]: no builtin conversion from fn () -> () to i32 is known __intrinsic_implicit_as_convert(F, i32); return 0; } diff --git a/explorer/testdata/class/conversion.carbon b/explorer/testdata/class/conversion.carbon new file mode 100644 index 000000000000..40a283137250 --- /dev/null +++ b/explorer/testdata/class/conversion.carbon @@ -0,0 +1,51 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// CHECK:STDOUT: 1: 1 +// CHECK:STDOUT: 2: 2 +// CHECK:STDOUT: 3: 3 +// CHECK:STDOUT: 4: 4 +// CHECK:STDOUT: 5: 5 +// CHECK:STDOUT: 6: 6 +// CHECK:STDOUT: 7: 7 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class A { + var n: i32; + impl as ImplicitAs(i32) { + fn Convert[self: Self]() -> i32 { return self.n; } + } +} + +impl i32 as ImplicitAs(A) { + fn Convert[self: Self]() -> A { return {.n = self}; } +} + +base class B { + var a: A; + var b: i32; +} + +class C extends B { + var c: i32; +} + +fn Main() -> i32 { + var x: B = {.a = 1, .b = 2 as A}; + Print("1: {0}", x.a.n); + Print("2: {0}", x.b); + + x = {.a = 3, .b = 4 as A}; + Print("3: {0}", x.a.n); + Print("4: {0}", x.b); + + var y: C = {.base = {.a = 5, .b = 6 as A}, .c = 7 as A}; + Print("5: {0}", y.a.n); + Print("6: {0}", y.b); + Print("7: {0}", y.c); + return 0; +} diff --git a/explorer/testdata/generic_function/fail_wrong_variable_substituation.carbon b/explorer/testdata/generic_function/fail_wrong_variable_substituation.carbon index 4e56978a25fb..9b91e57c0fba 100644 --- a/explorer/testdata/generic_function/fail_wrong_variable_substituation.carbon +++ b/explorer/testdata/generic_function/fail_wrong_variable_substituation.carbon @@ -9,11 +9,11 @@ package Foo api; class X{ } +// CHECK:STDERR: COMPILATION ERROR: fail_wrong_variable_substituation.carbon:[[@LINE+1]]: value of generic binding T is not known fn Get[T:! ()](n: T) -> i32 { return 2; } fn Main() -> i32 { var x : X = {}; - // CHECK:STDERR: COMPILATION ERROR: fail_wrong_variable_substituation.carbon:[[@LINE+1]]: cannot convert deduced value class X for T to parameter type () Get(x); return 0; } diff --git a/explorer/testdata/struct/conversion.carbon b/explorer/testdata/struct/conversion.carbon new file mode 100644 index 000000000000..6cf96fcc911a --- /dev/null +++ b/explorer/testdata/struct/conversion.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 +// +// AUTOUPDATE +// CHECK:STDOUT: 1 +// CHECK:STDOUT: 2 +// CHECK:STDOUT: 3 +// CHECK:STDOUT: 4 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class A { + var n: i32; + impl as ImplicitAs(i32) { + fn Convert[self: Self]() -> i32 { return self.n; } + } +} +impl i32 as ImplicitAs(A) { + fn Convert[self: Self]() -> A { return {.n = self}; } +} + +fn Main() -> i32 { + var x: {.a: A, .b: i32} = {.a = 1, .b = 2 as A}; + Print("{0}", x.a.n); + Print("{0}", x.b); + + var y: {.a: i32, .b: A} = {.a = 3, .b = 4 as A}; + x = y; + Print("{0}", x.a.n); + Print("{0}", x.b); + return 0; +} diff --git a/explorer/testdata/tuple/fail_type_tuple_as_type.carbon b/explorer/testdata/tuple/fail_type_tuple_as_type.carbon index cd1065fbd269..a5ea8a5f3962 100644 --- a/explorer/testdata/tuple/fail_type_tuple_as_type.carbon +++ b/explorer/testdata/tuple/fail_type_tuple_as_type.carbon @@ -6,8 +6,8 @@ package ExplorerTest api; +// CHECK:STDERR: COMPILATION ERROR: fail_type_tuple_as_type.carbon:[[@LINE+1]]: value of generic binding T is not known fn F[T:! ((), ())](x: T) -> () { - // CHECK:STDERR: COMPILATION ERROR: fail_type_tuple_as_type.carbon:[[@LINE+1]]: only arrays and tuples can be indexed, found T return x[0]; } diff --git a/explorer/testdata/tuple/indirect_to_type.carbon b/explorer/testdata/tuple/indirect_to_type.carbon new file mode 100644 index 000000000000..d8f2c4ff1cef --- /dev/null +++ b/explorer/testdata/tuple/indirect_to_type.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 +// +// AUTOUPDATE +// CHECK:STDOUT: 1: 1 +// CHECK:STDOUT: 2: 2 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class A { + impl as ImplicitAs(type) { + fn Convert[self: Self]() -> type { return i32; } + } +} + +fn MakePair() -> (A, A) { return ({}, {}); } + +fn Main() -> i32 { + let b: MakePair() = (1, 2); + Print("1: {0}", b[0]); + Print("2: {0}", b[1]); + return 0; +}