From 0d279b388acfb1e282d4f540b398b5e290a61d48 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 18 Jan 2023 17:36:55 -0800 Subject: [PATCH] Make built-in conversions visible to `ImplicitAs`. (#2525) Add a blanket `ImplicitAs` implementation to perform the conversions that explorer can perform as built-in conversions. This allows those conversions to be detected by constraints and to be used as part of other user-defined conversions. For now, a single monolithic conversion is exposed. I intend to split this up into multiple smaller conversion kinds for each kind of conversion in a follow-up change. Co-authored-by: Jon Ross-Perkins --- explorer/ast/expression.cpp | 6 ++ explorer/ast/expression.h | 2 + explorer/data/prelude.carbon | 57 ++++++++------ explorer/interpreter/impl_scope.cpp | 23 +++++- explorer/interpreter/interpreter.cpp | 36 +++++++++ explorer/interpreter/type_checker.cpp | 74 ++++++++++++++++++- explorer/interpreter/type_checker.h | 6 ++ explorer/interpreter/value.cpp | 17 +++++ explorer/interpreter/value.h | 44 +++++++++-- explorer/testdata/as/struct_as_class.carbon | 19 +++++ .../testdata/class/pointer_conversion.carbon | 34 +++++++++ .../member_access/convert_lhs_struct.carbon | 19 +++++ explorer/testdata/tuple/to_type.carbon | 19 +++++ 13 files changed, 319 insertions(+), 37 deletions(-) create mode 100644 explorer/testdata/as/struct_as_class.carbon create mode 100644 explorer/testdata/class/pointer_conversion.carbon create mode 100644 explorer/testdata/member_access/convert_lhs_struct.carbon create mode 100644 explorer/testdata/tuple/to_type.carbon diff --git a/explorer/ast/expression.cpp b/explorer/ast/expression.cpp index 2b3968d50a63..d39717103fe5 100644 --- a/explorer/ast/expression.cpp +++ b/explorer/ast/expression.cpp @@ -31,6 +31,8 @@ auto IntrinsicExpression::FindIntrinsic(std::string_view name, {"new", Intrinsic::Alloc}, {"delete", Intrinsic::Dealloc}, {"rand", Intrinsic::Rand}, + {"implicit_as", Intrinsic::ImplicitAs}, + {"implicit_as_convert", Intrinsic::ImplicitAsConvert}, {"int_eq", Intrinsic::IntEq}, {"int_compare", Intrinsic::IntCompare}, {"int_bit_complement", Intrinsic::IntBitComplement}, @@ -61,6 +63,10 @@ auto IntrinsicExpression::name() const -> std::string_view { return "__intrinsic_delete"; case IntrinsicExpression::Intrinsic::Rand: return "__intrinsic_rand"; + case IntrinsicExpression::Intrinsic::ImplicitAs: + return "__intrinsic_implicit_as"; + case IntrinsicExpression::Intrinsic::ImplicitAsConvert: + return "__intrinsic_implicit_as_convert"; case IntrinsicExpression::Intrinsic::IntEq: return "__intrinsic_int_eq"; case IntrinsicExpression::Intrinsic::IntCompare: diff --git a/explorer/ast/expression.h b/explorer/ast/expression.h index e221637ab83c..b37fb0d3c922 100644 --- a/explorer/ast/expression.h +++ b/explorer/ast/expression.h @@ -751,6 +751,8 @@ class IntrinsicExpression : public Expression { Alloc, Dealloc, Rand, + ImplicitAs, + ImplicitAsConvert, IntEq, StrEq, StrCompare, diff --git a/explorer/data/prelude.carbon b/explorer/data/prelude.carbon index fba3f5e667ac..0200ac52f965 100644 --- a/explorer/data/prelude.carbon +++ b/explorer/data/prelude.carbon @@ -30,33 +30,42 @@ impl forall [U:! type] U as __EqualConverter where .T = U { fn Convert(u: U) -> U { return 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); } -} +__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, T:! __intrinsic_implicit_as(U)] T as ImplicitAs(U) { + fn Convert[self: Self]() -> U { return __intrinsic_implicit_as_convert(self, U); } + } -// 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(),); + // 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); } } -} -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()); + + // 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(),); + } } -} -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()); + 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, 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()); + } } } diff --git a/explorer/interpreter/impl_scope.cpp b/explorer/interpreter/impl_scope.cpp index cf9e9dd78336..2ed0ad6c1256 100644 --- a/explorer/interpreter/impl_scope.cpp +++ b/explorer/interpreter/impl_scope.cpp @@ -128,13 +128,15 @@ auto ImplScope::Resolve(Nonnull constraint_type, witnesses.push_back(result); } - // Check that all equality and rewrite constraints are satisfied in this - // scope. + // Check that all intrinsic, equality, and rewrite constraints + // are satisfied in this scope. + llvm::ArrayRef intrinsics = + constraint->intrinsic_constraints(); llvm::ArrayRef equals = constraint->equality_constraints(); llvm::ArrayRef rewrites = constraint->rewrite_constraints(); - if (!equals.empty() || !rewrites.empty()) { + if (!intrinsics.empty() || !equals.empty() || !rewrites.empty()) { std::optional> witness; if (constraint->self_binding()->impl_binding()) { witness = type_checker.MakeConstraintWitness(witnesses); @@ -142,6 +144,21 @@ auto ImplScope::Resolve(Nonnull constraint_type, Bindings local_bindings = bindings; local_bindings.Add(constraint->self_binding(), impl_type, witness); SingleStepEqualityContext equality_ctx(this); + for (const auto& intrinsic : intrinsics) { + IntrinsicConstraint converted = { + .type = type_checker.Substitute(local_bindings, intrinsic.type), + .kind = intrinsic.kind, + .arguments = {}}; + converted.arguments.reserve(intrinsic.arguments.size()); + for (Nonnull argument : intrinsic.arguments) { + converted.arguments.push_back( + type_checker.Substitute(local_bindings, argument)); + } + if (!type_checker.IsIntrinsicConstraintSatisfied(converted, *this)) { + return ProgramError(source_loc) + << "constraint requires that " << converted; + } + } for (const auto& equal : equals) { auto it = equal.values.begin(); Nonnull first = diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 2db0232c3b87..769b6719a65d 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -1467,6 +1467,42 @@ auto Interpreter::StepExp() -> ErrorOr { int r = (generator() % (high - low)) + low; return todo_.FinishAction(arena_->New(r)); } + case IntrinsicExpression::Intrinsic::ImplicitAs: { + CARBON_CHECK(args.size() == 1); + // Build a constraint type that constrains its .Self type to satisfy + // the "ImplicitAs" intrinsic constraint. This involves creating a + // number of objects that all point to each other. + // TODO: Factor out a simple version of ConstraintTypeBuilder and use + // it from here. + auto* self_binding = arena_->New( + exp.source_loc(), ".Self", + arena_->New(exp.source_loc())); + auto* self = arena_->New(self_binding); + auto* impl_binding = arena_->New( + exp.source_loc(), self_binding, std::nullopt); + impl_binding->set_symbolic_identity( + arena_->New(impl_binding)); + self_binding->set_symbolic_identity(self); + self_binding->set_value(self); + self_binding->set_impl_binding(impl_binding); + IntrinsicConstraint constraint = { + .type = self, + .kind = IntrinsicConstraint::ImplicitAs, + .arguments = args}; + auto* result = arena_->New( + self_binding, std::vector{}, + std::vector{std::move(constraint)}, + std::vector{}, + std::vector{}, std::vector{}); + impl_binding->set_interface(result); + 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); + } case IntrinsicExpression::Intrinsic::IntEq: { CARBON_CHECK(args.size() == 2); auto lhs = cast(*args[0]).value(); diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index d8ab555ad152..1bcd3d9b17a7 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -729,6 +729,20 @@ auto TypeChecker::ImplicitlyConvert(std::string_view context, return *converted; } +auto TypeChecker::IsIntrinsicConstraintSatisfied( + const IntrinsicConstraint& constraint, const ImplScope& impl_scope) const + -> bool { + // TODO: Check to see if this constraint is known in the current impl scope. + switch (constraint.kind) { + 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); + } +} + auto TypeChecker::GetBuiltinInterfaceType(SourceLocation source_loc, BuiltinInterfaceName interface) const -> ErrorOr> { @@ -1397,6 +1411,12 @@ class TypeChecker::ConstraintTypeBuilder { return impl_constraints_.size() - 1; } + // Adds an intrinsic constraint, if not already present. + void AddIntrinsicConstraint(IntrinsicConstraint intrinsic) { + // TODO: Consider performing deduplication. + intrinsic_constraints_.push_back(std::move(intrinsic)); + } + // Adds an equality constraint -- `A == B`. void AddEqualityConstraint(EqualityConstraint equal) { if (equal.values.size() < 2) { @@ -1514,6 +1534,21 @@ class TypeChecker::ConstraintTypeBuilder { AddEqualityConstraint({.values = std::move(values)}); } + for (const auto& intrinsic_constraint : + constraint->intrinsic_constraints()) { + IntrinsicConstraint converted = { + .type = type_checker.Substitute(local_bindings, + intrinsic_constraint.type), + .kind = intrinsic_constraint.kind, + .arguments = {}}; + converted.arguments.reserve(intrinsic_constraint.arguments.size()); + for (Nonnull argument : intrinsic_constraint.arguments) { + converted.arguments.push_back( + type_checker.Substitute(local_bindings, argument)); + } + AddIntrinsicConstraint(std::move(converted)); + } + if (add_lookup_contexts) { for (const auto& lookup_context : constraint->lookup_contexts()) { AddLookupContext({.context = type_checker.Substitute( @@ -1578,8 +1613,8 @@ class TypeChecker::ConstraintTypeBuilder { // Create the new type. auto* result = arena_->New( self_binding_, std::move(impl_constraints_), - std::move(equality_constraints_), std::move(rewrite_constraints_), - std::move(lookup_contexts_)); + std::move(intrinsic_constraints_), std::move(equality_constraints_), + std::move(rewrite_constraints_), std::move(lookup_contexts_)); // Update the impl binding to denote the constraint type itself. impl_binding_->set_interface(result); return result; @@ -1740,6 +1775,15 @@ class TypeChecker::ConstraintTypeBuilder { type_checker.RebuildValue(impl_constraint.interface)); } + // Apply rewrites throughout intrinsic constraints. + for (auto& intrinsic_constraint : intrinsic_constraints_) { + intrinsic_constraint.type = + type_checker.RebuildValue(intrinsic_constraint.type); + for (auto& argument : intrinsic_constraint.arguments) { + argument = type_checker.RebuildValue(argument); + } + } + // Apply rewrites throughout equality constraints. for (auto& equality_constraint : equality_constraints_) { for (auto*& value : equality_constraint.values) { @@ -1758,6 +1802,7 @@ class TypeChecker::ConstraintTypeBuilder { Nonnull self_binding_; Nonnull impl_binding_; std::vector impl_constraints_; + std::vector intrinsic_constraints_; std::vector equality_constraints_; std::vector rewrite_constraints_; std::vector lookup_contexts_; @@ -3342,7 +3387,30 @@ auto TypeChecker::TypeCheckExp(Nonnull e, &args[1]->static_type(), impl_scope)); e->set_static_type(arena_->New()); - + e->set_value_category(ValueCategory::Let); + return Success(); + } + case IntrinsicExpression::Intrinsic::ImplicitAs: { + if (args.size() != 1) { + return ProgramError(e->source_loc()) + << "__intrinsic_implicit_as takes 1 argument"; + } + CARBON_RETURN_IF_ERROR(TypeCheckTypeExp(args[0], impl_scope)); + e->set_static_type(arena_->New()); + e->set_value_category(ValueCategory::Let); + return Success(); + } + case IntrinsicExpression::Intrinsic::ImplicitAsConvert: { + if (args.size() != 2) { + return ProgramError(e->source_loc()) + << "__intrinsic_implicit_as_convert takes 2 arguments"; + } + 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_value_category(ValueCategory::Let); return Success(); } case IntrinsicExpression::Intrinsic::IntEq: { diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index 491ce095bbc4..2b016e890b9f 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -92,6 +92,12 @@ class TypeChecker { int impl_offset) const -> Nonnull; + // Determine whether the given intrinsic constraint is known to be satisfied + // in the given scope. + auto IsIntrinsicConstraintSatisfied(const IntrinsicConstraint& constraint, + const ImplScope& impl_scope) const + -> bool; + private: class ConstraintTypeBuilder; class SubstitutedGenericBindings; diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index 5a3ab1040632..fcf31350eb57 100644 --- a/explorer/interpreter/value.cpp +++ b/explorer/interpreter/value.cpp @@ -698,6 +698,23 @@ void Value::Print(llvm::raw_ostream& out) const { } } +void IntrinsicConstraint::Print(llvm::raw_ostream& out) const { + out << *type << " is "; + switch (kind) { + case IntrinsicConstraint::ImplicitAs: + out << "__intrinsic_implicit_as"; + break; + } + if (!arguments.empty()) { + out << "("; + llvm::ListSeparator comma; + for (Nonnull argument : arguments) { + out << comma << *argument; + } + out << ")"; + } +} + ContinuationValue::StackFragment::~StackFragment() { CARBON_CHECK(reversed_todo_.empty()) << "All StackFragments must be empty before the Carbon program ends."; diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index 4867dd02849f..57c2b910e6f6 100644 --- a/explorer/interpreter/value.h +++ b/explorer/interpreter/value.h @@ -935,6 +935,26 @@ struct ImplConstraint { Nonnull interface; }; +// A constraint that requires an intrinsic property of a type. +struct IntrinsicConstraint { + // Print the intrinsic constraint. + void Print(llvm::raw_ostream& out) const; + + // The type that is required to satisfy the intrinsic property. + Nonnull type; + // The kind of the intrinsic property. + enum Kind { + // `type` intrinsically implicitly converts to `parameters[0]`. + // TODO: Split ImplicitAs into more specific constraints (such as + // derived-to-base pointer conversions). + ImplicitAs, + }; + Kind kind; + // Arguments for the intrinsic property. The meaning of these depends on + // `kind`. + std::vector> arguments; +}; + // A constraint that a collection of values are known to be the same. struct EqualityConstraint { // Visit the values in this equality constraint that are a single step away @@ -979,6 +999,8 @@ struct LookupContext { // // * A collection of (type, interface) pairs for interfaces that are known to // be implemented by a type satisfying the constraint. +// * A collection of (type, intrinsic) pairs for intrinsic properties that are +// known to be satisfied by a type satisfying the constraint. // * A collection of sets of values, typically associated constants, that are // known to be the same. // * A collection of contexts in which member name lookups will be performed @@ -988,14 +1010,17 @@ struct LookupContext { // `VariableType` naming the `self_binding`. class ConstraintType : public Value { public: - explicit ConstraintType(Nonnull self_binding, - std::vector impl_constraints, - std::vector equality_constraints, - std::vector rewrite_constraints, - std::vector lookup_contexts) + explicit ConstraintType( + Nonnull self_binding, + std::vector impl_constraints, + std::vector intrinsic_constraints, + std::vector equality_constraints, + std::vector rewrite_constraints, + std::vector lookup_contexts) : Value(Kind::ConstraintType), self_binding_(self_binding), impl_constraints_(std::move(impl_constraints)), + intrinsic_constraints_(std::move(intrinsic_constraints)), equality_constraints_(std::move(equality_constraints)), rewrite_constraints_(std::move(rewrite_constraints)), lookup_contexts_(std::move(lookup_contexts)) {} @@ -1006,8 +1031,8 @@ class ConstraintType : public Value { template auto Decompose(F f) const { - return f(self_binding_, impl_constraints_, equality_constraints_, - rewrite_constraints_, lookup_contexts_); + return f(self_binding_, impl_constraints_, intrinsic_constraints_, + equality_constraints_, rewrite_constraints_, lookup_contexts_); } auto self_binding() const -> Nonnull { @@ -1018,6 +1043,10 @@ class ConstraintType : public Value { return impl_constraints_; } + auto intrinsic_constraints() const -> llvm::ArrayRef { + return intrinsic_constraints_; + } + auto equality_constraints() const -> llvm::ArrayRef { return equality_constraints_; } @@ -1044,6 +1073,7 @@ class ConstraintType : public Value { private: Nonnull self_binding_; std::vector impl_constraints_; + std::vector intrinsic_constraints_; std::vector equality_constraints_; std::vector rewrite_constraints_; std::vector lookup_contexts_; diff --git a/explorer/testdata/as/struct_as_class.carbon b/explorer/testdata/as/struct_as_class.carbon new file mode 100644 index 000000000000..2653ff70904d --- /dev/null +++ b/explorer/testdata/as/struct_as_class.carbon @@ -0,0 +1,19 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// RUN: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: result: 5 + +package ExplorerTest api; + +class A { + var n: i32; + fn Get[self: Self]() -> i32 { return self.n; } +} + +fn Main() -> i32 { + return ({.n = 5} as A).Get(); +} diff --git a/explorer/testdata/class/pointer_conversion.carbon b/explorer/testdata/class/pointer_conversion.carbon new file mode 100644 index 000000000000..cac15543e0d6 --- /dev/null +++ b/explorer/testdata/class/pointer_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 +// RUN: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: 1 +// CHECK:STDOUT: 2 +// CHECK:STDOUT: 3 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +base class A { + var a: i32; +} + +base class B extends A { + var b: i32; +} + +class C extends B { + var c: i32; +} + +fn Main() -> i32 { + var c: C = {.base = {.base = {.a = 1}, .b = 2}, .c = 3}; + let (pa: A*, pb: B*, pc: C*) = (&c, &c, &c); + Print("{0}", pa->a); + Print("{0}", pb->b); + Print("{0}", pc->c); + return 0; +} diff --git a/explorer/testdata/member_access/convert_lhs_struct.carbon b/explorer/testdata/member_access/convert_lhs_struct.carbon new file mode 100644 index 000000000000..827e8b94f7be --- /dev/null +++ b/explorer/testdata/member_access/convert_lhs_struct.carbon @@ -0,0 +1,19 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// RUN: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: result: 3 + +package Foo api; + +class X { + fn F[self: Self](o: Self) -> Self { return {.n = self.n + o.n}; } + var n: i32; +} + +fn Main() -> i32 { + return {.n = 1}.(X.F)({.n = 2}).n; +} diff --git a/explorer/testdata/tuple/to_type.carbon b/explorer/testdata/tuple/to_type.carbon new file mode 100644 index 000000000000..6a933ba285ae --- /dev/null +++ b/explorer/testdata/tuple/to_type.carbon @@ -0,0 +1,19 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE +// RUN: %{explorer-run} +// RUN: %{explorer-run-trace} +// CHECK:STDOUT: result: 4 + +package ExplorerTest api; + +fn F[T:! ImplicitAs(type)](x: T) -> type { return x; } + +fn Main() -> i32 { + var v: (i32, i32) as type = (1, 2); + var w: F((i32, i32)) = (3, 4); + v = w; + return v[1]; +}