diff --git a/common/fuzzing/proto_to_carbon.cpp b/common/fuzzing/proto_to_carbon.cpp index f2c59b854cbf..fde2952e99e0 100644 --- a/common/fuzzing/proto_to_carbon.cpp +++ b/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; } diff --git a/explorer/ast/statement.cpp b/explorer/ast/statement.cpp index 4e6a50714cad..32dfcb36da0f 100644 --- a/explorer/ast/statement.cpp +++ b/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: diff --git a/explorer/ast/statement.h b/explorer/ast/statement.h index 3a72fa857771..f670eb4c8725 100644 --- a/explorer/ast/statement.h +++ b/explorer/ast/statement.h @@ -117,8 +117,8 @@ class VariableDefinition : public Statement { }; VariableDefinition(SourceLocation source_loc, Nonnull pattern, - Nonnull init, ValueCategory value_category, - DefinitionType def_type) + std::optional> 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 init) { init_ = init; } + void set_init(Nonnull 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_; - Nonnull init_; + std::optional> init_; ValueCategory value_category_; const DefinitionType def_type_; }; diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index 9d18ad8ea6d8..2aa7ea52c999 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/explorer/fuzzing/ast_to_proto.cpp @@ -411,7 +411,9 @@ static auto StatementToProto(const Statement& statement) -> Fuzzing::Statement { const auto& def = cast(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; } diff --git a/explorer/interpreter/heap.cpp b/explorer/interpreter/heap.cpp index 4d2c30726d09..46aa19c077dc 100644 --- a/explorer/interpreter/heap.cpp +++ b/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 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> { + CARBON_RETURN_IF_ERROR(this->CheckInit(a.allocation_, source_loc)); CARBON_RETURN_IF_ERROR(this->CheckAlive(a.allocation_, source_loc)); Nonnull 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 v, SourceLocation source_loc) -> ErrorOr { 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 v, auto Heap::CheckAlive(AllocationId allocation, SourceLocation source_loc) const -> ErrorOr { - 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 { + 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]; diff --git a/explorer/interpreter/heap.h b/explorer/interpreter/heap.h index bed2bb5a2d15..cae098200377 100644 --- a/explorer/interpreter/heap.h +++ b/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){}; @@ -35,7 +41,8 @@ class Heap : public HeapAllocationInterface { auto Write(const Address& a, Nonnull v, SourceLocation source_loc) -> ErrorOr; - // 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 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; + // Signal an error if the allocation has not been initialized. + auto CheckInit(AllocationId allocation, SourceLocation source_loc) const + -> ErrorOr; + Nonnull arena_; std::vector> values_; - std::vector alive_; + std::vector states_; }; } // namespace Carbon diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index e8604d940224..c89474cdd717 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -269,6 +269,17 @@ auto PatternMatch(Nonnull p, Nonnull v, } // for return true; } + case Value::Kind::UninitializedValue: { + const auto& p_tup = cast(*p); + for (auto& ele : p_tup.elements()) { + if (!PatternMatch(ele, arena->New(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 p, Nonnull 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 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 { } case StatementKind::VariableDefinition: { const auto& definition = cast(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 { } else { // { { v :: (x = []) :: C, E, F} :: S, H} // -> { { C, E(x := a), F} :: S, H(a := copy(v))} - CARBON_ASSIGN_OR_RETURN( - Nonnull v, - Convert(act.results()[0], &definition.pattern().static_type(), - stmt.source_loc())); Nonnull p = &cast(stmt).pattern().value(); + Nonnull 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(p); + } RuntimeScope matches(&heap_); BindingMap generic_args; diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index 6ab750f12c40..b96c940411e8 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/explorer/interpreter/resolve_names.cpp @@ -346,7 +346,9 @@ static auto ResolveNames(Statement& statement, StaticScope& enclosing_scope) } case StatementKind::VariableDefinition: { auto& def = cast(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) diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index e1b2b93d0c22..ebcf4648a5a1 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -173,6 +173,7 @@ static auto IsTypeOfType(Nonnull 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 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 s, } case StatementKind::VariableDefinition: { auto& var = cast(*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 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 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 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: diff --git a/explorer/interpreter/value.cpp b/explorer/interpreter/value.cpp index fd9991da3146..d921fa361cdb 100644 --- a/explorer/interpreter/value.cpp +++ b/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(*this); + out << "Uninit<" << uninit.pattern() << ">"; + break; + } case Value::Kind::NominalClassType: { const auto& class_type = cast(*this); out << "class "; @@ -721,6 +726,7 @@ auto TypeEqual(Nonnull t1, Nonnull 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. diff --git a/explorer/interpreter/value.h b/explorer/interpreter/value.h index 8b9772127804..5ac8331d5aee 100644 --- a/explorer/interpreter/value.h +++ b/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 pattern_; }; +// Value for uninitialized local variables. +class UninitializedValue : public Value { + public: + explicit UninitializedValue(Nonnull 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 pattern_; +}; + // The int type. class IntType : public Value { public: diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index ea1746ab62c2..81d3782354ee 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -756,6 +756,12 @@ clause_list: statement: statement_expression EQUAL expression SEMICOLON { $$ = arena->New(context.source_loc(), $1, $3); } +| VAR pattern SEMICOLON + { + $$ = arena->New( + context.source_loc(), $2, std::nullopt, ValueCategory::Var, + VariableDefinition::DefinitionType::Var); + } | VAR pattern EQUAL expression SEMICOLON { $$ = arena->New( diff --git a/explorer/testdata/basic_syntax/fail_var_named_self.carbon b/explorer/testdata/basic_syntax/fail_var_named_self.carbon index ecc8a48b4a59..79e9e2ded9e7 100644 --- a/explorer/testdata/basic_syntax/fail_var_named_self.carbon +++ b/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; } diff --git a/explorer/testdata/uninitialized/fail_uninitialized_assign.carbon b/explorer/testdata/uninitialized/fail_uninitialized_assign.carbon new file mode 100644 index 000000000000..099c4c319093 --- /dev/null +++ b/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> + y = x; + return y; +} diff --git a/explorer/testdata/uninitialized/fail_uninitialized_init.carbon b/explorer/testdata/uninitialized/fail_uninitialized_init.carbon new file mode 100644 index 000000000000..c50f2c9cc394 --- /dev/null +++ b/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> + var y: i32 = x; + return x; +} diff --git a/explorer/testdata/uninitialized/fail_uninitialized_param.carbon b/explorer/testdata/uninitialized/fail_uninitialized_param.carbon new file mode 100644 index 000000000000..adfcf8add8ea --- /dev/null +++ b/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> + return AddInt(x, 2); +} diff --git a/explorer/testdata/uninitialized/fail_uninitialized_pattern.carbon b/explorer/testdata/uninitialized/fail_uninitialized_pattern.carbon new file mode 100644 index 000000000000..07d733fb8b21 --- /dev/null +++ b/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> + return y; +} diff --git a/explorer/testdata/uninitialized/fail_uninitialized_return.carbon b/explorer/testdata/uninitialized/fail_uninitialized_return.carbon new file mode 100644 index 000000000000..cf7ba710aa46 --- /dev/null +++ b/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> + return x; +} diff --git a/explorer/testdata/uninitialized/local_declare.carbon b/explorer/testdata/uninitialized/local_declare.carbon new file mode 100644 index 000000000000..f5e9d9592e55 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/uninitialized/local_declare_pattern.carbon b/explorer/testdata/uninitialized/local_declare_pattern.carbon new file mode 100644 index 000000000000..19311799c736 --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/uninitialized/local_uninit_without_use.carbon b/explorer/testdata/uninitialized/local_uninit_without_use.carbon new file mode 100644 index 000000000000..a4f69675a5ef --- /dev/null +++ b/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; +} diff --git a/explorer/testdata/uninitialized/uninitialized_escape.carbon b/explorer/testdata/uninitialized/uninitialized_escape.carbon new file mode 100644 index 000000000000..699d54a547ef --- /dev/null +++ b/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; +}