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>
This commit is contained in:
Adrien Leravat
2022-12-19 11:32:32 -08:00
committed by GitHub
co-authored by Jon Ross-Perkins Geoff Romer
parent 8e6d48004b
commit 9299e51511
10 changed files with 245 additions and 10 deletions
+41 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
@@ -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
View File
@@ -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;
}
@@ -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;
}
@@ -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;
}