فهرست منبع

Feature destructor (#2116)

pmqtt 3 سال پیش
والد
کامیت
cfa295c0af
29فایلهای تغییر یافته به همراه827 افزوده شده و 82 حذف شده
  1. 6 0
      common/fuzzing/carbon.proto
  2. 21 0
      common/fuzzing/proto_to_carbon.cpp
  3. 3 1
      explorer/ast/ast_rtti.txt
  4. 46 13
      explorer/ast/declaration.cpp
  5. 69 19
      explorer/ast/declaration.h
  6. 5 5
      explorer/ast/statement.h
  7. 29 0
      explorer/fuzzing/ast_to_proto.cpp
  8. 1 0
      explorer/interpreter/BUILD
  9. 21 5
      explorer/interpreter/action.cpp
  10. 52 2
      explorer/interpreter/action.h
  11. 82 12
      explorer/interpreter/action_stack.cpp
  12. 18 1
      explorer/interpreter/action_stack.h
  13. 79 2
      explorer/interpreter/interpreter.cpp
  14. 7 5
      explorer/interpreter/resolve_control_flow.cpp
  15. 11 1
      explorer/interpreter/resolve_names.cpp
  16. 5 4
      explorer/interpreter/resolve_unformed.cpp
  17. 32 8
      explorer/interpreter/type_checker.cpp
  18. 2 2
      explorer/interpreter/type_checker.h
  19. 12 2
      explorer/interpreter/value.cpp
  20. 19 0
      explorer/interpreter/value.h
  21. 2 0
      explorer/syntax/lexer.lpp
  22. 21 0
      explorer/syntax/parser.ypp
  23. 45 0
      explorer/testdata/destructor/call_destructor_from_destructor.carbon
  24. 29 0
      explorer/testdata/destructor/destructor_with_return.carbon
  25. 24 0
      explorer/testdata/destructor/fail_multiple_destructor_declaration.carbon
  26. 43 0
      explorer/testdata/destructor/function_with_return.carbon
  27. 36 0
      explorer/testdata/destructor/loop_block.carbon
  28. 58 0
      explorer/testdata/destructor/loop_break_continue.carbon
  29. 49 0
      explorer/testdata/destructor/nested_block_with_return.carbon

+ 6 - 0
common/fuzzing/carbon.proto

@@ -340,6 +340,11 @@ message FunctionDeclaration {
   optional BlockStatement body = 6;
 }
 
+message DestructorDeclaration {
+  optional Pattern me_pattern = 1;
+  optional BlockStatement body = 2;
+}
+
 message ClassDeclaration {
   optional string name = 1;
   repeated Declaration members = 2;
@@ -417,6 +422,7 @@ message Declaration {
     LetDeclaration let = 8;
     MixinDeclaration mixin = 9;
     MixDeclaration mix = 10;
+    DestructorDeclaration destructor = 11;
   }
 }
 

+ 21 - 0
common/fuzzing/proto_to_carbon.cpp

@@ -677,6 +677,27 @@ static auto DeclarationToCarbon(const Fuzzing::Declaration& declaration,
       out << "var x: i32;";
       break;
 
+    case Fuzzing::Declaration::kDestructor: {
+      const auto& function = declaration.destructor();
+      out << "destructor";
+      llvm::ListSeparator sep;
+      out << "[";
+      if (function.has_me_pattern()) {
+        // This is a class method.
+        out << sep;
+        PatternToCarbon(function.me_pattern(), out);
+      }
+      out << "]";
+
+      // Body is optional.
+      if (function.has_body()) {
+        out << "\n";
+        BlockStatementToCarbon(function.body(), out);
+      } else {
+        out << ";";
+      }
+      break;
+    }
     case Fuzzing::Declaration::kFunction: {
       const auto& function = declaration.function();
       out << "fn ";

+ 3 - 1
explorer/ast/ast_rtti.txt

@@ -13,7 +13,9 @@ abstract class Pattern : AstNode;
   class AlternativePattern : Pattern;
   class ExpressionPattern : Pattern;
 abstract class Declaration : AstNode;
-  class FunctionDeclaration : Declaration;
+  abstract class CallableDeclaration : Declaration;
+    class FunctionDeclaration : CallableDeclaration;
+    class DestructorDeclaration : CallableDeclaration;
   class SelfDeclaration : Declaration;
   class ClassDeclaration : Declaration;
   class MixinDeclaration : Declaration;

+ 46 - 13
explorer/ast/declaration.cpp

@@ -38,7 +38,9 @@ void Declaration::Print(llvm::raw_ostream& out) const {
     case DeclarationKind::FunctionDeclaration:
       cast<FunctionDeclaration>(*this).PrintDepth(-1, out);
       break;
-
+    case DeclarationKind::DestructorDeclaration:
+      cast<DestructorDeclaration>(*this).PrintDepth(-1, out);
+      break;
     case DeclarationKind::ClassDeclaration: {
       const auto& class_decl = cast<ClassDeclaration>(*this);
       PrintID(out);
@@ -131,7 +133,9 @@ void Declaration::PrintID(llvm::raw_ostream& out) const {
     case DeclarationKind::FunctionDeclaration:
       out << "fn " << cast<FunctionDeclaration>(*this).name();
       break;
-
+    case DeclarationKind::DestructorDeclaration:
+      out << cast<DestructorDeclaration>(*this).name();
+      break;
     case DeclarationKind::ClassDeclaration: {
       const auto& class_decl = cast<ClassDeclaration>(*this);
       out << "class " << class_decl.name();
@@ -185,6 +189,8 @@ auto GetName(const Declaration& declaration)
   switch (declaration.kind()) {
     case DeclarationKind::FunctionDeclaration:
       return cast<FunctionDeclaration>(declaration).name();
+    case DeclarationKind::DestructorDeclaration:
+      return cast<DestructorDeclaration>(declaration).name();
     case DeclarationKind::ClassDeclaration:
       return cast<ClassDeclaration>(declaration).name();
     case DeclarationKind::MixinDeclaration: {
@@ -231,17 +237,13 @@ void ReturnTerm::Print(llvm::raw_ostream& out) const {
   }
 }
 
-auto FunctionDeclaration::Create(Nonnull<Arena*> arena,
-                                 SourceLocation source_loc, std::string name,
-                                 std::vector<Nonnull<AstNode*>> deduced_params,
-                                 std::optional<Nonnull<Pattern*>> me_pattern,
-                                 Nonnull<TuplePattern*> param_pattern,
-                                 ReturnTerm return_term,
-                                 std::optional<Nonnull<Block*>> body)
-    -> ErrorOr<Nonnull<FunctionDeclaration*>> {
+// Look for the `me` parameter in the `deduced_parameters`
+// and put it in the `me_pattern`.
+static auto MoveMeParameterToPattern(
+    SourceLocation source_loc, std::optional<Nonnull<Pattern*>>& me_pattern,
+    const std::vector<Nonnull<AstNode*>>& deduced_params)
+    -> ErrorOr<std::vector<Nonnull<GenericBinding*>>> {
   std::vector<Nonnull<GenericBinding*>> resolved_params;
-  // Look for the `me` parameter in the `deduced_parameters`
-  // and put it in the `me_pattern`.
   for (Nonnull<AstNode*> param : deduced_params) {
     switch (param->kind()) {
       case AstNodeKind::GenericBinding:
@@ -271,12 +273,43 @@ auto FunctionDeclaration::Create(Nonnull<Arena*> arena,
                << "illegal AST node in implicit parameter list";
     }
   }
+  return resolved_params;
+}
+
+auto DestructorDeclaration::CreateDestructor(
+    Nonnull<Arena*> arena, SourceLocation source_loc,
+    std::vector<Nonnull<AstNode*>> deduced_params,
+    Nonnull<TuplePattern*> param_pattern, ReturnTerm return_term,
+    std::optional<Nonnull<Block*>> body)
+    -> ErrorOr<Nonnull<DestructorDeclaration*>> {
+  std::vector<Nonnull<GenericBinding*>> resolved_params;
+  std::optional<Nonnull<Pattern*>> me_pattern;
+  CARBON_ASSIGN_OR_RETURN(
+      resolved_params,
+      MoveMeParameterToPattern(source_loc, me_pattern, deduced_params));
+  return arena->New<DestructorDeclaration>(
+      source_loc, std::move(resolved_params), me_pattern, param_pattern,
+      return_term, body);
+}
+
+auto FunctionDeclaration::Create(Nonnull<Arena*> arena,
+                                 SourceLocation source_loc, std::string name,
+                                 std::vector<Nonnull<AstNode*>> deduced_params,
+                                 std::optional<Nonnull<Pattern*>> me_pattern,
+                                 Nonnull<TuplePattern*> param_pattern,
+                                 ReturnTerm return_term,
+                                 std::optional<Nonnull<Block*>> body)
+    -> ErrorOr<Nonnull<FunctionDeclaration*>> {
+  std::vector<Nonnull<GenericBinding*>> resolved_params;
+  CARBON_ASSIGN_OR_RETURN(
+      resolved_params,
+      MoveMeParameterToPattern(source_loc, me_pattern, deduced_params));
   return arena->New<FunctionDeclaration>(source_loc, name,
                                          std::move(resolved_params), me_pattern,
                                          param_pattern, return_term, body);
 }
 
-void FunctionDeclaration::PrintDepth(int depth, llvm::raw_ostream& out) const {
+void CallableDeclaration::PrintDepth(int depth, llvm::raw_ostream& out) const {
   out << "fn " << name_ << " ";
   if (!deduced_parameters_.empty()) {
     out << "[";

+ 69 - 19
explorer/ast/declaration.h

@@ -102,27 +102,15 @@ class Declaration : public AstNode {
   std::optional<Nonnull<const Value*>> constant_value_;
 };
 
-class FunctionDeclaration : public Declaration {
+class CallableDeclaration : public Declaration {
  public:
-  using ImplementsCarbonValueNode = void;
-
-  static auto Create(Nonnull<Arena*> arena, SourceLocation source_loc,
-                     std::string name,
-                     std::vector<Nonnull<AstNode*>> deduced_params,
-                     std::optional<Nonnull<Pattern*>> me_pattern,
-                     Nonnull<TuplePattern*> param_pattern,
-                     ReturnTerm return_term,
-                     std::optional<Nonnull<Block*>> body)
-      -> ErrorOr<Nonnull<FunctionDeclaration*>>;
-
-  // Use `Create()` instead. This is public only so Arena::New() can call it.
-  FunctionDeclaration(SourceLocation source_loc, std::string name,
+  CallableDeclaration(AstNodeKind kind, SourceLocation loc, std::string name,
                       std::vector<Nonnull<GenericBinding*>> deduced_params,
                       std::optional<Nonnull<Pattern*>> me_pattern,
                       Nonnull<TuplePattern*> param_pattern,
                       ReturnTerm return_term,
                       std::optional<Nonnull<Block*>> body)
-      : Declaration(AstNodeKind::FunctionDeclaration, source_loc),
+      : Declaration(kind, loc),
         name_(std::move(name)),
         deduced_parameters_(std::move(deduced_params)),
         me_pattern_(me_pattern),
@@ -130,12 +118,9 @@ class FunctionDeclaration : public Declaration {
         return_term_(return_term),
         body_(body) {}
 
-  static auto classof(const AstNode* node) -> bool {
-    return InheritsFromFunctionDeclaration(node->kind());
-  }
-
   void PrintDepth(int depth, llvm::raw_ostream& out) const;
 
+  // TODO: Move name() and name_ to FunctionDeclaration
   auto name() const -> const std::string& { return name_; }
   auto deduced_parameters() const
       -> llvm::ArrayRef<Nonnull<const GenericBinding*>> {
@@ -166,6 +151,62 @@ class FunctionDeclaration : public Declaration {
   std::optional<Nonnull<Block*>> body_;
 };
 
+class FunctionDeclaration : public CallableDeclaration {
+ public:
+  using ImplementsCarbonValueNode = void;
+
+  static auto Create(Nonnull<Arena*> arena, SourceLocation source_loc,
+                     std::string name,
+                     std::vector<Nonnull<AstNode*>> deduced_params,
+                     std::optional<Nonnull<Pattern*>> me_pattern,
+                     Nonnull<TuplePattern*> param_pattern,
+                     ReturnTerm return_term,
+                     std::optional<Nonnull<Block*>> body)
+      -> ErrorOr<Nonnull<FunctionDeclaration*>>;
+
+  // Use `Create()` instead. This is public only so Arena::New() can call it.
+  FunctionDeclaration(SourceLocation source_loc, std::string name,
+                      std::vector<Nonnull<GenericBinding*>> deduced_params,
+                      std::optional<Nonnull<Pattern*>> me_pattern,
+                      Nonnull<TuplePattern*> param_pattern,
+                      ReturnTerm return_term,
+                      std::optional<Nonnull<Block*>> body)
+      : CallableDeclaration(AstNodeKind::FunctionDeclaration, source_loc,
+                            std::move(name), std::move(deduced_params),
+                            me_pattern, param_pattern, return_term, body) {}
+
+  static auto classof(const AstNode* node) -> bool {
+    return InheritsFromFunctionDeclaration(node->kind());
+  }
+};
+
+class DestructorDeclaration : public CallableDeclaration {
+ public:
+  using ImplementsCarbonValueNode = void;
+
+  static auto CreateDestructor(Nonnull<Arena*> arena, SourceLocation source_loc,
+                               std::vector<Nonnull<AstNode*>> deduced_params,
+                               Nonnull<TuplePattern*> param_pattern,
+                               ReturnTerm return_term,
+                               std::optional<Nonnull<Block*>> body)
+      -> ErrorOr<Nonnull<DestructorDeclaration*>>;
+
+  // Use `Create()` instead. This is public only so Arena::New() can call it.
+  DestructorDeclaration(SourceLocation source_loc,
+                        std::vector<Nonnull<GenericBinding*>> deduced_params,
+                        std::optional<Nonnull<Pattern*>> me_pattern,
+                        Nonnull<TuplePattern*> param_pattern,
+                        ReturnTerm return_term,
+                        std::optional<Nonnull<Block*>> body)
+      : CallableDeclaration(AstNodeKind::DestructorDeclaration, source_loc,
+                            "destructor", std::move(deduced_params), me_pattern,
+                            param_pattern, return_term, body) {}
+
+  static auto classof(const AstNode* node) -> bool {
+    return InheritsFromDestructorDeclaration(node->kind());
+  }
+};
+
 class SelfDeclaration : public Declaration {
  public:
   using ImplementsCarbonValueNode = void;
@@ -222,6 +263,14 @@ class ClassDeclaration : public Declaration {
   auto members() const -> llvm::ArrayRef<Nonnull<Declaration*>> {
     return members_;
   }
+  auto destructor() const -> std::optional<Nonnull<DestructorDeclaration*>> {
+    for (auto& x : members_) {
+      if (x->kind() == DeclarationKind::DestructorDeclaration) {
+        return llvm::cast<DestructorDeclaration>(x);
+      }
+    }
+    return std::nullopt;
+  }
 
   auto value_category() const -> ValueCategory { return ValueCategory::Let; }
 
@@ -232,6 +281,7 @@ class ClassDeclaration : public Declaration {
   std::optional<Nonnull<TuplePattern*>> type_params_;
   std::optional<Nonnull<Expression*>> extends_;
   std::vector<Nonnull<Declaration*>> members_;
+  std::optional<Nonnull<FunctionDeclaration*>> destructor_;
 };
 
 // EXPERIMENTAL MIXIN FEATURE

+ 5 - 5
explorer/ast/statement.h

@@ -22,7 +22,7 @@
 
 namespace Carbon {
 
-class FunctionDeclaration;
+class CallableDeclaration;
 
 class Statement : public AstNode {
  public:
@@ -203,11 +203,11 @@ class Return : public Statement {
   // Note that this function does not represent an edge in the tree
   // structure of the AST: the return value is not a child of this node,
   // but an ancestor.
-  auto function() const -> const FunctionDeclaration& { return **function_; }
-  auto function() -> FunctionDeclaration& { return **function_; }
+  auto function() const -> const CallableDeclaration& { return **function_; }
+  auto function() -> CallableDeclaration& { return **function_; }
 
   // Can only be called once, by ResolveControlFlow.
-  void set_function(Nonnull<FunctionDeclaration*> function) {
+  void set_function(Nonnull<CallableDeclaration*> function) {
     CARBON_CHECK(!function_.has_value());
     function_ = function;
   }
@@ -217,7 +217,7 @@ class Return : public Statement {
       : Statement(node_kind, source_loc) {}
 
  private:
-  std::optional<Nonnull<FunctionDeclaration*>> function_;
+  std::optional<Nonnull<CallableDeclaration*>> function_;
 };
 
 class ReturnVar : public Return {

+ 29 - 0
explorer/fuzzing/ast_to_proto.cpp

@@ -566,6 +566,35 @@ static auto DeclarationToProto(const Declaration& declaration)
     -> Fuzzing::Declaration {
   Fuzzing::Declaration declaration_proto;
   switch (declaration.kind()) {
+    case DeclarationKind::DestructorDeclaration: {
+      const auto& function = cast<DestructorDeclaration>(declaration);
+      auto* function_proto = declaration_proto.mutable_destructor();
+      if (function.is_method()) {
+        switch (function.me_pattern().kind()) {
+          case PatternKind::AddrPattern:
+            *function_proto->mutable_me_pattern() =
+                PatternToProto(cast<AddrPattern>(function.me_pattern()));
+            break;
+          case PatternKind::BindingPattern:
+            *function_proto->mutable_me_pattern() =
+                PatternToProto(cast<BindingPattern>(function.me_pattern()));
+            break;
+          default:
+            // Parser shouldn't allow me_pattern to be anything other than
+            // AddrPattern or BindingPattern
+            CARBON_FATAL() << "me_pattern in method declaration can be either "
+                              "AddrPattern or BindingPattern. Actual pattern: "
+                           << function.me_pattern();
+            break;
+        }
+      }
+      if (function.body().has_value()) {
+        *function_proto->mutable_body() =
+            BlockStatementToProto(**function.body());
+      }
+      break;
+    }
+
     case DeclarationKind::FunctionDeclaration: {
       const auto& function = cast<FunctionDeclaration>(declaration);
       auto* function_proto = declaration_proto.mutable_function();

+ 1 - 0
explorer/interpreter/BUILD

@@ -40,6 +40,7 @@ cc_library(
     deps = [
         ":action_and_value",
         ":stack",
+        "//common:error",
         "//common:ostream",
         "//explorer/ast",
         "@llvm-project//llvm:Support",

+ 21 - 5
explorer/interpreter/action.cpp

@@ -25,13 +25,15 @@ RuntimeScope::RuntimeScope(RuntimeScope&& other) noexcept
     : locals_(std::move(other.locals_)),
       // To transfer ownership of other.allocations_, we have to empty it out.
       allocations_(std::exchange(other.allocations_, {})),
-      heap_(other.heap_) {}
+      heap_(other.heap_),
+      destructor_scope_(other.destructor_scope_) {}
 
 auto RuntimeScope::operator=(RuntimeScope&& rhs) noexcept -> RuntimeScope& {
   locals_ = std::move(rhs.locals_);
   // To transfer ownership of rhs.allocations_, we have to empty it out.
   allocations_ = std::exchange(rhs.allocations_, {});
   heap_ = rhs.heap_;
+  destructor_scope_ = rhs.destructor_scope_;
   return *this;
 }
 
@@ -62,10 +64,11 @@ void RuntimeScope::Initialize(ValueNodeView value_node,
 
 void RuntimeScope::Merge(RuntimeScope other) {
   CARBON_CHECK(heap_ == other.heap_);
-  locals_.merge(other.locals_);
-  CARBON_CHECK(other.locals_.empty())
-      << "Duplicate definition of " << other.locals_.size()
-      << " names, including " << other.locals_.begin()->first.base();
+  for (auto& element : other.locals_) {
+    CARBON_CHECK(locals_.count(element.first) == 0)
+        << "Duplicate definition of" << element.first;
+    locals_.insert(element);
+  }
   allocations_.insert(allocations_.end(), other.allocations_.begin(),
                       other.allocations_.end());
   other.allocations_.clear();
@@ -95,6 +98,16 @@ auto RuntimeScope::Capture(
   return result;
 }
 
+void RuntimeScope::TransitState() {
+  if (destructor_scope_ == State::Normal) {
+    destructor_scope_ = State::Destructor;
+  } else if (destructor_scope_ == State::Destructor) {
+    destructor_scope_ = State::CleanUpped;
+  } else {
+    destructor_scope_ = State::CleanUpped;
+  }
+}
+
 void Action::Print(llvm::raw_ostream& out) const {
   switch (kind()) {
     case Action::Kind::LValAction:
@@ -119,6 +132,9 @@ void Action::Print(llvm::raw_ostream& out) const {
     case Action::Kind::RecursiveAction:
       out << "recursive";
       break;
+    case Action::Kind::CleanUpAction:
+      out << "clean up";
+      break;
   }
   out << "." << pos_ << ".";
   if (!results_.empty()) {

+ 52 - 2
explorer/interpreter/action.h

@@ -5,7 +5,9 @@
 #ifndef CARBON_EXPLORER_INTERPRETER_ACTION_H_
 #define CARBON_EXPLORER_INTERPRETER_ACTION_H_
 
+#include <list>
 #include <map>
+#include <tuple>
 #include <vector>
 
 #include "common/ostream.h"
@@ -16,6 +18,7 @@
 #include "explorer/interpreter/heap_allocation_interface.h"
 #include "explorer/interpreter/stack.h"
 #include "explorer/interpreter/value.h"
+#include "llvm/ADT/MapVector.h"
 #include "llvm/Support/Compiler.h"
 
 namespace Carbon {
@@ -24,6 +27,12 @@ namespace Carbon {
 // not compile-time constants.
 class RuntimeScope {
  public:
+  enum class State {
+    Normal = 0,
+    Destructor = 1,
+    CleanUpped = 2,
+  };
+
   // Returns a RuntimeScope whose Get() operation for a given name returns the
   // storage owned by the first entry in `scopes` that defines that name. This
   // behavior is closely analogous to a `[&]` capture in C++, hence the name.
@@ -33,7 +42,8 @@ class RuntimeScope {
       -> RuntimeScope;
 
   // Constructs a RuntimeScope that allocates storage in `heap`.
-  explicit RuntimeScope(Nonnull<HeapAllocationInterface*> heap) : heap_(heap) {}
+  explicit RuntimeScope(Nonnull<HeapAllocationInterface*> heap)
+      : heap_(heap), destructor_scope_(State::Normal) {}
 
   // Moving a RuntimeScope transfers ownership of its allocations.
   RuntimeScope(RuntimeScope&&) noexcept;
@@ -58,10 +68,32 @@ class RuntimeScope {
   auto Get(ValueNodeView value_node) const
       -> std::optional<Nonnull<const LValue*>>;
 
+  // Returns the local values in created order
+  auto locals() const -> std::vector<Nonnull<const LValue*>> {
+    std::vector<Nonnull<const LValue*>> res;
+    for (auto& entry : locals_) {
+      res.push_back(entry.second);
+    }
+    return res;
+  }
+
+  // Return scope state
+  // Normal     = Scope is not bind at the top of a destructor call
+  // Destructor = Scope is bind to a destructor call and was not cleaned up,
+  // CleanedUp  = Scope is bind to a destructor call and is cleaned up
+  auto DestructionState() const -> State { return destructor_scope_; }
+
+  // Transit the state from Normal to Destructor
+  // Transit the state from Destructor to CleanUpped
+  void TransitState();
+
  private:
-  std::map<ValueNodeView, Nonnull<const LValue*>> locals_;
+  llvm::MapVector<ValueNodeView, Nonnull<const LValue*>,
+                  std::map<ValueNodeView, unsigned>>
+      locals_;
   std::vector<AllocationId> allocations_;
   Nonnull<HeapAllocationInterface*> heap_;
+  State destructor_scope_;
 };
 
 // An Action represents the current state of a self-contained computation,
@@ -86,6 +118,7 @@ class Action {
     DeclarationAction,
     ScopeAction,
     RecursiveAction,
+    CleanUpAction,
   };
 
   Action(const Value&) = delete;
@@ -239,6 +272,23 @@ class DeclarationAction : public Action {
   Nonnull<const Declaration*> declaration_;
 };
 
+class CleanupAction : public Action {
+ public:
+  explicit CleanupAction(RuntimeScope scope) : Action(Kind::CleanUpAction) {
+    locals_count_ = scope.locals().size();
+    StartScope(std::move(scope));
+  }
+
+  auto locals_count() const -> int { return locals_count_; }
+
+  static auto classof(const Action* action) -> bool {
+    return action->kind() == Kind::CleanUpAction;
+  }
+
+ private:
+  int locals_count_;
+};
+
 // Action which does nothing except introduce a new scope into the action
 // stack. This is useful when a distinct scope doesn't otherwise have an
 // Action it can naturally be associated with. ScopeActions are not associated

+ 82 - 12
explorer/interpreter/action_stack.cpp

@@ -4,6 +4,7 @@
 
 #include "explorer/interpreter/action_stack.h"
 
+#include "common/error.h"
 #include "explorer/interpreter/action.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/Casting.h"
@@ -126,8 +127,10 @@ void ActionStack::InitializeFragment(ContinuationValue::StackFragment& fragment,
 }
 
 auto ActionStack::FinishAction() -> ErrorOr<Success> {
+  std::stack<std::unique_ptr<Action>> scopes_to_destroy;
   std::unique_ptr<Action> act = todo_.Pop();
   switch (act->kind()) {
+    case Action::Kind::CleanUpAction:
     case Action::Kind::ExpressionAction:
     case Action::Kind::LValAction:
     case Action::Kind::PatternAction:
@@ -136,16 +139,22 @@ auto ActionStack::FinishAction() -> ErrorOr<Success> {
       CARBON_FATAL() << "ScopeAction at top of stack";
     case Action::Kind::StatementAction:
     case Action::Kind::DeclarationAction:
-    case Action::Kind::RecursiveAction:
-      PopScopes();
+    case Action::Kind::RecursiveAction: {
+      PopScopes(scopes_to_destroy);
+      break;
+    }
   }
+  PushCleanUpAction(std::move(act));
+  PushCleanUpActions(std::move(scopes_to_destroy));
   return Success();
 }
 
 auto ActionStack::FinishAction(Nonnull<const Value*> result)
     -> ErrorOr<Success> {
+  std::stack<std::unique_ptr<Action>> scopes_to_destroy;
   std::unique_ptr<Action> act = todo_.Pop();
   switch (act->kind()) {
+    case Action::Kind::CleanUpAction:
     case Action::Kind::StatementAction:
     case Action::Kind::DeclarationAction:
     case Action::Kind::RecursiveAction:
@@ -155,9 +164,12 @@ auto ActionStack::FinishAction(Nonnull<const Value*> result)
     case Action::Kind::ExpressionAction:
     case Action::Kind::LValAction:
     case Action::Kind::PatternAction:
-      PopScopes();
+      PopScopes(scopes_to_destroy);
       SetResult(result);
+      break;
   }
+  PushCleanUpAction(std::move(act));
+  PushCleanUpActions(std::move(scopes_to_destroy));
   return Success();
 }
 
@@ -192,8 +204,9 @@ auto ActionStack::RunAgain() -> ErrorOr<Success> {
   return Success();
 }
 
-auto ActionStack::UnwindTo(Nonnull<const Statement*> ast_node)
-    -> ErrorOr<Success> {
+auto ActionStack::UnwindToWithCaptureScopesToDestroy(
+    Nonnull<const Statement*> ast_node) -> std::stack<std::unique_ptr<Action>> {
+  std::stack<std::unique_ptr<Action>> scopes_to_destroy;
   while (true) {
     if (const auto* statement_action =
             llvm::dyn_cast<StatementAction>(todo_.Top().get());
@@ -201,23 +214,50 @@ auto ActionStack::UnwindTo(Nonnull<const Statement*> ast_node)
         &statement_action->statement() == ast_node) {
       break;
     }
-    todo_.Pop();
+    auto item = todo_.Pop();
+    auto& scope = item->scope();
+    if (scope && item->kind() != Action::Kind::CleanUpAction) {
+      std::unique_ptr<Action> cleanup_action =
+          std::make_unique<CleanupAction>(std::move(*scope));
+      scopes_to_destroy.push(std::move(cleanup_action));
+    }
   }
+  return scopes_to_destroy;
+}
+
+auto ActionStack::UnwindTo(Nonnull<const Statement*> ast_node)
+    -> ErrorOr<Success> {
+  std::stack<std::unique_ptr<Action>> scopes_to_destroy =
+      UnwindToWithCaptureScopesToDestroy(ast_node);
+  PushCleanUpActions(std::move(scopes_to_destroy));
   return Success();
 }
 
 auto ActionStack::UnwindPast(Nonnull<const Statement*> ast_node)
     -> ErrorOr<Success> {
-  CARBON_RETURN_IF_ERROR(UnwindTo(ast_node));
-  todo_.Pop();
-  PopScopes();
+  std::stack<std::unique_ptr<Action>> scopes_to_destroy =
+      UnwindPastWithCaptureScopesToDestroy(ast_node);
+  PushCleanUpActions(std::move(scopes_to_destroy));
+
   return Success();
 }
 
+auto ActionStack::UnwindPastWithCaptureScopesToDestroy(
+    Nonnull<const Statement*> ast_node) -> std::stack<std::unique_ptr<Action>> {
+  std::stack<std::unique_ptr<Action>> scopes_to_destroy =
+      UnwindToWithCaptureScopesToDestroy(ast_node);
+  auto item = todo_.Pop();
+  scopes_to_destroy.push(std::move(item));
+  PopScopes(scopes_to_destroy);
+  return scopes_to_destroy;
+}
+
 auto ActionStack::UnwindPast(Nonnull<const Statement*> ast_node,
                              Nonnull<const Value*> result) -> ErrorOr<Success> {
-  CARBON_RETURN_IF_ERROR(UnwindPast(ast_node));
+  std::stack<std::unique_ptr<Action>> scopes_to_destroy =
+      UnwindPastWithCaptureScopesToDestroy(ast_node);
   SetResult(result);
+  PushCleanUpActions(std::move(scopes_to_destroy));
   return Success();
 }
 
@@ -248,9 +288,17 @@ auto ActionStack::Suspend() -> ErrorOr<Success> {
   return Success();
 }
 
-void ActionStack::PopScopes() {
+void ActionStack::PopScopes(
+    std::stack<std::unique_ptr<Action>>& cleanup_stack) {
   while (!todo_.IsEmpty() && llvm::isa<ScopeAction>(*todo_.Top())) {
-    todo_.Pop();
+    auto act = todo_.Pop();
+    if (act->scope()) {
+      if ((*act->scope()).DestructionState() <
+          RuntimeScope::State::CleanUpped) {
+        (*act->scope()).TransitState();
+        cleanup_stack.push(std::move(act));
+      }
+    }
   }
 }
 
@@ -262,4 +310,26 @@ void ActionStack::SetResult(Nonnull<const Value*> result) {
   }
 }
 
+void ActionStack::PushCleanUpActions(
+    std::stack<std::unique_ptr<Action>> actions) {
+  while (!actions.empty()) {
+    auto& act = actions.top();
+    if (act->scope()) {
+      std::unique_ptr<Action> cleanup_action =
+          std::make_unique<CleanupAction>(std::move(*act->scope()));
+      todo_.Push(std::move(cleanup_action));
+    }
+    actions.pop();
+  }
+}
+
+void ActionStack::PushCleanUpAction(std::unique_ptr<Action> act) {
+  auto& scope = act->scope();
+  if (scope && act->kind() != Action::Kind::CleanUpAction) {
+    std::unique_ptr<Action> cleanup_action =
+        std::make_unique<CleanupAction>(std::move(*scope));
+    todo_.Push(std::move(cleanup_action));
+  }
+}
+
 }  // namespace Carbon

+ 18 - 1
explorer/interpreter/action_stack.h

@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <optional>
+#include <stack>
 
 #include "common/ostream.h"
 #include "explorer/ast/statement.h"
@@ -118,15 +119,31 @@ class ActionStack {
   // Suspends execution of the currently-executing continuation.
   auto Suspend() -> ErrorOr<Success>;
 
+  void Pop() { todo_.Pop(); }
+
  private:
   // Pop any ScopeActions from the top of the stack, propagating results as
   // needed, to restore the invariant that todo_.Top() is not a ScopeAction.
-  void PopScopes();
+  // Store the popped scope action into cleanup_stack, so that the destructor
+  // can be called for the variables
+  void PopScopes(std::stack<std::unique_ptr<Action>>& cleanup_stack);
 
   // Set `result` as the result of the Action most recently removed from the
   // stack.
   void SetResult(Nonnull<const Value*> result);
 
+  auto UnwindToWithCaptureScopesToDestroy(Nonnull<const Statement*> ast_node)
+      -> std::stack<std::unique_ptr<Action>>;
+
+  auto UnwindPastWithCaptureScopesToDestroy(Nonnull<const Statement*> ast_node)
+      -> std::stack<std::unique_ptr<Action>>;
+
+  // Create CleanUpActions for all actions
+  void PushCleanUpActions(std::stack<std::unique_ptr<Action>> actions);
+
+  // Create and push a CleanUpAction on the stack
+  void PushCleanUpAction(std::unique_ptr<Action> act);
+
   // TODO: consider defining a non-nullable unique_ptr-like type to use here.
   Stack<std::unique_ptr<Action>> todo_;
   std::optional<Nonnull<const Value*>> result_;

+ 79 - 2
explorer/interpreter/interpreter.cpp

@@ -4,6 +4,8 @@
 
 #include "explorer/interpreter/interpreter.h"
 
+#include <llvm/Support/raw_ostream.h>
+
 #include <iterator>
 #include <map>
 #include <optional>
@@ -83,6 +85,8 @@ class Interpreter {
   auto StepStmt() -> ErrorOr<Success>;
   // State transition for declarations.
   auto StepDeclaration() -> ErrorOr<Success>;
+  // State transition for object destruction.
+  auto StepCleanUp() -> ErrorOr<Success>;
 
   auto CreateStruct(const std::vector<FieldInitializer>& fields,
                     const std::vector<Nonnull<const Value*>>& values)
@@ -142,6 +146,9 @@ class Interpreter {
                     Nonnull<const Value*> arg, ImplWitnessMap&& witnesses)
       -> ErrorOr<Success>;
 
+  auto CallDestructor(Nonnull<const DestructorDeclaration*> fun,
+                      Nonnull<const Value*> receiver) -> ErrorOr<Success>;
+
   void PrintState(llvm::raw_ostream& out);
 
   Phase phase() const { return phase_; }
@@ -615,6 +622,7 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
   switch (value->kind()) {
     case Value::Kind::IntValue:
     case Value::Kind::FunctionValue:
+    case Value::Kind::DestructorValue:
     case Value::Kind::BoundMethodValue:
     case Value::Kind::PointerValue:
     case Value::Kind::LValue:
@@ -739,6 +747,26 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
   }
 }
 
+auto Interpreter::CallDestructor(Nonnull<const DestructorDeclaration*> fun,
+                                 Nonnull<const Value*> receiver)
+    -> ErrorOr<Success> {
+  const DestructorDeclaration& method = *fun;
+  CARBON_CHECK(method.is_method());
+  RuntimeScope method_scope(&heap_);
+  BindingMap generic_args;
+  CARBON_CHECK(PatternMatch(&method.me_pattern().value(), receiver,
+                            fun->source_loc(), &method_scope, generic_args,
+                            trace_stream_, this->arena_));
+
+  CARBON_CHECK(method.body().has_value())
+      << "Calling a method that's missing a body";
+
+  auto act = std::make_unique<StatementAction>(*method.body());
+  method_scope.TransitState();
+  return todo_.Spawn(std::unique_ptr<Action>(std::move(act)),
+                     std::move(method_scope));
+}
+
 auto Interpreter::CallFunction(const CallExpression& call,
                                Nonnull<const Value*> fun,
                                Nonnull<const Value*> arg,
@@ -1756,7 +1784,7 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
         CARBON_ASSIGN_OR_RETURN(
             value, heap_.Read(lvalue->address(), ret_var.source_loc()));
       }
-      const FunctionDeclaration& function = cast<Return>(stmt).function();
+      const CallableDeclaration& function = cast<Return>(stmt).function();
       CARBON_ASSIGN_OR_RETURN(
           Nonnull<const Value*> return_value,
           Convert(value, &function.return_term().static_type(),
@@ -1772,7 +1800,7 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
       } else {
         //    { {v :: return [] :: C, E, F} :: {C', E', F'} :: S, H}
         // -> { {v :: C', E', F'} :: S, H}
-        const FunctionDeclaration& function = cast<Return>(stmt).function();
+        const CallableDeclaration& function = cast<Return>(stmt).function();
         CARBON_ASSIGN_OR_RETURN(
             Nonnull<const Value*> return_value,
             Convert(act.results()[0], &function.return_term().static_type(),
@@ -1841,6 +1869,7 @@ auto Interpreter::StepDeclaration() -> ErrorOr<Success> {
         return todo_.FinishAction();
       }
     }
+    case DeclarationKind::DestructorDeclaration:
     case DeclarationKind::FunctionDeclaration:
     case DeclarationKind::ClassDeclaration:
     case DeclarationKind::MixinDeclaration:
@@ -1856,6 +1885,51 @@ auto Interpreter::StepDeclaration() -> ErrorOr<Success> {
   }
 }
 
+auto Interpreter::StepCleanUp() -> ErrorOr<Success> {
+  Action& act = todo_.CurrentAction();
+  CleanupAction& cleanup = cast<CleanupAction>(act);
+  if (act.pos() < cleanup.locals_count()) {
+    auto lvalue = act.scope()->locals()[cleanup.locals_count() - act.pos() - 1];
+    SourceLocation source_loc("destructor", 1);
+    auto value = heap_.Read(lvalue->address(), source_loc);
+    if (value.ok()) {
+      if (act.scope()->DestructionState() < RuntimeScope::State::CleanUpped) {
+        if (const auto* class_obj = dyn_cast<NominalClassValue>(*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);
+          }
+        }
+      } else {
+        if (const auto* class_obj = dyn_cast<NominalClassValue>(*value)) {
+          const auto& class_type = cast<NominalClassType>(class_obj->type());
+          const auto& class_dec = class_type.declaration();
+          const auto& class_members = class_dec.members();
+          for (const auto& member : class_members) {
+            if (const auto* var = dyn_cast<VariableDeclaration>(member)) {
+              const auto& type = var->static_type();
+              if (const auto* c_type = dyn_cast<NominalClassType>(&type)) {
+                auto& c_dec = c_type->declaration();
+                if (c_dec.destructor().has_value()) {
+                  Address object = lvalue->address();
+                  Address mem = object.SubobjectAddress(Member(var));
+                  auto v = heap_.Read(mem, source_loc);
+                  act.scope()->TransitState();
+                  return CallDestructor(*c_dec.destructor(), *v);
+                }
+              }
+            }
+          }
+        }
+        act.scope()->TransitState();
+      }
+    }
+  }
+  todo_.Pop();
+  return Success();
+}
+
 // State transition.
 auto Interpreter::Step() -> ErrorOr<Success> {
   Action& act = todo_.CurrentAction();
@@ -1875,6 +1949,9 @@ auto Interpreter::Step() -> ErrorOr<Success> {
     case Action::Kind::DeclarationAction:
       CARBON_RETURN_IF_ERROR(StepDeclaration());
       break;
+    case Action::Kind::CleanUpAction:
+      CARBON_RETURN_IF_ERROR(StepCleanUp());
+      break;
     case Action::Kind::ScopeAction:
       CARBON_FATAL() << "ScopeAction escaped ActionStack";
     case Action::Kind::RecursiveAction:

+ 7 - 5
explorer/interpreter/resolve_control_flow.cpp

@@ -18,7 +18,7 @@ namespace Carbon {
 // Aggregate information about a function being analyzed.
 struct FunctionData {
   // The function declaration.
-  Nonnull<FunctionDeclaration*> declaration;
+  Nonnull<CallableDeclaration*> declaration;
 
   // True if the function has a deduced return type, and we've already seen
   // a `return` statement in its body.
@@ -72,6 +72,7 @@ static auto ResolveControlFlow(Nonnull<Statement*> statement,
                     "signature.";
         }
       }
+
       return Success();
     }
     case StatementKind::Break:
@@ -138,12 +139,13 @@ static auto ResolveControlFlow(Nonnull<Statement*> statement,
 
 auto ResolveControlFlow(Nonnull<Declaration*> declaration) -> ErrorOr<Success> {
   switch (declaration->kind()) {
+    case DeclarationKind::DestructorDeclaration:
     case DeclarationKind::FunctionDeclaration: {
-      auto& function = cast<FunctionDeclaration>(*declaration);
-      if (function.body().has_value()) {
-        FunctionData data = {.declaration = &function};
+      auto& callable = cast<CallableDeclaration>(*declaration);
+      if (callable.body().has_value()) {
+        FunctionData data = {.declaration = &callable};
         CARBON_RETURN_IF_ERROR(
-            ResolveControlFlow(*function.body(), std::nullopt, &data));
+            ResolveControlFlow(*callable.body(), std::nullopt, &data));
       }
       break;
     }

+ 11 - 1
explorer/interpreter/resolve_names.cpp

@@ -33,6 +33,15 @@ static auto AddExposedNames(const Declaration& declaration,
       // Nothing to do here
       break;
     }
+    case DeclarationKind::DestructorDeclaration: {
+      // TODO: Remove this code. With this code, it is possible to create not
+      // useful carbon code.
+      //       Without this code, a Segfault is generated
+      auto& func = cast<DestructorDeclaration>(declaration);
+      CARBON_RETURN_IF_ERROR(enclosing_scope.Add(
+          "destructor", &func, StaticScope::NameStatus::KnownButNotDeclared));
+      break;
+    }
     case DeclarationKind::FunctionDeclaration: {
       auto& func = cast<FunctionDeclaration>(declaration);
       CARBON_RETURN_IF_ERROR(enclosing_scope.Add(
@@ -531,8 +540,9 @@ static auto ResolveNames(Declaration& declaration, StaticScope& enclosing_scope,
           ResolveMemberNames(impl.members(), impl_scope, bodies));
       break;
     }
+    case DeclarationKind::DestructorDeclaration:
     case DeclarationKind::FunctionDeclaration: {
-      auto& function = cast<FunctionDeclaration>(declaration);
+      auto& function = cast<CallableDeclaration>(declaration);
       StaticScope function_scope;
       function_scope.AddParent(&enclosing_scope);
       enclosing_scope.MarkDeclared(function.name());

+ 5 - 4
explorer/interpreter/resolve_unformed.cpp

@@ -284,11 +284,12 @@ static auto ResolveUnformed(Nonnull<const Declaration*> declaration)
     // Checks formed/unformed state intraprocedurally.
     // Can be extended to an interprocedural analysis when a call graph is
     // available.
-    case DeclarationKind::FunctionDeclaration: {
-      auto& function = cast<FunctionDeclaration>(*declaration);
-      if (function.body().has_value()) {
+    case DeclarationKind::FunctionDeclaration:
+    case DeclarationKind::DestructorDeclaration: {
+      auto& callable = cast<CallableDeclaration>(*declaration);
+      if (callable.body().has_value()) {
         FlowFacts flow_facts;
-        CARBON_RETURN_IF_ERROR(ResolveUnformed(*function.body(), flow_facts,
+        CARBON_RETURN_IF_ERROR(ResolveUnformed(*callable.body(), flow_facts,
                                                FlowFacts::ActionType::None));
       }
       break;

+ 32 - 8
explorer/interpreter/type_checker.cpp

@@ -167,6 +167,7 @@ static auto ExpectPointerType(SourceLocation source_loc,
 static auto IsTypeOfType(Nonnull<const Value*> value) -> bool {
   switch (value->kind()) {
     case Value::Kind::IntValue:
+    case Value::Kind::DestructorValue:
     case Value::Kind::FunctionValue:
     case Value::Kind::BoundMethodValue:
     case Value::Kind::PointerValue:
@@ -228,6 +229,7 @@ static auto IsType(Nonnull<const Value*> value, bool concrete = false) -> bool {
   switch (value->kind()) {
     case Value::Kind::IntValue:
     case Value::Kind::FunctionValue:
+    case Value::Kind::DestructorValue:
     case Value::Kind::BoundMethodValue:
     case Value::Kind::PointerValue:
     case Value::Kind::LValue:
@@ -871,6 +873,7 @@ auto TypeChecker::ArgumentDeduction(
     case Value::Kind::IntValue:
     case Value::Kind::BoolValue:
     case Value::Kind::FunctionValue:
+    case Value::Kind::DestructorValue:
     case Value::Kind::BoundMethodValue:
     case Value::Kind::PointerValue:
     case Value::Kind::LValue:
@@ -1179,6 +1182,7 @@ auto TypeChecker::Substitute(
     case Value::Kind::IntValue:
     case Value::Kind::BoolValue:
     case Value::Kind::FunctionValue:
+    case Value::Kind::DestructorValue:
     case Value::Kind::BoundMethodValue:
     case Value::Kind::PointerValue:
     case Value::Kind::LValue:
@@ -3214,7 +3218,7 @@ auto TypeChecker::ExpectReturnOnAllPaths(
 
 // TODO: Add checking to function definitions to ensure that
 //   all deduced type parameters will be deduced.
-auto TypeChecker::DeclareFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
+auto TypeChecker::DeclareCallableDeclaration(Nonnull<CallableDeclaration*> f,
                                              const ScopeInfo& scope_info)
     -> ErrorOr<Success> {
   if (trace_stream_) {
@@ -3287,7 +3291,20 @@ auto TypeChecker::DeclareFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
   f->set_static_type(arena_->New<FunctionType>(
       &f->param_pattern().static_type(), generic_parameters,
       &f->return_term().static_type(), deduced_bindings, impl_bindings));
-  SetConstantValue(f, arena_->New<FunctionValue>(f));
+  switch (f->kind()) {
+    case DeclarationKind::FunctionDeclaration:
+      SetConstantValue(
+          cast<FunctionDeclaration>(f),
+          arena_->New<FunctionValue>(cast<FunctionDeclaration>(f)));
+      break;
+    case DeclarationKind::DestructorDeclaration:
+      SetConstantValue(
+          cast<DestructorDeclaration>(f),
+          arena_->New<DestructorValue>(cast<DestructorDeclaration>(f)));
+      break;
+    default:
+      CARBON_FATAL() << "f is not a callable declaration";
+  }
 
   if (f->name() == "Main") {
     if (!f->return_term().type_expression().has_value()) {
@@ -3308,7 +3325,7 @@ auto TypeChecker::DeclareFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
   return Success();
 }
 
-auto TypeChecker::TypeCheckFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
+auto TypeChecker::TypeCheckCallableDeclaration(Nonnull<CallableDeclaration*> f,
                                                const ImplScope& impl_scope)
     -> ErrorOr<Success> {
   if (trace_stream_) {
@@ -4003,6 +4020,7 @@ static bool IsValidTypeForAliasTarget(Nonnull<const Value*> type) {
   switch (type->kind()) {
     case Value::Kind::IntValue:
     case Value::Kind::FunctionValue:
+    case Value::Kind::DestructorValue:
     case Value::Kind::BoundMethodValue:
     case Value::Kind::PointerValue:
     case Value::Kind::LValue:
@@ -4110,9 +4128,10 @@ auto TypeChecker::TypeCheckDeclaration(
           TypeCheckImplDeclaration(&cast<ImplDeclaration>(*d), impl_scope));
       break;
     }
+    case DeclarationKind::DestructorDeclaration:
     case DeclarationKind::FunctionDeclaration:
-      CARBON_RETURN_IF_ERROR(TypeCheckFunctionDeclaration(
-          &cast<FunctionDeclaration>(*d), impl_scope));
+      CARBON_RETURN_IF_ERROR(TypeCheckCallableDeclaration(
+          &cast<CallableDeclaration>(*d), impl_scope));
       return Success();
     case DeclarationKind::ClassDeclaration:
       CARBON_RETURN_IF_ERROR(
@@ -4181,11 +4200,16 @@ auto TypeChecker::DeclareDeclaration(Nonnull<Declaration*> d,
       break;
     }
     case DeclarationKind::FunctionDeclaration: {
-      auto& func_def = cast<FunctionDeclaration>(*d);
-      CARBON_RETURN_IF_ERROR(DeclareFunctionDeclaration(&func_def, scope_info));
+      auto& func_def = cast<CallableDeclaration>(*d);
+      CARBON_RETURN_IF_ERROR(DeclareCallableDeclaration(&func_def, scope_info));
+      break;
+    }
+    case DeclarationKind::DestructorDeclaration: {
+      auto& destructor_def = cast<CallableDeclaration>(*d);
+      CARBON_RETURN_IF_ERROR(
+          DeclareCallableDeclaration(&destructor_def, scope_info));
       break;
     }
-
     case DeclarationKind::ClassDeclaration: {
       auto& class_decl = cast<ClassDeclaration>(*d);
       CARBON_RETURN_IF_ERROR(DeclareClassDeclaration(&class_decl, scope_info));

+ 2 - 2
explorer/interpreter/type_checker.h

@@ -192,7 +192,7 @@ class TypeChecker {
   auto DeclareDeclaration(Nonnull<Declaration*> d, const ScopeInfo& scope_info)
       -> ErrorOr<Success>;
 
-  auto DeclareFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
+  auto DeclareCallableDeclaration(Nonnull<CallableDeclaration*> f,
                                   const ScopeInfo& scope_info)
       -> ErrorOr<Success>;
 
@@ -275,7 +275,7 @@ class TypeChecker {
       -> ErrorOr<Success>;
 
   // Type check the body of the function.
-  auto TypeCheckFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
+  auto TypeCheckCallableDeclaration(Nonnull<CallableDeclaration*> f,
                                     const ImplScope& impl_scope)
       -> ErrorOr<Success>;
 

+ 12 - 2
explorer/interpreter/value.cpp

@@ -291,6 +291,13 @@ void Value::Print(llvm::raw_ostream& out) const {
     case Value::Kind::BoolValue:
       out << (cast<BoolValue>(*this).value() ? "true" : "false");
       break;
+    case Value::Kind::DestructorValue: {
+      const DestructorValue& destructor = cast<DestructorValue>(*this);
+      out << "destructor [ ";
+      out << destructor.declaration().me_pattern();
+      out << " ]";
+      break;
+    }
     case Value::Kind::FunctionValue: {
       const FunctionValue& fun = cast<FunctionValue>(*this);
       out << "fun<" << fun.declaration().name() << ">";
@@ -736,6 +743,7 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2,
     }
     case Value::Kind::IntValue:
     case Value::Kind::BoolValue:
+    case Value::Kind::DestructorValue:
     case Value::Kind::FunctionValue:
     case Value::Kind::BoundMethodValue:
     case Value::Kind::StructValue:
@@ -789,6 +797,8 @@ auto ValueStructurallyEqual(
       return body1.has_value() == body2.has_value() &&
              (!body1.has_value() || *body1 == *body2);
     }
+    case Value::Kind::DestructorValue:
+      return false;
     case Value::Kind::BoundMethodValue: {
       const auto& m1 = cast<BoundMethodValue>(*v1);
       const auto& m2 = cast<BoundMethodValue>(*v2);
@@ -986,7 +996,7 @@ auto NominalClassType::FindFunction(std::string_view name) const
         break;
       }
       case DeclarationKind::FunctionDeclaration: {
-        const auto& fun = cast<FunctionDeclaration>(*member);
+        const auto& fun = cast<CallableDeclaration>(*member);
         if (fun.name() == name) {
           return &cast<FunctionValue>(**fun.constant_value());
         }
@@ -1014,7 +1024,7 @@ auto MixinPseudoType::FindFunction(const std::string_view& name) const
         break;
       }
       case DeclarationKind::FunctionDeclaration: {
-        const auto& fun = cast<FunctionDeclaration>(*member);
+        const auto& fun = cast<CallableDeclaration>(*member);
         if (fun.name() == name) {
           return &cast<FunctionValue>(**fun.constant_value());
         }

+ 19 - 0
explorer/interpreter/value.h

@@ -39,6 +39,7 @@ class Value {
   enum class Kind {
     IntValue,
     FunctionValue,
+    DestructorValue,
     BoundMethodValue,
     PointerValue,
     LValue,
@@ -186,6 +187,24 @@ class FunctionValue : public Value {
   Nonnull<const Bindings*> bindings_ = Bindings::None();
 };
 
+// A destructor value.
+class DestructorValue : public Value {
+ public:
+  explicit DestructorValue(Nonnull<const DestructorDeclaration*> declaration)
+      : Value(Kind::DestructorValue), declaration_(declaration) {}
+
+  static auto classof(const Value* value) -> bool {
+    return value->kind() == Kind::DestructorValue;
+  }
+
+  auto declaration() const -> const DestructorDeclaration& {
+    return *declaration_;
+  }
+
+ private:
+  Nonnull<const DestructorDeclaration*> declaration_;
+};
+
 // A bound method value. It includes the receiver object.
 class BoundMethodValue : public Value {
  public:

+ 2 - 0
explorer/syntax/lexer.lpp

@@ -55,6 +55,7 @@ CONTINUATION         "__continuation"
 CONTINUATION_TYPE    "__Continuation"
 CONTINUE             "continue"
 DEFAULT              "default"
+DESTRUCTOR           "destructor"
 DOUBLE_ARROW         "=>"
 ELSE                 "else"
 EQUAL                "="
@@ -160,6 +161,7 @@ operand_start         [(A-Za-z0-9_\"]
 {CONTINUATION}        { return CARBON_SIMPLE_TOKEN(CONTINUATION);        }
 {CONTINUE}            { return CARBON_SIMPLE_TOKEN(CONTINUE);            }
 {DEFAULT}             { return CARBON_SIMPLE_TOKEN(DEFAULT);             }
+{DESTRUCTOR}          { return CARBON_SIMPLE_TOKEN(DESTRUCTOR);          }
 {DOUBLE_ARROW}        { return CARBON_SIMPLE_TOKEN(DOUBLE_ARROW);        }
 {ELSE}                { return CARBON_SIMPLE_TOKEN(ELSE);                }
 {EQUAL_EQUAL}         { return CARBON_SIMPLE_TOKEN(EQUAL_EQUAL);         }

+ 21 - 0
explorer/syntax/parser.ypp

@@ -108,6 +108,7 @@
 %type <std::optional<Nonnull<Expression*>>> class_declaration_extends
 %type <Nonnull<Declaration*>> declaration
 %type <Nonnull<FunctionDeclaration*>> function_declaration
+%type <Nonnull<DestructorDeclaration*>> destructor_declaration;
 %type <Nonnull<MixDeclaration*>> mix_declaration
 %type <Nonnull<AliasDeclaration*>> alias_declaration
 %type <std::vector<Nonnull<Declaration*>>> declaration_list
@@ -223,6 +224,7 @@
   CONTINUATION_TYPE
   CONTINUE
   DEFAULT
+  DESTRUCTOR
   DOUBLE_ARROW
   ELSE
   EQUAL
@@ -1145,6 +1147,8 @@ class_declaration_extends:
 declaration:
   function_declaration
     { $$ = $1; }
+| destructor_declaration
+    { $$ = $1; }
 | class_declaration_extensibility CLASS identifier type_params class_declaration_extends LEFT_CURLY_BRACE class_body RIGHT_CURLY_BRACE
     {
       $$ = arena->New<ClassDeclaration>(
@@ -1211,6 +1215,23 @@ impl_type:
     { $$ = arena->New<IdentifierExpression>(context.source_loc(), "Self"); }
 | type_expression
 ;
+destructor_declaration:
+  DESTRUCTOR deduced_params block
+  {
+    ErrorOr<DestructorDeclaration*> fn =
+        DestructorDeclaration::CreateDestructor(
+            arena, context.source_loc(), $2,
+            arena->New<TuplePattern>(context.source_loc(),
+                                     std::vector<Nonnull<Pattern*>>()),
+            ReturnTerm::Omitted(context.source_loc()), $3);
+    if (fn.ok()) {
+      $$ = *fn;
+    } else {
+      context.RecordSyntaxError(std::move(fn).error());
+      YYERROR;
+    }
+  }
+;
 declaration_list:
   // Empty
     { $$ = {}; }

+ 45 - 0
explorer/testdata/destructor/call_destructor_from_destructor.carbon

@@ -0,0 +1,45 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: DESTRUCTOR A 1
+// CHECK: DESTRUCTOR B 2
+// CHECK: DESTRUCTOR A 3
+// CHECK: DESTRUCTOR A 4
+// CHECK: result: 1
+
+package ExplorerTest api;
+
+class A{
+    destructor[me: Self]{
+        Print("DESTRUCTOR A {0}",me.n);
+    }
+    fn Create(x: i32) -> A{
+        return {.n = x};
+    }
+    var n: i32;
+}
+
+class B{
+    destructor[me: Self]{
+        Print("DESTRUCTOR B {0}",me.n);
+    }
+    fn Create(x: i32) -> B{
+       return {.n = x, .a = A.Create(3) };
+    }
+    var n: i32;
+    var a: A;
+}
+
+
+fn Main() -> i32 {
+  var a: A = A.Create(4);
+  var b: B = B.Create(2);
+  var c: A = A.Create(1);
+  return 1;
+}

+ 29 - 0
explorer/testdata/destructor/destructor_with_return.carbon

@@ -0,0 +1,29 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: DESTRUCTOR A 3
+// CHECK: result: 1
+
+package ExplorerTest api;
+
+class A{
+    destructor[me: Self]{
+        Print("DESTRUCTOR A {0}",me.n);
+        if(me.n == 3){
+            return;
+        }
+        Print("DESTRUCTOR A: This message should not printed");
+    }
+    var n: i32;
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 3};
+  return 1;
+}

+ 24 - 0
explorer/testdata/destructor/fail_multiple_destructor_declaration.carbon

@@ -0,0 +1,24 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 2>&1 | %{FileCheck} %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+class A{
+    destructor[me: Self]{
+        Print("DESTRUCTOR A1 {0}",me.n);
+    }
+    destructor[me: Self]{
+        Print("DESTRUCTOR A2 {0}",me.n);
+    // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/destructor/fail_multiple_destructor_declaration.carbon:[[@LINE+1]]: Duplicate name `destructor` also found at {{.*}}/explorer/testdata/destructor/fail_multiple_destructor_declaration.carbon:13
+    }
+    var n: i32;
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 3};
+  return 1;
+}

+ 43 - 0
explorer/testdata/destructor/function_with_return.carbon

@@ -0,0 +1,43 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: DESTRUCTOR C 1
+// CHECK: DESTRUCTOR B 2
+// CHECK: DESTRUCTOR A 3
+// CHECK: result: 1
+
+package ExplorerTest api;
+
+class A{
+    destructor[me: Self]{
+        Print("DESTRUCTOR A {0}",me.n);
+    }
+    var n: i32;
+}
+
+class B{
+    destructor[me: Self]{
+        Print("DESTRUCTOR B {0}",me.n);
+    }
+    var n: i32;
+}
+
+class C{
+    destructor[me: Self]{
+        Print("DESTRUCTOR C {0}",me.n);
+    }
+    var n: i32;
+}
+
+fn Main() -> i32 {
+  var a: A = {.n = 3};
+  var b: B = {.n = 2};
+  var c: C = {.n = 1};
+  return 1;
+}

+ 36 - 0
explorer/testdata/destructor/loop_block.carbon

@@ -0,0 +1,36 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: DESTRUCTOR A 1
+// CHECK: DESTRUCTOR A 2
+// CHECK: DESTRUCTOR A 3
+// CHECK: DESTRUCTOR A 1
+// CHECK: DESTRUCTOR A 2
+// CHECK: DESTRUCTOR A 3
+// CHECK: result: 1
+
+package ExplorerTest api;
+
+class A{
+    destructor[me: Self]{
+        Print("DESTRUCTOR A {0}",me.n);
+    }
+    var n: i32;
+}
+
+fn Main() -> i32 {
+  var i: i32 = 0;
+  while( i < 2){
+    var a: A = {.n = 3};
+    var b: A = {.n = 2};
+    var c: A = {.n = 1};
+    i = i + 1;
+  }
+  return 1;
+}

+ 58 - 0
explorer/testdata/destructor/loop_break_continue.carbon

@@ -0,0 +1,58 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: DESTRUCTOR A 1
+// CHECK: DESTRUCTOR A 2
+// CHECK: DESTRUCTOR A 3
+// CHECK: DESTRUCTOR A 4
+// CHECK: DESTRUCTOR A 5
+// CHECK: DESTRUCTOR A 6
+// CHECK: DESTRUCTOR A 7
+// CHECK: DESTRUCTOR A 8
+// CHECK: DESTRUCTOR A 6
+// CHECK: DESTRUCTOR A 7
+// CHECK: DESTRUCTOR A 8
+// CHECK: result: 1
+
+package ExplorerTest api;
+
+class A{
+    destructor[me: Self]{
+        Print("DESTRUCTOR A {0}",me.n);
+    }
+    var n: i32;
+}
+
+fn Main() -> i32 {
+  var i: i32 = 0;
+  while(true){
+      var a: A = {.n = 4};
+      var b: A = {.n = 3};
+      var c: A = {.n = 2};
+      if(true){
+        var a: A = {.n = 1};
+        break;
+      }
+  }
+
+  i = 0;
+  while(i < 2 ){
+    var a: A = {.n = 8};
+    var b: A = {.n = 7};
+    var c: A = {.n = 6};
+    if(i == 0){
+      var a: A = {.n = 5};
+      i = i+1;
+      continue;
+    }
+    i = i+1;
+  }
+
+  return 1;
+}

+ 49 - 0
explorer/testdata/destructor/nested_block_with_return.carbon

@@ -0,0 +1,49 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: DESTRUCTOR A 1
+// CHECK: DESTRUCTOR A 2
+// CHECK: DESTRUCTOR A 3
+// CHECK: DESTRUCTOR A 4
+// CHECK: result: 2
+
+package ExplorerTest api;
+
+class A{
+    destructor[me: Self]{
+        Print("DESTRUCTOR A {0}",me.n);
+    }
+    var n: i32;
+}
+
+fn ident(x: bool)-> bool{
+    return x;
+}
+
+fn ident_i32(x: i32)-> i32{
+    return x;
+}
+// It should be enforced that different runtime scopes are on the stack.
+//So that it can be tested that the wrong scopes are not removed from the stack.
+fn Main() -> i32 {
+  var i: i32 = 0;
+  var d: A = {.n = 4};
+  if(ident(true)){
+    var a: A = {.n = 3};
+    if(true){
+       var b: A = {.n = 2};
+       ident_i32(2);
+       if(ident_i32(0) == 0){
+         var c: A = {.n = 1};
+         return 2;
+       }
+    }
+  }
+  return 1;
+}