diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index e831bc871187..99f936475a9b 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -641,6 +641,13 @@ auto Interpreter::InstantiateType(Nonnull type, EvalAssociatedConstant(cast(type), source_loc)); return type_value; } + case Value::Kind::PointerType: { + const auto* ptr = cast(type); + CARBON_ASSIGN_OR_RETURN( + const auto* actual_type, + InstantiateType(&ptr->pointee_type(), source_loc)); + return arena_->New(actual_type); + } default: return type; } @@ -712,7 +719,6 @@ auto Interpreter::Convert(Nonnull 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 value, } return Convert(value, destination_type, source_loc); } + case Value::Kind::PointerValue: { + if (destination_type->kind() != Value::Kind::PointerType || + cast(destination_type)->pointee_type().kind() != + Value::Kind::NominalClassType) { + // No conversion needed. + return value; + } + + // Get pointee value. + const auto* src_ptr = cast(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(destination_type); + std::optional> class_subobj = + cast(pointee); + auto new_addr = src_ptr->address(); + while (class_subobj) { + if (TypeEqual(&(*class_subobj)->type(), &dest_ptr->pointee_type(), + std::nullopt)) { + return arena_->New(new_addr); + } + class_subobj = (*class_subobj)->base(); + new_addr = new_addr.ElementAddress( + arena_->New(&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; + } } } diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index ffa0318e46ba..cbadbc711e04 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/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(source); + const auto* dest_ptr = cast(destination); + if (src_ptr->pointee_type().kind() != Value::Kind::NominalClassType || + dest_ptr->pointee_type().kind() != Value::Kind::NominalClassType) { + break; + } + const auto& src_class = cast(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 param, if (arg->kind() != Value::Kind::PointerType) { return handle_non_deduced_type(); } - return Deduce(&cast(*param).pointee_type(), - &cast(*arg).pointee_type(), + const auto& param_pointee = cast(param)->pointee_type(); + const auto& arg_pointee = cast(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(&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`. diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index 479004f95657..9067afbdf61e 100644 --- a/explorer/interpreter/value.cpp +++ b/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 other) const + -> bool { + const auto* other_class = dyn_cast(other); + if (!other_class) { + return false; + } + std::optional> 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 diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index ea4a503eee27..46d5daee6e1d 100644 --- a/explorer/interpreter/value.h +++ b/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 other) const -> bool; + private: Nonnull declaration_; Nonnull bindings_ = Bindings::None(); diff --git a/explorer/testdata/class/abstract_class_subtyping.carbon b/explorer/testdata/class/abstract_class_subtyping.carbon new file mode 100644 index 000000000000..82934b0cef9f --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/class/class_subtyping.carbon b/explorer/testdata/class/class_subtyping.carbon new file mode 100644 index 000000000000..dde7b8fdc4a7 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/class/fail_abstract_class_subtyping.carbon b/explorer/testdata/class/fail_invalid_subtyping.carbon similarity index 61% rename from explorer/testdata/class/fail_abstract_class_subtyping.carbon rename to explorer/testdata/class/fail_invalid_subtyping.carbon index 0789fc136b15..2872d389d90a 100644 --- a/explorer/testdata/class/fail_abstract_class_subtyping.carbon +++ b/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; } diff --git a/explorer/testdata/class/non_virtual_dispatch.carbon b/explorer/testdata/class/non_virtual_dispatch.carbon new file mode 100644 index 000000000000..7ec4252fed6f --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/pointer/fail_invalid_ptr_conversion1.carbon b/explorer/testdata/pointer/fail_invalid_ptr_conversion1.carbon new file mode 100644 index 000000000000..1287e266033b --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/pointer/fail_invalid_ptr_conversion2.carbon b/explorer/testdata/pointer/fail_invalid_ptr_conversion2.carbon new file mode 100644 index 000000000000..b2710abd5c08 --- /dev/null +++ b/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; +}