Procházet zdrojové kódy

Initial implementation of unformed state for local variables. (#1387)

Allows unformed state for local variables. Reports a run-time error when an unformed local variable is used.
- Added declaration without initialization for local variables in the parser.
- Made the init expression of VariableDefinition optional.
- Expanded (alive, dead) to (uninitialized, alive, dead) in the Heap.

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Zenong Zhang před 3 roky
rodič
revize
52ee050019

+ 4 - 2
common/fuzzing/proto_to_carbon.cpp

@@ -518,8 +518,10 @@ static auto StatementToCarbon(const Fuzzing::Statement& statement,
       }
       out << "var ";
       PatternToCarbon(def.pattern(), out);
-      out << " = ";
-      ExpressionToCarbon(def.init(), out);
+      if (def.has_init()) {
+        out << " = ";
+        ExpressionToCarbon(def.init(), out);
+      }
       out << ";";
       break;
     }

+ 5 - 1
explorer/ast/statement.cpp

@@ -53,7 +53,11 @@ void Statement::PrintDepth(int depth, llvm::raw_ostream& out) const {
       if (var.is_returned()) {
         out << "returned ";
       }
-      out << "var " << var.pattern() << " = " << var.init() << ";";
+      out << "var " << var.pattern();
+      if (var.has_init()) {
+        out << " = " << var.init();
+      }
+      out << ";";
       break;
     }
     case StatementKind::ExpressionStatement:

+ 22 - 8
explorer/ast/statement.h

@@ -117,8 +117,8 @@ class VariableDefinition : public Statement {
   };
 
   VariableDefinition(SourceLocation source_loc, Nonnull<Pattern*> pattern,
-                     Nonnull<Expression*> init, ValueCategory value_category,
-                     DefinitionType def_type)
+                     std::optional<Nonnull<Expression*>> init,
+                     ValueCategory value_category, DefinitionType def_type)
       : Statement(AstNodeKind::VariableDefinition, source_loc),
         pattern_(pattern),
         init_(init),
@@ -131,17 +131,31 @@ class VariableDefinition : public Statement {
 
   auto pattern() const -> const Pattern& { return *pattern_; }
   auto pattern() -> Pattern& { return *pattern_; }
-  auto init() const -> const Expression& { return *init_; }
-  auto init() -> Expression& { return *init_; }
-  auto value_category() const -> ValueCategory { return value_category_; }
-  auto is_returned() const -> bool { return def_type_ == Returned; };
+
+  auto init() const -> const Expression& {
+    CARBON_CHECK(has_init());
+    return **init_;
+  }
+  auto init() -> Expression& {
+    CARBON_CHECK(has_init());
+    return **init_;
+  }
+
+  auto has_init() const -> bool { return init_.has_value(); }
 
   // Can only be called by type-checking, if a conversion was required.
-  void set_init(Nonnull<Expression*> init) { init_ = init; }
+  void set_init(Nonnull<Expression*> init) {
+    CARBON_CHECK(has_init()) << "should not add a new initializer";
+    init_ = init;
+  }
+
+  auto value_category() const -> ValueCategory { return value_category_; }
+
+  auto is_returned() const -> bool { return def_type_ == Returned; };
 
  private:
   Nonnull<Pattern*> pattern_;
-  Nonnull<Expression*> init_;
+  std::optional<Nonnull<Expression*>> init_;
   ValueCategory value_category_;
   const DefinitionType def_type_;
 };

+ 3 - 1
explorer/fuzzing/ast_to_proto.cpp

@@ -411,7 +411,9 @@ static auto StatementToProto(const Statement& statement) -> Fuzzing::Statement {
       const auto& def = cast<VariableDefinition>(statement);
       auto* def_proto = statement_proto.mutable_variable_definition();
       *def_proto->mutable_pattern() = PatternToProto(def.pattern());
-      *def_proto->mutable_init() = ExpressionToProto(def.init());
+      if (def.has_init()) {
+        *def_proto->mutable_init() = ExpressionToProto(def.init());
+      }
       def_proto->set_is_returned(def.is_returned());
       break;
     }

+ 26 - 5
explorer/interpreter/heap.cpp

@@ -5,6 +5,7 @@
 #include "explorer/interpreter/heap.h"
 
 #include "explorer/common/error_builders.h"
+#include "explorer/interpreter/value.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/Error.h"
 
@@ -17,12 +18,17 @@ auto Heap::AllocateValue(Nonnull<const Value*> v) -> AllocationId {
   // or to leave it up to the caller.
   AllocationId a(values_.size());
   values_.push_back(v);
-  alive_.push_back(true);
+  if (v->kind() == Carbon::Value::Kind::UninitializedValue) {
+    states_.push_back(ValueState::Uninitialized);
+  } else {
+    states_.push_back(ValueState::Alive);
+  }
   return a;
 }
 
 auto Heap::Read(const Address& a, SourceLocation source_loc) const
     -> ErrorOr<Nonnull<const Value*>> {
+  CARBON_RETURN_IF_ERROR(this->CheckInit(a.allocation_, source_loc));
   CARBON_RETURN_IF_ERROR(this->CheckAlive(a.allocation_, source_loc));
   Nonnull<const Value*> value = values_[a.allocation_.index_];
   return value->GetMember(arena_, a.field_path_, source_loc, value);
@@ -31,6 +37,9 @@ auto Heap::Read(const Address& a, SourceLocation source_loc) const
 auto Heap::Write(const Address& a, Nonnull<const Value*> v,
                  SourceLocation source_loc) -> ErrorOr<Success> {
   CARBON_RETURN_IF_ERROR(this->CheckAlive(a.allocation_, source_loc));
+  if (states_[a.allocation_.index_] == ValueState::Uninitialized) {
+    states_[a.allocation_.index_] = ValueState::Alive;
+  }
   CARBON_ASSIGN_OR_RETURN(values_[a.allocation_.index_],
                           values_[a.allocation_.index_]->SetField(
                               arena_, a.field_path_, v, source_loc));
@@ -39,7 +48,7 @@ auto Heap::Write(const Address& a, Nonnull<const Value*> v,
 
 auto Heap::CheckAlive(AllocationId allocation, SourceLocation source_loc) const
     -> ErrorOr<Success> {
-  if (!alive_[allocation.index_]) {
+  if (states_[allocation.index_] == ValueState::Dead) {
     return RuntimeError(source_loc)
            << "undefined behavior: access to dead value "
            << *values_[allocation.index_];
@@ -47,9 +56,19 @@ auto Heap::CheckAlive(AllocationId allocation, SourceLocation source_loc) const
   return Success();
 }
 
+auto Heap::CheckInit(AllocationId allocation, SourceLocation source_loc) const
+    -> ErrorOr<Success> {
+  if (states_[allocation.index_] == ValueState::Uninitialized) {
+    return RuntimeError(source_loc)
+           << "undefined behavior: access to uninitialized value "
+           << *values_[allocation.index_];
+  }
+  return Success();
+}
+
 void Heap::Deallocate(AllocationId allocation) {
-  if (alive_[allocation.index_]) {
-    alive_[allocation.index_] = false;
+  if (states_[allocation.index_] != ValueState::Dead) {
+    states_[allocation.index_] = ValueState::Dead;
   } else {
     CARBON_FATAL() << "deallocating an already dead value: "
                    << *values_[allocation.index_];
@@ -63,7 +82,9 @@ void Heap::Print(llvm::raw_ostream& out) const {
   for (size_t i = 0; i < values_.size(); ++i) {
     out << sep;
     out << i << ": ";
-    if (!alive_[i]) {
+    if (states_[i] == ValueState::Uninitialized) {
+      out << "!";
+    } else if (states_[i] == ValueState::Dead) {
       out << "!!";
     }
     out << *values_[i];

+ 13 - 2
explorer/interpreter/heap.h

@@ -19,6 +19,12 @@ namespace Carbon {
 // A Heap represents the abstract machine's dynamically allocated memory.
 class Heap : public HeapAllocationInterface {
  public:
+  enum class ValueState {
+    Uninitialized,
+    Alive,
+    Dead,
+  };
+
   // Constructs an empty Heap.
   explicit Heap(Nonnull<Arena*> arena) : arena_(arena){};
 
@@ -35,7 +41,8 @@ class Heap : public HeapAllocationInterface {
   auto Write(const Address& a, Nonnull<const Value*> v,
              SourceLocation source_loc) -> ErrorOr<Success>;
 
-  // Put the given value on the heap and mark it as alive.
+  // Put the given value on the heap and mark its state.
+  // Mark UninitializedValue as uninitialized and other values as alive.
   auto AllocateValue(Nonnull<const Value*> v) -> AllocationId override;
 
   // Marks this allocation, and all of its sub-objects, as dead.
@@ -54,9 +61,13 @@ class Heap : public HeapAllocationInterface {
   auto CheckAlive(AllocationId allocation, SourceLocation source_loc) const
       -> ErrorOr<Success>;
 
+  // Signal an error if the allocation has not been initialized.
+  auto CheckInit(AllocationId allocation, SourceLocation source_loc) const
+      -> ErrorOr<Success>;
+
   Nonnull<Arena*> arena_;
   std::vector<Nonnull<const Value*>> values_;
-  std::vector<bool> alive_;
+  std::vector<ValueState> states_;
 };
 
 }  // namespace Carbon

+ 23 - 5
explorer/interpreter/interpreter.cpp

@@ -269,6 +269,17 @@ auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
           }  // for
           return true;
         }
+        case Value::Kind::UninitializedValue: {
+          const auto& p_tup = cast<TupleValue>(*p);
+          for (auto& ele : p_tup.elements()) {
+            if (!PatternMatch(ele, arena->New<UninitializedValue>(ele),
+                              source_loc, bindings, generic_args, trace_stream,
+                              arena)) {
+              return false;
+            }
+          }
+          return true;
+        }
         default:
           CARBON_FATAL() << "expected a tuple value in pattern, not " << *v;
       }
@@ -303,6 +314,8 @@ auto PatternMatch(Nonnull<const Value*> p, Nonnull<const Value*> v,
           CARBON_FATAL() << "expected a choice alternative in pattern, not "
                          << *v;
       }
+    case Value::Kind::UninitializedValue:
+      CARBON_FATAL() << "uninitialized value is not allowed in pattern " << *v;
     case Value::Kind::FunctionType:
       switch (v->kind()) {
         case Value::Kind::FunctionType: {
@@ -584,6 +597,7 @@ auto Interpreter::Convert(Nonnull<const Value*> value,
     case Value::Kind::BoolValue:
     case Value::Kind::NominalClassValue:
     case Value::Kind::AlternativeValue:
+    case Value::Kind::UninitializedValue:
     case Value::Kind::IntType:
     case Value::Kind::BoolType:
     case Value::Kind::TypeType:
@@ -1421,7 +1435,7 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
     }
     case StatementKind::VariableDefinition: {
       const auto& definition = cast<VariableDefinition>(stmt);
-      if (act.pos() == 0) {
+      if (act.pos() == 0 && definition.has_init()) {
         //    { {(var x = e) :: C, E, F} :: S, H}
         // -> { {e :: (var x = []) :: C, E, F} :: S, H}
         return todo_.Spawn(
@@ -1429,12 +1443,16 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
       } else {
         //    { { v :: (x = []) :: C, E, F} :: S, H}
         // -> { { C, E(x := a), F} :: S, H(a := copy(v))}
-        CARBON_ASSIGN_OR_RETURN(
-            Nonnull<const Value*> v,
-            Convert(act.results()[0], &definition.pattern().static_type(),
-                    stmt.source_loc()));
         Nonnull<const Value*> p =
             &cast<VariableDefinition>(stmt).pattern().value();
+        Nonnull<const Value*> v;
+        if (definition.has_init()) {
+          CARBON_ASSIGN_OR_RETURN(
+              v, Convert(act.results()[0], &definition.pattern().static_type(),
+                         stmt.source_loc()));
+        } else {
+          v = arena_->New<UninitializedValue>(p);
+        }
 
         RuntimeScope matches(&heap_);
         BindingMap generic_args;

+ 3 - 1
explorer/interpreter/resolve_names.cpp

@@ -346,7 +346,9 @@ static auto ResolveNames(Statement& statement, StaticScope& enclosing_scope)
     }
     case StatementKind::VariableDefinition: {
       auto& def = cast<VariableDefinition>(statement);
-      CARBON_RETURN_IF_ERROR(ResolveNames(def.init(), enclosing_scope));
+      if (def.has_init()) {
+        CARBON_RETURN_IF_ERROR(ResolveNames(def.init(), enclosing_scope));
+      }
       CARBON_RETURN_IF_ERROR(ResolveNames(def.pattern(), enclosing_scope));
       if (def.is_returned()) {
         CARBON_CHECK(def.pattern().kind() == PatternKind::BindingPattern)

+ 27 - 17
explorer/interpreter/type_checker.cpp

@@ -173,6 +173,7 @@ static auto IsTypeOfType(Nonnull<const Value*> value) -> bool {
     case Value::Kind::AlternativeConstructorValue:
     case Value::Kind::ContinuationValue:
     case Value::Kind::StringValue:
+    case Value::Kind::UninitializedValue:
     case Value::Kind::ImplWitness:
     case Value::Kind::SymbolicWitness:
     case Value::Kind::ParameterizedEntityName:
@@ -230,6 +231,7 @@ static auto IsType(Nonnull<const Value*> value, bool concrete = false) -> bool {
     case Value::Kind::AlternativeConstructorValue:
     case Value::Kind::ContinuationValue:
     case Value::Kind::StringValue:
+    case Value::Kind::UninitializedValue:
     case Value::Kind::ImplWitness:
     case Value::Kind::SymbolicWitness:
     case Value::Kind::ParameterizedEntityName:
@@ -852,7 +854,8 @@ auto TypeChecker::ArgumentDeduction(
     case Value::Kind::AddrValue:
     case Value::Kind::AlternativeConstructorValue:
     case Value::Kind::ContinuationValue:
-    case Value::Kind::StringValue: {
+    case Value::Kind::StringValue:
+    case Value::Kind::UninitializedValue: {
       // Argument deduction within the parameters of a parameterized class type
       // or interface type can compare values, rather than types.
       // TODO: Deduce within the values where possible.
@@ -1155,6 +1158,7 @@ auto TypeChecker::Substitute(
     case Value::Kind::AlternativeConstructorValue:
     case Value::Kind::ContinuationValue:
     case Value::Kind::StringValue:
+    case Value::Kind::UninitializedValue:
       // This can happen when substituting into the arguments of a class or
       // interface.
       // TODO: Implement substitution for these cases.
@@ -2719,24 +2723,29 @@ auto TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
     }
     case StatementKind::VariableDefinition: {
       auto& var = cast<VariableDefinition>(*s);
-      CARBON_RETURN_IF_ERROR(TypeCheckExp(&var.init(), impl_scope));
-      const Value& rhs_ty = var.init().static_type();
-      // TODO: If the pattern contains a binding that implies a new impl is
-      // available, should that remain in scope for as long as its binding?
-      // ```
-      // var a: (T:! Widget) = ...;
-      // // Is the `impl T as Widget` in scope here?
-      // a.(Widget.F)();
-      // ```
       ImplScope var_scope;
       var_scope.AddParent(&impl_scope);
-      CARBON_RETURN_IF_ERROR(TypeCheckPattern(&var.pattern(), &rhs_ty,
-                                              var_scope, var.value_category()));
-      CARBON_ASSIGN_OR_RETURN(
-          Nonnull<Expression*> converted_init,
-          ImplicitlyConvert("initializer of variable", impl_scope, &var.init(),
-                            &var.pattern().static_type()));
-      var.set_init(converted_init);
+      if (var.has_init()) {
+        CARBON_RETURN_IF_ERROR(TypeCheckExp(&var.init(), impl_scope));
+        const Value& rhs_ty = var.init().static_type();
+        // TODO: If the pattern contains a binding that implies a new impl is
+        // available, should that remain in scope for as long as its binding?
+        // ```
+        // var a: (T:! Widget) = ...;
+        // // Is the `impl T as Widget` in scope here?
+        // a.(Widget.F)();
+        // ```
+        CARBON_RETURN_IF_ERROR(TypeCheckPattern(
+            &var.pattern(), &rhs_ty, var_scope, var.value_category()));
+        CARBON_ASSIGN_OR_RETURN(
+            Nonnull<Expression*> converted_init,
+            ImplicitlyConvert("initializer of variable", impl_scope,
+                              &var.init(), &var.pattern().static_type()));
+        var.set_init(converted_init);
+      } else {
+        CARBON_RETURN_IF_ERROR(TypeCheckPattern(
+            &var.pattern(), std::nullopt, var_scope, var.value_category()));
+      }
       return Success();
     }
     case StatementKind::Assign: {
@@ -3548,6 +3557,7 @@ static bool IsValidTypeForAliasTarget(Nonnull<const Value*> type) {
     case Value::Kind::AlternativeConstructorValue:
     case Value::Kind::ContinuationValue:
     case Value::Kind::StringValue:
+    case Value::Kind::UninitializedValue:
       CARBON_FATAL() << "type of alias target is not a type";
 
     case Value::Kind::AutoType:

+ 7 - 0
explorer/interpreter/value.cpp

@@ -381,6 +381,11 @@ void Value::Print(llvm::raw_ostream& out) const {
       out << "}";
       break;
     }
+    case Value::Kind::UninitializedValue: {
+      const auto& uninit = cast<UninitializedValue>(*this);
+      out << "Uninit<" << uninit.pattern() << ">";
+      break;
+    }
     case Value::Kind::NominalClassType: {
       const auto& class_type = cast<NominalClassType>(*this);
       out << "class ";
@@ -721,6 +726,7 @@ auto TypeEqual(Nonnull<const Value*> t1, Nonnull<const Value*> t2,
     case Value::Kind::BindingPlaceholderValue:
     case Value::Kind::AddrValue:
     case Value::Kind::ContinuationValue:
+    case Value::Kind::UninitializedValue:
     case Value::Kind::ParameterizedEntityName:
     case Value::Kind::MemberName:
     case Value::Kind::TypeOfParameterizedEntityName:
@@ -848,6 +854,7 @@ auto ValueStructurallyEqual(
     case Value::Kind::ContinuationValue:
     case Value::Kind::PointerValue:
     case Value::Kind::LValue:
+    case Value::Kind::UninitializedValue:
     case Value::Kind::MemberName:
       // TODO: support pointer comparisons once we have a clearer distinction
       // between pointers and lvalues.

+ 17 - 0
explorer/interpreter/value.h

@@ -47,6 +47,7 @@ class Value {
     NominalClassValue,
     AlternativeValue,
     TupleValue,
+    UninitializedValue,
     ImplWitness,
     SymbolicWitness,
     IntType,
@@ -428,6 +429,22 @@ class AddrValue : public Value {
   Nonnull<const Value*> pattern_;
 };
 
+// Value for uninitialized local variables.
+class UninitializedValue : public Value {
+ public:
+  explicit UninitializedValue(Nonnull<const Value*> pattern)
+      : Value(Kind::UninitializedValue), pattern_(pattern) {}
+
+  static auto classof(const Value* value) -> bool {
+    return value->kind() == Kind::UninitializedValue;
+  }
+
+  auto pattern() const -> const Value& { return *pattern_; }
+
+ private:
+  Nonnull<const Value*> pattern_;
+};
+
 // The int type.
 class IntType : public Value {
  public:

+ 6 - 0
explorer/syntax/parser.ypp

@@ -756,6 +756,12 @@ clause_list:
 statement:
   statement_expression EQUAL expression SEMICOLON
     { $$ = arena->New<Assign>(context.source_loc(), $1, $3); }
+| VAR pattern SEMICOLON
+    {
+      $$ = arena->New<VariableDefinition>(
+          context.source_loc(), $2, std::nullopt, ValueCategory::Var,
+          VariableDefinition::DefinitionType::Var);
+    }
 | VAR pattern EQUAL expression SEMICOLON
     {
       $$ = arena->New<VariableDefinition>(

+ 1 - 1
explorer/testdata/basic_syntax/fail_var_named_self.carbon

@@ -14,7 +14,7 @@ fn Main() -> i32 {
   // Error: can't use keyword `Self` as the name of a variable.
   // TODO: Current error message is unclear, better would be to say
   // something like: unexpected `Self`, expecting identifier
-  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_var_named_self.carbon:[[@LINE+1]]: syntax error, unexpected COLON, expecting EQUAL
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/basic_syntax/fail_var_named_self.carbon:[[@LINE+1]]: syntax error, unexpected COLON, expecting EQUAL or SEMICOLON
   var Self : i32 = 0;
   return Self;
 }

+ 19 - 0
explorer/testdata/uninitialized/fail_uninitialized_assign.carbon

@@ -0,0 +1,19 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var x: i32;
+  var y: i32;
+  // CHECK: RUNTIME ERROR: {{.*}}/explorer/testdata/uninitialized/fail_uninitialized_assign.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit<Placeholder<x>>
+  y = x;
+  return y;
+}

+ 18 - 0
explorer/testdata/uninitialized/fail_uninitialized_init.carbon

@@ -0,0 +1,18 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var x: i32;
+  // CHECK: RUNTIME ERROR: {{.*}}/explorer/testdata/uninitialized/fail_uninitialized_init.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit<Placeholder<x>>
+  var y: i32 = x;
+  return x;
+}

+ 21 - 0
explorer/testdata/uninitialized/fail_uninitialized_param.carbon

@@ -0,0 +1,21 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn AddInt(a: i32, b: i32) -> auto {
+  return a + b;
+}
+
+fn Main() -> i32 {
+  var x: i32;
+  // CHECK: RUNTIME ERROR: {{.*}}/explorer/testdata/uninitialized/fail_uninitialized_param.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit<Placeholder<x>>
+  return AddInt(x, 2);
+}

+ 18 - 0
explorer/testdata/uninitialized/fail_uninitialized_pattern.carbon

@@ -0,0 +1,18 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var (x: i32, y: i32);
+  x = 1;
+  // CHECK: RUNTIME ERROR: {{.*}}/explorer/testdata/uninitialized/fail_uninitialized_pattern.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit<Placeholder<y>>
+  return y;
+}

+ 17 - 0
explorer/testdata/uninitialized/fail_uninitialized_return.carbon

@@ -0,0 +1,17 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var x: i32;
+  // CHECK: RUNTIME ERROR: {{.*}}/explorer/testdata/uninitialized/fail_uninitialized_return.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit<Placeholder<x>>
+  return x;
+}

+ 18 - 0
explorer/testdata/uninitialized/local_declare.carbon

@@ -0,0 +1,18 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 1
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var x: i32;
+  x = 1;
+  return x;
+}

+ 18 - 0
explorer/testdata/uninitialized/local_declare_pattern.carbon

@@ -0,0 +1,18 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 1
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var (x: i32, y: i32);
+  x = 1;
+  return x;
+}

+ 17 - 0
explorer/testdata/uninitialized/local_uninit_without_use.carbon

@@ -0,0 +1,17 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 1
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var x: i32;
+  return 1;
+}

+ 24 - 0
explorer/testdata/uninitialized/uninitialized_escape.carbon

@@ -0,0 +1,24 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 42
+
+package ExplorerTest api;
+
+fn AssignIntTo(x: i32, destination: i32*) {
+  *destination = x;
+}
+
+fn Main() -> i32 {
+  var y: i32;
+  // `y` is unformed here.
+  AssignIntTo(42, &y);
+  // `y` is fully formed and usable.
+  return y;
+}