فهرست منبع

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`
Adrien Leravat 3 سال پیش
والد
کامیت
e37a69a6d5

+ 2 - 0
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",
     ],
 )

+ 1 - 1
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<const LValue*> lvalue,

+ 72 - 50
explorer/interpreter/interpreter.cpp

@@ -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));
+      } 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)) {
+          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();
         }
-        // Type of tuple element is integral type e.g. i32
-        // or the type has no destructor
+      } 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();
       }
     }
-  }
-
-  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];
-        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));
+    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> {

+ 35 - 0
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;
+}

+ 59 - 0
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;
+}