From 19c74ead49929ebfbaf7624081da07a521bd5186 Mon Sep 17 00:00:00 2001 From: Adrien Leravat Date: Fri, 23 Jun 2023 21:42:00 -0700 Subject: [PATCH] Explorer: Add initial initializing expression support for variable declaration (#2907) Add partial support for initializing expressions for variable declaration. This is based on https://github.com/carbon-language/carbon-lang/pull/2006, which introduces expression categories, and how it is possible to convert to/from those different categories. ## Functional changes * Initializing expressions initialize directly the provided storage when used to initialize a variable. * Allows initializing expressions to avoid a copy when using `[var|let] name: type = call_expression(...)` by initializing `name` in-place. * Support `returned var: ...` and `return ` * Support nested initializing expressions ## Main implementation changes * Updated PatternMatch logic to handle expression categories * Updated `VariableDefinition` interpreter statement to allocate and pass a location to initializing expressions * Update statement actions to allow passing an allocation, used by return expr or returned var * Modified the RuntimeScope API to be one step closer to the memory model we want to have * Remove `GetAllocationId` and older `Bind` which don't apply * New set of tests to highlight those different situations * Added a new intrinsic to print the allocation stack (and make sure we behave correctly, beyond visible side effects) ## Next work * Dedicated `Action` to retrieve expression category information in the interpreter (https://github.com/carbon-language/carbon-lang/pull/2927) * Avoid copies when initializing value expression from reference expression and prevent mutations for the duration of the "pinning" (https://github.com/carbon-language/carbon-lang/pull/2927) * Avoid unnecessary copies from value expression to value expression, after ensuring that even value expression temporaries are registered for destruction. * Avoid unnecessary copies when binding function arguments --- explorer/ast/address.h | 1 + explorer/ast/expression.cpp | 3 + explorer/ast/expression.h | 1 + explorer/ast/expression_category.h | 4 + explorer/ast/value.cpp | 11 + explorer/ast/value.h | 47 ++- explorer/data/prelude.carbon | 3 + explorer/interpreter/BUILD | 2 + explorer/interpreter/action.cpp | 49 +-- explorer/interpreter/action.h | 73 +++- explorer/interpreter/heap.cpp | 31 +- explorer/interpreter/heap.h | 11 +- .../interpreter/heap_allocation_interface.h | 5 - explorer/interpreter/interpreter.cpp | 317 +++++++++++++----- explorer/interpreter/interpreter.h | 10 +- explorer/interpreter/type_checker.cpp | 32 +- .../testdata/basic_syntax/print_allocs.carbon | 16 + .../basic_syntax/print_allocs_empty.carbon | 14 + explorer/testdata/let/destroyed.carbon | 20 ++ .../value_expr_binding_from_reference.carbon | 34 ++ .../let/value_expr_binding_from_value.carbon | 34 ++ .../let/value_expr_from_initializing.carbon | 38 +++ ...lue_expr_from_initializing_returned.carbon | 39 +++ ...r_from_initializing_returned_nested.carbon | 52 +++ .../testdata/let/value_expr_from_ref.carbon | 32 ++ .../testdata/let/value_expr_from_value.carbon | 32 ++ .../pointer/fail_use_after_free.carbon | 2 +- explorer/testdata/var/local/destroyed.carbon | 20 ++ ...ference_expr_binding_from_reference.carbon | 34 ++ .../reference_expr_binding_from_value.carbon | 34 ++ .../reference_expr_from_initializing.carbon | 42 +++ ...nce_expr_from_initializing_returned.carbon | 39 +++ ...r_from_initializing_returned_nested.carbon | 52 +++ .../var/local/reference_expr_from_ref.carbon | 32 ++ .../local/reference_expr_from_value.carbon | 32 ++ 35 files changed, 1036 insertions(+), 162 deletions(-) create mode 100644 explorer/testdata/basic_syntax/print_allocs.carbon create mode 100644 explorer/testdata/basic_syntax/print_allocs_empty.carbon create mode 100644 explorer/testdata/let/destroyed.carbon create mode 100644 explorer/testdata/let/value_expr_binding_from_reference.carbon create mode 100644 explorer/testdata/let/value_expr_binding_from_value.carbon create mode 100644 explorer/testdata/let/value_expr_from_initializing.carbon create mode 100644 explorer/testdata/let/value_expr_from_initializing_returned.carbon create mode 100644 explorer/testdata/let/value_expr_from_initializing_returned_nested.carbon create mode 100644 explorer/testdata/let/value_expr_from_ref.carbon create mode 100644 explorer/testdata/let/value_expr_from_value.carbon create mode 100644 explorer/testdata/var/local/destroyed.carbon create mode 100644 explorer/testdata/var/local/reference_expr_binding_from_reference.carbon create mode 100644 explorer/testdata/var/local/reference_expr_binding_from_value.carbon create mode 100644 explorer/testdata/var/local/reference_expr_from_initializing.carbon create mode 100644 explorer/testdata/var/local/reference_expr_from_initializing_returned.carbon create mode 100644 explorer/testdata/var/local/reference_expr_from_initializing_returned_nested.carbon create mode 100644 explorer/testdata/var/local/reference_expr_from_ref.carbon create mode 100644 explorer/testdata/var/local/reference_expr_from_value.carbon diff --git a/explorer/ast/address.h b/explorer/ast/address.h index 4e3cd43347b1..67647523d847 100644 --- a/explorer/ast/address.h +++ b/explorer/ast/address.h @@ -86,6 +86,7 @@ class Address { // the Heap, so its implementation details are tied to the implementation // details of the Heap. friend class Heap; + friend class RuntimeScope; AllocationId allocation_; ElementPath element_path_; diff --git a/explorer/ast/expression.cpp b/explorer/ast/expression.cpp index a7f64374378a..e1263373770f 100644 --- a/explorer/ast/expression.cpp +++ b/explorer/ast/expression.cpp @@ -31,6 +31,7 @@ auto IntrinsicExpression::FindIntrinsic(std::string_view name, {{"print", Intrinsic::Print}, {"new", Intrinsic::Alloc}, {"delete", Intrinsic::Dealloc}, + {"print_allocs", Intrinsic::PrintAllocs}, {"rand", Intrinsic::Rand}, {"implicit_as", Intrinsic::ImplicitAs}, {"implicit_as_convert", Intrinsic::ImplicitAsConvert}, @@ -62,6 +63,8 @@ auto IntrinsicExpression::name() const -> std::string_view { return "__intrinsic_new"; case IntrinsicExpression::Intrinsic::Dealloc: return "__intrinsic_delete"; + case IntrinsicExpression::Intrinsic::PrintAllocs: + return "__intrinsic_print_allocs"; case IntrinsicExpression::Intrinsic::Rand: return "__intrinsic_rand"; case IntrinsicExpression::Intrinsic::ImplicitAs: diff --git a/explorer/ast/expression.h b/explorer/ast/expression.h index 48044d9847f8..34b6c52d8507 100644 --- a/explorer/ast/expression.h +++ b/explorer/ast/expression.h @@ -858,6 +858,7 @@ class IntrinsicExpression : public RewritableMixin { Print, Alloc, Dealloc, + PrintAllocs, Rand, ImplicitAs, ImplicitAsConvert, diff --git a/explorer/ast/expression_category.h b/explorer/ast/expression_category.h index fa3aff401bf7..8cb6011dda89 100644 --- a/explorer/ast/expression_category.h +++ b/explorer/ast/expression_category.h @@ -5,6 +5,8 @@ #ifndef CARBON_EXPLORER_AST_EXPRESSION_CATEGORY_H_ #define CARBON_EXPLORER_AST_EXPRESSION_CATEGORY_H_ +#include + namespace Carbon { // The category of a Carbon expression indicates whether it evaluates @@ -18,6 +20,8 @@ enum class ExpressionCategory { Initializing, }; +auto ExpressionCategoryToString(ExpressionCategory cat) -> llvm::StringRef; + } // namespace Carbon #endif // CARBON_EXPLORER_AST_EXPRESSION_CATEGORY_H_ diff --git a/explorer/ast/value.cpp b/explorer/ast/value.cpp index 599884af5d58..e1961fe2c42f 100644 --- a/explorer/ast/value.cpp +++ b/explorer/ast/value.cpp @@ -1318,4 +1318,15 @@ auto NominalClassType::InheritsClass(Nonnull other) const return false; } +auto ExpressionCategoryToString(ExpressionCategory cat) -> llvm::StringRef { + switch (cat) { + case ExpressionCategory::Value: + return "value"; + case ExpressionCategory::Reference: + return "reference"; + case ExpressionCategory::Initializing: + return "initializing"; + } +} + } // namespace Carbon diff --git a/explorer/ast/value.h b/explorer/ast/value.h index 49c8c91d7224..397eff2b7b28 100644 --- a/explorer/ast/value.h +++ b/explorer/ast/value.h @@ -16,6 +16,7 @@ #include "explorer/ast/declaration.h" #include "explorer/ast/element.h" #include "explorer/ast/element_path.h" +#include "explorer/ast/expression_category.h" #include "explorer/ast/statement.h" #include "explorer/common/nonnull.h" #include "llvm/ADT/StringMap.h" @@ -95,6 +96,38 @@ class Value { const Kind kind_; }; +// Contains the result of the evaluation of an expression, including a value, +// the original expression category, and an optional address if available. +class ExpressionResult { + public: + static auto Value(Nonnull v) -> ExpressionResult { + return ExpressionResult(v, std::nullopt, ExpressionCategory::Value); + } + static auto Reference(Nonnull v, Address address) + -> ExpressionResult { + return ExpressionResult(v, std::move(address), + ExpressionCategory::Reference); + } + static auto Initializing(Nonnull v, Address address) + -> ExpressionResult { + return ExpressionResult(v, std::move(address), + ExpressionCategory::Initializing); + } + + ExpressionResult(Nonnull v, + std::optional
address, ExpressionCategory cat) + : value_(v), address_(std::move(address)), expr_cat_(cat) {} + + auto value() const -> Nonnull { return value_; } + auto address() const -> const std::optional
& { return address_; } + auto expression_category() const -> ExpressionCategory { return expr_cat_; } + + private: + Nonnull value_; + std::optional
address_; + ExpressionCategory expr_cat_; +}; + // Returns whether the fully-resolved kind that this value will eventually have // is currently unknown, because it depends on a generic parameter. inline auto IsValueKindDependent(Nonnull type) -> bool { @@ -631,19 +664,22 @@ class FunctionType : public Value { FunctionType(Nonnull parameters, Nonnull return_type) - : FunctionType(parameters, {}, return_type, {}, {}) {} + : FunctionType(parameters, {}, return_type, {}, {}, + /*is_initializing=*/false) {} FunctionType(Nonnull parameters, std::vector generic_parameters, Nonnull return_type, std::vector> deduced_bindings, - std::vector> impl_bindings) + std::vector> impl_bindings, + bool is_initializing) : Value(Kind::FunctionType), parameters_(parameters), generic_parameters_(std::move(generic_parameters)), return_type_(return_type), deduced_bindings_(std::move(deduced_bindings)), - impl_bindings_(std::move(impl_bindings)) {} + impl_bindings_(std::move(impl_bindings)), + is_initializing_(is_initializing) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::FunctionType; @@ -652,7 +688,7 @@ class FunctionType : public Value { template auto Decompose(F f) const { return f(parameters_, generic_parameters_, return_type_, deduced_bindings_, - impl_bindings_); + impl_bindings_, is_initializing_); } // The type of the function parameter tuple. @@ -674,6 +710,8 @@ class FunctionType : public Value { auto impl_bindings() const -> llvm::ArrayRef> { return impl_bindings_; } + // Return whether the function type is an initializing expression or not. + auto is_initializing() const -> bool { return is_initializing_; } private: Nonnull parameters_; @@ -681,6 +719,7 @@ class FunctionType : public Value { Nonnull return_type_; std::vector> deduced_bindings_; std::vector> impl_bindings_; + bool is_initializing_; }; // A pointer type. diff --git a/explorer/data/prelude.carbon b/explorer/data/prelude.carbon index dea79da15105..e85205abb950 100644 --- a/explorer/data/prelude.carbon +++ b/explorer/data/prelude.carbon @@ -707,6 +707,9 @@ class Heap { fn Delete[T:! type, self: Self](p : T*) { __intrinsic_delete(p); } + fn PrintAllocs[self: Self]() { + __intrinsic_print_allocs(); + } } var heap: Heap = {}; diff --git a/explorer/interpreter/BUILD b/explorer/interpreter/BUILD index 74b868178272..6c9931c1161d 100644 --- a/explorer/interpreter/BUILD +++ b/explorer/interpreter/BUILD @@ -79,6 +79,7 @@ cc_library( deps = [ ":action", ":heap_allocation_interface", + "//common:check", "//common:ostream", "//explorer/ast", "//explorer/common:error_builders", @@ -115,6 +116,7 @@ cc_library( "//common:error", "//common:ostream", "//explorer/ast", + "//explorer/ast:expression_category", "//explorer/common:arena", "//explorer/common:error_builders", "//explorer/common:source_location", diff --git a/explorer/interpreter/action.cpp b/explorer/interpreter/action.cpp index c1ec94ce017e..7110f04f2363 100644 --- a/explorer/interpreter/action.cpp +++ b/explorer/interpreter/action.cpp @@ -10,8 +10,10 @@ #include #include +#include "common/check.h" #include "explorer/ast/declaration.h" #include "explorer/ast/expression.h" +#include "explorer/ast/value.h" #include "explorer/common/arena.h" #include "explorer/interpreter/stack.h" #include "llvm/ADT/StringExtras.h" @@ -44,32 +46,39 @@ void RuntimeScope::Print(llvm::raw_ostream& out) const { out << "}"; } -void RuntimeScope::Bind(ValueNodeView value_node, Nonnull value) { +void RuntimeScope::Bind(ValueNodeView value_node, Address address) { CARBON_CHECK(!value_node.constant_value().has_value()); - CARBON_CHECK(value->kind() != Value::Kind::LocationValue); - auto allocation_id = heap_->GetAllocationId(value); - if (!allocation_id) { - auto id = heap_->AllocateValue(value); - auto [it, success] = locals_.insert( - {value_node, heap_->arena().New(Address(id))}); - CARBON_CHECK(success) << "Duplicate definition of " << value_node.base(); - } else { - auto [it, success] = locals_.insert( - {value_node, - heap_->arena().New(Address(*allocation_id))}); - CARBON_CHECK(success) << "Duplicate definition of " << value_node.base(); - } + bool success = + locals_.insert({value_node, heap_->arena().New(address)}) + .second; + CARBON_CHECK(success) << "Duplicate definition of " << value_node.base(); } -void RuntimeScope::Initialize(ValueNodeView value_node, - Nonnull value) { +void RuntimeScope::BindLifetimeToScope(Address address) { + CARBON_CHECK(address.element_path_.IsEmpty()) + << "Cannot extend lifetime of a specific sub-element"; + allocations_.push_back(address.allocation_); +} + +void RuntimeScope::BindValue(ValueNodeView value_node, + Nonnull value) { + CARBON_CHECK(!value_node.constant_value().has_value()); + CARBON_CHECK(value->kind() != Value::Kind::LocationValue); + bool success = locals_.insert({value_node, value}).second; + CARBON_CHECK(success) << "Duplicate definition of " << value_node.base(); +} + +auto RuntimeScope::Initialize(ValueNodeView value_node, + Nonnull value) + -> Nonnull { CARBON_CHECK(!value_node.constant_value().has_value()); CARBON_CHECK(value->kind() != Value::Kind::LocationValue); allocations_.push_back(heap_->AllocateValue(value)); - auto [it, success] = locals_.insert( - {value_node, - heap_->arena().New(Address(allocations_.back()))}); + const auto* location = + heap_->arena().New(Address(allocations_.back())); + bool success = locals_.insert({value_node, location}).second; CARBON_CHECK(success) << "Duplicate definition of " << value_node.base(); + return location; } void RuntimeScope::Merge(RuntimeScope other) { @@ -85,7 +94,7 @@ void RuntimeScope::Merge(RuntimeScope other) { } auto RuntimeScope::Get(ValueNodeView value_node) const - -> std::optional> { + -> std::optional> { auto it = locals_.find(value_node); if (it != locals_.end()) { return it->second; diff --git a/explorer/interpreter/action.h b/explorer/interpreter/action.h index a51835f3ac33..eff455050e63 100644 --- a/explorer/interpreter/action.h +++ b/explorer/interpreter/action.h @@ -7,10 +7,13 @@ #include #include +#include #include #include +#include "common/check.h" #include "common/ostream.h" +#include "explorer/ast/address.h" #include "explorer/ast/expression.h" #include "explorer/ast/pattern.h" #include "explorer/ast/statement.h" @@ -45,30 +48,42 @@ 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 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 value); + auto Initialize(ValueNodeView value_node, Nonnull value) + -> Nonnull; + + // Bind allocation lifetime to scope. Should only be called with unowned + // allocations to avoid a double free. + void BindLifetimeToScope(Address address); + + // Binds location `address` of a reference value to `value_node` without + // allocating local storage. + void Bind(ValueNodeView value_node, Address address); + + // Binds unlocated `value` to `value_node` without allocating local storage. + // TODO: BindValue should pin the lifetime of `value` and make sure it isn't + // mutated. + void BindValue(ValueNodeView value_node, Nonnull value); // Transfers the names and allocations from `other` into *this. The two // scopes must not define the same name, and must be backed by the same Heap. void Merge(RuntimeScope other); - // Returns the local storage for value_node, if it has storage local to - // this scope. + // Given node `value_node`, returns: + // - its `LocationValue*` if bound to a reference expression in this scope, + // - a `Value*` if bound to a value expression in this scope, or + // - `nullptr` if not bound. auto Get(ValueNodeView value_node) const - -> std::optional>; + -> std::optional>; - // Returns the local values in created order + // Returns the local values with allocation in created order. auto allocations() const -> const std::vector& { return allocations_; } private: - llvm::MapVector, + llvm::MapVector, std::map> locals_; std::vector allocations_; @@ -184,8 +199,12 @@ class LocationAction : public Action { // An Action which implements evaluation of an Expression to produce a `Value*`. class ExpressionAction : public Action { public: - explicit ExpressionAction(Nonnull expression) - : Action(Kind::ExpressionAction), expression_(expression) {} + explicit ExpressionAction( + Nonnull expression, + std::optional initialized_location = std::nullopt) + : Action(Kind::ExpressionAction), + expression_(expression), + location_received_(initialized_location) {} static auto classof(const Action* action) -> bool { return action->kind() == Kind::ExpressionAction; @@ -194,8 +213,14 @@ class ExpressionAction : public Action { // The Expression this Action evaluates. auto expression() const -> const Expression& { return *expression_; } + // The location provided for the initializing expression, if any. + auto location_received() const -> std::optional { + return location_received_; + } + private: Nonnull expression_; + std::optional location_received_; }; // An Action which implements the Instantiation of Type. The result is expressed @@ -242,8 +267,11 @@ class WitnessAction : public Action { // result. class StatementAction : public Action { public: - explicit StatementAction(Nonnull statement) - : Action(Kind::StatementAction), statement_(statement) {} + explicit StatementAction(Nonnull statement, + std::optional location_received) + : Action(Kind::StatementAction), + statement_(statement), + location_received_(location_received) {} static auto classof(const Action* action) -> bool { return action->kind() == Kind::StatementAction; @@ -252,8 +280,25 @@ class StatementAction : public Action { // The Statement this Action executes. auto statement() const -> const Statement& { return *statement_; } + // The location provided for the initializing expression, if any. + auto location_received() const -> std::optional { + return location_received_; + } + + // Sets the location provided to an initializing expression. + auto set_location_created(AllocationId location_created) { + CARBON_CHECK(!location_created_) << "location created set twice"; + location_created_ = location_created; + } + // Returns the location provided to an initializing expression, if any. + auto location_created() const -> std::optional { + return location_created_; + } + private: Nonnull statement_; + std::optional location_received_; + std::optional location_created_; }; // Action which implements the run-time effects of executing a Declaration. diff --git a/explorer/interpreter/heap.cpp b/explorer/interpreter/heap.cpp index 27df83ba37fd..6b7673f8161a 100644 --- a/explorer/interpreter/heap.cpp +++ b/explorer/interpreter/heap.cpp @@ -4,6 +4,7 @@ #include "explorer/interpreter/heap.h" +#include "common/check.h" #include "explorer/ast/value.h" #include "explorer/common/error_builders.h" #include "llvm/ADT/StringExtras.h" @@ -51,23 +52,12 @@ auto Heap::Write(const Address& a, Nonnull v, return Success(); } -auto Heap::GetAllocationId(Nonnull v) const - -> std::optional { - 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 { - if (states_[allocation.index_] == ValueState::Dead) { + if (states_[allocation.index_] == ValueState::Dead || + states_[allocation.index_] == ValueState::Discarded) { return ProgramError(source_loc) - << "undefined behavior: access to dead value " + << "undefined behavior: access to dead or discarded value " << *values_[allocation.index_]; } return Success(); @@ -94,6 +84,19 @@ void Heap::Deallocate(AllocationId allocation) { void Heap::Deallocate(const Address& a) { Deallocate(a.allocation_); } +auto Heap::is_initialized(AllocationId allocation) const -> bool { + return states_[allocation.index_] != ValueState::Uninitialized; +} + +auto Heap::is_discarded(AllocationId allocation) const -> bool { + return states_[allocation.index_] == ValueState::Discarded; +} + +void Heap::Discard(AllocationId allocation) { + CARBON_CHECK(states_[allocation.index_] == ValueState::Uninitialized); + states_[allocation.index_] = ValueState::Discarded; +} + void Heap::Print(llvm::raw_ostream& out) const { llvm::ListSeparator sep; for (size_t i = 0; i < values_.size(); ++i) { diff --git a/explorer/interpreter/heap.h b/explorer/interpreter/heap.h index de512aad7b4a..fb67092e410d 100644 --- a/explorer/interpreter/heap.h +++ b/explorer/interpreter/heap.h @@ -21,6 +21,7 @@ class Heap : public HeapAllocationInterface { public: enum class ValueState { Uninitialized, + Discarded, Alive, Dead, }; @@ -41,9 +42,6 @@ class Heap : public HeapAllocationInterface { auto Write(const Address& a, Nonnull v, SourceLocation source_loc) -> ErrorOr; - auto GetAllocationId(Nonnull v) const - -> std::optional override; - // Put the given value on the heap and mark its state. // Mark UninitializedValue as uninitialized and other values as alive. auto AllocateValue(Nonnull v) -> AllocationId override; @@ -52,6 +50,13 @@ class Heap : public HeapAllocationInterface { void Deallocate(AllocationId allocation) override; void Deallocate(const Address& a); + // Marks this allocation, and all its sub-objects, as discarded. + void Discard(AllocationId allocation); + // Returns whether the given allocation was unused and discarded. + auto is_discarded(AllocationId allocation) const -> bool; + // Returns whether the given allocation was initialized. + auto is_initialized(AllocationId allocation) const -> bool; + // Print all the values on the heap to the stream `out`. void Print(llvm::raw_ostream& out) const; diff --git a/explorer/interpreter/heap_allocation_interface.h b/explorer/interpreter/heap_allocation_interface.h index 0a59bb93c154..dc67c1432580 100644 --- a/explorer/interpreter/heap_allocation_interface.h +++ b/explorer/interpreter/heap_allocation_interface.h @@ -30,11 +30,6 @@ 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 v) const - -> std::optional = 0; - protected: HeapAllocationInterface() = default; virtual ~HeapAllocationInterface() = default; diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 9fa69fc8a2c9..e810fe0248b8 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -21,6 +21,7 @@ #include "explorer/ast/declaration.h" #include "explorer/ast/element.h" #include "explorer/ast/expression.h" +#include "explorer/ast/expression_category.h" #include "explorer/ast/value.h" #include "explorer/common/arena.h" #include "explorer/common/error_builders.h" @@ -169,7 +170,8 @@ class Interpreter { // Call the function `fun` with the given `arg` and the `witnesses` // for the function's impl bindings. auto CallFunction(const CallExpression& call, Nonnull fun, - Nonnull arg, ImplWitnessMap&& witnesses) + Nonnull arg, ImplWitnessMap&& witnesses, + std::optional location_received) -> ErrorOr; auto CallDestructor(Nonnull fun, @@ -291,46 +293,90 @@ auto Interpreter::CreateStruct(const std::vector& fields, return arena_->New(std::move(elements)); } -auto PatternMatch(Nonnull p, Nonnull v, +static auto InitializePlaceholderValue( + const ValueNodeView& value_node, ExpressionResult v, + std::optional> bindings) { + switch (value_node.expression_category()) { + case ExpressionCategory::Reference: + if (v.expression_category() == ExpressionCategory::Value || + v.expression_category() == ExpressionCategory::Reference) { + // Build by copying from value or reference expression. + (*bindings)->Initialize(value_node, v.value()); + } else { + // Location initialized by initializing expression, bind node to + // address. + CARBON_CHECK(v.address()) + << "Missing location from initializing expression"; + (*bindings)->Bind(value_node, *v.address()); + } + break; + case ExpressionCategory::Value: + if (v.expression_category() == ExpressionCategory::Value) { + // TODO: Ensure value expressions of temporaries are registered as + // allocation to allow us to reference it without the need for a copy. + (*bindings)->Initialize(value_node, v.value()); + } else if (v.expression_category() == ExpressionCategory::Reference) { + // TODO: Prevent mutation, error on mutation, or copy + // Bind the reference expression value directly. + (*bindings)->BindValue(value_node, v.value()); + } else { + // Location initialized by initializing expression, bind node to + // address. + CARBON_CHECK(v.address()) + << "Missing location from initializing expression"; + (*bindings)->Bind(value_node, *v.address()); + } + break; + case ExpressionCategory::Initializing: + CARBON_FATAL() << "Cannot pattern match an initializing expression"; + break; + } +} + +auto PatternMatch(Nonnull p, ExpressionResult v, SourceLocation source_loc, std::optional> bindings, BindingMap& generic_args, Nonnull trace_stream, Nonnull arena) -> bool { if (trace_stream->is_enabled()) { - *trace_stream << "match pattern " << *p << "\nwith value " << *v << "\n"; + *trace_stream << "match pattern " << *p << "\nfrom " + << ExpressionCategoryToString(v.expression_category()) + << " expression with value " << *v.value() << "\n"; } switch (p->kind()) { case Value::Kind::BindingPlaceholderValue: { CARBON_CHECK(bindings.has_value()); const auto& placeholder = cast(*p); if (placeholder.value_node().has_value()) { - (*bindings)->Initialize(*placeholder.value_node(), v); + InitializePlaceholderValue(*placeholder.value_node(), v, bindings); } return true; } case Value::Kind::AddrValue: { const auto& addr = cast(*p); - CARBON_CHECK(v->kind() == Value::Kind::LocationValue); - const auto& location = cast(*v); + CARBON_CHECK(v.value()->kind() == Value::Kind::LocationValue); + const auto& location = cast(*v.value()); return PatternMatch( - &addr.pattern(), arena->New(location.address()), + &addr.pattern(), + ExpressionResult::Value(arena->New(location.address())), source_loc, bindings, generic_args, trace_stream, arena); } case Value::Kind::VariableType: { const auto& var_type = cast(*p); - generic_args[&var_type.binding()] = v; + generic_args[&var_type.binding()] = v.value(); return true; } case Value::Kind::TupleType: case Value::Kind::TupleValue: - switch (v->kind()) { + switch (v.value()->kind()) { case Value::Kind::TupleType: case Value::Kind::TupleValue: { const auto& p_tup = cast(*p); - const auto& v_tup = cast(*v); + const auto& v_tup = cast(*v.value()); CARBON_CHECK(p_tup.elements().size() == v_tup.elements().size()); for (size_t i = 0; i < p_tup.elements().size(); ++i) { - if (!PatternMatch(p_tup.elements()[i], v_tup.elements()[i], + if (!PatternMatch(p_tup.elements()[i], + ExpressionResult::Value(v_tup.elements()[i]), source_loc, bindings, generic_args, trace_stream, arena)) { return false; @@ -341,7 +387,9 @@ auto PatternMatch(Nonnull p, Nonnull v, case Value::Kind::UninitializedValue: { const auto& p_tup = cast(*p); for (const auto& ele : p_tup.elements()) { - if (!PatternMatch(ele, arena->New(ele), + if (!PatternMatch(ele, + ExpressionResult::Value( + arena->New(ele)), source_loc, bindings, generic_args, trace_stream, arena)) { return false; @@ -350,28 +398,30 @@ auto PatternMatch(Nonnull p, Nonnull v, return true; } default: - CARBON_FATAL() << "expected a tuple value in pattern, not " << *v; + CARBON_FATAL() << "expected a tuple value in pattern, not " + << *v.value(); } case Value::Kind::StructValue: { const auto& p_struct = cast(*p); - const auto& v_struct = cast(*v); + const auto& v_struct = cast(*v.value()); CARBON_CHECK(p_struct.elements().size() == v_struct.elements().size()); for (size_t i = 0; i < p_struct.elements().size(); ++i) { CARBON_CHECK(p_struct.elements()[i].name == v_struct.elements()[i].name); if (!PatternMatch(p_struct.elements()[i].value, - v_struct.elements()[i].value, source_loc, bindings, - generic_args, trace_stream, arena)) { + ExpressionResult::Value(v_struct.elements()[i].value), + source_loc, bindings, generic_args, trace_stream, + arena)) { return false; } } return true; } case Value::Kind::AlternativeValue: - switch (v->kind()) { + switch (v.value()->kind()) { case Value::Kind::AlternativeValue: { const auto& p_alt = cast(*p); - const auto& v_alt = cast(*v); + const auto& v_alt = cast(*v.value()); if (&p_alt.alternative() != &v_alt.alternative()) { return false; } @@ -380,25 +430,30 @@ auto PatternMatch(Nonnull p, Nonnull v, if (!p_alt.argument().has_value()) { return true; } - return PatternMatch(*p_alt.argument(), *v_alt.argument(), source_loc, - bindings, generic_args, trace_stream, arena); + return PatternMatch( + *p_alt.argument(), ExpressionResult::Value(*v_alt.argument()), + source_loc, bindings, generic_args, trace_stream, arena); } default: CARBON_FATAL() << "expected a choice alternative in pattern, not " - << *v; + << *v.value(); } case Value::Kind::UninitializedValue: - CARBON_FATAL() << "uninitialized value is not allowed in pattern " << *v; + CARBON_FATAL() << "uninitialized value is not allowed in pattern " + << *v.value(); case Value::Kind::FunctionType: - switch (v->kind()) { + switch (v.value()->kind()) { case Value::Kind::FunctionType: { const auto& p_fn = cast(*p); - const auto& v_fn = cast(*v); - if (!PatternMatch(&p_fn.parameters(), &v_fn.parameters(), source_loc, - bindings, generic_args, trace_stream, arena)) { + const auto& v_fn = cast(*v.value()); + if (!PatternMatch(&p_fn.parameters(), + ExpressionResult::Value(&v_fn.parameters()), + source_loc, bindings, generic_args, trace_stream, + arena)) { return false; } - if (!PatternMatch(&p_fn.return_type(), &v_fn.return_type(), + if (!PatternMatch(&p_fn.return_type(), + ExpressionResult::Value(&v_fn.return_type()), source_loc, bindings, generic_args, trace_stream, arena)) { return false; @@ -410,16 +465,16 @@ auto PatternMatch(Nonnull p, Nonnull v, } case Value::Kind::AutoType: // `auto` matches any type, without binding any new names. We rely - // on the typechecker to ensure that `v` is a type. + // on the typechecker to ensure that `v.value()` is a type. return true; case Value::Kind::StaticArrayType: { - switch (v->kind()) { + switch (v.value()->kind()) { case Value::Kind::TupleType: case Value::Kind::TupleValue: { return true; } case Value::Kind::StaticArrayType: { - const auto& v_arr = cast(*v); + const auto& v_arr = cast(*v.value()); return v_arr.has_size(); } default: @@ -427,7 +482,7 @@ auto PatternMatch(Nonnull p, Nonnull v, } } default: - return ValueEqual(p, v, std::nullopt); + return ValueEqual(p, v.value(), std::nullopt); } } @@ -993,13 +1048,18 @@ auto Interpreter::CallDestructor(Nonnull fun, return ProgramError(fun->source_loc()) << "destructors currently don't support `addr self` bindings"; } - if (placeholder->value_node().has_value()) { - method_scope.Bind(*placeholder->value_node(), receiver); + if (auto& value_node = placeholder->value_node()) { + if (value_node->expression_category() == ExpressionCategory::Value) { + method_scope.BindValue(*placeholder->value_node(), receiver); + } else { + CARBON_FATAL() + << "TODO: [self addr: Self*] destructors not implemented yet"; + } } CARBON_CHECK(method.body().has_value()) << "Calling a method that's missing a body"; - auto act = std::make_unique(*method.body()); + auto act = std::make_unique(*method.body(), std::nullopt); return todo_.Spawn(std::unique_ptr(std::move(act)), std::move(method_scope)); } @@ -1007,7 +1067,9 @@ auto Interpreter::CallDestructor(Nonnull fun, auto Interpreter::CallFunction(const CallExpression& call, Nonnull fun, Nonnull arg, - ImplWitnessMap&& witnesses) -> ErrorOr { + ImplWitnessMap&& witnesses, + std::optional location_received) + -> ErrorOr { if (trace_stream_->is_enabled()) { *trace_stream_ << "calling function: " << *fun << "\n"; } @@ -1039,31 +1101,26 @@ auto Interpreter::CallFunction(const CallExpression& call, for (const auto& [bind, val] : call.deduced_args()) { CARBON_ASSIGN_OR_RETURN(Nonnull inst_val, InstantiateType(val, call.source_loc())); - binding_scope.Initialize(bind->original(), inst_val); + binding_scope.BindValue(bind->original(), inst_val); } for (const auto& [impl_bind, witness] : witnesses) { - binding_scope.Initialize(impl_bind->original(), witness); + binding_scope.BindValue(impl_bind->original(), witness); } // Bring the arguments that are determined by the function value into // scope. This includes the arguments for the class of which the function // is a member. for (const auto& [bind, val] : func_val->type_args()) { - binding_scope.Initialize(bind->original(), val); + binding_scope.BindValue(bind->original(), val); } for (const auto& [impl_bind, witness] : func_val->witnesses()) { - binding_scope.Initialize(impl_bind->original(), witness); + binding_scope.BindValue(impl_bind->original(), witness); } // Enter the binding scope to make any deduced arguments visible before // we resolve the self type and parameter type. todo_.CurrentAction().StartScope(std::move(binding_scope)); - CARBON_ASSIGN_OR_RETURN( - Nonnull converted_args, - Convert(arg, &function.param_pattern().static_type(), - call.source_loc())); - RuntimeScope function_scope(&heap_); BindingMap generic_args; @@ -1073,23 +1130,36 @@ auto Interpreter::CallFunction(const CallExpression& call, const auto* self_pattern = &function.self_pattern().value(); if (const auto* placeholder = dyn_cast(self_pattern)) { + // Immutable self with `[self: Self]` // TODO: move this logic into PatternMatch if (placeholder->value_node().has_value()) { - function_scope.Bind(*placeholder->value_node(), - method_val->receiver()); + function_scope.BindValue(*placeholder->value_node(), + method_val->receiver()); } } else { - CARBON_CHECK(PatternMatch(self_pattern, method_val->receiver(), - call.source_loc(), &function_scope, - generic_args, trace_stream_, this->arena_)); + // Mutable self with `[addr self: Self*]` + CARBON_CHECK(isa(self_pattern)); + CARBON_CHECK(PatternMatch( + self_pattern, ExpressionResult::Value(method_val->receiver()), + call.source_loc(), &function_scope, generic_args, trace_stream_, + this->arena_)); } } + // TODO: Preserve expression category to allow appropriate binding in + // `PatternMatch`. + CARBON_ASSIGN_OR_RETURN( + Nonnull converted_args, + Convert(arg, &function.param_pattern().static_type(), + call.source_loc())); + // Bind the arguments to the parameters. - CARBON_CHECK(PatternMatch( - &function.param_pattern().value(), converted_args, call.source_loc(), - &function_scope, generic_args, trace_stream_, this->arena_)); - return todo_.Spawn(std::make_unique(*function.body()), + CARBON_CHECK(PatternMatch(&function.param_pattern().value(), + ExpressionResult::Value(converted_args), + call.source_loc(), &function_scope, + generic_args, trace_stream_, this->arena_)); + return todo_.Spawn(std::make_unique(*function.body(), + location_received), std::move(function_scope)); } case Value::Kind::ParameterizedEntityName: { @@ -1097,7 +1167,8 @@ auto Interpreter::CallFunction(const CallExpression& call, const Declaration& decl = name.declaration(); RuntimeScope params_scope(&heap_); BindingMap generic_args; - CARBON_CHECK(PatternMatch(&name.params().value(), arg, call.source_loc(), + CARBON_CHECK(PatternMatch(&name.params().value(), + ExpressionResult::Value(arg), call.source_loc(), ¶ms_scope, generic_args, trace_stream_, this->arena_)); Nonnull bindings = @@ -1227,8 +1298,8 @@ auto Interpreter::StepInstantiateType() -> ErrorOr { } auto Interpreter::StepExp() -> ErrorOr { - Action& act = todo_.CurrentAction(); - const Expression& exp = cast(act).expression(); + auto& act = cast(todo_.CurrentAction()); + const Expression& exp = act.expression(); if (trace_stream_->is_enabled()) { *trace_stream_ << "--- step exp " << exp << " ." << act.pos() << "." << " (" << exp.source_loc() << ") --->\n"; @@ -1587,7 +1658,7 @@ auto Interpreter::StepExp() -> ErrorOr { } } return CallFunction(call, act.results()[0], act.results()[1], - std::move(witnesses)); + std::move(witnesses), act.location_received()); } else if (act.pos() == 3 + static_cast(num_witnesses)) { if (act.results().size() < 3 + num_witnesses) { // Control fell through without explicit return. @@ -1702,6 +1773,12 @@ auto Interpreter::StepExp() -> ErrorOr { } } } + case IntrinsicExpression::Intrinsic::PrintAllocs: { + CARBON_CHECK(args.empty()); + heap_.Print(*print_stream_); + *print_stream_ << "\n"; + return todo_.FinishAction(TupleValue::Empty()); + } case IntrinsicExpression::Intrinsic::Rand: { CARBON_CHECK(args.size() == 2); const int64_t low = cast(*args[0]).value(); @@ -1996,8 +2073,8 @@ auto Interpreter::StepWitness() -> ErrorOr { } auto Interpreter::StepStmt() -> ErrorOr { - Action& act = todo_.CurrentAction(); - const Statement& stmt = cast(act).statement(); + auto& act = cast(todo_.CurrentAction()); + const Statement& stmt = act.statement(); if (trace_stream_->is_enabled()) { *trace_stream_ << "--- step stmt "; stmt.PrintDepth(1, trace_stream_->stream()); @@ -2025,12 +2102,14 @@ auto Interpreter::StepStmt() -> ErrorOr { Nonnull val, Convert(act.results()[0], &c.pattern().static_type(), stmt.source_loc())); - if (PatternMatch(&c.pattern().value(), val, stmt.source_loc(), &matches, - generic_args, trace_stream_, this->arena_)) { + if (PatternMatch(&c.pattern().value(), ExpressionResult::Value(val), + stmt.source_loc(), &matches, generic_args, + trace_stream_, this->arena_)) { // Ensure we don't process any more clauses. act.set_pos(match_stmt.clauses().size() + 1); todo_.MergeScope(std::move(matches)); - return todo_.Spawn(std::make_unique(&c.statement())); + return todo_.Spawn( + std::make_unique(&c.statement(), std::nullopt)); } else { return todo_.RunAgain(); } @@ -2061,8 +2140,8 @@ auto Interpreter::StepStmt() -> ErrorOr { source_array->elements()[start_index]); act.ReplaceResult(CurrentIndexPosInResult, arena_->New(start_index + 1)); - return todo_.Spawn( - std::make_unique(&cast(stmt).body())); + return todo_.Spawn(std::make_unique( + &cast(stmt).body(), std::nullopt)); } if (act.pos() >= 2) { auto current_index = @@ -2085,8 +2164,8 @@ auto Interpreter::StepStmt() -> ErrorOr { act.ReplaceResult(CurrentIndexPosInResult, arena_->New(current_index + 1)); - return todo_.Spawn( - std::make_unique(&cast(stmt).body())); + return todo_.Spawn(std::make_unique( + &cast(stmt).body(), std::nullopt)); } } return todo_.FinishAction(); @@ -2109,8 +2188,8 @@ auto Interpreter::StepStmt() -> ErrorOr { if (cast(*condition).value()) { // { {true :: (while ([]) s) :: C, E, F} :: S, H} // -> { { s :: (while (e) s) :: C, E, F } :: S, H} - return todo_.Spawn( - std::make_unique(&cast(stmt).body())); + return todo_.Spawn(std::make_unique( + &cast(stmt).body(), std::nullopt)); } else { // { {false :: (while ([]) s) :: C, E, F} :: S, H} // -> { { C, E, F } :: S, H} @@ -2142,37 +2221,90 @@ auto Interpreter::StepStmt() -> ErrorOr { } // Process the next statement in the block. The position will be // incremented as part of Spawn. - return todo_.Spawn( - std::make_unique(block.statements()[act.pos()])); + return todo_.Spawn(std::make_unique( + block.statements()[act.pos()], act.location_received())); } case StatementKind::VariableDefinition: { const auto& definition = cast(stmt); - const auto* dest_type = &definition.pattern().static_type(); + const bool has_initializing_expr = + definition.has_init() && + definition.init().kind() == ExpressionKind::CallExpression && + definition.init().expression_category() == + ExpressionCategory::Initializing; + auto init_location = (act.location_received() && definition.is_returned()) + ? act.location_received() + : act.location_created(); if (act.pos() == 0 && definition.has_init()) { // { {(var x = e) :: C, E, F} :: S, H} // -> { {e :: (var x = []) :: C, E, F} :: S, H} - return todo_.Spawn( - std::make_unique(&definition.init())); + if (has_initializing_expr && !init_location) { + // Allocate storage for initializing expression. + const auto allocation_id = + heap_.AllocateValue(arena_->New( + &definition.init().static_type())); + act.set_location_created(allocation_id); + init_location = allocation_id; + RuntimeScope scope(&heap_); + scope.BindLifetimeToScope(Address(allocation_id)); + todo_.MergeScope(std::move(scope)); + } + return todo_.Spawn(std::make_unique( + &definition.init(), init_location)); } else { // { { v :: (x = []) :: C, E, F} :: S, H} // -> { { C, E(x := a), F} :: S, H(a := copy(v))} - Nonnull p = - &cast(stmt).pattern().value(); + Nonnull p = &definition.pattern().value(); Nonnull v; + std::optional
v_location; + ExpressionCategory expr_category = + definition.has_init() ? definition.init().expression_category() + : ExpressionCategory::Value; if (definition.has_init()) { - CARBON_ASSIGN_OR_RETURN( - v, Convert(act.results()[0], dest_type, stmt.source_loc())); + if (has_initializing_expr && init_location && + heap_.is_initialized(*init_location)) { + const auto address = Address(*init_location); + CARBON_ASSIGN_OR_RETURN( + v, heap_.Read(address, definition.source_loc())); + CARBON_CHECK(v == act.results()[0]); + v_location = address; + } else { + // TODO: Prevent copies for Value expressions from Reference + // expression, once able to prevent mutations. + if (init_location && act.location_created()) { + // Location provided to initializing expression was not used. + heap_.Discard(*init_location); + } + expr_category = ExpressionCategory::Value; + const auto* dest_type = &definition.pattern().static_type(); + CARBON_ASSIGN_OR_RETURN( + v, Convert(act.results()[0], dest_type, stmt.source_loc())); + } } else { v = arena_->New(p); } - RuntimeScope matches(&heap_); - BindingMap generic_args; - CARBON_CHECK(PatternMatch(p, v, stmt.source_loc(), &matches, - generic_args, trace_stream_, this->arena_)) - << stmt.source_loc() - << ": internal error in variable definition, match failed"; - todo_.MergeScope(std::move(matches)); + // If declaring a returned var, bind name to the location provided to + // initializing expression, if any. + RuntimeScope scope(&heap_); + if (definition.is_returned() && init_location) { + CARBON_CHECK(p->kind() == Value::Kind::BindingPlaceholderValue); + const auto value_node = + cast(*p).value_node(); + CARBON_CHECK(value_node); + const auto address = Address(*init_location); + scope.Bind(*value_node, address); + CARBON_RETURN_IF_ERROR(heap_.Write(address, v, stmt.source_loc())); + } else { + BindingMap generic_args; + bool matched = + PatternMatch(p, ExpressionResult(v, v_location, expr_category), + stmt.source_loc(), &scope, generic_args, + trace_stream_, this->arena_); + CARBON_CHECK(matched) + << stmt.source_loc() + << ": internal error in variable definition, match failed"; + } + todo_.MergeScope(std::move(scope)); return todo_.FinishAction(); } } @@ -2239,14 +2371,14 @@ auto Interpreter::StepStmt() -> ErrorOr { // { {true :: if ([]) then_stmt else else_stmt :: C, E, F} :: // S, H} // -> { { then_stmt :: C, E, F } :: S, H} - return todo_.Spawn( - std::make_unique(&cast(stmt).then_block())); + return todo_.Spawn(std::make_unique( + &cast(stmt).then_block(), std::nullopt)); } else if (cast(stmt).else_block()) { // { {false :: if ([]) then_stmt else else_stmt :: C, E, F} :: // S, H} // -> { { else_stmt :: C, E, F } :: S, H} - return todo_.Spawn( - std::make_unique(*cast(stmt).else_block())); + return todo_.Spawn(std::make_unique( + *cast(stmt).else_block(), std::nullopt)); } else { return todo_.FinishAction(); } @@ -2289,6 +2421,11 @@ auto Interpreter::StepStmt() -> ErrorOr { Nonnull return_value, Convert(act.results()[0], &function.return_term().static_type(), stmt.source_loc())); + // Write to initialized storage location, if any. + if (const auto location = act.location_received()) { + CARBON_RETURN_IF_ERROR( + heap_.Write(Address(*location), return_value, stmt.source_loc())); + } return todo_.UnwindPast(*function.body(), return_value); } } @@ -2433,6 +2570,10 @@ auto Interpreter::StepCleanUp() -> ErrorOr { if (act.pos() < cleanup.allocations_count() * 2) { const size_t alloc_index = cleanup.allocations_count() - act.pos() / 2 - 1; auto allocation = act.scope()->allocations()[alloc_index]; + if (heap_.is_discarded(allocation)) { + // Initializing expressions can generate discarded allocations. + return todo_.RunAgain(); + } if (act.pos() % 2 == 0) { auto* location = arena_->New(Address(allocation)); auto value = diff --git a/explorer/interpreter/interpreter.h b/explorer/interpreter/interpreter.h index 15a48d017277..829af85d0376 100644 --- a/explorer/interpreter/interpreter.h +++ b/explorer/interpreter/interpreter.h @@ -45,10 +45,12 @@ auto InterpExp(Nonnull e, Nonnull arena, // The matches for generic variables in the pattern are output in // `generic_args`. // TODO: consider moving this to a separate header. -[[nodiscard]] auto PatternMatch( - Nonnull p, Nonnull v, SourceLocation source_loc, - std::optional> bindings, BindingMap& generic_args, - Nonnull trace_stream, Nonnull arena) -> bool; +[[nodiscard]] auto PatternMatch(Nonnull p, ExpressionResult v, + SourceLocation source_loc, + std::optional> bindings, + BindingMap& generic_args, + Nonnull trace_stream, + Nonnull arena) -> bool; } // namespace Carbon diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index db0569bb714c..f345bc05141a 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -21,6 +21,7 @@ #include "common/ostream.h" #include "explorer/ast/declaration.h" #include "explorer/ast/expression.h" +#include "explorer/ast/pattern.h" #include "explorer/ast/value.h" #include "explorer/ast/value_transform.h" #include "explorer/common/arena.h" @@ -1116,9 +1117,9 @@ auto TypeChecker::GetBuiltinInterfaceType(SourceLocation source_loc, BindingMap binding_args; if (has_arguments) { TupleValue args(interface.arguments); - if (!PatternMatch(&iface_decl->params().value()->value(), &args, source_loc, - std::nullopt, binding_args, trace_stream_, - this->arena_)) { + if (!PatternMatch(&iface_decl->params().value()->value(), + ExpressionResult::Value(&args), source_loc, std::nullopt, + binding_args, trace_stream_, this->arena_)) { return bad_builtin(); } } @@ -2431,7 +2432,8 @@ class TypeChecker::SubstituteTransform &fn_type->return_type())); return type_checker_->arena_->New( param, std::move(generic_parameters), ret, std::move(deduced_bindings), - std::move(subst_bindings).TakeImplBindings()); + std::move(subst_bindings).TakeImplBindings(), + fn_type->is_initializing()); } // Substituting into a `ConstraintType` needs special handling if we replace @@ -3161,6 +3163,7 @@ auto TypeChecker::TypeCheckExpImpl(Nonnull e, access.set_found_in_interface(result.interface); access.set_is_type_access(!IsInstanceMember(&access.member())); access.set_static_type(inst_member_type); + access.set_expression_category(ExpressionCategory::Value); if (const auto* func_decl = dyn_cast(result.member)) { @@ -3774,7 +3777,9 @@ auto TypeChecker::TypeCheckExpImpl(Nonnull e, Nonnull return_type, Substitute(call.bindings(), &fun_t.return_type())); call.set_static_type(return_type); - call.set_expression_category(ExpressionCategory::Value); + call.set_expression_category(fun_t.is_initializing() + ? ExpressionCategory::Initializing + : ExpressionCategory::Value); return Success(); } case Value::Kind::TypeOfParameterizedEntityName: { @@ -3912,6 +3917,15 @@ auto TypeChecker::TypeCheckExpImpl(Nonnull e, e->set_expression_category(ExpressionCategory::Value); return Success(); } + case IntrinsicExpression::Intrinsic::PrintAllocs: { + if (!args.empty()) { + return ProgramError(e->source_loc()) + << "__intrinsic_print_allocs takes no arguments"; + } + e->set_static_type(TupleType::Empty()); + e->set_expression_category(ExpressionCategory::Value); + return Success(); + } case IntrinsicExpression::Intrinsic::Rand: { if (args.size() != 2) { return ProgramError(e->source_loc()) @@ -4467,9 +4481,9 @@ auto TypeChecker::TypeCheckPattern( // this level rather than on the overall initializer. if (!IsNonDeduceableType(type)) { BindingMap generic_args; - if (!PatternMatch(type, *expected, binding.type().source_loc(), - std::nullopt, generic_args, trace_stream_, - this->arena_)) { + if (!PatternMatch(type, ExpressionResult::Value(*expected), + binding.type().source_loc(), std::nullopt, + generic_args, trace_stream_, this->arena_)) { return ProgramError(binding.type().source_loc()) << "type pattern '" << *type << "' does not match actual type '" << **expected << "'"; @@ -5112,7 +5126,7 @@ auto TypeChecker::DeclareCallableDeclaration(Nonnull f, f->set_static_type(arena_->New( &f->param_pattern().static_type(), std::move(generic_parameters), &f->return_term().static_type(), std::move(deduced_bindings), - std::move(impl_bindings))); + std::move(impl_bindings), /*is_initializing*/ true)); switch (f->kind()) { case DeclarationKind::FunctionDeclaration: // TODO: Should we pass in the bindings from the enclosing scope? diff --git a/explorer/testdata/basic_syntax/print_allocs.carbon b/explorer/testdata/basic_syntax/print_allocs.carbon new file mode 100644 index 000000000000..98f6b8aa2407 --- /dev/null +++ b/explorer/testdata/basic_syntax/print_allocs.carbon @@ -0,0 +1,16 @@ +// 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 +// CHECK:STDOUT: 0: Heap{}, 1: 1, 2: 2 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +fn Main() -> i32 { + var a: i32 = 1; + var b: i32 = 2; + heap.PrintAllocs(); + return 0; +} diff --git a/explorer/testdata/basic_syntax/print_allocs_empty.carbon b/explorer/testdata/basic_syntax/print_allocs_empty.carbon new file mode 100644 index 000000000000..f49e04319da6 --- /dev/null +++ b/explorer/testdata/basic_syntax/print_allocs_empty.carbon @@ -0,0 +1,14 @@ +// 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 +// CHECK:STDOUT: 0: Heap{} +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +fn Main() -> i32 { + heap.PrintAllocs(); + return 0; +} diff --git a/explorer/testdata/let/destroyed.carbon b/explorer/testdata/let/destroyed.carbon new file mode 100644 index 000000000000..ee875ff50c93 --- /dev/null +++ b/explorer/testdata/let/destroyed.carbon @@ -0,0 +1,20 @@ +// 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 +// CHECK:STDOUT: Destructor +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("Destructor"); + } +} + +fn Main() -> i32 { + let c: C = {}; + return 0; +} diff --git a/explorer/testdata/let/value_expr_binding_from_reference.carbon b/explorer/testdata/let/value_expr_binding_from_reference.carbon new file mode 100644 index 000000000000..58f4a92ef4b5 --- /dev/null +++ b/explorer/testdata/let/value_expr_binding_from_reference.carbon @@ -0,0 +1,34 @@ +// 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 +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: Bind from c reference expression +// CHECK:STDOUT: Binding scope end +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: !!C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn CallWithValueExpressionBinding(c: C) { + Print("Binding scope end"); +} + +fn Main() -> i32 { + var c_var: C = {}; + heap.PrintAllocs(); + + Print("Bind from c reference expression"); + CallWithValueExpressionBinding(c_var); + heap.PrintAllocs(); + return 0; +} diff --git a/explorer/testdata/let/value_expr_binding_from_value.carbon b/explorer/testdata/let/value_expr_binding_from_value.carbon new file mode 100644 index 000000000000..26c4dccf6553 --- /dev/null +++ b/explorer/testdata/let/value_expr_binding_from_value.carbon @@ -0,0 +1,34 @@ +// 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 +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: Bind from c value expression +// CHECK:STDOUT: Binding scope end +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: !!C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn CallWithValueExpressionBinding(c: C) { + Print("Binding scope end"); +} + +fn Main() -> i32 { + let c_let: C = {}; + heap.PrintAllocs(); + + Print("Bind from c value expression"); + CallWithValueExpressionBinding(c_let); + heap.PrintAllocs(); + return 0; +} diff --git a/explorer/testdata/let/value_expr_from_initializing.carbon b/explorer/testdata/let/value_expr_from_initializing.carbon new file mode 100644 index 000000000000..a4e6df96db2e --- /dev/null +++ b/explorer/testdata/let/value_expr_from_initializing.carbon @@ -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 +// CHECK:STDOUT: Initialize c from initializing expression (return ) +// CHECK:STDOUT: 0: Heap{}, 1: !Uninit, 2: C{} +// CHECK:STDOUT: Object created, returning +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: !!C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn CallWithReturnExpression() -> C { + var c: C = {}; + heap.PrintAllocs(); + Print("Object created, returning"); + return c; +} + +fn FromInitializingExpression_ReturnExpr() { + Print("Initialize c from initializing expression (return )"); + let c: C = CallWithReturnExpression(); + heap.PrintAllocs(); +} + +fn Main() -> i32 { + FromInitializingExpression_ReturnExpr(); + return 0; +} diff --git a/explorer/testdata/let/value_expr_from_initializing_returned.carbon b/explorer/testdata/let/value_expr_from_initializing_returned.carbon new file mode 100644 index 000000000000..cefd40642072 --- /dev/null +++ b/explorer/testdata/let/value_expr_from_initializing_returned.carbon @@ -0,0 +1,39 @@ +// 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 +// CHECK:STDOUT: Initialize c from initializing expression (returned var) +// CHECK:STDOUT: Entering call +// CHECK:STDOUT: 0: Heap{}, 1: !Uninit +// CHECK:STDOUT: Object created, returning +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn CallWithReturnedVar() -> C { + Print("Entering call"); + heap.PrintAllocs(); + returned var c: C = {}; + Print("Object created, returning"); + return var; +} + +fn FromInitializingExpression_ReturnedVar() { + Print("Initialize c from initializing expression (returned var)"); + let c: C = CallWithReturnedVar(); + heap.PrintAllocs(); +} + +fn Main() -> i32 { + FromInitializingExpression_ReturnedVar(); + return 0; +} diff --git a/explorer/testdata/let/value_expr_from_initializing_returned_nested.carbon b/explorer/testdata/let/value_expr_from_initializing_returned_nested.carbon new file mode 100644 index 000000000000..b8da28ebed8f --- /dev/null +++ b/explorer/testdata/let/value_expr_from_initializing_returned_nested.carbon @@ -0,0 +1,52 @@ +// 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 +// CHECK:STDOUT: Initialize c1 from initializing expression (returned var) +// CHECK:STDOUT: Before nested init +// CHECK:STDOUT: 0: Heap{}, 1: !Uninit +// CHECK:STDOUT: Nested call return +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: First call return +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: Declaration scope +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn CallWithReturnedVar2() -> C { + Print("Before nested init"); + heap.PrintAllocs(); + returned var c: C = {}; + Print("Nested call return"); + heap.PrintAllocs(); + return var; +} + +fn CallWithReturnedVar() -> C { + returned var c: C = CallWithReturnedVar2(); + Print("First call return"); + heap.PrintAllocs(); + return var; +} + +fn FromInitializingExpression_ReturnedVar() { + Print("Initialize c1 from initializing expression (returned var)"); + let c: C = CallWithReturnedVar(); + Print("Declaration scope"); + heap.PrintAllocs(); +} + +fn Main() -> i32 { + FromInitializingExpression_ReturnedVar(); + return 0; +} diff --git a/explorer/testdata/let/value_expr_from_ref.carbon b/explorer/testdata/let/value_expr_from_ref.carbon new file mode 100644 index 000000000000..99b63b94f11a --- /dev/null +++ b/explorer/testdata/let/value_expr_from_ref.carbon @@ -0,0 +1,32 @@ +// 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 +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: Initialize c from reference expression +// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn FromReferenceExpression() { + var c_var: C = {}; + heap.PrintAllocs(); + Print("Initialize c from reference expression"); + let c: C = c_var; + heap.PrintAllocs(); +} + +fn Main() -> i32 { + FromReferenceExpression(); + return 0; +} diff --git a/explorer/testdata/let/value_expr_from_value.carbon b/explorer/testdata/let/value_expr_from_value.carbon new file mode 100644 index 000000000000..8ef997c57670 --- /dev/null +++ b/explorer/testdata/let/value_expr_from_value.carbon @@ -0,0 +1,32 @@ +// 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 +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: Initialize c from value expression +// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn FromValueExpression() { + let c_let: C = {}; + heap.PrintAllocs(); + Print("Initialize c from value expression"); + let c: C = c_let; + heap.PrintAllocs(); +} + +fn Main() -> i32 { + FromValueExpression(); + return 0; +} diff --git a/explorer/testdata/pointer/fail_use_after_free.carbon b/explorer/testdata/pointer/fail_use_after_free.carbon index 9ccc23fd6a2e..0c6b28551062 100644 --- a/explorer/testdata/pointer/fail_use_after_free.carbon +++ b/explorer/testdata/pointer/fail_use_after_free.carbon @@ -9,6 +9,6 @@ package ExplorerTest api; fn Main() -> i32 { var p: i32* = heap.New(5); heap.Delete(p); - // CHECK:STDERR: RUNTIME ERROR: fail_use_after_free.carbon:[[@LINE+1]]: undefined behavior: access to dead value 5 + // CHECK:STDERR: RUNTIME ERROR: fail_use_after_free.carbon:[[@LINE+1]]: undefined behavior: access to dead or discarded value 5 return *p; } diff --git a/explorer/testdata/var/local/destroyed.carbon b/explorer/testdata/var/local/destroyed.carbon new file mode 100644 index 000000000000..be3c3bb2ddf2 --- /dev/null +++ b/explorer/testdata/var/local/destroyed.carbon @@ -0,0 +1,20 @@ +// 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 +// CHECK:STDOUT: Destructor +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("Destructor"); + } +} + +fn Main() -> i32 { + var c: C = {}; + return 0; +} diff --git a/explorer/testdata/var/local/reference_expr_binding_from_reference.carbon b/explorer/testdata/var/local/reference_expr_binding_from_reference.carbon new file mode 100644 index 000000000000..3f7f46ea75d5 --- /dev/null +++ b/explorer/testdata/var/local/reference_expr_binding_from_reference.carbon @@ -0,0 +1,34 @@ +// 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 +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: Bind from c reference expression +// CHECK:STDOUT: Binding scope end +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: !!C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn CallWithReferenceExpressionBinding(var c: C) { + Print("Binding scope end"); +} + +fn Main() -> i32 { + var c_var: C = {}; + heap.PrintAllocs(); + + Print("Bind from c reference expression"); + CallWithReferenceExpressionBinding(c_var); + heap.PrintAllocs(); + return 0; +} diff --git a/explorer/testdata/var/local/reference_expr_binding_from_value.carbon b/explorer/testdata/var/local/reference_expr_binding_from_value.carbon new file mode 100644 index 000000000000..ab0424ade29e --- /dev/null +++ b/explorer/testdata/var/local/reference_expr_binding_from_value.carbon @@ -0,0 +1,34 @@ +// 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 +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: Bind from c value expression +// CHECK:STDOUT: Binding scope end +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: !!C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn CallWithReferenceExpressionBinding(var c: C) { + Print("Binding scope end"); +} + +fn Main() -> i32 { + let c_let: C = {}; + heap.PrintAllocs(); + + Print("Bind from c value expression"); + CallWithReferenceExpressionBinding(c_let); + heap.PrintAllocs(); + return 0; +} diff --git a/explorer/testdata/var/local/reference_expr_from_initializing.carbon b/explorer/testdata/var/local/reference_expr_from_initializing.carbon new file mode 100644 index 000000000000..6f55ad5a0405 --- /dev/null +++ b/explorer/testdata/var/local/reference_expr_from_initializing.carbon @@ -0,0 +1,42 @@ +// 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 +// CHECK:STDOUT: Initialize c from initializing expression (return ) +// CHECK:STDOUT: Entering call +// CHECK:STDOUT: 0: Heap{}, 1: !Uninit +// CHECK:STDOUT: Object created, returning +// CHECK:STDOUT: 0: Heap{}, 1: !Uninit, 2: C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: !!C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn CallWithReturnExpression() -> C { + Print("Entering call"); + heap.PrintAllocs(); + var c: C = {}; + Print("Object created, returning"); + heap.PrintAllocs(); + return c; +} + +fn FromInitializingExpression_ReturnExpr() { + Print("Initialize c from initializing expression (return )"); + var c: C = CallWithReturnExpression(); + heap.PrintAllocs(); +} + +fn Main() -> i32 { + FromInitializingExpression_ReturnExpr(); + return 0; +} diff --git a/explorer/testdata/var/local/reference_expr_from_initializing_returned.carbon b/explorer/testdata/var/local/reference_expr_from_initializing_returned.carbon new file mode 100644 index 000000000000..f7129f5e8ad1 --- /dev/null +++ b/explorer/testdata/var/local/reference_expr_from_initializing_returned.carbon @@ -0,0 +1,39 @@ +// 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 +// CHECK:STDOUT: Initialize c from initializing expression (returned var) +// CHECK:STDOUT: Entering call +// CHECK:STDOUT: 0: Heap{}, 1: !Uninit +// CHECK:STDOUT: Object created, returning +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn CallWithReturnedVar() -> C { + Print("Entering call"); + heap.PrintAllocs(); + returned var c: C = {}; + Print("Object created, returning"); + return var; +} + +fn FromInitializingExpression_ReturnedVar() { + Print("Initialize c from initializing expression (returned var)"); + var c: C = CallWithReturnedVar(); + heap.PrintAllocs(); +} + +fn Main() -> i32 { + FromInitializingExpression_ReturnedVar(); + return 0; +} diff --git a/explorer/testdata/var/local/reference_expr_from_initializing_returned_nested.carbon b/explorer/testdata/var/local/reference_expr_from_initializing_returned_nested.carbon new file mode 100644 index 000000000000..622dcf1910a4 --- /dev/null +++ b/explorer/testdata/var/local/reference_expr_from_initializing_returned_nested.carbon @@ -0,0 +1,52 @@ +// 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 +// CHECK:STDOUT: Initialize c from initializing expression (returned var) +// CHECK:STDOUT: Before nested init +// CHECK:STDOUT: 0: Heap{}, 1: !Uninit +// CHECK:STDOUT: Nested call return +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: First call return +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: Declaration scope +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn CallWithReturnedVar2() -> C { + Print("Before nested init"); + heap.PrintAllocs(); + returned var c: C = {}; + Print("Nested call return"); + heap.PrintAllocs(); + return var; +} + +fn CallWithReturnedVar() -> C { + returned var c: C = CallWithReturnedVar2(); + Print("First call return"); + heap.PrintAllocs(); + return var; +} + +fn FromInitializingExpression_ReturnedVar() { + Print("Initialize c from initializing expression (returned var)"); + var c: C = CallWithReturnedVar(); + Print("Declaration scope"); + heap.PrintAllocs(); +} + +fn Main() -> i32 { + FromInitializingExpression_ReturnedVar(); + return 0; +} diff --git a/explorer/testdata/var/local/reference_expr_from_ref.carbon b/explorer/testdata/var/local/reference_expr_from_ref.carbon new file mode 100644 index 000000000000..0e09f4662eba --- /dev/null +++ b/explorer/testdata/var/local/reference_expr_from_ref.carbon @@ -0,0 +1,32 @@ +// 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 +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: Initialize c from reference expression +// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn FromReferenceExpression() { + var c_var: C = {}; + heap.PrintAllocs(); + Print("Initialize c from reference expression"); + var c: C = c_var; + heap.PrintAllocs(); +} + +fn Main() -> i32 { + FromReferenceExpression(); + return 0; +} diff --git a/explorer/testdata/var/local/reference_expr_from_value.carbon b/explorer/testdata/var/local/reference_expr_from_value.carbon new file mode 100644 index 000000000000..f67825553000 --- /dev/null +++ b/explorer/testdata/var/local/reference_expr_from_value.carbon @@ -0,0 +1,32 @@ +// 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 +// CHECK:STDOUT: 0: Heap{}, 1: C{} +// CHECK:STDOUT: Initialize c from value expression +// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: C{} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn FromValueExpression() { + let c_let: C = {}; + heap.PrintAllocs(); + Print("Initialize c from value expression"); + var c: C = c_let; + heap.PrintAllocs(); +} + +fn Main() -> i32 { + FromValueExpression(); + return 0; +}