mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 21:00:13 +01:00
Destroy class hierarchy when destroying class with a base (#2378)
Depends on #2361, #2421 Add support for destructors of base classes. Features: - Call destructors from derived to base class - Support addressing `TupleValue` using a new `Member` variant Changes: - Update `StepDestroy()` to recursively call destructors from derived to base class - Add new `Member` variant and `IndexedValue` struct to be usable with `TupleValue`
This commit is contained in:
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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<const LValue*> lvalue,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <utility>
|
||||
@@ -15,11 +16,13 @@
|
||||
#include <vector>
|
||||
|
||||
#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<Success> {
|
||||
}
|
||||
|
||||
auto Interpreter::StepDestroy() -> ErrorOr<Success> {
|
||||
// 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<DestroyAction>(act);
|
||||
if (act.pos() == 0) {
|
||||
if (const auto* class_obj =
|
||||
dyn_cast<NominalClassValue>(destroy_act.value())) {
|
||||
const auto& class_type = cast<NominalClassType>(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<TupleValue>(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<NominalClassValue>(item)) {
|
||||
const auto& class_type = cast<NominalClassType>(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<DestroyAction>(act);
|
||||
switch (destroy_act.value()->kind()) {
|
||||
case Value::Kind::NominalClassValue: {
|
||||
const auto* class_obj = cast<NominalClassValue>(destroy_act.value());
|
||||
const auto& class_decl =
|
||||
cast<NominalClassType>(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<DestroyAction>(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<NominalClassValue>(destroy_act.value())) {
|
||||
const auto& class_type = cast<NominalClassType>(class_obj->type());
|
||||
const auto& class_dec = class_type.declaration();
|
||||
int index = class_dec.members().size() - act.pos();
|
||||
if (index >= 0 && index < static_cast<int>(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<VariableDeclaration>(member)) {
|
||||
Address object = destroy_act.lvalue()->address();
|
||||
Address mem = object.ElementAddress(arena_->New<NamedElement>(var));
|
||||
SourceLocation source_loc("destructor", 1);
|
||||
auto v = heap_.Read(mem, source_loc);
|
||||
return todo_.Spawn(
|
||||
std::make_unique<DestroyAction>(destroy_act.lvalue(), *v));
|
||||
const Address object = destroy_act.lvalue()->address();
|
||||
const Address var_addr =
|
||||
object.ElementAddress(arena_->New<NamedElement>(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<DestroyAction>(
|
||||
arena_->New<LValue>(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<BaseElement>(class_obj));
|
||||
return todo_.Spawn(std::make_unique<DestroyAction>(
|
||||
arena_->New<LValue>(base_addr), base.value()));
|
||||
} else {
|
||||
return todo_.RunAgain();
|
||||
}
|
||||
} else {
|
||||
todo_.Pop();
|
||||
return Success();
|
||||
}
|
||||
}
|
||||
case Value::Kind::TupleValue: {
|
||||
const auto* tuple = cast<TupleValue>(destroy_act.value());
|
||||
const auto element_count = tuple->elements().size();
|
||||
if (static_cast<size_t>(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<PositionalElement>(index, item));
|
||||
if (item->kind() == Value::Kind::NominalClassValue ||
|
||||
item->kind() == Value::Kind::TupleValue) {
|
||||
return todo_.Spawn(std::make_unique<DestroyAction>(
|
||||
arena_->New<LValue>(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<Success> {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user