mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
Feature call destructor for tuples and bug fixes for the destructor process (#2255)
This PR includes the following changes: * Added destruction process for tuples * Fix: In the current version only the last member of a object can be destroyed * Fix: In the current version, the destructor of the object is called after each method call I hope it is useful Co-authored-by: m new <michael.burzan@outlook.de> Co-authored-by: Geoff Romer <gromer@google.com>
This commit is contained in:
co-authored by
m new
Geoff Romer
parent
4ea3e1951b
commit
cef93fba5e
@@ -25,20 +25,19 @@ 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_),
|
||||
destructor_scope_(other.destructor_scope_) {}
|
||||
heap_(other.heap_) {}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
RuntimeScope::~RuntimeScope() {
|
||||
for (AllocationId allocation : allocations_) {
|
||||
for (auto allocation : allocations_) {
|
||||
// TODO: move this into StepCleanUp
|
||||
heap_->Deallocate(allocation);
|
||||
}
|
||||
}
|
||||
@@ -52,6 +51,22 @@ void RuntimeScope::Print(llvm::raw_ostream& out) const {
|
||||
out << "}";
|
||||
}
|
||||
|
||||
void RuntimeScope::Bind(ValueNodeView value_node, Nonnull<const Value*> value) {
|
||||
CARBON_CHECK(!value_node.constant_value().has_value());
|
||||
CARBON_CHECK(value->kind() != Value::Kind::LValue);
|
||||
auto allocation_id = heap_->GetAllocationId(value);
|
||||
if (!allocation_id) {
|
||||
auto id = heap_->AllocateValue(value);
|
||||
auto [it, success] =
|
||||
locals_.insert({value_node, heap_->arena().New<LValue>(Address(id))});
|
||||
CARBON_CHECK(success) << "Duplicate definition of " << value_node.base();
|
||||
} else {
|
||||
auto [it, success] = locals_.insert(
|
||||
{value_node, heap_->arena().New<LValue>(Address(*allocation_id))});
|
||||
CARBON_CHECK(success) << "Duplicate definition of " << value_node.base();
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeScope::Initialize(ValueNodeView value_node,
|
||||
Nonnull<const Value*> value) {
|
||||
CARBON_CHECK(!value_node.constant_value().has_value());
|
||||
@@ -98,16 +113,6 @@ 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:
|
||||
@@ -138,6 +143,9 @@ void Action::Print(llvm::raw_ostream& out) const {
|
||||
case Action::Kind::CleanUpAction:
|
||||
out << "clean up";
|
||||
break;
|
||||
case Action::Kind::DestroyAction:
|
||||
out << "destroy";
|
||||
break;
|
||||
}
|
||||
out << "." << pos_ << ".";
|
||||
if (!results_.empty()) {
|
||||
|
||||
@@ -27,12 +27,6 @@ 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.
|
||||
@@ -42,8 +36,7 @@ class RuntimeScope {
|
||||
-> RuntimeScope;
|
||||
|
||||
// Constructs a RuntimeScope that allocates storage in `heap`.
|
||||
explicit RuntimeScope(Nonnull<HeapAllocationInterface*> heap)
|
||||
: heap_(heap), destructor_scope_(State::Normal) {}
|
||||
explicit RuntimeScope(Nonnull<HeapAllocationInterface*> heap) : heap_(heap) {}
|
||||
|
||||
// Moving a RuntimeScope transfers ownership of its allocations.
|
||||
RuntimeScope(RuntimeScope&&) noexcept;
|
||||
@@ -55,8 +48,12 @@ class RuntimeScope {
|
||||
void Print(llvm::raw_ostream& out) const;
|
||||
LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
|
||||
|
||||
// Binds `value` as the value of `value_node`.
|
||||
void Bind(ValueNodeView value_node, Nonnull<const Value*> value);
|
||||
|
||||
// Allocates storage for `value_node` in `heap`, and initializes it with
|
||||
// `value`.
|
||||
// TODO: Update existing callers to use Bind instead, where appropriate.
|
||||
void Initialize(ValueNodeView value_node, Nonnull<const Value*> value);
|
||||
|
||||
// Transfers the names and allocations from `other` into *this. The two
|
||||
@@ -69,31 +66,16 @@ class RuntimeScope {
|
||||
-> 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 (const auto& entry : locals_) {
|
||||
res.push_back(entry.second);
|
||||
}
|
||||
return res;
|
||||
auto allocations() const -> const std::vector<AllocationId>& {
|
||||
return allocations_;
|
||||
}
|
||||
|
||||
// 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:
|
||||
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,
|
||||
@@ -120,6 +102,7 @@ class Action {
|
||||
ScopeAction,
|
||||
RecursiveAction,
|
||||
CleanUpAction,
|
||||
DestroyAction
|
||||
};
|
||||
|
||||
Action(const Value&) = delete;
|
||||
@@ -291,21 +274,50 @@ class DeclarationAction : public Action {
|
||||
Nonnull<const Declaration*> declaration_;
|
||||
};
|
||||
|
||||
// An Action which implements destroying all local allocations in a scope.
|
||||
class CleanupAction : public Action {
|
||||
public:
|
||||
explicit CleanupAction(RuntimeScope scope) : Action(Kind::CleanUpAction) {
|
||||
locals_count_ = scope.locals().size();
|
||||
explicit CleanupAction(RuntimeScope scope)
|
||||
: Action(Kind::CleanUpAction),
|
||||
allocations_count_(scope.allocations().size()) {
|
||||
StartScope(std::move(scope));
|
||||
}
|
||||
|
||||
auto locals_count() const -> int { return locals_count_; }
|
||||
auto allocations_count() const -> int { return allocations_count_; }
|
||||
|
||||
static auto classof(const Action* action) -> bool {
|
||||
return action->kind() == Kind::CleanUpAction;
|
||||
}
|
||||
|
||||
private:
|
||||
int locals_count_;
|
||||
int allocations_count_;
|
||||
};
|
||||
|
||||
// An Action which implements destroying a single value, including all nested
|
||||
// values.
|
||||
class DestroyAction : public Action {
|
||||
public:
|
||||
// lvalue: Address of the object to be destroyed
|
||||
// value: The value to be destroyed
|
||||
// In most cases the lvalue address points to value
|
||||
// In the case that the member of a class is to be destroyed, points
|
||||
// the lvalue points to the address of the class object
|
||||
// and the value is the member of the class
|
||||
explicit DestroyAction(Nonnull<const LValue*> lvalue,
|
||||
Nonnull<const Value*> value)
|
||||
: Action(Kind::DestroyAction), lvalue_(lvalue), value_(value) {}
|
||||
|
||||
static auto classof(const Action* action) -> bool {
|
||||
return action->kind() == Kind::DestroyAction;
|
||||
}
|
||||
|
||||
auto lvalue() const -> Nonnull<const LValue*> { return lvalue_; }
|
||||
|
||||
auto value() const -> Nonnull<const Value*> { return value_; }
|
||||
|
||||
private:
|
||||
Nonnull<const LValue*> lvalue_;
|
||||
Nonnull<const Value*> value_;
|
||||
};
|
||||
|
||||
// Action which does nothing except introduce a new scope into the action
|
||||
|
||||
@@ -152,6 +152,7 @@ static auto FinishActionKindFor(Action::Kind kind) -> FinishActionKind {
|
||||
return FinishActionKind::NoValue;
|
||||
case Action::Kind::ScopeAction:
|
||||
case Action::Kind::CleanUpAction:
|
||||
case Action::Kind::DestroyAction:
|
||||
return FinishActionKind::NeverCalled;
|
||||
}
|
||||
}
|
||||
@@ -313,11 +314,7 @@ void ActionStack::PopScopes(
|
||||
while (!todo_.IsEmpty() && llvm::isa<ScopeAction>(*todo_.Top())) {
|
||||
auto act = todo_.Pop();
|
||||
if (act->scope()) {
|
||||
if ((*act->scope()).DestructionState() <
|
||||
RuntimeScope::State::CleanUpped) {
|
||||
(*act->scope()).TransitState();
|
||||
cleanup_stack.push(std::move(act));
|
||||
}
|
||||
cleanup_stack.push(std::move(act));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,18 @@ auto Heap::Write(const Address& a, Nonnull<const Value*> v,
|
||||
return Success();
|
||||
}
|
||||
|
||||
auto Heap::GetAllocationId(Nonnull<const Value*> v) const
|
||||
-> std::optional<AllocationId> {
|
||||
auto iter = std::find(values_.begin(), values_.end(), v);
|
||||
if (iter != values_.end()) {
|
||||
auto index = iter - values_.begin();
|
||||
if (states_[index] == ValueState::Alive) {
|
||||
return AllocationId(index);
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto Heap::CheckAlive(AllocationId allocation, SourceLocation source_loc) const
|
||||
-> ErrorOr<Success> {
|
||||
if (states_[allocation.index_] == ValueState::Dead) {
|
||||
|
||||
@@ -41,6 +41,9 @@ class Heap : public HeapAllocationInterface {
|
||||
auto Write(const Address& a, Nonnull<const Value*> v,
|
||||
SourceLocation source_loc) -> ErrorOr<Success>;
|
||||
|
||||
auto GetAllocationId(Nonnull<const Value*> v) const
|
||||
-> std::optional<AllocationId> override;
|
||||
|
||||
// Put the given value on the heap and mark its state.
|
||||
// Mark UninitializedValue as uninitialized and other values as alive.
|
||||
auto AllocateValue(Nonnull<const Value*> v) -> AllocationId override;
|
||||
|
||||
@@ -30,6 +30,11 @@ class HeapAllocationInterface {
|
||||
// Returns the arena used to allocate the values in this heap.
|
||||
virtual auto arena() const -> Arena& = 0;
|
||||
|
||||
// Returns the ID of the first allocation that holds `v`, if one exists.
|
||||
// TODO: Find a way to remove this.
|
||||
virtual auto GetAllocationId(Nonnull<const Value*> v) const
|
||||
-> std::optional<AllocationId> = 0;
|
||||
|
||||
protected:
|
||||
HeapAllocationInterface() = default;
|
||||
virtual ~HeapAllocationInterface() = default;
|
||||
|
||||
@@ -89,6 +89,9 @@ class Interpreter {
|
||||
auto StepDeclaration() -> ErrorOr<Success>;
|
||||
// State transition for object destruction.
|
||||
auto StepCleanUp() -> ErrorOr<Success>;
|
||||
auto StepDestroy() -> ErrorOr<Success>;
|
||||
// State transition for tuple destruction.
|
||||
auto StepCleanUpTuple() -> ErrorOr<Success>;
|
||||
|
||||
auto CreateStruct(const std::vector<FieldInitializer>& fields,
|
||||
const std::vector<Nonnull<const Value*>>& values)
|
||||
@@ -818,15 +821,17 @@ auto Interpreter::CallDestructor(Nonnull<const DestructorDeclaration*> 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_));
|
||||
|
||||
// TODO: move this logic into PatternMatch, and call it here.
|
||||
auto p = &method.me_pattern().value();
|
||||
const auto& placeholder = cast<BindingPlaceholderValue>(*p);
|
||||
if (placeholder.value_node().has_value()) {
|
||||
method_scope.Bind(*placeholder.value_node(), receiver);
|
||||
}
|
||||
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));
|
||||
}
|
||||
@@ -900,9 +905,18 @@ auto Interpreter::CallFunction(const CallExpression& call,
|
||||
RuntimeScope method_scope(&heap_);
|
||||
BindingMap generic_args;
|
||||
// Bind the receiver to the `me` parameter.
|
||||
CARBON_CHECK(PatternMatch(&method.me_pattern().value(), m.receiver(),
|
||||
call.source_loc(), &method_scope, generic_args,
|
||||
trace_stream_, this->arena_));
|
||||
auto p = &method.me_pattern().value();
|
||||
if (p->kind() == Value::Kind::BindingPlaceholderValue) {
|
||||
// TODO: move this logic into PatternMatch
|
||||
const auto& placeholder = cast<BindingPlaceholderValue>(*p);
|
||||
if (placeholder.value_node().has_value()) {
|
||||
method_scope.Bind(*placeholder.value_node(), m.receiver());
|
||||
}
|
||||
} else {
|
||||
CARBON_CHECK(PatternMatch(&method.me_pattern().value(), m.receiver(),
|
||||
call.source_loc(), &method_scope,
|
||||
generic_args, trace_stream_, this->arena_));
|
||||
}
|
||||
// Bind the arguments to the parameters.
|
||||
CARBON_CHECK(PatternMatch(&method.param_pattern().value(), converted_args,
|
||||
call.source_loc(), &method_scope, generic_args,
|
||||
@@ -1998,48 +2012,81 @@ auto Interpreter::StepDeclaration() -> ErrorOr<Success> {
|
||||
}
|
||||
}
|
||||
|
||||
auto Interpreter::StepCleanUp() -> ErrorOr<Success> {
|
||||
auto Interpreter::StepDestroy() -> ErrorOr<Success> {
|
||||
// TODO: find a way to avoid dyn_cast in this code, and instead use static
|
||||
// type information the way the compiler would.
|
||||
Action& act = todo_.CurrentAction();
|
||||
auto& cleanup = cast<CleanupAction>(act);
|
||||
if (act.pos() < cleanup.locals_count()) {
|
||||
const 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)) {
|
||||
DestroyAction& destroy_act = cast<DestroyAction>(act);
|
||||
if (act.pos() == 0) {
|
||||
if (const auto* class_obj =
|
||||
dyn_cast<NominalClassValue>(destroy_act.value())) {
|
||||
const auto& class_type = cast<NominalClassType>(class_obj->type());
|
||||
const auto& class_dec = class_type.declaration();
|
||||
if (class_dec.destructor().has_value()) {
|
||||
return CallDestructor(*class_dec.destructor(), class_obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto* tuple = dyn_cast<TupleValue>(destroy_act.value())) {
|
||||
if (tuple->elements().size() > 0) {
|
||||
int index = tuple->elements().size() - act.pos() - 1;
|
||||
if (index >= 0) {
|
||||
const auto& item = tuple->elements()[index];
|
||||
if (const auto* class_obj = dyn_cast<NominalClassValue>(item)) {
|
||||
const auto& class_type = cast<NominalClassType>(class_obj->type());
|
||||
const auto& class_dec = class_type.declaration();
|
||||
if (class_dec.destructor().has_value()) {
|
||||
return CallDestructor(*class_dec.destructor(), class_obj);
|
||||
}
|
||||
}
|
||||
} 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)) {
|
||||
const 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item->kind() == Value::Kind::TupleValue) {
|
||||
return todo_.Spawn(
|
||||
std::make_unique<DestroyAction>(destroy_act.lvalue(), item));
|
||||
}
|
||||
act.scope()->TransitState();
|
||||
// Type of tuple element is integral type e.g. i32
|
||||
// or the type has no destructor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (act.pos() > 0) {
|
||||
if (const auto* class_obj =
|
||||
dyn_cast<NominalClassValue>(destroy_act.value())) {
|
||||
const auto& class_type = cast<NominalClassType>(class_obj->type());
|
||||
const auto& class_dec = class_type.declaration();
|
||||
int index = class_dec.members().size() - act.pos();
|
||||
if (index >= 0 && index < static_cast<int>(class_dec.members().size())) {
|
||||
const auto& member = class_dec.members()[index];
|
||||
if (const auto* var = dyn_cast<VariableDeclaration>(member)) {
|
||||
Address object = destroy_act.lvalue()->address();
|
||||
Address mem = object.SubobjectAddress(Member(var));
|
||||
SourceLocation source_loc("destructor", 1);
|
||||
auto v = heap_.Read(mem, source_loc);
|
||||
return todo_.Spawn(
|
||||
std::make_unique<DestroyAction>(destroy_act.lvalue(), *v));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
todo_.Pop();
|
||||
return Success();
|
||||
}
|
||||
|
||||
auto Interpreter::StepCleanUp() -> ErrorOr<Success> {
|
||||
Action& act = todo_.CurrentAction();
|
||||
CleanupAction& cleanup = cast<CleanupAction>(act);
|
||||
if (act.pos() < cleanup.allocations_count()) {
|
||||
auto allocation =
|
||||
act.scope()->allocations()[cleanup.allocations_count() - act.pos() - 1];
|
||||
auto lvalue = arena_->New<LValue>(Address(allocation));
|
||||
SourceLocation source_loc("destructor", 1);
|
||||
auto value = heap_.Read(lvalue->address(), source_loc);
|
||||
// Step over uninitialized values
|
||||
if (value.ok()) {
|
||||
return todo_.Spawn(std::make_unique<DestroyAction>(lvalue, *value));
|
||||
}
|
||||
}
|
||||
todo_.Pop();
|
||||
return Success();
|
||||
}
|
||||
@@ -2069,6 +2116,9 @@ auto Interpreter::Step() -> ErrorOr<Success> {
|
||||
case Action::Kind::CleanUpAction:
|
||||
CARBON_RETURN_IF_ERROR(StepCleanUp());
|
||||
break;
|
||||
case Action::Kind::DestroyAction:
|
||||
CARBON_RETURN_IF_ERROR(StepDestroy());
|
||||
break;
|
||||
case Action::Kind::ScopeAction:
|
||||
CARBON_FATAL() << "ScopeAction escaped ActionStack";
|
||||
case Action::Kind::RecursiveAction:
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// CHECK:STDOUT: DESTRUCTOR B 2
|
||||
// CHECK:STDOUT: DESTRUCTOR A 3
|
||||
// CHECK:STDOUT: DESTRUCTOR A 4
|
||||
// CHECK:STDOUT: DESTRUCTOR A 5
|
||||
// CHECK:STDOUT: result: 1
|
||||
|
||||
package ExplorerTest api;
|
||||
@@ -28,15 +29,16 @@ class B{
|
||||
Print("DESTRUCTOR B {0}",me.n);
|
||||
}
|
||||
fn Create(x: i32) -> B{
|
||||
return {.n = x, .a = A.Create(3) };
|
||||
return {.n = x, .a1 = A.Create(4),.a2 = A.Create(3) };
|
||||
}
|
||||
var a1: A;
|
||||
var n: i32;
|
||||
var a: A;
|
||||
var a2: A;
|
||||
}
|
||||
|
||||
|
||||
fn Main() -> i32 {
|
||||
var a: A = A.Create(4);
|
||||
var a: A = A.Create(5);
|
||||
var b: B = B.Create(2);
|
||||
var c: A = A.Create(1);
|
||||
return 1;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
// AUTOUPDATE
|
||||
// RUN: %{explorer-run}
|
||||
// RUN: %{explorer-run-trace}
|
||||
// CHECK:STDOUT: DESTRUCTOR A 1
|
||||
// CHECK:STDOUT: DESTRUCTOR A 2
|
||||
// CHECK:STDOUT: DESTRUCTOR A 3
|
||||
// CHECK:STDOUT: DESTRUCTOR A 4
|
||||
// CHECK:STDOUT: DESTRUCTOR A 5
|
||||
// CHECK:STDOUT: DESTRUCTOR A 6
|
||||
// CHECK:STDOUT: result: 1
|
||||
|
||||
package ExplorerTest api;
|
||||
|
||||
|
||||
class A{
|
||||
destructor[me: Self]{
|
||||
Print("DESTRUCTOR A {0}",me.n);
|
||||
}
|
||||
var n: i32;
|
||||
}
|
||||
|
||||
fn Main() -> i32 {
|
||||
var a: [A; 2] = ({.n = 6},{.n = 5});
|
||||
var b: [[A; 2]; 2] = (({.n = 4},{.n = 3}), ({.n = 2},{.n = 1}));
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
// AUTOUPDATE
|
||||
// RUN: %{explorer-run}
|
||||
// RUN: %{explorer-run-trace}
|
||||
// CHECK:STDOUT: DESTRUCTOR A 0
|
||||
// CHECK:STDOUT: DESTRUCTOR A 1
|
||||
// CHECK:STDOUT: DESTRUCTOR A 2
|
||||
// CHECK:STDOUT: result: 1
|
||||
|
||||
package ExplorerTest api;
|
||||
|
||||
|
||||
class A{
|
||||
destructor[me: Self]{
|
||||
Print("DESTRUCTOR A {0}",me.n);
|
||||
}
|
||||
var n: i32;
|
||||
}
|
||||
|
||||
fn Main() -> i32 {
|
||||
var a1: A = {.n = 2};
|
||||
var a: [A; 2] = ({.n = 1},{.n = 0});
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
// AUTOUPDATE
|
||||
// RUN: %{explorer-run}
|
||||
// RUN: %{explorer-run-trace}
|
||||
// CHECK:STDOUT: DESTRUCTOR A 1
|
||||
// CHECK:STDOUT: DESTRUCTOR B 2
|
||||
// CHECK:STDOUT: result: 1
|
||||
|
||||
package ExplorerTest api;
|
||||
|
||||
class A{
|
||||
class B{
|
||||
destructor[me: Self]{
|
||||
Print("DESTRUCTOR B {0}",me.n);
|
||||
}
|
||||
fn Create(x: i32) -> B{
|
||||
return {.n = x };
|
||||
}
|
||||
var n: i32;
|
||||
}
|
||||
|
||||
destructor[me: Self]{
|
||||
Print("DESTRUCTOR A {0}",me.n);
|
||||
}
|
||||
fn Create(x: i32) -> A{
|
||||
return {.n = x, .b = B.Create(2)};
|
||||
}
|
||||
var n: i32;
|
||||
var b : B;
|
||||
}
|
||||
|
||||
fn Main() -> i32 {
|
||||
var a: A = A.Create(1);
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
// AUTOUPDATE
|
||||
// RUN: %{explorer-run}
|
||||
// RUN: %{explorer-run-trace}
|
||||
// AUTOUPDATE: %{explorer} %s
|
||||
// CHECK:STDOUT: TEST
|
||||
// CHECK:STDOUT: TEST 2
|
||||
// CHECK:STDOUT: DESTRUCTOR A 1
|
||||
// CHECK:STDOUT: result: 1
|
||||
|
||||
package ExplorerTest api;
|
||||
|
||||
class B{
|
||||
var n : i32;
|
||||
}
|
||||
|
||||
class A{
|
||||
destructor[me: Self]{
|
||||
Print("DESTRUCTOR A {0}",me.n);
|
||||
}
|
||||
fn test[me: Self](){
|
||||
Print("TEST");
|
||||
}
|
||||
fn test2[me: Self](){
|
||||
Print("TEST 2");
|
||||
}
|
||||
var n: i32;
|
||||
}
|
||||
|
||||
fn Main() -> i32 {
|
||||
var a: A = {.n = 1 };
|
||||
a.test();
|
||||
a.test2();
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user