Jelajahi Sumber

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 <expr>`
   * 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
Adrien Leravat 2 tahun lalu
induk
melakukan
19c74ead49
35 mengubah file dengan 1035 tambahan dan 161 penghapusan
  1. 1 0
      explorer/ast/address.h
  2. 3 0
      explorer/ast/expression.cpp
  3. 1 0
      explorer/ast/expression.h
  4. 4 0
      explorer/ast/expression_category.h
  5. 11 0
      explorer/ast/value.cpp
  6. 43 4
      explorer/ast/value.h
  7. 3 0
      explorer/data/prelude.carbon
  8. 2 0
      explorer/interpreter/BUILD
  9. 28 19
      explorer/interpreter/action.cpp
  10. 59 14
      explorer/interpreter/action.h
  11. 17 14
      explorer/interpreter/heap.cpp
  12. 8 3
      explorer/interpreter/heap.h
  13. 0 5
      explorer/interpreter/heap_allocation_interface.h
  14. 229 88
      explorer/interpreter/interpreter.cpp
  15. 6 4
      explorer/interpreter/interpreter.h
  16. 23 9
      explorer/interpreter/type_checker.cpp
  17. 16 0
      explorer/testdata/basic_syntax/print_allocs.carbon
  18. 14 0
      explorer/testdata/basic_syntax/print_allocs_empty.carbon
  19. 20 0
      explorer/testdata/let/destroyed.carbon
  20. 34 0
      explorer/testdata/let/value_expr_binding_from_reference.carbon
  21. 34 0
      explorer/testdata/let/value_expr_binding_from_value.carbon
  22. 38 0
      explorer/testdata/let/value_expr_from_initializing.carbon
  23. 39 0
      explorer/testdata/let/value_expr_from_initializing_returned.carbon
  24. 52 0
      explorer/testdata/let/value_expr_from_initializing_returned_nested.carbon
  25. 32 0
      explorer/testdata/let/value_expr_from_ref.carbon
  26. 32 0
      explorer/testdata/let/value_expr_from_value.carbon
  27. 1 1
      explorer/testdata/pointer/fail_use_after_free.carbon
  28. 20 0
      explorer/testdata/var/local/destroyed.carbon
  29. 34 0
      explorer/testdata/var/local/reference_expr_binding_from_reference.carbon
  30. 34 0
      explorer/testdata/var/local/reference_expr_binding_from_value.carbon
  31. 42 0
      explorer/testdata/var/local/reference_expr_from_initializing.carbon
  32. 39 0
      explorer/testdata/var/local/reference_expr_from_initializing_returned.carbon
  33. 52 0
      explorer/testdata/var/local/reference_expr_from_initializing_returned_nested.carbon
  34. 32 0
      explorer/testdata/var/local/reference_expr_from_ref.carbon
  35. 32 0
      explorer/testdata/var/local/reference_expr_from_value.carbon

+ 1 - 0
explorer/ast/address.h

@@ -86,6 +86,7 @@ class Address {
   // the Heap, so its implementation details are tied to the implementation
   // the Heap, so its implementation details are tied to the implementation
   // details of the Heap.
   // details of the Heap.
   friend class Heap;
   friend class Heap;
+  friend class RuntimeScope;
 
 
   AllocationId allocation_;
   AllocationId allocation_;
   ElementPath element_path_;
   ElementPath element_path_;

+ 3 - 0
explorer/ast/expression.cpp

@@ -31,6 +31,7 @@ auto IntrinsicExpression::FindIntrinsic(std::string_view name,
       {{"print", Intrinsic::Print},
       {{"print", Intrinsic::Print},
        {"new", Intrinsic::Alloc},
        {"new", Intrinsic::Alloc},
        {"delete", Intrinsic::Dealloc},
        {"delete", Intrinsic::Dealloc},
+       {"print_allocs", Intrinsic::PrintAllocs},
        {"rand", Intrinsic::Rand},
        {"rand", Intrinsic::Rand},
        {"implicit_as", Intrinsic::ImplicitAs},
        {"implicit_as", Intrinsic::ImplicitAs},
        {"implicit_as_convert", Intrinsic::ImplicitAsConvert},
        {"implicit_as_convert", Intrinsic::ImplicitAsConvert},
@@ -62,6 +63,8 @@ auto IntrinsicExpression::name() const -> std::string_view {
       return "__intrinsic_new";
       return "__intrinsic_new";
     case IntrinsicExpression::Intrinsic::Dealloc:
     case IntrinsicExpression::Intrinsic::Dealloc:
       return "__intrinsic_delete";
       return "__intrinsic_delete";
+    case IntrinsicExpression::Intrinsic::PrintAllocs:
+      return "__intrinsic_print_allocs";
     case IntrinsicExpression::Intrinsic::Rand:
     case IntrinsicExpression::Intrinsic::Rand:
       return "__intrinsic_rand";
       return "__intrinsic_rand";
     case IntrinsicExpression::Intrinsic::ImplicitAs:
     case IntrinsicExpression::Intrinsic::ImplicitAs:

+ 1 - 0
explorer/ast/expression.h

@@ -858,6 +858,7 @@ class IntrinsicExpression : public RewritableMixin<Expression> {
     Print,
     Print,
     Alloc,
     Alloc,
     Dealloc,
     Dealloc,
+    PrintAllocs,
     Rand,
     Rand,
     ImplicitAs,
     ImplicitAs,
     ImplicitAsConvert,
     ImplicitAsConvert,

+ 4 - 0
explorer/ast/expression_category.h

@@ -5,6 +5,8 @@
 #ifndef CARBON_EXPLORER_AST_EXPRESSION_CATEGORY_H_
 #ifndef CARBON_EXPLORER_AST_EXPRESSION_CATEGORY_H_
 #define CARBON_EXPLORER_AST_EXPRESSION_CATEGORY_H_
 #define CARBON_EXPLORER_AST_EXPRESSION_CATEGORY_H_
 
 
+#include <llvm/ADT/StringRef.h>
+
 namespace Carbon {
 namespace Carbon {
 
 
 // The category of a Carbon expression indicates whether it evaluates
 // The category of a Carbon expression indicates whether it evaluates
@@ -18,6 +20,8 @@ enum class ExpressionCategory {
   Initializing,
   Initializing,
 };
 };
 
 
+auto ExpressionCategoryToString(ExpressionCategory cat) -> llvm::StringRef;
+
 }  // namespace Carbon
 }  // namespace Carbon
 
 
 #endif  // CARBON_EXPLORER_AST_EXPRESSION_CATEGORY_H_
 #endif  // CARBON_EXPLORER_AST_EXPRESSION_CATEGORY_H_

+ 11 - 0
explorer/ast/value.cpp

@@ -1318,4 +1318,15 @@ auto NominalClassType::InheritsClass(Nonnull<const Value*> other) const
   return false;
   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
 }  // namespace Carbon

+ 43 - 4
explorer/ast/value.h

@@ -16,6 +16,7 @@
 #include "explorer/ast/declaration.h"
 #include "explorer/ast/declaration.h"
 #include "explorer/ast/element.h"
 #include "explorer/ast/element.h"
 #include "explorer/ast/element_path.h"
 #include "explorer/ast/element_path.h"
+#include "explorer/ast/expression_category.h"
 #include "explorer/ast/statement.h"
 #include "explorer/ast/statement.h"
 #include "explorer/common/nonnull.h"
 #include "explorer/common/nonnull.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringMap.h"
@@ -95,6 +96,38 @@ class Value {
   const Kind kind_;
   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<const Carbon::Value*> v) -> ExpressionResult {
+    return ExpressionResult(v, std::nullopt, ExpressionCategory::Value);
+  }
+  static auto Reference(Nonnull<const Carbon::Value*> v, Address address)
+      -> ExpressionResult {
+    return ExpressionResult(v, std::move(address),
+                            ExpressionCategory::Reference);
+  }
+  static auto Initializing(Nonnull<const Carbon::Value*> v, Address address)
+      -> ExpressionResult {
+    return ExpressionResult(v, std::move(address),
+                            ExpressionCategory::Initializing);
+  }
+
+  ExpressionResult(Nonnull<const Carbon::Value*> v,
+                   std::optional<Address> address, ExpressionCategory cat)
+      : value_(v), address_(std::move(address)), expr_cat_(cat) {}
+
+  auto value() const -> Nonnull<const Carbon::Value*> { return value_; }
+  auto address() const -> const std::optional<Address>& { return address_; }
+  auto expression_category() const -> ExpressionCategory { return expr_cat_; }
+
+ private:
+  Nonnull<const Carbon::Value*> value_;
+  std::optional<Address> address_;
+  ExpressionCategory expr_cat_;
+};
+
 // Returns whether the fully-resolved kind that this value will eventually have
 // Returns whether the fully-resolved kind that this value will eventually have
 // is currently unknown, because it depends on a generic parameter.
 // is currently unknown, because it depends on a generic parameter.
 inline auto IsValueKindDependent(Nonnull<const Value*> type) -> bool {
 inline auto IsValueKindDependent(Nonnull<const Value*> type) -> bool {
@@ -631,19 +664,22 @@ class FunctionType : public Value {
 
 
   FunctionType(Nonnull<const Value*> parameters,
   FunctionType(Nonnull<const Value*> parameters,
                Nonnull<const Value*> return_type)
                Nonnull<const Value*> return_type)
-      : FunctionType(parameters, {}, return_type, {}, {}) {}
+      : FunctionType(parameters, {}, return_type, {}, {},
+                     /*is_initializing=*/false) {}
 
 
   FunctionType(Nonnull<const Value*> parameters,
   FunctionType(Nonnull<const Value*> parameters,
                std::vector<GenericParameter> generic_parameters,
                std::vector<GenericParameter> generic_parameters,
                Nonnull<const Value*> return_type,
                Nonnull<const Value*> return_type,
                std::vector<Nonnull<const GenericBinding*>> deduced_bindings,
                std::vector<Nonnull<const GenericBinding*>> deduced_bindings,
-               std::vector<Nonnull<const ImplBinding*>> impl_bindings)
+               std::vector<Nonnull<const ImplBinding*>> impl_bindings,
+               bool is_initializing)
       : Value(Kind::FunctionType),
       : Value(Kind::FunctionType),
         parameters_(parameters),
         parameters_(parameters),
         generic_parameters_(std::move(generic_parameters)),
         generic_parameters_(std::move(generic_parameters)),
         return_type_(return_type),
         return_type_(return_type),
         deduced_bindings_(std::move(deduced_bindings)),
         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 {
   static auto classof(const Value* value) -> bool {
     return value->kind() == Kind::FunctionType;
     return value->kind() == Kind::FunctionType;
@@ -652,7 +688,7 @@ class FunctionType : public Value {
   template <typename F>
   template <typename F>
   auto Decompose(F f) const {
   auto Decompose(F f) const {
     return f(parameters_, generic_parameters_, return_type_, deduced_bindings_,
     return f(parameters_, generic_parameters_, return_type_, deduced_bindings_,
-             impl_bindings_);
+             impl_bindings_, is_initializing_);
   }
   }
 
 
   // The type of the function parameter tuple.
   // The type of the function parameter tuple.
@@ -674,6 +710,8 @@ class FunctionType : public Value {
   auto impl_bindings() const -> llvm::ArrayRef<Nonnull<const ImplBinding*>> {
   auto impl_bindings() const -> llvm::ArrayRef<Nonnull<const ImplBinding*>> {
     return impl_bindings_;
     return impl_bindings_;
   }
   }
+  // Return whether the function type is an initializing expression or not.
+  auto is_initializing() const -> bool { return is_initializing_; }
 
 
  private:
  private:
   Nonnull<const Value*> parameters_;
   Nonnull<const Value*> parameters_;
@@ -681,6 +719,7 @@ class FunctionType : public Value {
   Nonnull<const Value*> return_type_;
   Nonnull<const Value*> return_type_;
   std::vector<Nonnull<const GenericBinding*>> deduced_bindings_;
   std::vector<Nonnull<const GenericBinding*>> deduced_bindings_;
   std::vector<Nonnull<const ImplBinding*>> impl_bindings_;
   std::vector<Nonnull<const ImplBinding*>> impl_bindings_;
+  bool is_initializing_;
 };
 };
 
 
 // A pointer type.
 // A pointer type.

+ 3 - 0
explorer/data/prelude.carbon

@@ -707,6 +707,9 @@ class Heap {
   fn Delete[T:! type, self: Self](p : T*) {
   fn Delete[T:! type, self: Self](p : T*) {
     __intrinsic_delete(p);
     __intrinsic_delete(p);
   }
   }
+  fn PrintAllocs[self: Self]() {
+    __intrinsic_print_allocs();
+  }
 }
 }
 
 
 var heap: Heap = {};
 var heap: Heap = {};

+ 2 - 0
explorer/interpreter/BUILD

@@ -79,6 +79,7 @@ cc_library(
     deps = [
     deps = [
         ":action",
         ":action",
         ":heap_allocation_interface",
         ":heap_allocation_interface",
+        "//common:check",
         "//common:ostream",
         "//common:ostream",
         "//explorer/ast",
         "//explorer/ast",
         "//explorer/common:error_builders",
         "//explorer/common:error_builders",
@@ -115,6 +116,7 @@ cc_library(
         "//common:error",
         "//common:error",
         "//common:ostream",
         "//common:ostream",
         "//explorer/ast",
         "//explorer/ast",
+        "//explorer/ast:expression_category",
         "//explorer/common:arena",
         "//explorer/common:arena",
         "//explorer/common:error_builders",
         "//explorer/common:error_builders",
         "//explorer/common:source_location",
         "//explorer/common:source_location",

+ 28 - 19
explorer/interpreter/action.cpp

@@ -10,8 +10,10 @@
 #include <utility>
 #include <utility>
 #include <vector>
 #include <vector>
 
 
+#include "common/check.h"
 #include "explorer/ast/declaration.h"
 #include "explorer/ast/declaration.h"
 #include "explorer/ast/expression.h"
 #include "explorer/ast/expression.h"
+#include "explorer/ast/value.h"
 #include "explorer/common/arena.h"
 #include "explorer/common/arena.h"
 #include "explorer/interpreter/stack.h"
 #include "explorer/interpreter/stack.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringExtras.h"
@@ -44,32 +46,39 @@ void RuntimeScope::Print(llvm::raw_ostream& out) const {
   out << "}";
   out << "}";
 }
 }
 
 
-void RuntimeScope::Bind(ValueNodeView value_node, Nonnull<const Value*> value) {
+void RuntimeScope::Bind(ValueNodeView value_node, Address address) {
+  CARBON_CHECK(!value_node.constant_value().has_value());
+  bool success =
+      locals_.insert({value_node, heap_->arena().New<LocationValue>(address)})
+          .second;
+  CARBON_CHECK(success) << "Duplicate definition of " << value_node.base();
+}
+
+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<const Value*> value) {
   CARBON_CHECK(!value_node.constant_value().has_value());
   CARBON_CHECK(!value_node.constant_value().has_value());
   CARBON_CHECK(value->kind() != Value::Kind::LocationValue);
   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<LocationValue>(Address(id))});
-    CARBON_CHECK(success) << "Duplicate definition of " << value_node.base();
-  } else {
-    auto [it, success] = locals_.insert(
-        {value_node,
-         heap_->arena().New<LocationValue>(Address(*allocation_id))});
-    CARBON_CHECK(success) << "Duplicate definition of " << value_node.base();
-  }
+  bool success = locals_.insert({value_node, value}).second;
+  CARBON_CHECK(success) << "Duplicate definition of " << value_node.base();
 }
 }
 
 
-void RuntimeScope::Initialize(ValueNodeView value_node,
-                              Nonnull<const Value*> value) {
+auto RuntimeScope::Initialize(ValueNodeView value_node,
+                              Nonnull<const Value*> value)
+    -> Nonnull<const LocationValue*> {
   CARBON_CHECK(!value_node.constant_value().has_value());
   CARBON_CHECK(!value_node.constant_value().has_value());
   CARBON_CHECK(value->kind() != Value::Kind::LocationValue);
   CARBON_CHECK(value->kind() != Value::Kind::LocationValue);
   allocations_.push_back(heap_->AllocateValue(value));
   allocations_.push_back(heap_->AllocateValue(value));
-  auto [it, success] = locals_.insert(
-      {value_node,
-       heap_->arena().New<LocationValue>(Address(allocations_.back()))});
+  const auto* location =
+      heap_->arena().New<LocationValue>(Address(allocations_.back()));
+  bool success = locals_.insert({value_node, location}).second;
   CARBON_CHECK(success) << "Duplicate definition of " << value_node.base();
   CARBON_CHECK(success) << "Duplicate definition of " << value_node.base();
+  return location;
 }
 }
 
 
 void RuntimeScope::Merge(RuntimeScope other) {
 void RuntimeScope::Merge(RuntimeScope other) {
@@ -85,7 +94,7 @@ void RuntimeScope::Merge(RuntimeScope other) {
 }
 }
 
 
 auto RuntimeScope::Get(ValueNodeView value_node) const
 auto RuntimeScope::Get(ValueNodeView value_node) const
-    -> std::optional<Nonnull<const LocationValue*>> {
+    -> std::optional<Nonnull<const Value*>> {
   auto it = locals_.find(value_node);
   auto it = locals_.find(value_node);
   if (it != locals_.end()) {
   if (it != locals_.end()) {
     return it->second;
     return it->second;

+ 59 - 14
explorer/interpreter/action.h

@@ -7,10 +7,13 @@
 
 
 #include <list>
 #include <list>
 #include <map>
 #include <map>
+#include <optional>
 #include <tuple>
 #include <tuple>
 #include <vector>
 #include <vector>
 
 
+#include "common/check.h"
 #include "common/ostream.h"
 #include "common/ostream.h"
+#include "explorer/ast/address.h"
 #include "explorer/ast/expression.h"
 #include "explorer/ast/expression.h"
 #include "explorer/ast/pattern.h"
 #include "explorer/ast/pattern.h"
 #include "explorer/ast/statement.h"
 #include "explorer/ast/statement.h"
@@ -45,30 +48,42 @@ class RuntimeScope {
   void Print(llvm::raw_ostream& out) const;
   void Print(llvm::raw_ostream& out) const;
   LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
   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
   // Allocates storage for `value_node` in `heap`, and initializes it with
   // `value`.
   // `value`.
-  // TODO: Update existing callers to use Bind instead, where appropriate.
-  void Initialize(ValueNodeView value_node, Nonnull<const Value*> value);
+  auto Initialize(ValueNodeView value_node, Nonnull<const Value*> value)
+      -> Nonnull<const LocationValue*>;
+
+  // 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<const Value*> value);
 
 
   // Transfers the names and allocations from `other` into *this. The two
   // 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.
   // scopes must not define the same name, and must be backed by the same Heap.
   void Merge(RuntimeScope other);
   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
   auto Get(ValueNodeView value_node) const
-      -> std::optional<Nonnull<const LocationValue*>>;
+      -> std::optional<Nonnull<const Value*>>;
 
 
-  // Returns the local values in created order
+  // Returns the local values with allocation in created order.
   auto allocations() const -> const std::vector<AllocationId>& {
   auto allocations() const -> const std::vector<AllocationId>& {
     return allocations_;
     return allocations_;
   }
   }
 
 
  private:
  private:
-  llvm::MapVector<ValueNodeView, Nonnull<const LocationValue*>,
+  llvm::MapVector<ValueNodeView, Nonnull<const Value*>,
                   std::map<ValueNodeView, unsigned>>
                   std::map<ValueNodeView, unsigned>>
       locals_;
       locals_;
   std::vector<AllocationId> allocations_;
   std::vector<AllocationId> allocations_;
@@ -184,8 +199,12 @@ class LocationAction : public Action {
 // An Action which implements evaluation of an Expression to produce a `Value*`.
 // An Action which implements evaluation of an Expression to produce a `Value*`.
 class ExpressionAction : public Action {
 class ExpressionAction : public Action {
  public:
  public:
-  explicit ExpressionAction(Nonnull<const Expression*> expression)
-      : Action(Kind::ExpressionAction), expression_(expression) {}
+  explicit ExpressionAction(
+      Nonnull<const Expression*> expression,
+      std::optional<AllocationId> initialized_location = std::nullopt)
+      : Action(Kind::ExpressionAction),
+        expression_(expression),
+        location_received_(initialized_location) {}
 
 
   static auto classof(const Action* action) -> bool {
   static auto classof(const Action* action) -> bool {
     return action->kind() == Kind::ExpressionAction;
     return action->kind() == Kind::ExpressionAction;
@@ -194,8 +213,14 @@ class ExpressionAction : public Action {
   // The Expression this Action evaluates.
   // The Expression this Action evaluates.
   auto expression() const -> const Expression& { return *expression_; }
   auto expression() const -> const Expression& { return *expression_; }
 
 
+  // The location provided for the initializing expression, if any.
+  auto location_received() const -> std::optional<AllocationId> {
+    return location_received_;
+  }
+
  private:
  private:
   Nonnull<const Expression*> expression_;
   Nonnull<const Expression*> expression_;
+  std::optional<AllocationId> location_received_;
 };
 };
 
 
 // An Action which implements the Instantiation of Type. The result is expressed
 // An Action which implements the Instantiation of Type. The result is expressed
@@ -242,8 +267,11 @@ class WitnessAction : public Action {
 // result.
 // result.
 class StatementAction : public Action {
 class StatementAction : public Action {
  public:
  public:
-  explicit StatementAction(Nonnull<const Statement*> statement)
-      : Action(Kind::StatementAction), statement_(statement) {}
+  explicit StatementAction(Nonnull<const Statement*> statement,
+                           std::optional<AllocationId> location_received)
+      : Action(Kind::StatementAction),
+        statement_(statement),
+        location_received_(location_received) {}
 
 
   static auto classof(const Action* action) -> bool {
   static auto classof(const Action* action) -> bool {
     return action->kind() == Kind::StatementAction;
     return action->kind() == Kind::StatementAction;
@@ -252,8 +280,25 @@ class StatementAction : public Action {
   // The Statement this Action executes.
   // The Statement this Action executes.
   auto statement() const -> const Statement& { return *statement_; }
   auto statement() const -> const Statement& { return *statement_; }
 
 
+  // The location provided for the initializing expression, if any.
+  auto location_received() const -> std::optional<AllocationId> {
+    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<AllocationId> {
+    return location_created_;
+  }
+
  private:
  private:
   Nonnull<const Statement*> statement_;
   Nonnull<const Statement*> statement_;
+  std::optional<AllocationId> location_received_;
+  std::optional<AllocationId> location_created_;
 };
 };
 
 
 // Action which implements the run-time effects of executing a Declaration.
 // Action which implements the run-time effects of executing a Declaration.

+ 17 - 14
explorer/interpreter/heap.cpp

@@ -4,6 +4,7 @@
 
 
 #include "explorer/interpreter/heap.h"
 #include "explorer/interpreter/heap.h"
 
 
+#include "common/check.h"
 #include "explorer/ast/value.h"
 #include "explorer/ast/value.h"
 #include "explorer/common/error_builders.h"
 #include "explorer/common/error_builders.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringExtras.h"
@@ -51,23 +52,12 @@ auto Heap::Write(const Address& a, Nonnull<const Value*> v,
   return Success();
   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
 auto Heap::CheckAlive(AllocationId allocation, SourceLocation source_loc) const
     -> ErrorOr<Success> {
     -> ErrorOr<Success> {
-  if (states_[allocation.index_] == ValueState::Dead) {
+  if (states_[allocation.index_] == ValueState::Dead ||
+      states_[allocation.index_] == ValueState::Discarded) {
     return ProgramError(source_loc)
     return ProgramError(source_loc)
-           << "undefined behavior: access to dead value "
+           << "undefined behavior: access to dead or discarded value "
            << *values_[allocation.index_];
            << *values_[allocation.index_];
   }
   }
   return Success();
   return Success();
@@ -94,6 +84,19 @@ void Heap::Deallocate(AllocationId allocation) {
 
 
 void Heap::Deallocate(const Address& a) { Deallocate(a.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 {
 void Heap::Print(llvm::raw_ostream& out) const {
   llvm::ListSeparator sep;
   llvm::ListSeparator sep;
   for (size_t i = 0; i < values_.size(); ++i) {
   for (size_t i = 0; i < values_.size(); ++i) {

+ 8 - 3
explorer/interpreter/heap.h

@@ -21,6 +21,7 @@ class Heap : public HeapAllocationInterface {
  public:
  public:
   enum class ValueState {
   enum class ValueState {
     Uninitialized,
     Uninitialized,
+    Discarded,
     Alive,
     Alive,
     Dead,
     Dead,
   };
   };
@@ -41,9 +42,6 @@ class Heap : public HeapAllocationInterface {
   auto Write(const Address& a, Nonnull<const Value*> v,
   auto Write(const Address& a, Nonnull<const Value*> v,
              SourceLocation source_loc) -> ErrorOr<Success>;
              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.
   // Put the given value on the heap and mark its state.
   // Mark UninitializedValue as uninitialized and other values as alive.
   // Mark UninitializedValue as uninitialized and other values as alive.
   auto AllocateValue(Nonnull<const Value*> v) -> AllocationId override;
   auto AllocateValue(Nonnull<const Value*> v) -> AllocationId override;
@@ -52,6 +50,13 @@ class Heap : public HeapAllocationInterface {
   void Deallocate(AllocationId allocation) override;
   void Deallocate(AllocationId allocation) override;
   void Deallocate(const Address& a);
   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`.
   // Print all the values on the heap to the stream `out`.
   void Print(llvm::raw_ostream& out) const;
   void Print(llvm::raw_ostream& out) const;
 
 

+ 0 - 5
explorer/interpreter/heap_allocation_interface.h

@@ -30,11 +30,6 @@ class HeapAllocationInterface {
   // Returns the arena used to allocate the values in this heap.
   // Returns the arena used to allocate the values in this heap.
   virtual auto arena() const -> Arena& = 0;
   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:
  protected:
   HeapAllocationInterface() = default;
   HeapAllocationInterface() = default;
   virtual ~HeapAllocationInterface() = default;
   virtual ~HeapAllocationInterface() = default;

+ 229 - 88
explorer/interpreter/interpreter.cpp

@@ -21,6 +21,7 @@
 #include "explorer/ast/declaration.h"
 #include "explorer/ast/declaration.h"
 #include "explorer/ast/element.h"
 #include "explorer/ast/element.h"
 #include "explorer/ast/expression.h"
 #include "explorer/ast/expression.h"
+#include "explorer/ast/expression_category.h"
 #include "explorer/ast/value.h"
 #include "explorer/ast/value.h"
 #include "explorer/common/arena.h"
 #include "explorer/common/arena.h"
 #include "explorer/common/error_builders.h"
 #include "explorer/common/error_builders.h"
@@ -169,7 +170,8 @@ class Interpreter {
   // Call the function `fun` with the given `arg` and the `witnesses`
   // Call the function `fun` with the given `arg` and the `witnesses`
   // for the function's impl bindings.
   // for the function's impl bindings.
   auto CallFunction(const CallExpression& call, Nonnull<const Value*> fun,
   auto CallFunction(const CallExpression& call, Nonnull<const Value*> fun,
-                    Nonnull<const Value*> arg, ImplWitnessMap&& witnesses)
+                    Nonnull<const Value*> arg, ImplWitnessMap&& witnesses,
+                    std::optional<AllocationId> location_received)
       -> ErrorOr<Success>;
       -> ErrorOr<Success>;
 
 
   auto CallDestructor(Nonnull<const DestructorDeclaration*> fun,
   auto CallDestructor(Nonnull<const DestructorDeclaration*> fun,
@@ -291,46 +293,90 @@ auto Interpreter::CreateStruct(const std::vector<FieldInitializer>& fields,
   return arena_->New<StructValue>(std::move(elements));
   return arena_->New<StructValue>(std::move(elements));
 }
 }
 
 
-auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
+static auto InitializePlaceholderValue(
+    const ValueNodeView& value_node, ExpressionResult v,
+    std::optional<Nonnull<RuntimeScope*>> 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<const Value*> p, ExpressionResult v,
                   SourceLocation source_loc,
                   SourceLocation source_loc,
                   std::optional<Nonnull<RuntimeScope*>> bindings,
                   std::optional<Nonnull<RuntimeScope*>> bindings,
                   BindingMap& generic_args, Nonnull<TraceStream*> trace_stream,
                   BindingMap& generic_args, Nonnull<TraceStream*> trace_stream,
                   Nonnull<Arena*> arena) -> bool {
                   Nonnull<Arena*> arena) -> bool {
   if (trace_stream->is_enabled()) {
   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()) {
   switch (p->kind()) {
     case Value::Kind::BindingPlaceholderValue: {
     case Value::Kind::BindingPlaceholderValue: {
       CARBON_CHECK(bindings.has_value());
       CARBON_CHECK(bindings.has_value());
       const auto& placeholder = cast<BindingPlaceholderValue>(*p);
       const auto& placeholder = cast<BindingPlaceholderValue>(*p);
       if (placeholder.value_node().has_value()) {
       if (placeholder.value_node().has_value()) {
-        (*bindings)->Initialize(*placeholder.value_node(), v);
+        InitializePlaceholderValue(*placeholder.value_node(), v, bindings);
       }
       }
       return true;
       return true;
     }
     }
     case Value::Kind::AddrValue: {
     case Value::Kind::AddrValue: {
       const auto& addr = cast<AddrValue>(*p);
       const auto& addr = cast<AddrValue>(*p);
-      CARBON_CHECK(v->kind() == Value::Kind::LocationValue);
-      const auto& location = cast<LocationValue>(*v);
+      CARBON_CHECK(v.value()->kind() == Value::Kind::LocationValue);
+      const auto& location = cast<LocationValue>(*v.value());
       return PatternMatch(
       return PatternMatch(
-          &addr.pattern(), arena->New<PointerValue>(location.address()),
+          &addr.pattern(),
+          ExpressionResult::Value(arena->New<PointerValue>(location.address())),
           source_loc, bindings, generic_args, trace_stream, arena);
           source_loc, bindings, generic_args, trace_stream, arena);
     }
     }
     case Value::Kind::VariableType: {
     case Value::Kind::VariableType: {
       const auto& var_type = cast<VariableType>(*p);
       const auto& var_type = cast<VariableType>(*p);
-      generic_args[&var_type.binding()] = v;
+      generic_args[&var_type.binding()] = v.value();
       return true;
       return true;
     }
     }
     case Value::Kind::TupleType:
     case Value::Kind::TupleType:
     case Value::Kind::TupleValue:
     case Value::Kind::TupleValue:
-      switch (v->kind()) {
+      switch (v.value()->kind()) {
         case Value::Kind::TupleType:
         case Value::Kind::TupleType:
         case Value::Kind::TupleValue: {
         case Value::Kind::TupleValue: {
           const auto& p_tup = cast<TupleValueBase>(*p);
           const auto& p_tup = cast<TupleValueBase>(*p);
-          const auto& v_tup = cast<TupleValueBase>(*v);
+          const auto& v_tup = cast<TupleValueBase>(*v.value());
           CARBON_CHECK(p_tup.elements().size() == v_tup.elements().size());
           CARBON_CHECK(p_tup.elements().size() == v_tup.elements().size());
           for (size_t i = 0; i < p_tup.elements().size(); ++i) {
           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,
                               source_loc, bindings, generic_args, trace_stream,
                               arena)) {
                               arena)) {
               return false;
               return false;
@@ -341,7 +387,9 @@ auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
         case Value::Kind::UninitializedValue: {
         case Value::Kind::UninitializedValue: {
           const auto& p_tup = cast<TupleValueBase>(*p);
           const auto& p_tup = cast<TupleValueBase>(*p);
           for (const auto& ele : p_tup.elements()) {
           for (const auto& ele : p_tup.elements()) {
-            if (!PatternMatch(ele, arena->New<UninitializedValue>(ele),
+            if (!PatternMatch(ele,
+                              ExpressionResult::Value(
+                                  arena->New<UninitializedValue>(ele)),
                               source_loc, bindings, generic_args, trace_stream,
                               source_loc, bindings, generic_args, trace_stream,
                               arena)) {
                               arena)) {
               return false;
               return false;
@@ -350,28 +398,30 @@ auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
           return true;
           return true;
         }
         }
         default:
         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: {
     case Value::Kind::StructValue: {
       const auto& p_struct = cast<StructValue>(*p);
       const auto& p_struct = cast<StructValue>(*p);
-      const auto& v_struct = cast<StructValue>(*v);
+      const auto& v_struct = cast<StructValue>(*v.value());
       CARBON_CHECK(p_struct.elements().size() == v_struct.elements().size());
       CARBON_CHECK(p_struct.elements().size() == v_struct.elements().size());
       for (size_t i = 0; i < p_struct.elements().size(); ++i) {
       for (size_t i = 0; i < p_struct.elements().size(); ++i) {
         CARBON_CHECK(p_struct.elements()[i].name ==
         CARBON_CHECK(p_struct.elements()[i].name ==
                      v_struct.elements()[i].name);
                      v_struct.elements()[i].name);
         if (!PatternMatch(p_struct.elements()[i].value,
         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 false;
         }
         }
       }
       }
       return true;
       return true;
     }
     }
     case Value::Kind::AlternativeValue:
     case Value::Kind::AlternativeValue:
-      switch (v->kind()) {
+      switch (v.value()->kind()) {
         case Value::Kind::AlternativeValue: {
         case Value::Kind::AlternativeValue: {
           const auto& p_alt = cast<AlternativeValue>(*p);
           const auto& p_alt = cast<AlternativeValue>(*p);
-          const auto& v_alt = cast<AlternativeValue>(*v);
+          const auto& v_alt = cast<AlternativeValue>(*v.value());
           if (&p_alt.alternative() != &v_alt.alternative()) {
           if (&p_alt.alternative() != &v_alt.alternative()) {
             return false;
             return false;
           }
           }
@@ -380,25 +430,30 @@ auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
           if (!p_alt.argument().has_value()) {
           if (!p_alt.argument().has_value()) {
             return true;
             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:
         default:
           CARBON_FATAL() << "expected a choice alternative in pattern, not "
           CARBON_FATAL() << "expected a choice alternative in pattern, not "
-                         << *v;
+                         << *v.value();
       }
       }
     case Value::Kind::UninitializedValue:
     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:
     case Value::Kind::FunctionType:
-      switch (v->kind()) {
+      switch (v.value()->kind()) {
         case Value::Kind::FunctionType: {
         case Value::Kind::FunctionType: {
           const auto& p_fn = cast<FunctionType>(*p);
           const auto& p_fn = cast<FunctionType>(*p);
-          const auto& v_fn = cast<FunctionType>(*v);
-          if (!PatternMatch(&p_fn.parameters(), &v_fn.parameters(), source_loc,
-                            bindings, generic_args, trace_stream, arena)) {
+          const auto& v_fn = cast<FunctionType>(*v.value());
+          if (!PatternMatch(&p_fn.parameters(),
+                            ExpressionResult::Value(&v_fn.parameters()),
+                            source_loc, bindings, generic_args, trace_stream,
+                            arena)) {
             return false;
             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,
                             source_loc, bindings, generic_args, trace_stream,
                             arena)) {
                             arena)) {
             return false;
             return false;
@@ -410,16 +465,16 @@ auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
       }
       }
     case Value::Kind::AutoType:
     case Value::Kind::AutoType:
       // `auto` matches any type, without binding any new names. We rely
       // `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;
       return true;
     case Value::Kind::StaticArrayType: {
     case Value::Kind::StaticArrayType: {
-      switch (v->kind()) {
+      switch (v.value()->kind()) {
         case Value::Kind::TupleType:
         case Value::Kind::TupleType:
         case Value::Kind::TupleValue: {
         case Value::Kind::TupleValue: {
           return true;
           return true;
         }
         }
         case Value::Kind::StaticArrayType: {
         case Value::Kind::StaticArrayType: {
-          const auto& v_arr = cast<StaticArrayType>(*v);
+          const auto& v_arr = cast<StaticArrayType>(*v.value());
           return v_arr.has_size();
           return v_arr.has_size();
         }
         }
         default:
         default:
@@ -427,7 +482,7 @@ auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
       }
       }
     }
     }
     default:
     default:
-      return ValueEqual(p, v, std::nullopt);
+      return ValueEqual(p, v.value(), std::nullopt);
   }
   }
 }
 }
 
 
@@ -993,13 +1048,18 @@ auto Interpreter::CallDestructor(Nonnull<const DestructorDeclaration*> fun,
     return ProgramError(fun->source_loc())
     return ProgramError(fun->source_loc())
            << "destructors currently don't support `addr self` bindings";
            << "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())
   CARBON_CHECK(method.body().has_value())
       << "Calling a method that's missing a body";
       << "Calling a method that's missing a body";
 
 
-  auto act = std::make_unique<StatementAction>(*method.body());
+  auto act = std::make_unique<StatementAction>(*method.body(), std::nullopt);
   return todo_.Spawn(std::unique_ptr<Action>(std::move(act)),
   return todo_.Spawn(std::unique_ptr<Action>(std::move(act)),
                      std::move(method_scope));
                      std::move(method_scope));
 }
 }
@@ -1007,7 +1067,9 @@ auto Interpreter::CallDestructor(Nonnull<const DestructorDeclaration*> fun,
 auto Interpreter::CallFunction(const CallExpression& call,
 auto Interpreter::CallFunction(const CallExpression& call,
                                Nonnull<const Value*> fun,
                                Nonnull<const Value*> fun,
                                Nonnull<const Value*> arg,
                                Nonnull<const Value*> arg,
-                               ImplWitnessMap&& witnesses) -> ErrorOr<Success> {
+                               ImplWitnessMap&& witnesses,
+                               std::optional<AllocationId> location_received)
+    -> ErrorOr<Success> {
   if (trace_stream_->is_enabled()) {
   if (trace_stream_->is_enabled()) {
     *trace_stream_ << "calling function: " << *fun << "\n";
     *trace_stream_ << "calling function: " << *fun << "\n";
   }
   }
@@ -1039,31 +1101,26 @@ auto Interpreter::CallFunction(const CallExpression& call,
       for (const auto& [bind, val] : call.deduced_args()) {
       for (const auto& [bind, val] : call.deduced_args()) {
         CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> inst_val,
         CARBON_ASSIGN_OR_RETURN(Nonnull<const Value*> inst_val,
                                 InstantiateType(val, call.source_loc()));
                                 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) {
       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
       // Bring the arguments that are determined by the function value into
       // scope. This includes the arguments for the class of which the function
       // scope. This includes the arguments for the class of which the function
       // is a member.
       // is a member.
       for (const auto& [bind, val] : func_val->type_args()) {
       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()) {
       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
       // Enter the binding scope to make any deduced arguments visible before
       // we resolve the self type and parameter type.
       // we resolve the self type and parameter type.
       todo_.CurrentAction().StartScope(std::move(binding_scope));
       todo_.CurrentAction().StartScope(std::move(binding_scope));
 
 
-      CARBON_ASSIGN_OR_RETURN(
-          Nonnull<const Value*> converted_args,
-          Convert(arg, &function.param_pattern().static_type(),
-                  call.source_loc()));
-
       RuntimeScope function_scope(&heap_);
       RuntimeScope function_scope(&heap_);
       BindingMap generic_args;
       BindingMap generic_args;
 
 
@@ -1073,23 +1130,36 @@ auto Interpreter::CallFunction(const CallExpression& call,
         const auto* self_pattern = &function.self_pattern().value();
         const auto* self_pattern = &function.self_pattern().value();
         if (const auto* placeholder =
         if (const auto* placeholder =
                 dyn_cast<BindingPlaceholderValue>(self_pattern)) {
                 dyn_cast<BindingPlaceholderValue>(self_pattern)) {
+          // Immutable self with `[self: Self]`
           // TODO: move this logic into PatternMatch
           // TODO: move this logic into PatternMatch
           if (placeholder->value_node().has_value()) {
           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 {
         } 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<AddrValue>(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<const Value*> converted_args,
+          Convert(arg, &function.param_pattern().static_type(),
+                  call.source_loc()));
+
       // Bind the arguments to the parameters.
       // 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<StatementAction>(*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<StatementAction>(*function.body(),
+                                                           location_received),
                          std::move(function_scope));
                          std::move(function_scope));
     }
     }
     case Value::Kind::ParameterizedEntityName: {
     case Value::Kind::ParameterizedEntityName: {
@@ -1097,7 +1167,8 @@ auto Interpreter::CallFunction(const CallExpression& call,
       const Declaration& decl = name.declaration();
       const Declaration& decl = name.declaration();
       RuntimeScope params_scope(&heap_);
       RuntimeScope params_scope(&heap_);
       BindingMap generic_args;
       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(),
                                 &params_scope, generic_args, trace_stream_,
                                 &params_scope, generic_args, trace_stream_,
                                 this->arena_));
                                 this->arena_));
       Nonnull<const Bindings*> bindings =
       Nonnull<const Bindings*> bindings =
@@ -1227,8 +1298,8 @@ auto Interpreter::StepInstantiateType() -> ErrorOr<Success> {
 }
 }
 
 
 auto Interpreter::StepExp() -> ErrorOr<Success> {
 auto Interpreter::StepExp() -> ErrorOr<Success> {
-  Action& act = todo_.CurrentAction();
-  const Expression& exp = cast<ExpressionAction>(act).expression();
+  auto& act = cast<ExpressionAction>(todo_.CurrentAction());
+  const Expression& exp = act.expression();
   if (trace_stream_->is_enabled()) {
   if (trace_stream_->is_enabled()) {
     *trace_stream_ << "--- step exp " << exp << " ." << act.pos() << "."
     *trace_stream_ << "--- step exp " << exp << " ." << act.pos() << "."
                    << " (" << exp.source_loc() << ") --->\n";
                    << " (" << exp.source_loc() << ") --->\n";
@@ -1587,7 +1658,7 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
           }
           }
         }
         }
         return CallFunction(call, act.results()[0], act.results()[1],
         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<int>(num_witnesses)) {
       } else if (act.pos() == 3 + static_cast<int>(num_witnesses)) {
         if (act.results().size() < 3 + num_witnesses) {
         if (act.results().size() < 3 + num_witnesses) {
           // Control fell through without explicit return.
           // Control fell through without explicit return.
@@ -1702,6 +1773,12 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
             }
             }
           }
           }
         }
         }
+        case IntrinsicExpression::Intrinsic::PrintAllocs: {
+          CARBON_CHECK(args.empty());
+          heap_.Print(*print_stream_);
+          *print_stream_ << "\n";
+          return todo_.FinishAction(TupleValue::Empty());
+        }
         case IntrinsicExpression::Intrinsic::Rand: {
         case IntrinsicExpression::Intrinsic::Rand: {
           CARBON_CHECK(args.size() == 2);
           CARBON_CHECK(args.size() == 2);
           const int64_t low = cast<IntValue>(*args[0]).value();
           const int64_t low = cast<IntValue>(*args[0]).value();
@@ -1996,8 +2073,8 @@ auto Interpreter::StepWitness() -> ErrorOr<Success> {
 }
 }
 
 
 auto Interpreter::StepStmt() -> ErrorOr<Success> {
 auto Interpreter::StepStmt() -> ErrorOr<Success> {
-  Action& act = todo_.CurrentAction();
-  const Statement& stmt = cast<StatementAction>(act).statement();
+  auto& act = cast<StatementAction>(todo_.CurrentAction());
+  const Statement& stmt = act.statement();
   if (trace_stream_->is_enabled()) {
   if (trace_stream_->is_enabled()) {
     *trace_stream_ << "--- step stmt ";
     *trace_stream_ << "--- step stmt ";
     stmt.PrintDepth(1, trace_stream_->stream());
     stmt.PrintDepth(1, trace_stream_->stream());
@@ -2025,12 +2102,14 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
             Nonnull<const Value*> val,
             Nonnull<const Value*> val,
             Convert(act.results()[0], &c.pattern().static_type(),
             Convert(act.results()[0], &c.pattern().static_type(),
                     stmt.source_loc()));
                     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.
           // Ensure we don't process any more clauses.
           act.set_pos(match_stmt.clauses().size() + 1);
           act.set_pos(match_stmt.clauses().size() + 1);
           todo_.MergeScope(std::move(matches));
           todo_.MergeScope(std::move(matches));
-          return todo_.Spawn(std::make_unique<StatementAction>(&c.statement()));
+          return todo_.Spawn(
+              std::make_unique<StatementAction>(&c.statement(), std::nullopt));
         } else {
         } else {
           return todo_.RunAgain();
           return todo_.RunAgain();
         }
         }
@@ -2061,8 +2140,8 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
                          source_array->elements()[start_index]);
                          source_array->elements()[start_index]);
         act.ReplaceResult(CurrentIndexPosInResult,
         act.ReplaceResult(CurrentIndexPosInResult,
                           arena_->New<IntValue>(start_index + 1));
                           arena_->New<IntValue>(start_index + 1));
-        return todo_.Spawn(
-            std::make_unique<StatementAction>(&cast<For>(stmt).body()));
+        return todo_.Spawn(std::make_unique<StatementAction>(
+            &cast<For>(stmt).body(), std::nullopt));
       }
       }
       if (act.pos() >= 2) {
       if (act.pos() >= 2) {
         auto current_index =
         auto current_index =
@@ -2085,8 +2164,8 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
 
 
           act.ReplaceResult(CurrentIndexPosInResult,
           act.ReplaceResult(CurrentIndexPosInResult,
                             arena_->New<IntValue>(current_index + 1));
                             arena_->New<IntValue>(current_index + 1));
-          return todo_.Spawn(
-              std::make_unique<StatementAction>(&cast<For>(stmt).body()));
+          return todo_.Spawn(std::make_unique<StatementAction>(
+              &cast<For>(stmt).body(), std::nullopt));
         }
         }
       }
       }
       return todo_.FinishAction();
       return todo_.FinishAction();
@@ -2109,8 +2188,8 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
         if (cast<BoolValue>(*condition).value()) {
         if (cast<BoolValue>(*condition).value()) {
           //    { {true :: (while ([]) s) :: C, E, F} :: S, H}
           //    { {true :: (while ([]) s) :: C, E, F} :: S, H}
           // -> { { s :: (while (e) s) :: C, E, F } :: S, H}
           // -> { { s :: (while (e) s) :: C, E, F } :: S, H}
-          return todo_.Spawn(
-              std::make_unique<StatementAction>(&cast<While>(stmt).body()));
+          return todo_.Spawn(std::make_unique<StatementAction>(
+              &cast<While>(stmt).body(), std::nullopt));
         } else {
         } else {
           //    { {false :: (while ([]) s) :: C, E, F} :: S, H}
           //    { {false :: (while ([]) s) :: C, E, F} :: S, H}
           // -> { { C, E, F } :: S, H}
           // -> { { C, E, F } :: S, H}
@@ -2142,37 +2221,90 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
       }
       }
       // Process the next statement in the block. The position will be
       // Process the next statement in the block. The position will be
       // incremented as part of Spawn.
       // incremented as part of Spawn.
-      return todo_.Spawn(
-          std::make_unique<StatementAction>(block.statements()[act.pos()]));
+      return todo_.Spawn(std::make_unique<StatementAction>(
+          block.statements()[act.pos()], act.location_received()));
     }
     }
     case StatementKind::VariableDefinition: {
     case StatementKind::VariableDefinition: {
       const auto& definition = cast<VariableDefinition>(stmt);
       const auto& definition = cast<VariableDefinition>(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()) {
       if (act.pos() == 0 && definition.has_init()) {
         //    { {(var x = e) :: C, E, F} :: S, H}
         //    { {(var x = e) :: C, E, F} :: S, H}
         // -> { {e :: (var x = []) :: C, E, F} :: S, H}
         // -> { {e :: (var x = []) :: C, E, F} :: S, H}
-        return todo_.Spawn(
-            std::make_unique<ExpressionAction>(&definition.init()));
+        if (has_initializing_expr && !init_location) {
+          // Allocate storage for initializing expression.
+          const auto allocation_id =
+              heap_.AllocateValue(arena_->New<UninitializedValue>(
+                  &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<ExpressionAction>(
+            &definition.init(), init_location));
       } else {
       } else {
         //    { { v :: (x = []) :: C, E, F} :: S, H}
         //    { { v :: (x = []) :: C, E, F} :: S, H}
         // -> { { C, E(x := a), F} :: S, H(a := copy(v))}
         // -> { { C, E(x := a), F} :: S, H(a := copy(v))}
-        Nonnull<const Value*> p =
-            &cast<VariableDefinition>(stmt).pattern().value();
+        Nonnull<const Value*> p = &definition.pattern().value();
         Nonnull<const Value*> v;
         Nonnull<const Value*> v;
+        std::optional<Address> v_location;
+        ExpressionCategory expr_category =
+            definition.has_init() ? definition.init().expression_category()
+                                  : ExpressionCategory::Value;
         if (definition.has_init()) {
         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 {
         } else {
           v = arena_->New<UninitializedValue>(p);
           v = arena_->New<UninitializedValue>(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<BindingPlaceholderValue>(*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();
         return todo_.FinishAction();
       }
       }
     }
     }
@@ -2239,14 +2371,14 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
           //    { {true :: if ([]) then_stmt else else_stmt :: C, E, F} ::
           //    { {true :: if ([]) then_stmt else else_stmt :: C, E, F} ::
           //      S, H}
           //      S, H}
           // -> { { then_stmt :: C, E, F } :: S, H}
           // -> { { then_stmt :: C, E, F } :: S, H}
-          return todo_.Spawn(
-              std::make_unique<StatementAction>(&cast<If>(stmt).then_block()));
+          return todo_.Spawn(std::make_unique<StatementAction>(
+              &cast<If>(stmt).then_block(), std::nullopt));
         } else if (cast<If>(stmt).else_block()) {
         } else if (cast<If>(stmt).else_block()) {
           //    { {false :: if ([]) then_stmt else else_stmt :: C, E, F} ::
           //    { {false :: if ([]) then_stmt else else_stmt :: C, E, F} ::
           //      S, H}
           //      S, H}
           // -> { { else_stmt :: C, E, F } :: S, H}
           // -> { { else_stmt :: C, E, F } :: S, H}
-          return todo_.Spawn(
-              std::make_unique<StatementAction>(*cast<If>(stmt).else_block()));
+          return todo_.Spawn(std::make_unique<StatementAction>(
+              *cast<If>(stmt).else_block(), std::nullopt));
         } else {
         } else {
           return todo_.FinishAction();
           return todo_.FinishAction();
         }
         }
@@ -2289,6 +2421,11 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
             Nonnull<const Value*> return_value,
             Nonnull<const Value*> return_value,
             Convert(act.results()[0], &function.return_term().static_type(),
             Convert(act.results()[0], &function.return_term().static_type(),
                     stmt.source_loc()));
                     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);
         return todo_.UnwindPast(*function.body(), return_value);
       }
       }
   }
   }
@@ -2433,6 +2570,10 @@ auto Interpreter::StepCleanUp() -> ErrorOr<Success> {
   if (act.pos() < cleanup.allocations_count() * 2) {
   if (act.pos() < cleanup.allocations_count() * 2) {
     const size_t alloc_index = cleanup.allocations_count() - act.pos() / 2 - 1;
     const size_t alloc_index = cleanup.allocations_count() - act.pos() / 2 - 1;
     auto allocation = act.scope()->allocations()[alloc_index];
     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) {
     if (act.pos() % 2 == 0) {
       auto* location = arena_->New<LocationValue>(Address(allocation));
       auto* location = arena_->New<LocationValue>(Address(allocation));
       auto value =
       auto value =

+ 6 - 4
explorer/interpreter/interpreter.h

@@ -45,10 +45,12 @@ auto InterpExp(Nonnull<const Expression*> e, Nonnull<Arena*> arena,
 // The matches for generic variables in the pattern are output in
 // The matches for generic variables in the pattern are output in
 // `generic_args`.
 // `generic_args`.
 // TODO: consider moving this to a separate header.
 // TODO: consider moving this to a separate header.
-[[nodiscard]] auto PatternMatch(
-    Nonnull<const Value*> p, Nonnull<const Value*> v, SourceLocation source_loc,
-    std::optional<Nonnull<RuntimeScope*>> bindings, BindingMap& generic_args,
-    Nonnull<TraceStream*> trace_stream, Nonnull<Arena*> arena) -> bool;
+[[nodiscard]] auto PatternMatch(Nonnull<const Value*> p, ExpressionResult v,
+                                SourceLocation source_loc,
+                                std::optional<Nonnull<RuntimeScope*>> bindings,
+                                BindingMap& generic_args,
+                                Nonnull<TraceStream*> trace_stream,
+                                Nonnull<Arena*> arena) -> bool;
 
 
 }  // namespace Carbon
 }  // namespace Carbon
 
 

+ 23 - 9
explorer/interpreter/type_checker.cpp

@@ -21,6 +21,7 @@
 #include "common/ostream.h"
 #include "common/ostream.h"
 #include "explorer/ast/declaration.h"
 #include "explorer/ast/declaration.h"
 #include "explorer/ast/expression.h"
 #include "explorer/ast/expression.h"
+#include "explorer/ast/pattern.h"
 #include "explorer/ast/value.h"
 #include "explorer/ast/value.h"
 #include "explorer/ast/value_transform.h"
 #include "explorer/ast/value_transform.h"
 #include "explorer/common/arena.h"
 #include "explorer/common/arena.h"
@@ -1116,9 +1117,9 @@ auto TypeChecker::GetBuiltinInterfaceType(SourceLocation source_loc,
   BindingMap binding_args;
   BindingMap binding_args;
   if (has_arguments) {
   if (has_arguments) {
     TupleValue args(interface.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();
       return bad_builtin();
     }
     }
   }
   }
@@ -2431,7 +2432,8 @@ class TypeChecker::SubstituteTransform
                                                  &fn_type->return_type()));
                                                  &fn_type->return_type()));
     return type_checker_->arena_->New<FunctionType>(
     return type_checker_->arena_->New<FunctionType>(
         param, std::move(generic_parameters), ret, std::move(deduced_bindings),
         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
   // Substituting into a `ConstraintType` needs special handling if we replace
@@ -3161,6 +3163,7 @@ auto TypeChecker::TypeCheckExpImpl(Nonnull<Expression*> e,
           access.set_found_in_interface(result.interface);
           access.set_found_in_interface(result.interface);
           access.set_is_type_access(!IsInstanceMember(&access.member()));
           access.set_is_type_access(!IsInstanceMember(&access.member()));
           access.set_static_type(inst_member_type);
           access.set_static_type(inst_member_type);
+          access.set_expression_category(ExpressionCategory::Value);
 
 
           if (const auto* func_decl =
           if (const auto* func_decl =
                   dyn_cast<FunctionDeclaration>(result.member)) {
                   dyn_cast<FunctionDeclaration>(result.member)) {
@@ -3774,7 +3777,9 @@ auto TypeChecker::TypeCheckExpImpl(Nonnull<Expression*> e,
               Nonnull<const Value*> return_type,
               Nonnull<const Value*> return_type,
               Substitute(call.bindings(), &fun_t.return_type()));
               Substitute(call.bindings(), &fun_t.return_type()));
           call.set_static_type(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();
           return Success();
         }
         }
         case Value::Kind::TypeOfParameterizedEntityName: {
         case Value::Kind::TypeOfParameterizedEntityName: {
@@ -3912,6 +3917,15 @@ auto TypeChecker::TypeCheckExpImpl(Nonnull<Expression*> e,
           e->set_expression_category(ExpressionCategory::Value);
           e->set_expression_category(ExpressionCategory::Value);
           return Success();
           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: {
         case IntrinsicExpression::Intrinsic::Rand: {
           if (args.size() != 2) {
           if (args.size() != 2) {
             return ProgramError(e->source_loc())
             return ProgramError(e->source_loc())
@@ -4467,9 +4481,9 @@ auto TypeChecker::TypeCheckPattern(
         // this level rather than on the overall initializer.
         // this level rather than on the overall initializer.
         if (!IsNonDeduceableType(type)) {
         if (!IsNonDeduceableType(type)) {
           BindingMap generic_args;
           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())
             return ProgramError(binding.type().source_loc())
                    << "type pattern '" << *type
                    << "type pattern '" << *type
                    << "' does not match actual type '" << **expected << "'";
                    << "' does not match actual type '" << **expected << "'";
@@ -5112,7 +5126,7 @@ auto TypeChecker::DeclareCallableDeclaration(Nonnull<CallableDeclaration*> f,
   f->set_static_type(arena_->New<FunctionType>(
   f->set_static_type(arena_->New<FunctionType>(
       &f->param_pattern().static_type(), std::move(generic_parameters),
       &f->param_pattern().static_type(), std::move(generic_parameters),
       &f->return_term().static_type(), std::move(deduced_bindings),
       &f->return_term().static_type(), std::move(deduced_bindings),
-      std::move(impl_bindings)));
+      std::move(impl_bindings), /*is_initializing*/ true));
   switch (f->kind()) {
   switch (f->kind()) {
     case DeclarationKind::FunctionDeclaration:
     case DeclarationKind::FunctionDeclaration:
       // TODO: Should we pass in the bindings from the enclosing scope?
       // TODO: Should we pass in the bindings from the enclosing scope?

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

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

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

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

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

+ 38 - 0
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 <expr>)
+// CHECK:STDOUT: 0: Heap{}, 1: !Uninit<class C>, 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 <expr>)");
+  let c: C = CallWithReturnExpression();
+  heap.PrintAllocs();
+}
+
+fn Main() -> i32 {
+  FromInitializingExpression_ReturnExpr();
+  return 0;
+}

+ 39 - 0
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<class C>
+// 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;
+}

+ 52 - 0
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<class C>
+// 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;
+}

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

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

+ 1 - 1
explorer/testdata/pointer/fail_use_after_free.carbon

@@ -9,6 +9,6 @@ package ExplorerTest api;
 fn Main() -> i32 {
 fn Main() -> i32 {
   var p: i32* = heap.New(5);
   var p: i32* = heap.New(5);
   heap.Delete(p);
   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;
   return *p;
 }
 }

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

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

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

+ 42 - 0
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 <expr>)
+// CHECK:STDOUT: Entering call
+// CHECK:STDOUT: 0: Heap{}, 1: !Uninit<class C>
+// CHECK:STDOUT: Object created, returning
+// CHECK:STDOUT: 0: Heap{}, 1: !Uninit<class C>, 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 <expr>)");
+  var c: C = CallWithReturnExpression();
+  heap.PrintAllocs();
+}
+
+fn Main() -> i32 {
+  FromInitializingExpression_ReturnExpr();
+  return 0;
+}

+ 39 - 0
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<class C>
+// 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;
+}

+ 52 - 0
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<class C>
+// 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;
+}

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

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