From cfa295c0af416bebde910432a42e1f40313fec3b Mon Sep 17 00:00:00 2001 From: pmqtt <51272730+pmqtt@users.noreply.github.com> Date: Mon, 26 Sep 2022 19:11:05 +0200 Subject: [PATCH] Feature destructor (#2116) --- common/fuzzing/carbon.proto | 6 ++ common/fuzzing/proto_to_carbon.cpp | 21 +++++ explorer/ast/ast_rtti.txt | 4 +- explorer/ast/declaration.cpp | 59 +++++++++--- explorer/ast/declaration.h | 88 +++++++++++++---- explorer/ast/statement.h | 10 +- explorer/fuzzing/ast_to_proto.cpp | 29 ++++++ explorer/interpreter/BUILD | 1 + explorer/interpreter/action.cpp | 26 ++++- explorer/interpreter/action.h | 54 ++++++++++- explorer/interpreter/action_stack.cpp | 94 ++++++++++++++++--- explorer/interpreter/action_stack.h | 19 +++- explorer/interpreter/interpreter.cpp | 81 +++++++++++++++- explorer/interpreter/resolve_control_flow.cpp | 12 ++- explorer/interpreter/resolve_names.cpp | 12 ++- explorer/interpreter/resolve_unformed.cpp | 9 +- explorer/interpreter/type_checker.cpp | 40 ++++++-- explorer/interpreter/type_checker.h | 4 +- explorer/interpreter/value.cpp | 14 ++- explorer/interpreter/value.h | 19 ++++ explorer/syntax/lexer.lpp | 2 + explorer/syntax/parser.ypp | 21 +++++ .../call_destructor_from_destructor.carbon | 45 +++++++++ .../destructor/destructor_with_return.carbon | 29 ++++++ ...ail_multiple_destructor_declaration.carbon | 24 +++++ .../destructor/function_with_return.carbon | 43 +++++++++ .../testdata/destructor/loop_block.carbon | 36 +++++++ .../destructor/loop_break_continue.carbon | 58 ++++++++++++ .../nested_block_with_return.carbon | 49 ++++++++++ 29 files changed, 827 insertions(+), 82 deletions(-) create mode 100644 explorer/testdata/destructor/call_destructor_from_destructor.carbon create mode 100644 explorer/testdata/destructor/destructor_with_return.carbon create mode 100644 explorer/testdata/destructor/fail_multiple_destructor_declaration.carbon create mode 100644 explorer/testdata/destructor/function_with_return.carbon create mode 100644 explorer/testdata/destructor/loop_block.carbon create mode 100644 explorer/testdata/destructor/loop_break_continue.carbon create mode 100644 explorer/testdata/destructor/nested_block_with_return.carbon diff --git a/common/fuzzing/carbon.proto b/common/fuzzing/carbon.proto index 9ae4ebaef5a8..4cd5a5177781 100644 --- a/common/fuzzing/carbon.proto +++ b/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; } } diff --git a/common/fuzzing/proto_to_carbon.cpp b/common/fuzzing/proto_to_carbon.cpp index e7792d12701d..ede4bed6e8f5 100644 --- a/common/fuzzing/proto_to_carbon.cpp +++ b/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 "; diff --git a/explorer/ast/ast_rtti.txt b/explorer/ast/ast_rtti.txt index fe19c8844f9d..a29ee155a18f 100644 --- a/explorer/ast/ast_rtti.txt +++ b/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; diff --git a/explorer/ast/declaration.cpp b/explorer/ast/declaration.cpp index 487102131a71..be4ebfe60511 100644 --- a/explorer/ast/declaration.cpp +++ b/explorer/ast/declaration.cpp @@ -38,7 +38,9 @@ void Declaration::Print(llvm::raw_ostream& out) const { case DeclarationKind::FunctionDeclaration: cast(*this).PrintDepth(-1, out); break; - + case DeclarationKind::DestructorDeclaration: + cast(*this).PrintDepth(-1, out); + break; case DeclarationKind::ClassDeclaration: { const auto& class_decl = cast(*this); PrintID(out); @@ -131,7 +133,9 @@ void Declaration::PrintID(llvm::raw_ostream& out) const { case DeclarationKind::FunctionDeclaration: out << "fn " << cast(*this).name(); break; - + case DeclarationKind::DestructorDeclaration: + out << cast(*this).name(); + break; case DeclarationKind::ClassDeclaration: { const auto& class_decl = cast(*this); out << "class " << class_decl.name(); @@ -185,6 +189,8 @@ auto GetName(const Declaration& declaration) switch (declaration.kind()) { case DeclarationKind::FunctionDeclaration: return cast(declaration).name(); + case DeclarationKind::DestructorDeclaration: + return cast(declaration).name(); case DeclarationKind::ClassDeclaration: return cast(declaration).name(); case DeclarationKind::MixinDeclaration: { @@ -231,17 +237,13 @@ void ReturnTerm::Print(llvm::raw_ostream& out) const { } } -auto FunctionDeclaration::Create(Nonnull arena, - SourceLocation source_loc, std::string name, - std::vector> deduced_params, - std::optional> me_pattern, - Nonnull param_pattern, - ReturnTerm return_term, - std::optional> body) - -> ErrorOr> { +// Look for the `me` parameter in the `deduced_parameters` +// and put it in the `me_pattern`. +static auto MoveMeParameterToPattern( + SourceLocation source_loc, std::optional>& me_pattern, + const std::vector>& deduced_params) + -> ErrorOr>> { std::vector> resolved_params; - // Look for the `me` parameter in the `deduced_parameters` - // and put it in the `me_pattern`. for (Nonnull param : deduced_params) { switch (param->kind()) { case AstNodeKind::GenericBinding: @@ -271,12 +273,43 @@ auto FunctionDeclaration::Create(Nonnull arena, << "illegal AST node in implicit parameter list"; } } + return resolved_params; +} + +auto DestructorDeclaration::CreateDestructor( + Nonnull arena, SourceLocation source_loc, + std::vector> deduced_params, + Nonnull param_pattern, ReturnTerm return_term, + std::optional> body) + -> ErrorOr> { + std::vector> resolved_params; + std::optional> me_pattern; + CARBON_ASSIGN_OR_RETURN( + resolved_params, + MoveMeParameterToPattern(source_loc, me_pattern, deduced_params)); + return arena->New( + source_loc, std::move(resolved_params), me_pattern, param_pattern, + return_term, body); +} + +auto FunctionDeclaration::Create(Nonnull arena, + SourceLocation source_loc, std::string name, + std::vector> deduced_params, + std::optional> me_pattern, + Nonnull param_pattern, + ReturnTerm return_term, + std::optional> body) + -> ErrorOr> { + std::vector> resolved_params; + CARBON_ASSIGN_OR_RETURN( + resolved_params, + MoveMeParameterToPattern(source_loc, me_pattern, deduced_params)); return arena->New(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 << "["; diff --git a/explorer/ast/declaration.h b/explorer/ast/declaration.h index b6f5919e1f6c..74fab7a6bd10 100644 --- a/explorer/ast/declaration.h +++ b/explorer/ast/declaration.h @@ -102,27 +102,15 @@ class Declaration : public AstNode { std::optional> constant_value_; }; -class FunctionDeclaration : public Declaration { +class CallableDeclaration : public Declaration { public: - using ImplementsCarbonValueNode = void; - - static auto Create(Nonnull arena, SourceLocation source_loc, - std::string name, - std::vector> deduced_params, - std::optional> me_pattern, - Nonnull param_pattern, - ReturnTerm return_term, - std::optional> body) - -> ErrorOr>; - - // 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> deduced_params, std::optional> me_pattern, Nonnull param_pattern, ReturnTerm return_term, std::optional> 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> { @@ -166,6 +151,62 @@ class FunctionDeclaration : public Declaration { std::optional> body_; }; +class FunctionDeclaration : public CallableDeclaration { + public: + using ImplementsCarbonValueNode = void; + + static auto Create(Nonnull arena, SourceLocation source_loc, + std::string name, + std::vector> deduced_params, + std::optional> me_pattern, + Nonnull param_pattern, + ReturnTerm return_term, + std::optional> body) + -> ErrorOr>; + + // Use `Create()` instead. This is public only so Arena::New() can call it. + FunctionDeclaration(SourceLocation source_loc, std::string name, + std::vector> deduced_params, + std::optional> me_pattern, + Nonnull param_pattern, + ReturnTerm return_term, + std::optional> 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, SourceLocation source_loc, + std::vector> deduced_params, + Nonnull param_pattern, + ReturnTerm return_term, + std::optional> body) + -> ErrorOr>; + + // Use `Create()` instead. This is public only so Arena::New() can call it. + DestructorDeclaration(SourceLocation source_loc, + std::vector> deduced_params, + std::optional> me_pattern, + Nonnull param_pattern, + ReturnTerm return_term, + std::optional> 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> { return members_; } + auto destructor() const -> std::optional> { + for (auto& x : members_) { + if (x->kind() == DeclarationKind::DestructorDeclaration) { + return llvm::cast(x); + } + } + return std::nullopt; + } auto value_category() const -> ValueCategory { return ValueCategory::Let; } @@ -232,6 +281,7 @@ class ClassDeclaration : public Declaration { std::optional> type_params_; std::optional> extends_; std::vector> members_; + std::optional> destructor_; }; // EXPERIMENTAL MIXIN FEATURE diff --git a/explorer/ast/statement.h b/explorer/ast/statement.h index 0d96ec2bca9c..f6cba44280a6 100644 --- a/explorer/ast/statement.h +++ b/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 function) { + void set_function(Nonnull function) { CARBON_CHECK(!function_.has_value()); function_ = function; } @@ -217,7 +217,7 @@ class Return : public Statement { : Statement(node_kind, source_loc) {} private: - std::optional> function_; + std::optional> function_; }; class ReturnVar : public Return { diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index a454da502fac..02b98c7ac585 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/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(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(function.me_pattern())); + break; + case PatternKind::BindingPattern: + *function_proto->mutable_me_pattern() = + PatternToProto(cast(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(declaration); auto* function_proto = declaration_proto.mutable_function(); diff --git a/explorer/interpreter/BUILD b/explorer/interpreter/BUILD index 7a69670d03d1..1ee8df729568 100644 --- a/explorer/interpreter/BUILD +++ b/explorer/interpreter/BUILD @@ -40,6 +40,7 @@ cc_library( deps = [ ":action_and_value", ":stack", + "//common:error", "//common:ostream", "//explorer/ast", "@llvm-project//llvm:Support", diff --git a/explorer/interpreter/action.cpp b/explorer/interpreter/action.cpp index e2adbda74c8d..62f496652874 100644 --- a/explorer/interpreter/action.cpp +++ b/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()) { diff --git a/explorer/interpreter/action.h b/explorer/interpreter/action.h index 5accfff0c7bc..ee0a8dc6d69a 100644 --- a/explorer/interpreter/action.h +++ b/explorer/interpreter/action.h @@ -5,7 +5,9 @@ #ifndef CARBON_EXPLORER_INTERPRETER_ACTION_H_ #define CARBON_EXPLORER_INTERPRETER_ACTION_H_ +#include #include +#include #include #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 heap) : heap_(heap) {} + explicit RuntimeScope(Nonnull 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>; + // Returns the local values in created order + auto locals() const -> std::vector> { + std::vector> 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> locals_; + llvm::MapVector, + std::map> + locals_; std::vector allocations_; Nonnull 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 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 diff --git a/explorer/interpreter/action_stack.cpp b/explorer/interpreter/action_stack.cpp index 24792210872b..4ce6fa3866ea 100644 --- a/explorer/interpreter/action_stack.cpp +++ b/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 { + std::stack> scopes_to_destroy; std::unique_ptr 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 { 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 result) -> ErrorOr { + std::stack> scopes_to_destroy; std::unique_ptr 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 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 { return Success(); } -auto ActionStack::UnwindTo(Nonnull ast_node) - -> ErrorOr { +auto ActionStack::UnwindToWithCaptureScopesToDestroy( + Nonnull ast_node) -> std::stack> { + std::stack> scopes_to_destroy; while (true) { if (const auto* statement_action = llvm::dyn_cast(todo_.Top().get()); @@ -201,23 +214,50 @@ auto ActionStack::UnwindTo(Nonnull 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 cleanup_action = + std::make_unique(std::move(*scope)); + scopes_to_destroy.push(std::move(cleanup_action)); + } } + return scopes_to_destroy; +} + +auto ActionStack::UnwindTo(Nonnull ast_node) + -> ErrorOr { + std::stack> scopes_to_destroy = + UnwindToWithCaptureScopesToDestroy(ast_node); + PushCleanUpActions(std::move(scopes_to_destroy)); return Success(); } auto ActionStack::UnwindPast(Nonnull ast_node) -> ErrorOr { - CARBON_RETURN_IF_ERROR(UnwindTo(ast_node)); - todo_.Pop(); - PopScopes(); + std::stack> scopes_to_destroy = + UnwindPastWithCaptureScopesToDestroy(ast_node); + PushCleanUpActions(std::move(scopes_to_destroy)); + return Success(); } +auto ActionStack::UnwindPastWithCaptureScopesToDestroy( + Nonnull ast_node) -> std::stack> { + std::stack> 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 ast_node, Nonnull result) -> ErrorOr { - CARBON_RETURN_IF_ERROR(UnwindPast(ast_node)); + std::stack> scopes_to_destroy = + UnwindPastWithCaptureScopesToDestroy(ast_node); SetResult(result); + PushCleanUpActions(std::move(scopes_to_destroy)); return Success(); } @@ -248,9 +288,17 @@ auto ActionStack::Suspend() -> ErrorOr { return Success(); } -void ActionStack::PopScopes() { +void ActionStack::PopScopes( + std::stack>& cleanup_stack) { while (!todo_.IsEmpty() && llvm::isa(*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 result) { } } +void ActionStack::PushCleanUpActions( + std::stack> actions) { + while (!actions.empty()) { + auto& act = actions.top(); + if (act->scope()) { + std::unique_ptr cleanup_action = + std::make_unique(std::move(*act->scope())); + todo_.Push(std::move(cleanup_action)); + } + actions.pop(); + } +} + +void ActionStack::PushCleanUpAction(std::unique_ptr act) { + auto& scope = act->scope(); + if (scope && act->kind() != Action::Kind::CleanUpAction) { + std::unique_ptr cleanup_action = + std::make_unique(std::move(*scope)); + todo_.Push(std::move(cleanup_action)); + } +} + } // namespace Carbon diff --git a/explorer/interpreter/action_stack.h b/explorer/interpreter/action_stack.h index 57a37acb52af..995cce42e699 100644 --- a/explorer/interpreter/action_stack.h +++ b/explorer/interpreter/action_stack.h @@ -7,6 +7,7 @@ #include #include +#include #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; + 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>& cleanup_stack); // Set `result` as the result of the Action most recently removed from the // stack. void SetResult(Nonnull result); + auto UnwindToWithCaptureScopesToDestroy(Nonnull ast_node) + -> std::stack>; + + auto UnwindPastWithCaptureScopesToDestroy(Nonnull ast_node) + -> std::stack>; + + // Create CleanUpActions for all actions + void PushCleanUpActions(std::stack> actions); + + // Create and push a CleanUpAction on the stack + void PushCleanUpAction(std::unique_ptr act); + // TODO: consider defining a non-nullable unique_ptr-like type to use here. Stack> todo_; std::optional> result_; diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index a919bb948673..ed9cbeb769a8 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -4,6 +4,8 @@ #include "explorer/interpreter/interpreter.h" +#include + #include #include #include @@ -83,6 +85,8 @@ class Interpreter { auto StepStmt() -> ErrorOr; // State transition for declarations. auto StepDeclaration() -> ErrorOr; + // State transition for object destruction. + auto StepCleanUp() -> ErrorOr; auto CreateStruct(const std::vector& fields, const std::vector>& values) @@ -142,6 +146,9 @@ class Interpreter { Nonnull arg, ImplWitnessMap&& witnesses) -> ErrorOr; + auto CallDestructor(Nonnull fun, + Nonnull receiver) -> ErrorOr; + void PrintState(llvm::raw_ostream& out); Phase phase() const { return phase_; } @@ -615,6 +622,7 @@ auto Interpreter::Convert(Nonnull 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 value, } } +auto Interpreter::CallDestructor(Nonnull fun, + Nonnull receiver) + -> ErrorOr { + 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(*method.body()); + method_scope.TransitState(); + return todo_.Spawn(std::unique_ptr(std::move(act)), + std::move(method_scope)); +} + auto Interpreter::CallFunction(const CallExpression& call, Nonnull fun, Nonnull arg, @@ -1756,7 +1784,7 @@ auto Interpreter::StepStmt() -> ErrorOr { CARBON_ASSIGN_OR_RETURN( value, heap_.Read(lvalue->address(), ret_var.source_loc())); } - const FunctionDeclaration& function = cast(stmt).function(); + const CallableDeclaration& function = cast(stmt).function(); CARBON_ASSIGN_OR_RETURN( Nonnull return_value, Convert(value, &function.return_term().static_type(), @@ -1772,7 +1800,7 @@ auto Interpreter::StepStmt() -> ErrorOr { } else { // { {v :: return [] :: C, E, F} :: {C', E', F'} :: S, H} // -> { {v :: C', E', F'} :: S, H} - const FunctionDeclaration& function = cast(stmt).function(); + const CallableDeclaration& function = cast(stmt).function(); CARBON_ASSIGN_OR_RETURN( Nonnull return_value, Convert(act.results()[0], &function.return_term().static_type(), @@ -1841,6 +1869,7 @@ auto Interpreter::StepDeclaration() -> ErrorOr { return todo_.FinishAction(); } } + case DeclarationKind::DestructorDeclaration: case DeclarationKind::FunctionDeclaration: case DeclarationKind::ClassDeclaration: case DeclarationKind::MixinDeclaration: @@ -1856,6 +1885,51 @@ auto Interpreter::StepDeclaration() -> ErrorOr { } } +auto Interpreter::StepCleanUp() -> ErrorOr { + Action& act = todo_.CurrentAction(); + CleanupAction& cleanup = cast(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(*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); + } + } + } else { + if (const auto* class_obj = dyn_cast(*value)) { + const auto& class_type = cast(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(member)) { + const auto& type = var->static_type(); + if (const auto* c_type = dyn_cast(&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 { Action& act = todo_.CurrentAction(); @@ -1875,6 +1949,9 @@ auto Interpreter::Step() -> ErrorOr { 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: diff --git a/explorer/interpreter/resolve_control_flow.cpp b/explorer/interpreter/resolve_control_flow.cpp index 0d5cc786cf8b..98dfad86f4e7 100644 --- a/explorer/interpreter/resolve_control_flow.cpp +++ b/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 declaration; + Nonnull 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, "signature."; } } + return Success(); } case StatementKind::Break: @@ -138,12 +139,13 @@ static auto ResolveControlFlow(Nonnull statement, auto ResolveControlFlow(Nonnull declaration) -> ErrorOr { switch (declaration->kind()) { + case DeclarationKind::DestructorDeclaration: case DeclarationKind::FunctionDeclaration: { - auto& function = cast(*declaration); - if (function.body().has_value()) { - FunctionData data = {.declaration = &function}; + auto& callable = cast(*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; } diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index aacc8358b385..24be26cfc127 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/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(declaration); + CARBON_RETURN_IF_ERROR(enclosing_scope.Add( + "destructor", &func, StaticScope::NameStatus::KnownButNotDeclared)); + break; + } case DeclarationKind::FunctionDeclaration: { auto& func = cast(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(declaration); + auto& function = cast(declaration); StaticScope function_scope; function_scope.AddParent(&enclosing_scope); enclosing_scope.MarkDeclared(function.name()); diff --git a/explorer/interpreter/resolve_unformed.cpp b/explorer/interpreter/resolve_unformed.cpp index d34d9c300888..0f1c10098428 100644 --- a/explorer/interpreter/resolve_unformed.cpp +++ b/explorer/interpreter/resolve_unformed.cpp @@ -284,11 +284,12 @@ static auto ResolveUnformed(Nonnull 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(*declaration); - if (function.body().has_value()) { + case DeclarationKind::FunctionDeclaration: + case DeclarationKind::DestructorDeclaration: { + auto& callable = cast(*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; diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 58eb693cf705..057fe1560c8c 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -167,6 +167,7 @@ static auto ExpectPointerType(SourceLocation source_loc, static auto IsTypeOfType(Nonnull 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 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 f, +auto TypeChecker::DeclareCallableDeclaration(Nonnull f, const ScopeInfo& scope_info) -> ErrorOr { if (trace_stream_) { @@ -3287,7 +3291,20 @@ auto TypeChecker::DeclareFunctionDeclaration(Nonnull f, f->set_static_type(arena_->New( &f->param_pattern().static_type(), generic_parameters, &f->return_term().static_type(), deduced_bindings, impl_bindings)); - SetConstantValue(f, arena_->New(f)); + switch (f->kind()) { + case DeclarationKind::FunctionDeclaration: + SetConstantValue( + cast(f), + arena_->New(cast(f))); + break; + case DeclarationKind::DestructorDeclaration: + SetConstantValue( + cast(f), + arena_->New(cast(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 f, return Success(); } -auto TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, +auto TypeChecker::TypeCheckCallableDeclaration(Nonnull f, const ImplScope& impl_scope) -> ErrorOr { if (trace_stream_) { @@ -4003,6 +4020,7 @@ static bool IsValidTypeForAliasTarget(Nonnull 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(*d), impl_scope)); break; } + case DeclarationKind::DestructorDeclaration: case DeclarationKind::FunctionDeclaration: - CARBON_RETURN_IF_ERROR(TypeCheckFunctionDeclaration( - &cast(*d), impl_scope)); + CARBON_RETURN_IF_ERROR(TypeCheckCallableDeclaration( + &cast(*d), impl_scope)); return Success(); case DeclarationKind::ClassDeclaration: CARBON_RETURN_IF_ERROR( @@ -4181,11 +4200,16 @@ auto TypeChecker::DeclareDeclaration(Nonnull d, break; } case DeclarationKind::FunctionDeclaration: { - auto& func_def = cast(*d); - CARBON_RETURN_IF_ERROR(DeclareFunctionDeclaration(&func_def, scope_info)); + auto& func_def = cast(*d); + CARBON_RETURN_IF_ERROR(DeclareCallableDeclaration(&func_def, scope_info)); + break; + } + case DeclarationKind::DestructorDeclaration: { + auto& destructor_def = cast(*d); + CARBON_RETURN_IF_ERROR( + DeclareCallableDeclaration(&destructor_def, scope_info)); break; } - case DeclarationKind::ClassDeclaration: { auto& class_decl = cast(*d); CARBON_RETURN_IF_ERROR(DeclareClassDeclaration(&class_decl, scope_info)); diff --git a/explorer/interpreter/type_checker.h b/explorer/interpreter/type_checker.h index 7606746d5c20..0088819c9aa1 100644 --- a/explorer/interpreter/type_checker.h +++ b/explorer/interpreter/type_checker.h @@ -192,7 +192,7 @@ class TypeChecker { auto DeclareDeclaration(Nonnull d, const ScopeInfo& scope_info) -> ErrorOr; - auto DeclareFunctionDeclaration(Nonnull f, + auto DeclareCallableDeclaration(Nonnull f, const ScopeInfo& scope_info) -> ErrorOr; @@ -275,7 +275,7 @@ class TypeChecker { -> ErrorOr; // Type check the body of the function. - auto TypeCheckFunctionDeclaration(Nonnull f, + auto TypeCheckCallableDeclaration(Nonnull f, const ImplScope& impl_scope) -> ErrorOr; diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index 2a2b7f299e14..a9adfe888f3d 100644 --- a/explorer/interpreter/value.cpp +++ b/explorer/interpreter/value.cpp @@ -291,6 +291,13 @@ void Value::Print(llvm::raw_ostream& out) const { case Value::Kind::BoolValue: out << (cast(*this).value() ? "true" : "false"); break; + case Value::Kind::DestructorValue: { + const DestructorValue& destructor = cast(*this); + out << "destructor [ "; + out << destructor.declaration().me_pattern(); + out << " ]"; + break; + } case Value::Kind::FunctionValue: { const FunctionValue& fun = cast(*this); out << "fun<" << fun.declaration().name() << ">"; @@ -736,6 +743,7 @@ auto TypeEqual(Nonnull t1, Nonnull 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(*v1); const auto& m2 = cast(*v2); @@ -986,7 +996,7 @@ auto NominalClassType::FindFunction(std::string_view name) const break; } case DeclarationKind::FunctionDeclaration: { - const auto& fun = cast(*member); + const auto& fun = cast(*member); if (fun.name() == name) { return &cast(**fun.constant_value()); } @@ -1014,7 +1024,7 @@ auto MixinPseudoType::FindFunction(const std::string_view& name) const break; } case DeclarationKind::FunctionDeclaration: { - const auto& fun = cast(*member); + const auto& fun = cast(*member); if (fun.name() == name) { return &cast(**fun.constant_value()); } diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index 2058f40df57c..8f830788bf2c 100644 --- a/explorer/interpreter/value.h +++ b/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 bindings_ = Bindings::None(); }; +// A destructor value. +class DestructorValue : public Value { + public: + explicit DestructorValue(Nonnull 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 declaration_; +}; + // A bound method value. It includes the receiver object. class BoundMethodValue : public Value { public: diff --git a/explorer/syntax/lexer.lpp b/explorer/syntax/lexer.lpp index 82ef494482d5..593edd4f762d 100644 --- a/explorer/syntax/lexer.lpp +++ b/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); } diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index 8417ed37e50b..5d7abf98f58b 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -108,6 +108,7 @@ %type >> class_declaration_extends %type > declaration %type > function_declaration +%type > destructor_declaration; %type > mix_declaration %type > alias_declaration %type >> 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( @@ -1211,6 +1215,23 @@ impl_type: { $$ = arena->New(context.source_loc(), "Self"); } | type_expression ; +destructor_declaration: + DESTRUCTOR deduced_params block + { + ErrorOr fn = + DestructorDeclaration::CreateDestructor( + arena, context.source_loc(), $2, + arena->New(context.source_loc(), + std::vector>()), + ReturnTerm::Omitted(context.source_loc()), $3); + if (fn.ok()) { + $$ = *fn; + } else { + context.RecordSyntaxError(std::move(fn).error()); + YYERROR; + } + } +; declaration_list: // Empty { $$ = {}; } diff --git a/explorer/testdata/destructor/call_destructor_from_destructor.carbon b/explorer/testdata/destructor/call_destructor_from_destructor.carbon new file mode 100644 index 000000000000..5d0c792183a9 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/destructor/destructor_with_return.carbon b/explorer/testdata/destructor/destructor_with_return.carbon new file mode 100644 index 000000000000..1f04732b0ace --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/destructor/fail_multiple_destructor_declaration.carbon b/explorer/testdata/destructor/fail_multiple_destructor_declaration.carbon new file mode 100644 index 000000000000..74aa50c7a256 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/destructor/function_with_return.carbon b/explorer/testdata/destructor/function_with_return.carbon new file mode 100644 index 000000000000..66bdaa503ec0 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/destructor/loop_block.carbon b/explorer/testdata/destructor/loop_block.carbon new file mode 100644 index 000000000000..bfa3193fce9b --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/destructor/loop_break_continue.carbon b/explorer/testdata/destructor/loop_break_continue.carbon new file mode 100644 index 000000000000..f76b17ec6c25 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/destructor/nested_block_with_return.carbon b/explorer/testdata/destructor/nested_block_with_return.carbon new file mode 100644 index 000000000000..e5ffa2d68d10 --- /dev/null +++ b/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; +}