diff --git a/explorer/interpreter/BUILD b/explorer/interpreter/BUILD index 0363b3587d56..e4a098eeb5d8 100644 --- a/explorer/interpreter/BUILD +++ b/explorer/interpreter/BUILD @@ -139,10 +139,12 @@ cc_library( ":heap", ":stack", "//common:check", + "//common:error", "//common:ostream", "//explorer/ast", "//explorer/common:arena", "//explorer/common:error_builders", + "//explorer/common:source_location", "@llvm-project//llvm:Support", ], ) diff --git a/explorer/interpreter/action.h b/explorer/interpreter/action.h index 87b09d28926b..23d037471b95 100644 --- a/explorer/interpreter/action.h +++ b/explorer/interpreter/action.h @@ -281,7 +281,7 @@ class DestroyAction : public Action { // lvalue: Address of the object to be destroyed // value: The value to be destroyed // In most cases the lvalue address points to value - // In the case that the member of a class is to be destroyed, points + // In the case that the member of a class is to be destroyed, // the lvalue points to the address of the class object // and the value is the member of the class explicit DestroyAction(Nonnull lvalue, diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 6d8610deb783..9da94245dd5b 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -15,11 +16,13 @@ #include #include "common/check.h" +#include "common/error.h" #include "explorer/ast/declaration.h" #include "explorer/ast/element.h" #include "explorer/ast/expression.h" #include "explorer/common/arena.h" #include "explorer/common/error_builders.h" +#include "explorer/common/source_location.h" #include "explorer/interpreter/action.h" #include "explorer/interpreter/action_stack.h" #include "explorer/interpreter/stack.h" @@ -1991,64 +1994,83 @@ auto Interpreter::StepDeclaration() -> ErrorOr { } auto Interpreter::StepDestroy() -> ErrorOr { - // TODO: find a way to avoid dyn_cast in this code, and instead use static - // type information the way the compiler would. - Action& act = todo_.CurrentAction(); - DestroyAction& destroy_act = cast(act); - if (act.pos() == 0) { - if (const auto* class_obj = - dyn_cast(destroy_act.value())) { - const auto& class_type = cast(class_obj->type()); - const auto& class_dec = class_type.declaration(); - if (class_dec.destructor().has_value()) { - return CallDestructor(*class_dec.destructor(), class_obj); - } - } - } - - if (const auto* tuple = dyn_cast(destroy_act.value())) { - if (tuple->elements().size() > 0) { - int index = tuple->elements().size() - act.pos() - 1; - if (index >= 0) { - const auto& item = tuple->elements()[index]; - if (const auto* class_obj = dyn_cast(item)) { - const auto& class_type = cast(class_obj->type()); - const auto& class_dec = class_type.declaration(); - if (class_dec.destructor().has_value()) { - return CallDestructor(*class_dec.destructor(), class_obj); - } + const Action& act = todo_.CurrentAction(); + const auto& destroy_act = cast(act); + switch (destroy_act.value()->kind()) { + case Value::Kind::NominalClassValue: { + const auto* class_obj = cast(destroy_act.value()); + const auto& class_decl = + cast(class_obj->type()).declaration(); + const int member_count = class_decl.members().size(); + if (act.pos() == 0) { + // Run the destructor, if there is one. + if (auto destructor = class_decl.destructor()) { + return CallDestructor(*destructor, class_obj); + } else { + return todo_.RunAgain(); } - if (item->kind() == Value::Kind::TupleValue) { - return todo_.Spawn( - std::make_unique(destroy_act.lvalue(), item)); - } - // Type of tuple element is integral type e.g. i32 - // or the type has no destructor - } - } - } - - if (act.pos() > 0) { - if (const auto* class_obj = - dyn_cast(destroy_act.value())) { - const auto& class_type = cast(class_obj->type()); - const auto& class_dec = class_type.declaration(); - int index = class_dec.members().size() - act.pos(); - if (index >= 0 && index < static_cast(class_dec.members().size())) { - const auto& member = class_dec.members()[index]; + } else if (act.pos() <= member_count) { + // Destroy members. + const int index = class_decl.members().size() - act.pos(); + const auto& member = class_decl.members()[index]; if (const auto* var = dyn_cast(member)) { - Address object = destroy_act.lvalue()->address(); - Address mem = object.ElementAddress(arena_->New(var)); - SourceLocation source_loc("destructor", 1); - auto v = heap_.Read(mem, source_loc); - return todo_.Spawn( - std::make_unique(destroy_act.lvalue(), *v)); + const Address object = destroy_act.lvalue()->address(); + const Address var_addr = + object.ElementAddress(arena_->New(var)); + const auto v = heap_.Read(var_addr, SourceLocation("destructor", 1)); + CARBON_CHECK(v.ok()) + << "Failed to read member `" << var->binding().name() + << "` from class `" << class_decl.name() << "`"; + return todo_.Spawn(std::make_unique( + arena_->New(var_addr), *v)); + } else { + return todo_.RunAgain(); } + } else if (act.pos() == member_count + 1) { + // Destroy the parent, if there is one. + if (auto base = class_obj->base()) { + const Address obj_addr = destroy_act.lvalue()->address(); + const Address base_addr = + obj_addr.ElementAddress(arena_->New(class_obj)); + return todo_.Spawn(std::make_unique( + arena_->New(base_addr), base.value())); + } else { + return todo_.RunAgain(); + } + } else { + todo_.Pop(); + return Success(); } } + case Value::Kind::TupleValue: { + const auto* tuple = cast(destroy_act.value()); + const auto element_count = tuple->elements().size(); + if (static_cast(act.pos()) < element_count) { + const size_t index = element_count - act.pos() - 1; + const auto& item = tuple->elements()[index]; + const auto object_addr = destroy_act.lvalue()->address(); + Address field_address = object_addr.ElementAddress( + arena_->New(index, item)); + if (item->kind() == Value::Kind::NominalClassValue || + item->kind() == Value::Kind::TupleValue) { + return todo_.Spawn(std::make_unique( + arena_->New(field_address), item)); + } else { + // The tuple element's type is an integral type (e.g., i32) + // or the type doesn't support destruction. + return todo_.RunAgain(); + } + } else { + todo_.Pop(); + return Success(); + } + } + default: + // These declarations have no run-time effects. + todo_.Pop(); + return Success(); } - todo_.Pop(); - return Success(); + CARBON_FATAL() << "Unreachable"; } auto Interpreter::StepCleanUp() -> ErrorOr { diff --git a/explorer/testdata/destructor/destroy_base_class.carbon b/explorer/testdata/destructor/destroy_base_class.carbon new file mode 100644 index 000000000000..67b5eb8804f9 --- /dev/null +++ b/explorer/testdata/destructor/destroy_base_class.carbon @@ -0,0 +1,35 @@ +// 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: DESTRUCTOR C 3 +// CHECK:STDOUT: DESTRUCTOR A 1 +// CHECK:STDOUT: result: 1 + +package ExplorerTest api; + +base class A { + destructor[self: Self]{ + Print("DESTRUCTOR A {0}", self.a); + } + var a: i32; +} + +base class B extends A { + var b: i32; +} + +class C extends B { + destructor[self: Self]{ + Print("DESTRUCTOR C {0}", self.c); + } + var c: i32; +} + +fn Main() -> i32 { + var c: C = { .base={ .base={ .a=1 }, .b=2}, .c=3 }; + return 1; +} diff --git a/explorer/testdata/destructor/destroy_base_class_and_members.carbon b/explorer/testdata/destructor/destroy_base_class_and_members.carbon new file mode 100644 index 000000000000..12afb27e2833 --- /dev/null +++ b/explorer/testdata/destructor/destroy_base_class_and_members.carbon @@ -0,0 +1,59 @@ +// 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: Destroying 0 (C) +// CHECK:STDOUT: Destroying 1 +// CHECK:STDOUT: Destroying 2 +// CHECK:STDOUT: Destroying 3 (B) +// CHECK:STDOUT: Destroying 4 +// CHECK:STDOUT: Destroying 5 +// CHECK:STDOUT: Destroying 6 (A) +// CHECK:STDOUT: Destroying 7 +// CHECK:STDOUT: Destroying 8 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +base class Data { + fn Make(x: i32) -> Data { return {.data = x}; } + destructor[self: Self]{ + Print("Destroying {0}", self.data); + } + var data: i32; +} + +base class A { + fn Make() -> A { return {.a1=Data.Make(8), .a2=Data.Make(7)}; } + destructor[self: Self]{ + Print("Destroying 6 (A)"); + } + var a1: Data; + var a2: Data; +} + +base class B extends A { + fn Make() -> B { return {.base = A.Make(), .b1=Data.Make(5), .b2=Data.Make(4)}; } + destructor[self: Self]{ + Print("Destroying 3 (B)"); + } + var b1: Data; + var b2: Data; +} + +class C extends B { + fn Make() -> C { return {.base = B.Make(), .c1=Data.Make(2), .c2=Data.Make(1)}; } + destructor[self: Self]{ + Print("Destroying 0 (C)"); + } + var c1: Data; + var c2: Data; +} + +fn Main() -> i32 { + var c: C = C.Make(); + return 0; +}