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 <jperkins@google.com>
This commit is contained in:
Richard Smith
2023-01-18 17:36:55 -08:00
committed by GitHub
co-authored by Jon Ross-Perkins
parent 94872ef6da
commit 0d279b388a
13 changed files with 319 additions and 37 deletions
+6
View File
@@ -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:
+2
View File
@@ -751,6 +751,8 @@ class IntrinsicExpression : public Expression {
Alloc,
Dealloc,
Rand,
ImplicitAs,
ImplicitAsConvert,
IntEq,
StrEq,
StrCompare,
+33 -24
View File
@@ -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());
}
}
}
+20 -3
View File
@@ -128,13 +128,15 @@ auto ImplScope::Resolve(Nonnull<const Value*> 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<IntrinsicConstraint> intrinsics =
constraint->intrinsic_constraints();
llvm::ArrayRef<EqualityConstraint> equals =
constraint->equality_constraints();
llvm::ArrayRef<RewriteConstraint> rewrites =
constraint->rewrite_constraints();
if (!equals.empty() || !rewrites.empty()) {
if (!intrinsics.empty() || !equals.empty() || !rewrites.empty()) {
std::optional<Nonnull<const Witness*>> witness;
if (constraint->self_binding()->impl_binding()) {
witness = type_checker.MakeConstraintWitness(witnesses);
@@ -142,6 +144,21 @@ auto ImplScope::Resolve(Nonnull<const Value*> 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<const Value*> 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<const Value*> first =
+36
View File
@@ -1467,6 +1467,42 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
int r = (generator() % (high - low)) + low;
return todo_.FinishAction(arena_->New<IntValue>(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<GenericBinding>(
exp.source_loc(), ".Self",
arena_->New<TypeTypeLiteral>(exp.source_loc()));
auto* self = arena_->New<VariableType>(self_binding);
auto* impl_binding = arena_->New<ImplBinding>(
exp.source_loc(), self_binding, std::nullopt);
impl_binding->set_symbolic_identity(
arena_->New<BindingWitness>(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<ConstraintType>(
self_binding, std::vector<ImplConstraint>{},
std::vector<IntrinsicConstraint>{std::move(constraint)},
std::vector<EqualityConstraint>{},
std::vector<RewriteConstraint>{}, std::vector<LookupContext>{});
impl_binding->set_interface(result);
return todo_.FinishAction(result);
}
case IntrinsicExpression::Intrinsic::ImplicitAsConvert: {
CARBON_CHECK(args.size() == 2);
CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> 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<IntValue>(*args[0]).value();
+71 -3
View File
@@ -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<Nonnull<const InterfaceType*>> {
@@ -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<const Value*> 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<ConstraintType>(
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<GenericBinding*> self_binding_;
Nonnull<ImplBinding*> impl_binding_;
std::vector<ImplConstraint> impl_constraints_;
std::vector<IntrinsicConstraint> intrinsic_constraints_;
std::vector<EqualityConstraint> equality_constraints_;
std::vector<RewriteConstraint> rewrite_constraints_;
std::vector<LookupContext> lookup_contexts_;
@@ -3342,7 +3387,30 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
&args[1]->static_type(), impl_scope));
e->set_static_type(arena_->New<IntType>());
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<TypeType>());
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<const Value*> 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: {
+6
View File
@@ -92,6 +92,12 @@ class TypeChecker {
int impl_offset) const
-> Nonnull<const Witness*>;
// 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;
+17
View File
@@ -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<const Value*> argument : arguments) {
out << comma << *argument;
}
out << ")";
}
}
ContinuationValue::StackFragment::~StackFragment() {
CARBON_CHECK(reversed_todo_.empty())
<< "All StackFragments must be empty before the Carbon program ends.";
+37 -7
View File
@@ -935,6 +935,26 @@ struct ImplConstraint {
Nonnull<const InterfaceType*> 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<const Value*> 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<Nonnull<const Value*>> 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<const GenericBinding*> self_binding,
std::vector<ImplConstraint> impl_constraints,
std::vector<EqualityConstraint> equality_constraints,
std::vector<RewriteConstraint> rewrite_constraints,
std::vector<LookupContext> lookup_contexts)
explicit ConstraintType(
Nonnull<const GenericBinding*> self_binding,
std::vector<ImplConstraint> impl_constraints,
std::vector<IntrinsicConstraint> intrinsic_constraints,
std::vector<EqualityConstraint> equality_constraints,
std::vector<RewriteConstraint> rewrite_constraints,
std::vector<LookupContext> 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 <typename F>
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<const GenericBinding*> {
@@ -1018,6 +1043,10 @@ class ConstraintType : public Value {
return impl_constraints_;
}
auto intrinsic_constraints() const -> llvm::ArrayRef<IntrinsicConstraint> {
return intrinsic_constraints_;
}
auto equality_constraints() const -> llvm::ArrayRef<EqualityConstraint> {
return equality_constraints_;
}
@@ -1044,6 +1073,7 @@ class ConstraintType : public Value {
private:
Nonnull<const GenericBinding*> self_binding_;
std::vector<ImplConstraint> impl_constraints_;
std::vector<IntrinsicConstraint> intrinsic_constraints_;
std::vector<EqualityConstraint> equality_constraints_;
std::vector<RewriteConstraint> rewrite_constraints_;
std::vector<LookupContext> lookup_contexts_;
+19
View File
@@ -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();
}
+34
View File
@@ -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;
}
@@ -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;
}
+19
View File
@@ -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];
}