Parcourir la source

Explorer: Support class subtyping (#2460)

Features:
* Adds support for [subtyping](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/classes.md#subtyping) for local variables, and function parameters

Changes:
* Update function parameter `Deduce` to handle subtyping
* Update `InstantiateType` to support `PointerType`
* Update `Convert` to support convertion from child class to a base class

Relates to #1881 

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Co-authored-by: Geoff Romer <gromer@google.com>
Adrien Leravat il y a 3 ans
Parent
commit
9299e51511

+ 41 - 1
explorer/interpreter/interpreter.cpp

@@ -641,6 +641,13 @@ auto Interpreter::InstantiateType(Nonnull<const Value*> type,
           EvalAssociatedConstant(cast<AssociatedConstant>(type), source_loc));
       return type_value;
     }
+    case Value::Kind::PointerType: {
+      const auto* ptr = cast<PointerType>(type);
+      CARBON_ASSIGN_OR_RETURN(
+          const auto* actual_type,
+          InstantiateType(&ptr->pointee_type(), source_loc));
+      return arena_->New<PointerType>(actual_type);
+    }
     default:
       return type;
   }
@@ -712,7 +719,6 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
     case Value::Kind::FunctionValue:
     case Value::Kind::DestructorValue:
     case Value::Kind::BoundMethodValue:
-    case Value::Kind::PointerValue:
     case Value::Kind::LValue:
     case Value::Kind::BoolValue:
     case Value::Kind::NominalClassValue:
@@ -863,6 +869,40 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
       }
       return Convert(value, destination_type, source_loc);
     }
+    case Value::Kind::PointerValue: {
+      if (destination_type->kind() != Value::Kind::PointerType ||
+          cast<PointerType>(destination_type)->pointee_type().kind() !=
+              Value::Kind::NominalClassType) {
+        // No conversion needed.
+        return value;
+      }
+
+      // Get pointee value.
+      const auto* src_ptr = cast<PointerValue>(value);
+      CARBON_ASSIGN_OR_RETURN(const auto* pointee,
+                              heap_.Read(src_ptr->address(), source_loc))
+      CARBON_CHECK(pointee->kind() == Value::Kind::NominalClassValue)
+          << "Unexpected pointer type";
+
+      const auto* dest_ptr = cast<PointerType>(destination_type);
+      std::optional<Nonnull<const NominalClassValue*>> class_subobj =
+          cast<NominalClassValue>(pointee);
+      auto new_addr = src_ptr->address();
+      while (class_subobj) {
+        if (TypeEqual(&(*class_subobj)->type(), &dest_ptr->pointee_type(),
+                      std::nullopt)) {
+          return arena_->New<PointerValue>(new_addr);
+        }
+        class_subobj = (*class_subobj)->base();
+        new_addr = new_addr.ElementAddress(
+            arena_->New<BaseElement>(&dest_ptr->pointee_type()));
+      }
+
+      // Unable to resolve, return as-is.
+      // TODO: Produce error instead once we can properly substitute
+      // parameterized types for pointers in function call parameters.
+      return value;
+    }
   }
 }
 

+ 28 - 2
explorer/interpreter/type_checker.cpp

@@ -577,6 +577,22 @@ auto TypeChecker::IsImplicitlyConvertible(
       // 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;
+      }
+      const auto* src_ptr = cast<PointerType>(source);
+      const auto* dest_ptr = cast<PointerType>(destination);
+      if (src_ptr->pointee_type().kind() != Value::Kind::NominalClassType ||
+          dest_ptr->pointee_type().kind() != Value::Kind::NominalClassType) {
+        break;
+      }
+      const auto& src_class = cast<NominalClassType>(src_ptr->pointee_type());
+      if (src_class.InheritsClass(&dest_ptr->pointee_type())) {
+        return true;
+      }
+      break;
+    }
     default:
       break;
   }
@@ -987,8 +1003,18 @@ auto TypeChecker::ArgumentDeduction::Deduce(Nonnull<const Value*> param,
       if (arg->kind() != Value::Kind::PointerType) {
         return handle_non_deduced_type();
       }
-      return Deduce(&cast<PointerType>(*param).pointee_type(),
-                    &cast<PointerType>(*arg).pointee_type(),
+      const auto& param_pointee = cast<PointerType>(param)->pointee_type();
+      const auto& arg_pointee = cast<PointerType>(arg)->pointee_type();
+      if (allow_implicit_conversion) {
+        // TODO: Change based on whether we want to allow
+        // deduce-from-base-class, for parametrized base class. See
+        // https://github.com/carbon-language/carbon-lang/issues/2464.
+        if (const auto* arg_class = dyn_cast<NominalClassType>(&arg_pointee);
+            arg_class && arg_class->InheritsClass(&param_pointee)) {
+          return Success();
+        }
+      }
+      return Deduce(&param_pointee, &arg_pointee,
                     /*allow_implicit_conversion=*/false);
     }
     // Nothing to do in the case for `auto`.

+ 16 - 0
explorer/interpreter/value.cpp

@@ -1183,4 +1183,20 @@ void ImplBinding::PrintID(llvm::raw_ostream& out) const {
   out << *type_var_ << " as " << **iface_;
 }
 
+auto NominalClassType::InheritsClass(Nonnull<const Value*> other) const
+    -> bool {
+  const auto* other_class = dyn_cast<NominalClassType>(other);
+  if (!other_class) {
+    return false;
+  }
+  std::optional<Nonnull<const NominalClassType*>> ancestor_class = this;
+  while (ancestor_class) {
+    if (TypeEqual(*ancestor_class, other_class, std::nullopt)) {
+      return true;
+    }
+    ancestor_class = (*ancestor_class)->base();
+  }
+  return false;
+}
+
 }  // namespace Carbon

+ 3 - 0
explorer/interpreter/value.h

@@ -778,6 +778,9 @@ class NominalClassType : public Value {
     return declaration_->type_params().has_value() && type_args().empty();
   }
 
+  // Returns whether this class is, or inherits `other`.
+  auto InheritsClass(Nonnull<const Value*> other) const -> bool;
+
  private:
   Nonnull<const ClassDeclaration*> declaration_;
   Nonnull<const Bindings*> bindings_ = Bindings::None();

+ 42 - 0
explorer/testdata/class/abstract_class_subtyping.carbon

@@ -0,0 +1,42 @@
+// 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: (*c).a: 1
+// CHECK:STDOUT: (*c).Foo(): 1
+// CHECK:STDOUT: (*c).Bar(): 1
+// CHECK:STDOUT: result: 0
+
+package ExplorerTest api;
+
+abstract class C {
+  var a: i32;
+  fn Foo[self: Self]() -> i32 {
+    return 1;
+  }
+  fn Bar() -> i32 {
+    return 1;
+  }
+}
+
+class D extends C {
+  var b: i32;
+  fn Foo[self: Self]() -> i32 {
+    return 2;
+  }
+  fn Bar() -> i32 {
+    return 2;
+  }
+}
+
+fn Main() -> i32 {
+  var d: D = { .base = {.a = 1}, .b = 2 };
+  var c: C* = &d;
+  Print("(*c).a: {0}", (*c).a);
+  Print("(*c).Foo(): {0}", (*c).Foo());
+  Print("(*c).Bar(): {0}", (*c).Bar());
+  return 0;
+}

+ 31 - 0
explorer/testdata/class/class_subtyping.carbon

@@ -0,0 +1,31 @@
+// 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: (*c).a: 1
+// CHECK:STDOUT: Foo(&d): 1
+// CHECK:STDOUT: result: 0
+
+package ExplorerTest api;
+
+base class C {
+  var a: i32;
+}
+
+class D extends C {
+}
+
+fn Foo(c: C*) -> i32 {
+  return (*c).a;
+}
+
+fn Main() -> i32 {
+  var d: D = { .base = {.a = 1} };
+  var c: C* = &d;
+  Print("(*c).a: {0}", (*c).a);
+  Print("Foo(&d): {0}", Foo(&d));
+  return 0;
+}

+ 4 - 7
explorer/testdata/class/fail_abstract_class_subtyping.carbon → explorer/testdata/class/fail_invalid_subtyping.carbon

@@ -8,18 +8,15 @@
 
 package ExplorerTest api;
 
-abstract class C {
-  var a: i32;
+base class C {
 }
 
-class D extends C {
-  var b: i32;
+class D {
 }
 
 fn Main() -> i32 {
-  var d: D = { .base = {.a = 1}, .b = 2 };
-  // Not supported yet.
-  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_abstract_class_subtyping.carbon:[[@LINE+1]]: type error in name binding: 'class D*' is not implicitly convertible to 'class C*'
+  var d: D = {};
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/class/fail_invalid_subtyping.carbon:[[@LINE+1]]: type error in name binding: 'class D*' is not implicitly convertible to 'class C*'
   var c: C* = &d;
   return 0;
 }

+ 42 - 0
explorer/testdata/class/non_virtual_dispatch.carbon

@@ -0,0 +1,42 @@
+// 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: (*c).a: 1
+// CHECK:STDOUT: (*c).Foo(): 1
+// CHECK:STDOUT: (*c).Bar(): 1
+// CHECK:STDOUT: result: 0
+
+package ExplorerTest api;
+
+base class C {
+  var a: i32;
+  fn Foo[self: Self]() -> i32 {
+    return 1;
+  }
+  fn Bar() -> i32 {
+    return 1;
+  }
+}
+
+class D extends C {
+  var b: i32;
+  fn Foo[self: Self]() -> i32 {
+    return 2;
+  }
+  fn Bar() -> i32 {
+    return 2;
+  }
+}
+
+fn Main() -> i32 {
+  var d: D = { .base = {.a = 1}, .b = 2 };
+  var c: C* = &d;
+  Print("(*c).a: {0}", (*c).a);
+  Print("(*c).Foo(): {0}", (*c).Foo());
+  Print("(*c).Bar(): {0}", (*c).Bar());
+  return 0;
+}

+ 19 - 0
explorer/testdata/pointer/fail_invalid_ptr_conversion1.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: %{not} %{explorer-run}
+// RUN: %{not} %{explorer-run-trace}
+
+package ExplorerTest api;
+
+class A {}
+
+fn Main() -> i32 {
+  var a: A = {};
+  var b: A* = &a;
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/pointer/fail_invalid_ptr_conversion1.carbon:[[@LINE+1]]: type error in name binding: 'class A*' is not implicitly convertible to 'i32'
+  var c: i32 = b;
+  return 1;
+}

+ 19 - 0
explorer/testdata/pointer/fail_invalid_ptr_conversion2.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: %{not} %{explorer-run}
+// RUN: %{not} %{explorer-run-trace}
+
+package ExplorerTest api;
+
+class A {}
+
+fn Main() -> i32 {
+  var a: i32 = 0;
+  var b: i32* = &a;
+  // CHECK:STDERR: COMPILATION ERROR: {{.*}}/explorer/testdata/pointer/fail_invalid_ptr_conversion2.carbon:[[@LINE+1]]: type error in name binding: 'i32*' is not implicitly convertible to 'class A*'
+  var c: A* = b;
+  return 1;
+}