mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 19:20:13 +01:00
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:
co-authored by
Jon Ross-Perkins
Geoff Romer
parent
8e6d48004b
commit
9299e51511
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(¶m_pointee)) {
|
||||
return Success();
|
||||
}
|
||||
}
|
||||
return Deduce(¶m_pointee, &arg_pointee,
|
||||
/*allow_implicit_conversion=*/false);
|
||||
}
|
||||
// Nothing to do in the case for `auto`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,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
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user