diff --git a/common/fuzzing/carbon.proto b/common/fuzzing/carbon.proto index 350424e97ad8..e0a82ec0af01 100644 --- a/common/fuzzing/carbon.proto +++ b/common/fuzzing/carbon.proto @@ -241,6 +241,7 @@ message AssignStatement { message VariableDefinitionStatement { optional Pattern pattern = 1; optional Expression init = 2; + optional bool is_returned = 3; } message IfStatement { @@ -249,7 +250,9 @@ message IfStatement { optional BlockStatement else_block = 3; } -message ReturnStatement { +message ReturnVarStatement {} + +message ReturnExpressionStatement { optional Expression expression = 1; // Can be omitted. optional bool is_omitted_expression = 2; } @@ -294,15 +297,16 @@ message Statement { AssignStatement assign = 2; VariableDefinitionStatement variable_definition = 3; IfStatement if_statement = 4; - ReturnStatement return_statement = 5; - BlockStatement block = 6; - WhileStatement while_statement = 7; - MatchStatement match = 8; - ContinuationStatement continuation = 9; - RunStatement run = 10; - AwaitStatement await_statement = 11; - BreakStatement break_statement = 12; - ContinueStatement continue_statement = 13; + ReturnVarStatement return_var_statement = 5; + ReturnExpressionStatement return_expression_statement = 6; + BlockStatement block = 7; + WhileStatement while_statement = 8; + MatchStatement match = 9; + ContinuationStatement continuation = 10; + RunStatement run = 11; + AwaitStatement await_statement = 12; + BreakStatement break_statement = 13; + ContinueStatement continue_statement = 14; } } diff --git a/common/fuzzing/proto_to_carbon.cpp b/common/fuzzing/proto_to_carbon.cpp index e75ae8de1651..1d5b840eba1c 100644 --- a/common/fuzzing/proto_to_carbon.cpp +++ b/common/fuzzing/proto_to_carbon.cpp @@ -513,6 +513,9 @@ static auto StatementToCarbon(const Fuzzing::Statement& statement, case Fuzzing::Statement::kVariableDefinition: { const auto& def = statement.variable_definition(); + if (def.is_returned()) { + out << "returned "; + } out << "var "; PatternToCarbon(def.pattern(), out); out << " = "; @@ -535,8 +538,13 @@ static auto StatementToCarbon(const Fuzzing::Statement& statement, break; } - case Fuzzing::Statement::kReturnStatement: { - const auto& ret = statement.return_statement(); + case Fuzzing::Statement::kReturnVarStatement: { + out << "return var;"; + break; + } + + case Fuzzing::Statement::kReturnExpressionStatement: { + const auto& ret = statement.return_expression_statement(); out << "return"; if (!ret.is_omitted_expression()) { out << " "; diff --git a/explorer/ast/ast_rtti.txt b/explorer/ast/ast_rtti.txt index 61c38960540a..81af103afde5 100644 --- a/explorer/ast/ast_rtti.txt +++ b/explorer/ast/ast_rtti.txt @@ -28,7 +28,9 @@ abstract class Statement : AstNode; class Assign : Statement; class VariableDefinition : Statement; class If : Statement; - class Return : Statement; + abstract class Return : Statement; + class ReturnVar : Return; + class ReturnExpression : Return; class Block : Statement; class While : Statement; class Break : Statement; diff --git a/explorer/ast/ast_test_matchers_internal.cpp b/explorer/ast/ast_test_matchers_internal.cpp index 84d5cda9c838..7b0d0d3ff810 100644 --- a/explorer/ast/ast_test_matchers_internal.cpp +++ b/explorer/ast/ast_test_matchers_internal.cpp @@ -74,7 +74,7 @@ void BinaryOperatorExpressionMatcher::DescribeToImpl(std::ostream* out, auto MatchesReturnMatcher::MatchAndExplainImpl( const AstNode* node, ::testing::MatchResultListener* listener) const -> bool { - const auto* ret = llvm::dyn_cast(node); + const auto* ret = llvm::dyn_cast(node); if (ret == nullptr) { *listener << "which is not a return statement"; return false; diff --git a/explorer/ast/ast_test_matchers_test.cpp b/explorer/ast/ast_test_matchers_test.cpp index 819d73945fe4..aa79009f70c2 100644 --- a/explorer/ast/ast_test_matchers_test.cpp +++ b/explorer/ast/ast_test_matchers_test.cpp @@ -87,13 +87,15 @@ TEST(MatchesBinaryOpTest, BasicUsage) { TEST(MatchesReturnTest, BasicUsage) { TupleLiteral unit(DummyLoc); - Return empty_return(DummyLoc, &unit, /*is_omitted_expression=*/true); + ReturnExpression empty_return(DummyLoc, &unit, + /*is_omitted_expression=*/true); EXPECT_THAT(empty_return, MatchesEmptyReturn()); EXPECT_THAT(&empty_return, MatchesEmptyReturn()); EXPECT_THAT(empty_return, Not(MatchesReturn(_))); IntLiteral int_val(DummyLoc, 42); - Return explicit_return(DummyLoc, &int_val, /*is_omitted_expression=*/false); + ReturnExpression explicit_return(DummyLoc, &int_val, + /*is_omitted_expression=*/false); EXPECT_THAT(explicit_return, MatchesReturn(MatchesLiteral(42))); EXPECT_THAT(explicit_return, Not(MatchesEmptyReturn())); diff --git a/explorer/ast/statement.cpp b/explorer/ast/statement.cpp index 66ff21fbb9b9..4e6a50714cad 100644 --- a/explorer/ast/statement.cpp +++ b/explorer/ast/statement.cpp @@ -50,6 +50,9 @@ void Statement::PrintDepth(int depth, llvm::raw_ostream& out) const { break; case StatementKind::VariableDefinition: { const auto& var = cast(*this); + if (var.is_returned()) { + out << "returned "; + } out << "var " << var.pattern() << " = " << var.init() << ";"; break; } @@ -71,8 +74,12 @@ void Statement::PrintDepth(int depth, llvm::raw_ostream& out) const { } break; } - case StatementKind::Return: { - const auto& ret = cast(*this); + case StatementKind::ReturnVar: { + out << "return var;"; + break; + } + case StatementKind::ReturnExpression: { + const auto& ret = cast(*this); if (ret.is_omitted_expression()) { out << "return;"; } else { diff --git a/explorer/ast/statement.h b/explorer/ast/statement.h index 95370fb46864..3a72fa857771 100644 --- a/explorer/ast/statement.h +++ b/explorer/ast/statement.h @@ -111,12 +111,19 @@ class Assign : public Statement { class VariableDefinition : public Statement { public: + enum DefinitionType { + Var, + Returned, + }; + VariableDefinition(SourceLocation source_loc, Nonnull pattern, - Nonnull init, ValueCategory value_category) + Nonnull init, ValueCategory value_category, + DefinitionType def_type) : Statement(AstNodeKind::VariableDefinition, source_loc), pattern_(pattern), init_(init), - value_category_(value_category) {} + value_category_(value_category), + def_type_(def_type) {} static auto classof(const AstNode* node) -> bool { return InheritsFromVariableDefinition(node->kind()); @@ -127,6 +134,7 @@ class VariableDefinition : public Statement { 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; }; // Can only be called by type-checking, if a conversion was required. void set_init(Nonnull init) { init_ = init; } @@ -135,6 +143,7 @@ class VariableDefinition : public Statement { Nonnull pattern_; Nonnull init_; ValueCategory value_category_; + const DefinitionType def_type_; }; class If : public Statement { @@ -170,22 +179,10 @@ class If : public Statement { class Return : public Statement { public: - Return(Nonnull arena, SourceLocation source_loc) - : Return(source_loc, arena->New(source_loc), true) {} - Return(SourceLocation source_loc, Nonnull expression, - bool is_omitted_expression) - : Statement(AstNodeKind::Return, source_loc), - expression_(expression), - is_omitted_expression_(is_omitted_expression) {} - static auto classof(const AstNode* node) -> bool { return InheritsFromReturn(node->kind()); } - auto expression() const -> const Expression& { return *expression_; } - auto expression() -> Expression& { return *expression_; } - auto is_omitted_expression() const -> bool { return is_omitted_expression_; } - // The AST node representing the function body this statement returns from. // Can only be called after ResolveControlFlow has visited this node. // @@ -195,21 +192,71 @@ class Return : public Statement { auto function() const -> const FunctionDeclaration& { return **function_; } auto function() -> FunctionDeclaration& { return **function_; } - // Can only be called by type-checking, if a conversion was required. - void set_expression(Nonnull expression) { - expression_ = expression; - } - // Can only be called once, by ResolveControlFlow. void set_function(Nonnull function) { CARBON_CHECK(!function_.has_value()); function_ = function; } + protected: + Return(AstNodeKind node_kind, SourceLocation source_loc) + : Statement(node_kind, source_loc) {} + + private: + std::optional> function_; +}; + +class ReturnVar : public Return { + public: + explicit ReturnVar(SourceLocation source_loc) + : Return(AstNodeKind::ReturnVar, source_loc) {} + + static auto classof(const AstNode* node) -> bool { + return InheritsFromReturnVar(node->kind()); + } + + // Returns the value node of the BindingPattern of the returned var + // definition. Cannot be called before name resolution. + auto value_node() const -> const ValueNodeView& { return *value_node_; } + + // Can only be called once, by ResolveNames. + void set_value_node(ValueNodeView value_node) { + CARBON_CHECK(!value_node_.has_value()); + value_node_ = value_node; + } + + private: + // The value node of the BindingPattern of the returned var definition. + std::optional value_node_; +}; + +class ReturnExpression : public Return { + public: + ReturnExpression(Nonnull arena, SourceLocation source_loc) + : ReturnExpression(source_loc, arena->New(source_loc), + true) {} + ReturnExpression(SourceLocation source_loc, Nonnull expression, + bool is_omitted_expression) + : Return(AstNodeKind::ReturnExpression, source_loc), + expression_(expression), + is_omitted_expression_(is_omitted_expression) {} + + static auto classof(const AstNode* node) -> bool { + return InheritsFromReturnExpression(node->kind()); + } + + auto expression() const -> const Expression& { return *expression_; } + auto expression() -> Expression& { return *expression_; } + auto is_omitted_expression() const -> bool { return is_omitted_expression_; } + + // Can only be called by type-checking, if a conversion was required. + void set_expression(Nonnull expression) { + expression_ = expression; + } + private: Nonnull expression_; bool is_omitted_expression_; - std::optional> function_; }; class While : public Statement { diff --git a/explorer/ast/static_scope.cpp b/explorer/ast/static_scope.cpp index fb9709d4d602..dca8466deb4a 100644 --- a/explorer/ast/static_scope.cpp +++ b/explorer/ast/static_scope.cpp @@ -4,6 +4,8 @@ #include "explorer/ast/static_scope.h" +#include + #include "explorer/common/error_builders.h" #include "llvm/Support/Error.h" @@ -71,4 +73,30 @@ auto StaticScope::TryResolve(const std::string& name, return result; } +auto StaticScope::AddReturnedVar(ValueNodeView returned_var_def_view) + -> ErrorOr { + std::optional resolved_returned_var = ResolveReturned(); + if (resolved_returned_var.has_value()) { + return CompilationError(returned_var_def_view.base().source_loc()) + << "Duplicate definition of returned var also found at " + << resolved_returned_var->base().source_loc(); + } + returned_var_def_view_ = std::move(returned_var_def_view); + return Success(); +} + +auto StaticScope::ResolveReturned() const -> std::optional { + if (returned_var_def_view_.has_value()) { + return returned_var_def_view_; + } + for (Nonnull parent : parent_scopes_) { + std::optional parent_returned_var = + parent->ResolveReturned(); + if (parent_returned_var.has_value()) { + return parent_returned_var; + } + } + return std::nullopt; +} + } // namespace Carbon diff --git a/explorer/ast/static_scope.h b/explorer/ast/static_scope.h index 579cc3529144..e970941a7a57 100644 --- a/explorer/ast/static_scope.h +++ b/explorer/ast/static_scope.h @@ -173,6 +173,15 @@ class StaticScope { auto Resolve(const std::string& name, SourceLocation source_loc) const -> ErrorOr; + // Returns the value node of the BindingPattern of the returned var definition + // if it exists in the ancestor graph. + auto ResolveReturned() const -> std::optional; + + // Adds the value node of the BindingPattern of the returned var definition to + // this scope. Returns a compilation error when there is an existing returned + // var in the ancestor graph. + auto AddReturnedVar(ValueNodeView returned_var_def_view) -> ErrorOr; + private: // Equivalent to Resolve, but returns `nullopt` instead of raising an error // if no definition can be found. Still raises a compilation error if more @@ -189,6 +198,9 @@ class StaticScope { // A list of scopes used for name lookup within this scope. std::vector> parent_scopes_; + + // Stores the value node of the BindingPattern of the returned var definition. + std::optional returned_var_def_view_; }; } // namespace Carbon diff --git a/explorer/fuzzing/ast_to_proto.cpp b/explorer/fuzzing/ast_to_proto.cpp index 9fc59fccbdc1..ee301fc2fd02 100644 --- a/explorer/fuzzing/ast_to_proto.cpp +++ b/explorer/fuzzing/ast_to_proto.cpp @@ -412,6 +412,7 @@ static auto StatementToProto(const Statement& statement) -> Fuzzing::Statement { auto* def_proto = statement_proto.mutable_variable_definition(); *def_proto->mutable_pattern() = PatternToProto(def.pattern()); *def_proto->mutable_init() = ExpressionToProto(def.init()); + def_proto->set_is_returned(def.is_returned()); break; } @@ -428,9 +429,14 @@ static auto StatementToProto(const Statement& statement) -> Fuzzing::Statement { break; } - case StatementKind::Return: { - const auto& ret = cast(statement); - auto* ret_proto = statement_proto.mutable_return_statement(); + case StatementKind::ReturnVar: { + statement_proto.mutable_return_var_statement(); + break; + } + + case StatementKind::ReturnExpression: { + const auto& ret = cast(statement); + auto* ret_proto = statement_proto.mutable_return_expression_statement(); if (!ret.is_omitted_expression()) { *ret_proto->mutable_expression() = ExpressionToProto(ret.expression()); } else { diff --git a/explorer/interpreter/action_stack.cpp b/explorer/interpreter/action_stack.cpp index c4597fff3a98..1dd9814ab540 100644 --- a/explorer/interpreter/action_stack.cpp +++ b/explorer/interpreter/action_stack.cpp @@ -131,7 +131,6 @@ auto ActionStack::FinishAction() -> ErrorOr { case Action::Kind::ExpressionAction: case Action::Kind::LValAction: case Action::Kind::PatternAction: - CARBON_FATAL() << "This kind of action must produce a result: " << *act; case Action::Kind::ScopeAction: CARBON_FATAL() << "ScopeAction at top of stack"; case Action::Kind::StatementAction: diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 23777e215899..57abc415a915 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -1403,12 +1403,34 @@ auto Interpreter::StepStmt() -> ErrorOr { } else { return todo_.FinishAction(); } - case StatementKind::Return: + case StatementKind::ReturnVar: { + const ValueNodeView& value_node = cast(stmt).value_node(); + if (trace_stream_) { + **trace_stream_ << "--- step returned var " + << cast(value_node.base()).name() + << " ." << act.pos() << "." + << " (" << stmt.source_loc() << ") --->\n"; + } + CARBON_ASSIGN_OR_RETURN(Nonnull value, + todo_.ValueOfNode(value_node, stmt.source_loc())); + if (const auto* lvalue = dyn_cast(value)) { + CARBON_ASSIGN_OR_RETURN( + value, + heap_.Read(lvalue->address(), value_node.base().source_loc())); + } + const FunctionDeclaration& function = cast(stmt).function(); + CARBON_ASSIGN_OR_RETURN( + Nonnull return_value, + Convert(value, &function.return_term().static_type(), + stmt.source_loc())); + return todo_.UnwindPast(*function.body(), return_value); + } + case StatementKind::ReturnExpression: if (act.pos() == 0) { // { {return e :: C, E, F} :: S, H} // -> { {e :: return [] :: C, E, F} :: S, H} return todo_.Spawn(std::make_unique( - &cast(stmt).expression())); + &cast(stmt).expression())); } else { // { {v :: return [] :: C, E, F} :: {C', E', F'} :: S, H} // -> { {v :: C', E', F'} :: S, H} diff --git a/explorer/interpreter/resolve_control_flow.cpp b/explorer/interpreter/resolve_control_flow.cpp index 43486bb20f76..199b2703649f 100644 --- a/explorer/interpreter/resolve_control_flow.cpp +++ b/explorer/interpreter/resolve_control_flow.cpp @@ -37,7 +37,8 @@ static auto ResolveControlFlow(Nonnull statement, std::optional> function) -> ErrorOr { switch (statement->kind()) { - case StatementKind::Return: { + case StatementKind::ReturnVar: + case StatementKind::ReturnExpression: { if (!function.has_value()) { return CompilationError(statement->source_loc()) << "return is not within a function body"; @@ -54,11 +55,22 @@ static auto ResolveControlFlow(Nonnull statement, } auto& ret = cast(*statement); ret.set_function((*function)->declaration); - if (ret.is_omitted_expression() != function_return.is_omitted()) { - return CompilationError(ret.source_loc()) - << ret << " should" - << (function_return.is_omitted() ? " not" : "") - << " provide a return value, to match the function's signature."; + if (statement->kind() == StatementKind::ReturnVar && + function_return.is_omitted()) { + return CompilationError(statement->source_loc()) + << *statement + << " should not provide a return value, to match the function's " + "signature."; + } + if (statement->kind() == StatementKind::ReturnExpression) { + auto& ret_exp = cast(*statement); + if (ret_exp.is_omitted_expression() != function_return.is_omitted()) { + return CompilationError(ret_exp.source_loc()) + << ret_exp << " should" + << (function_return.is_omitted() ? " not" : "") + << " provide a return value, to match the function's " + "signature."; + } } return Success(); } diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index 5790fa1cd279..09b1504f8834 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/explorer/interpreter/resolve_names.cpp @@ -336,6 +336,13 @@ static auto ResolveNames(Statement& statement, StaticScope& enclosing_scope) auto& def = cast(statement); 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) + << def.pattern().source_loc() + << "returned var definition can only be a binding pattern"; + CARBON_RETURN_IF_ERROR(enclosing_scope.AddReturnedVar( + ValueNodeView(&cast(def.pattern())))); + } break; } case StatementKind::If: { @@ -350,10 +357,32 @@ static auto ResolveNames(Statement& statement, StaticScope& enclosing_scope) } break; } - case StatementKind::Return: - CARBON_RETURN_IF_ERROR( - ResolveNames(cast(statement).expression(), enclosing_scope)); + case StatementKind::ReturnVar: { + auto& ret_var_stmt = cast(statement); + std::optional returned_var_def_view = + enclosing_scope.ResolveReturned(); + if (!returned_var_def_view.has_value()) { + return CompilationError(ret_var_stmt.source_loc()) + << "`return var` is not allowed without a returned var defined " + "in scope."; + } + ret_var_stmt.set_value_node(*returned_var_def_view); break; + } + case StatementKind::ReturnExpression: { + auto& ret_exp_stmt = cast(statement); + std::optional returned_var_def_view = + enclosing_scope.ResolveReturned(); + if (returned_var_def_view.has_value()) { + return CompilationError(ret_exp_stmt.source_loc()) + << "`return ` is not allowed with a returned var " + "defined in scope: " + << returned_var_def_view->base().source_loc(); + } + CARBON_RETURN_IF_ERROR( + ResolveNames(ret_exp_stmt.expression(), enclosing_scope)); + break; + } case StatementKind::Block: { auto& block = cast(statement); StaticScope block_scope; diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index dc2791560aad..7bcb71369cf5 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -2630,8 +2630,26 @@ auto TypeChecker::TypeCheckStmt(Nonnull s, } return Success(); } - case StatementKind::Return: { - auto& ret = cast(*s); + case StatementKind::ReturnVar: { + auto& ret = cast(*s); + ReturnTerm& return_term = ret.function().return_term(); + if (return_term.is_auto()) { + return_term.set_static_type(&ret.value_node().static_type()); + } else { + CARBON_CHECK(IsConcreteType(&return_term.static_type())); + CARBON_CHECK(IsConcreteType(&ret.value_node().static_type())); + if (!TypeEqual(&return_term.static_type(), + &ret.value_node().static_type())) { + return CompilationError(ret.value_node().base().source_loc()) + << "type of returned var `" << ret.value_node().static_type() + << "` does not match return type `" + << return_term.static_type() << "`"; + } + } + return Success(); + } + case StatementKind::ReturnExpression: { + auto& ret = cast(*s); CARBON_RETURN_IF_ERROR(TypeCheckExp(&ret.expression(), impl_scope)); ReturnTerm& return_term = ret.function().return_term(); if (return_term.is_auto()) { @@ -2728,7 +2746,8 @@ auto TypeChecker::ExpectReturnOnAllPaths( ExpectReturnOnAllPaths(if_stmt.else_block(), stmt->source_loc())); return Success(); } - case StatementKind::Return: + case StatementKind::ReturnVar: + case StatementKind::ReturnExpression: return Success(); case StatementKind::Continuation: case StatementKind::Run: diff --git a/explorer/syntax/lexer.lpp b/explorer/syntax/lexer.lpp index 5451f496f927..20633fa7feff 100644 --- a/explorer/syntax/lexer.lpp +++ b/explorer/syntax/lexer.lpp @@ -79,6 +79,7 @@ PACKAGE "package" PERIOD "." PLUS "+" RETURN "return" +RETURNED "returned" RIGHT_CURLY_BRACE "}" RIGHT_PARENTHESIS ")" RIGHT_SQUARE_BRACKET "]" @@ -164,6 +165,7 @@ operand_start [(A-Za-z0-9_\"] {PACKAGE} { return CARBON_SIMPLE_TOKEN(PACKAGE); } {PERIOD} { return CARBON_SIMPLE_TOKEN(PERIOD); } {PLUS} { return CARBON_SIMPLE_TOKEN(PLUS); } +{RETURNED} { return CARBON_SIMPLE_TOKEN(RETURNED); } {RETURN} { return CARBON_SIMPLE_TOKEN(RETURN); } {RUN} { return CARBON_SIMPLE_TOKEN(RUN); } {SELF} { return CARBON_SIMPLE_TOKEN(SELF); } diff --git a/explorer/syntax/parser.ypp b/explorer/syntax/parser.ypp index ecb73c62c27c..17357e7e099a 100644 --- a/explorer/syntax/parser.ypp +++ b/explorer/syntax/parser.ypp @@ -229,6 +229,7 @@ PERIOD PLUS RETURN + RETURNED RIGHT_CURLY_BRACE RIGHT_PARENTHESIS RIGHT_SQUARE_BRACKET @@ -757,13 +758,21 @@ statement: { $$ = arena->New(context.source_loc(), $1, $3); } | VAR pattern EQUAL expression SEMICOLON { - $$ = arena->New(context.source_loc(), $2, $4, - ValueCategory::Var); + $$ = arena->New( + context.source_loc(), $2, $4, ValueCategory::Var, + VariableDefinition::DefinitionType::Var); + } +| RETURNED VAR variable_declaration EQUAL expression SEMICOLON + { + $$ = arena->New( + context.source_loc(), $3, $5, ValueCategory::Var, + VariableDefinition::DefinitionType::Returned); } | LET pattern EQUAL expression SEMICOLON { - $$ = arena->New(context.source_loc(), $2, $4, - ValueCategory::Let); + $$ = arena->New( + context.source_loc(), $2, $4, ValueCategory::Let, + VariableDefinition::DefinitionType::Var); } | statement_expression SEMICOLON { $$ = arena->New(context.source_loc(), $1); } @@ -778,8 +787,11 @@ statement: | RETURN return_expression SEMICOLON { auto [return_exp, is_omitted_exp] = $2; - $$ = arena->New(context.source_loc(), return_exp, is_omitted_exp); + $$ = arena->New(context.source_loc(), return_exp, + is_omitted_exp); } +| RETURN VAR SEMICOLON + { $$ = arena->New(context.source_loc()); } | MATCH LEFT_PARENTHESIS expression RIGHT_PARENTHESIS LEFT_CURLY_BRACE clause_list RIGHT_CURLY_BRACE { $$ = arena->New(context.source_loc(), $3, $6); } diff --git a/explorer/testdata/returned_var/fail_duplicate_return_var.carbon b/explorer/testdata/returned_var/fail_duplicate_return_var.carbon new file mode 100644 index 000000000000..2ed9ef9b661e --- /dev/null +++ b/explorer/testdata/returned_var/fail_duplicate_return_var.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: %{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) -> i32 { + returned var ret: i32 = a + b; + if (a == b) { + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/returned_var/fail_duplicate_return_var.carbon:[[@LINE+1]]: Duplicate definition of returned var also found at {{.*}}/explorer/testdata/returned_var/fail_duplicate_return_var.carbon:14 + returned var ret2: i32 = a + b; + } + return var; +} + +fn Main() -> i32 { + return AddInt(1, 2); +} diff --git a/explorer/testdata/returned_var/fail_missing_declaration.carbon b/explorer/testdata/returned_var/fail_missing_declaration.carbon new file mode 100644 index 000000000000..3b453b53a3c2 --- /dev/null +++ b/explorer/testdata/returned_var/fail_missing_declaration.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) -> i32 { + var ret: i32 = a + b; + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/returned_var/fail_missing_declaration.carbon:[[@LINE+1]]: `return var` is not allowed without a returned var defined in scope. + return var; +} + +fn Main() -> i32 { + return AddInt(1, 2); +} diff --git a/explorer/testdata/returned_var/fail_missing_return.carbon b/explorer/testdata/returned_var/fail_missing_return.carbon new file mode 100644 index 000000000000..3546566c0403 --- /dev/null +++ b/explorer/testdata/returned_var/fail_missing_return.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) -> i32 { + returned var ret: i32 = a + b; + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/returned_var/fail_missing_return.carbon:[[@LINE+1]]: `return ` is not allowed with a returned var defined in scope: {{.*}}/explorer/testdata/returned_var/fail_missing_return.carbon:14 + return ret; +} + +fn Main() -> i32 { + return AddInt(1, 2); +} diff --git a/explorer/testdata/returned_var/fail_returned_var_mismatch_signature.carbon b/explorer/testdata/returned_var/fail_returned_var_mismatch_signature.carbon new file mode 100644 index 000000000000..2761a053a151 --- /dev/null +++ b/explorer/testdata/returned_var/fail_returned_var_mismatch_signature.carbon @@ -0,0 +1,22 @@ +// 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 Foo() { + returned var x: () = (); + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/returned_var/fail_returned_var_mismatch_signature.carbon:[[@LINE+1]]: return var; should not provide a return value, to match the function's signature. + return var; +} + +fn Main() -> i32 { + Foo(); + return 0; +} diff --git a/explorer/testdata/returned_var/fail_returned_var_multi_auto.carbon b/explorer/testdata/returned_var/fail_returned_var_multi_auto.carbon new file mode 100644 index 000000000000..3c132c29da88 --- /dev/null +++ b/explorer/testdata/returned_var/fail_returned_var_multi_auto.carbon @@ -0,0 +1,26 @@ +// 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 Foo(a: i32, b: i32) -> auto { + returned var ret: i32 = a + b; + if (b == a) { + ret = b - a; + return var; + } + ret = a - b; + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/returned_var/fail_returned_var_multi_auto.carbon:[[@LINE+1]]: Only one return is allowed in a function with an `auto` return type. + return var; +} + +fn Main() -> i32 { + return Foo(1, 2); +} diff --git a/explorer/testdata/returned_var/fail_returned_var_type_mismatch.carbon b/explorer/testdata/returned_var/fail_returned_var_type_mismatch.carbon new file mode 100644 index 000000000000..9020f65815fb --- /dev/null +++ b/explorer/testdata/returned_var/fail_returned_var_type_mismatch.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) -> i32 { + // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/returned_var/fail_returned_var_type_mismatch.carbon:[[@LINE+1]]: type of returned var `Bool` does not match return type `i32` + returned var ret: Bool = true; + return var; +} + +fn Main() -> i32 { + return AddInt(1, 2); +} diff --git a/explorer/testdata/returned_var/multiple_returned_var_control_flow.carbon b/explorer/testdata/returned_var/multiple_returned_var_control_flow.carbon new file mode 100644 index 000000000000..fa04b9ad2986 --- /dev/null +++ b/explorer/testdata/returned_var/multiple_returned_var_control_flow.carbon @@ -0,0 +1,26 @@ +// 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 IntDiff(a: i32, b: i32) -> i32 { + if (a == b) { + returned var ret: i32 = a - b; + return var; + } else { + returned var ret2: i32 = b - a; + return var; + } +} + +fn Main() -> i32 { + return IntDiff(1, 2); +} diff --git a/explorer/testdata/returned_var/normal_return_control_flow.carbon b/explorer/testdata/returned_var/normal_return_control_flow.carbon new file mode 100644 index 000000000000..bc6c26aad3d9 --- /dev/null +++ b/explorer/testdata/returned_var/normal_return_control_flow.carbon @@ -0,0 +1,25 @@ +// 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: 3 + +package ExplorerTest api; + +fn AddInt(a: i32, b: i32) -> i32 { + if (a == b) { + returned var ret: i32 = a + b; + return var; + } else { + return a + b; + } +} + +fn Main() -> i32 { + return AddInt(1, 2); +} diff --git a/explorer/testdata/returned_var/returned_var_auto.carbon b/explorer/testdata/returned_var/returned_var_auto.carbon new file mode 100644 index 000000000000..8234d2ba81ae --- /dev/null +++ b/explorer/testdata/returned_var/returned_var_auto.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: %{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: 3 + +package ExplorerTest api; + +fn AddInt(a: i32, b: i32) -> auto { + returned var ret: i32 = a + b; + return var; +} + +fn Main() -> i32 { + return AddInt(1, 2); +} diff --git a/explorer/testdata/returned_var/returned_var_basic.carbon b/explorer/testdata/returned_var/returned_var_basic.carbon new file mode 100644 index 000000000000..83843f87ac0d --- /dev/null +++ b/explorer/testdata/returned_var/returned_var_basic.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: %{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: 3 + +package ExplorerTest api; + +fn AddInt(a: i32, b: i32) -> i32 { + returned var ret: i32 = a + b; + return var; +} + +fn Main() -> i32 { + return AddInt(1, 2); +} diff --git a/explorer/testdata/returned_var/returned_var_name_lookup.carbon b/explorer/testdata/returned_var/returned_var_name_lookup.carbon new file mode 100644 index 000000000000..5543562711e3 --- /dev/null +++ b/explorer/testdata/returned_var/returned_var_name_lookup.carbon @@ -0,0 +1,22 @@ +// 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 { + returned var x: i32 = 1; + if (true) { + var x: i32 = 2; + return var; + } + x = 3; + return var; +} diff --git a/explorer/testdata/returned_var/returned_var_omitted_expression.carbon b/explorer/testdata/returned_var/returned_var_omitted_expression.carbon new file mode 100644 index 000000000000..bc21a1e5ad25 --- /dev/null +++ b/explorer/testdata/returned_var/returned_var_omitted_expression.carbon @@ -0,0 +1,22 @@ +// 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: 0 + +package ExplorerTest api; + +fn Foo() -> () { + returned var x: () = (); + return var; +} + +fn Main() -> i32 { + Foo(); + return 0; +}