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; +}