Просмотр исходного кода

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>
Richard Smith 3 лет назад
Родитель
Сommit
0d279b388a

+ 6 - 0
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:

+ 2 - 0
explorer/ast/expression.h

@@ -751,6 +751,8 @@ class IntrinsicExpression : public Expression {
     Alloc,
     Dealloc,
     Rand,
+    ImplicitAs,
+    ImplicitAsConvert,
     IntEq,
     StrEq,
     StrCompare,

+ 34 - 25
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); }
-}
-
-// 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(),);
+__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); }
+  }
+
+  // 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); }
+  }
+
+  // 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, 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:! 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());
+  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
explorer/interpreter/impl_scope.cpp

@@ -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 - 0
explorer/interpreter/interpreter.cpp

@@ -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
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<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 - 0
explorer/interpreter/type_checker.h

@@ -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 - 0
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<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
explorer/interpreter/value.h

@@ -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 - 0
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();
+}

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

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

+ 19 - 0
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];
+}