From afd6d856103d865393996f9df2a8c794a01fde36 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 10 Nov 2023 11:52:30 -0800 Subject: [PATCH] Support for `returned var` and `return var`. (#3374) Implement toolchain support for `returned var` and `return var`. - Modeled `returned` in the parse tree as a `ReturnedSpecifier` appearing after the `VariableIntroducer`. - Modeled `return var` in the parse tree as a `ReturnVarSpecifier` appearing after the `ReturnStatementStart`. - Factored out the implementation of `return` statement and `returned var` handling in check into a new `return.{h,cpp}`. The parse nodes themselves are still handled in `handle_*.cpp`. This allows easy code reuse between `return` and `returned var`. --- toolchain/check/BUILD | 2 + toolchain/check/context.cpp | 25 ++- toolchain/check/context.h | 29 ++- .../check/handle_expression_statement.cpp | 29 +++ toolchain/check/handle_function.cpp | 3 +- toolchain/check/handle_pattern_binding.cpp | 17 +- toolchain/check/handle_return_statement.cpp | 60 ++++++ toolchain/check/handle_statement.cpp | 91 --------- toolchain/check/handle_variable.cpp | 48 +++-- toolchain/check/node_stack.h | 13 ++ toolchain/check/return.cpp | 180 ++++++++++++++++++ toolchain/check/return.h | 36 ++++ .../fail_return_var_no_returned_var.carbon | 21 ++ .../fail_return_with_returned_var.carbon | 81 ++++++++ .../fail_returned_var_no_return_type.carbon | 34 ++++ .../return/fail_returned_var_shadow.carbon | 89 +++++++++ .../return/fail_returned_var_type.carbon | 28 +++ .../return/fail_value_disallowed.carbon | 2 +- .../testdata/return/fail_value_missing.carbon | 5 +- .../check/testdata/return/returned_var.carbon | 72 +++++++ .../testdata/return/returned_var_scope.carbon | 79 ++++++++ toolchain/diagnostics/diagnostic_kind.def | 10 +- .../lower/testdata/return/return_var.carbon | 27 +++ .../testdata/return/return_var_byval.carbon | 20 ++ .../parse/handle_declaration_scope_loop.cpp | 2 +- toolchain/parse/handle_statement.cpp | 15 +- toolchain/parse/handle_var.cpp | 40 +++- toolchain/parse/node_kind.def | 14 +- toolchain/parse/state.def | 22 ++- .../testdata/for/fail_returned_var.carbon | 38 ++++ .../return/fail_returned_no_var.carbon | 27 +++ .../testdata/return/fail_var_no_semi.carbon | 27 +++ .../parse/testdata/return/returned_var.carbon | 35 ++++ toolchain/sem_ir/file.h | 2 + 34 files changed, 1077 insertions(+), 146 deletions(-) create mode 100644 toolchain/check/handle_expression_statement.cpp create mode 100644 toolchain/check/handle_return_statement.cpp delete mode 100644 toolchain/check/handle_statement.cpp create mode 100644 toolchain/check/return.cpp create mode 100644 toolchain/check/return.h create mode 100644 toolchain/check/testdata/return/fail_return_var_no_returned_var.carbon create mode 100644 toolchain/check/testdata/return/fail_return_with_returned_var.carbon create mode 100644 toolchain/check/testdata/return/fail_returned_var_no_return_type.carbon create mode 100644 toolchain/check/testdata/return/fail_returned_var_shadow.carbon create mode 100644 toolchain/check/testdata/return/fail_returned_var_type.carbon create mode 100644 toolchain/check/testdata/return/returned_var.carbon create mode 100644 toolchain/check/testdata/return/returned_var_scope.carbon create mode 100644 toolchain/lower/testdata/return/return_var.carbon create mode 100644 toolchain/lower/testdata/return/return_var_byval.carbon create mode 100644 toolchain/parse/testdata/for/fail_returned_var.carbon create mode 100644 toolchain/parse/testdata/return/fail_returned_no_var.carbon create mode 100644 toolchain/parse/testdata/return/fail_var_no_semi.carbon create mode 100644 toolchain/parse/testdata/return/returned_var.carbon diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index aed93aaf5776..00ff18a3b325 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -36,6 +36,8 @@ cc_library( "convert.cpp", "declaration_name_stack.cpp", "inst_block_stack.cpp", + "return.cpp", + "return.h", ] + # Glob handler files to avoid missing any. glob([ diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 1030e7170e86..4ec13e429c28 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -263,6 +263,12 @@ auto Context::PopScope() -> void { CARBON_CHECK(non_lexical_scope_stack_.back().first == scope.index); non_lexical_scope_stack_.pop_back(); } + + if (scope.has_returned_var) { + CARBON_CHECK(!return_scope_stack_.empty()); + CARBON_CHECK(return_scope_stack_.back().returned_var.is_valid()); + return_scope_stack_.back().returned_var = SemIR::InstId::Invalid; + } } auto Context::PopToScope(ScopeIndex index) -> void { @@ -274,6 +280,23 @@ auto Context::PopToScope(ScopeIndex index) -> void { << current_scope_index(); } +auto Context::SetReturnedVarOrGetExisting(SemIR::InstId inst_id) + -> SemIR::InstId { + CARBON_CHECK(!return_scope_stack_.empty()) << "`returned var` in no function"; + auto& returned_var = return_scope_stack_.back().returned_var; + if (returned_var.is_valid()) { + return returned_var; + } + + returned_var = inst_id; + CARBON_CHECK(!current_scope().has_returned_var) + << "Scope has returned var but none is set"; + if (inst_id.is_valid()) { + current_scope().has_returned_var = true; + } + return SemIR::InstId::Invalid; +} + auto Context::FollowNameReferences(SemIR::InstId inst_id) -> SemIR::InstId { while (auto name_ref = insts().Get(inst_id).TryAs()) { inst_id = name_ref->value_id; @@ -396,7 +419,7 @@ auto Context::AddCurrentCodeBlockToFunction(Parse::Node parse_node) -> void { auto function_id = insts() - .GetAs(return_scope_stack().back()) + .GetAs(return_scope_stack().back().decl_id) .function_id; functions() .Get(function_id) diff --git a/toolchain/check/context.h b/toolchain/check/context.h index 1a588715e71f..3ce6a1f95494 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -30,6 +30,17 @@ class Context { SemIR::InstBlockId continue_target; }; + // A scope in which `return` can be used. + struct ReturnScope { + // The declaration from which we can return. Inside a function, this will + // be a `FunctionDecl`. + SemIR::InstId decl_id; + + // The value corresponding to the current `returned var`, if any. Will be + // set and unset as `returned var`s are declared and go out of scope. + SemIR::InstId returned_var = SemIR::InstId::Invalid; + }; + // Stores references for work. explicit Context(const Lex::TokenizedBuffer& tokens, DiagnosticEmitter& emitter, const Parse::Tree& parse_tree, @@ -115,6 +126,11 @@ class Context { return insts().Get(current_scope_inst_id).TryAs(); } + // If there is no `returned var` in scope, sets the given instruction to be + // the current `returned var` and returns an invalid instruction ID. If there + // is already a `returned var`, returns it instead. + auto SetReturnedVarOrGetExisting(SemIR::InstId bind_id) -> SemIR::InstId; + // Follows NameReference instructions to find the value named by a given // instruction. auto FollowNameReferences(SemIR::InstId inst_id) -> SemIR::InstId; @@ -259,7 +275,7 @@ class Context { return args_type_info_stack_; } - auto return_scope_stack() -> llvm::SmallVector& { + auto return_scope_stack() -> llvm::SmallVector& { return return_scope_stack_; } @@ -332,9 +348,13 @@ class Context { SemIR::NameScopeId scope_id; // Names which are registered with name_lookup_, and will need to be - // deregistered when the scope ends. + // unregistered when the scope ends. llvm::DenseSet names; + // Whether a `returned var` was introduced in this scope, and needs to be + // unregistered when the scope ends. + bool has_returned_var = false; + // TODO: This likely needs to track things which need to be destructed. }; @@ -405,9 +425,8 @@ class Context { // for a type separate from the literal arguments. InstBlockStack args_type_info_stack_; - // A stack of return scopes; i.e., targets for `return`. Inside a function, - // this will be a FunctionDecl. - llvm::SmallVector return_scope_stack_; + // A stack of scopes from which we can `return`. + llvm::SmallVector return_scope_stack_; // A stack of `break` and `continue` targets. llvm::SmallVector break_continue_stack_; diff --git a/toolchain/check/handle_expression_statement.cpp b/toolchain/check/handle_expression_statement.cpp new file mode 100644 index 000000000000..d7d39c6688df --- /dev/null +++ b/toolchain/check/handle_expression_statement.cpp @@ -0,0 +1,29 @@ +// 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 + +#include "toolchain/check/context.h" +#include "toolchain/check/convert.h" +#include "toolchain/sem_ir/inst.h" + +namespace Carbon::Check { + +// TODO: Find a better home for this. We'll likely need it for more than just +// expression statements. +static auto HandleDiscardedExpr(Context& context, SemIR::InstId expr_id) + -> void { + // If we discard an initializing expression, convert it to a value or + // reference so that it has something to initialize. + auto expr = context.insts().Get(expr_id); + Convert(context, expr.parse_node(), expr_id, + {.kind = ConversionTarget::Discarded, .type_id = expr.type_id()}); + + // TODO: This will eventually need to do some "do not discard" analysis. +} + +auto HandleExprStatement(Context& context, Parse::Node /*parse_node*/) -> bool { + HandleDiscardedExpr(context, context.node_stack().PopExpr()); + return true; +} + +} // namespace Carbon::Check diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 858d0c694810..527395ae3c77 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -100,6 +100,7 @@ static auto BuildFunctionDecl(Context& context, bool is_definition) name_context.state == DeclNameStack::NameContext::State::Unresolved ? name_context.unresolved_name_id : SemIR::NameId::Invalid, + .declaration_id = function_decl_id, .implicit_param_refs_id = implicit_param_refs_id, .param_refs_id = param_refs_id, .return_type_id = return_type_id, @@ -182,7 +183,7 @@ auto HandleFunctionDefinitionStart(Context& context, Parse::Node parse_node) } // Create the function scope and the entry block. - context.return_scope_stack().push_back(decl_id); + context.return_scope_stack().push_back({.decl_id = decl_id}); context.inst_block_stack().Push(); context.PushScope(decl_id); context.AddCurrentCodeBlockToFunction(); diff --git a/toolchain/check/handle_pattern_binding.cpp b/toolchain/check/handle_pattern_binding.cpp index ba4cfd9bc3e1..ae4e157702e0 100644 --- a/toolchain/check/handle_pattern_binding.cpp +++ b/toolchain/check/handle_pattern_binding.cpp @@ -4,6 +4,7 @@ #include "toolchain/check/context.h" #include "toolchain/check/convert.h" +#include "toolchain/check/return.h" #include "toolchain/sem_ir/inst.h" namespace Carbon::Check { @@ -62,6 +63,7 @@ auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool { // error locations. switch (auto context_parse_node_kind = context.parse_tree().node_kind( context.node_stack().PeekParseNode())) { + case Parse::NodeKind::ReturnedSpecifier: case Parse::NodeKind::VariableIntroducer: { // A `var` declaration at class scope introduces a field. auto enclosing_class_decl = context.GetCurrentScopeAs(); @@ -78,7 +80,12 @@ auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool { } SemIR::InstId value_id = SemIR::InstId::Invalid; SemIR::TypeId value_type_id = cast_type_id; - if (enclosing_class_decl) { + if (context_parse_node_kind == Parse::NodeKind::ReturnedSpecifier) { + CARBON_CHECK(!enclosing_class_decl) << "`returned var` at class scope"; + value_id = + CheckReturnedVar(context, context.node_stack().PeekParseNode(), + name_node, name_id, type_node, cast_type_id); + } else if (enclosing_class_decl) { auto& class_info = context.classes().Get(enclosing_class_decl->class_id); auto field_type_inst_id = context.AddInst(SemIR::UnboundFieldType{ @@ -98,9 +105,13 @@ auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool { value_id = context.AddInst( SemIR::VarStorage{name_node, value_type_id, name_id}); } - context.AddInstAndPush( - parse_node, + auto bind_id = context.AddInst( SemIR::BindName{name_node, value_type_id, name_id, value_id}); + context.node_stack().Push(parse_node, bind_id); + + if (context_parse_node_kind == Parse::NodeKind::ReturnedSpecifier) { + RegisterReturnedVar(context, bind_id); + } break; } diff --git a/toolchain/check/handle_return_statement.cpp b/toolchain/check/handle_return_statement.cpp new file mode 100644 index 000000000000..60a2fa753953 --- /dev/null +++ b/toolchain/check/handle_return_statement.cpp @@ -0,0 +1,60 @@ +// 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 + +#include "toolchain/check/context.h" +#include "toolchain/check/return.h" + +namespace Carbon::Check { + +auto HandleReturnStatementStart(Context& context, Parse::Node parse_node) + -> bool { + // No action, just a bracketing node. + context.node_stack().Push(parse_node); + return true; +} + +auto HandleReturnVarSpecifier(Context& context, Parse::Node parse_node) + -> bool { + // No action, just a bracketing node. + context.node_stack().Push(parse_node); + return true; +} + +auto HandleReturnStatement(Context& context, Parse::Node parse_node) -> bool { + switch ( + context.parse_tree().node_kind(context.node_stack().PeekParseNode())) { + case Parse::NodeKind::ReturnStatementStart: + // This is a `return;` statement. + context.node_stack() + .PopAndDiscardSoloParseNode(); + BuildReturnWithNoExpr(context, parse_node); + break; + + case Parse::NodeKind::ReturnVarSpecifier: + // This is a `return var;` statement. + context.node_stack() + .PopAndDiscardSoloParseNode(); + context.node_stack() + .PopAndDiscardSoloParseNode(); + BuildReturnVar(context, parse_node); + break; + + default: + // This is a `return ;` statement. + auto expr_id = context.node_stack().PopExpr(); + context.node_stack() + .PopAndDiscardSoloParseNode(); + BuildReturnWithExpr(context, parse_node, expr_id); + break; + } + + // Switch to a new, unreachable, empty instruction block. This typically won't + // contain any semantics IR, but it can do if there are statements following + // the `return` statement. + context.inst_block_stack().Pop(); + context.inst_block_stack().PushUnreachable(); + return true; +} + +} // namespace Carbon::Check diff --git a/toolchain/check/handle_statement.cpp b/toolchain/check/handle_statement.cpp deleted file mode 100644 index f76e7a4300f4..000000000000 --- a/toolchain/check/handle_statement.cpp +++ /dev/null @@ -1,91 +0,0 @@ -// 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 - -#include "toolchain/check/context.h" -#include "toolchain/check/convert.h" -#include "toolchain/sem_ir/inst.h" - -namespace Carbon::Check { - -// TODO: Find a better home for this. We'll likely need it for more than just -// expression statements. -static auto HandleDiscardedExpr(Context& context, SemIR::InstId expr_id) - -> void { - // If we discard an initializing expression, convert it to a value or - // reference so that it has something to initialize. - auto expr = context.insts().Get(expr_id); - Convert(context, expr.parse_node(), expr_id, - {.kind = ConversionTarget::Discarded, .type_id = expr.type_id()}); - - // TODO: This will eventually need to do some "do not discard" analysis. -} - -auto HandleExprStatement(Context& context, Parse::Node /*parse_node*/) -> bool { - HandleDiscardedExpr(context, context.node_stack().PopExpr()); - return true; -} - -auto HandleReturnStatement(Context& context, Parse::Node parse_node) -> bool { - CARBON_CHECK(!context.return_scope_stack().empty()); - auto fn_inst = context.insts().GetAs( - context.return_scope_stack().back()); - const auto& callable = context.functions().Get(fn_inst.function_id); - - if (context.parse_tree().node_kind(context.node_stack().PeekParseNode()) == - Parse::NodeKind::ReturnStatementStart) { - context.node_stack() - .PopAndDiscardSoloParseNode(); - - if (callable.return_type_id.is_valid()) { - // TODO: Add a note pointing at the return type's parse node. - CARBON_DIAGNOSTIC(ReturnStatementMissingExpr, Error, "Must return a {0}.", - std::string); - context.emitter() - .Build(parse_node, ReturnStatementMissingExpr, - context.sem_ir().StringifyType(callable.return_type_id)) - .Emit(); - } - - context.AddInst(SemIR::Return{parse_node}); - } else { - auto arg = context.node_stack().PopExpr(); - context.node_stack() - .PopAndDiscardSoloParseNode(); - - if (!callable.return_type_id.is_valid()) { - CARBON_DIAGNOSTIC( - ReturnStatementDisallowExpr, Error, - "No return expression should be provided in this context."); - CARBON_DIAGNOSTIC(ReturnStatementImplicitNote, Note, - "There was no return type provided."); - context.emitter() - .Build(parse_node, ReturnStatementDisallowExpr) - .Note(fn_inst.parse_node, ReturnStatementImplicitNote) - .Emit(); - } else if (callable.return_slot_id.is_valid()) { - arg = Initialize(context, parse_node, callable.return_slot_id, arg); - } else { - arg = ConvertToValueOfType(context, parse_node, arg, - callable.return_type_id); - } - - context.AddInst(SemIR::ReturnExpr{parse_node, arg}); - } - - // Switch to a new, unreachable, empty instruction block. This typically won't - // contain any semantics IR, but it can do if there are statements following - // the `return` statement. - context.inst_block_stack().Pop(); - context.inst_block_stack().PushUnreachable(); - return true; -} - -auto HandleReturnStatementStart(Context& context, Parse::Node parse_node) - -> bool { - // No action, just a bracketing node. - context.node_stack().Push(parse_node); - return true; -} - -} // namespace Carbon::Check diff --git a/toolchain/check/handle_variable.cpp b/toolchain/check/handle_variable.cpp index 437f48b2b1b8..00d1108e8d7d 100644 --- a/toolchain/check/handle_variable.cpp +++ b/toolchain/check/handle_variable.cpp @@ -8,6 +8,26 @@ namespace Carbon::Check { +auto HandleVariableIntroducer(Context& context, Parse::Node parse_node) + -> bool { + // No action, just a bracketing node. + context.node_stack().Push(parse_node); + return true; +} + +auto HandleReturnedSpecifier(Context& context, Parse::Node parse_node) -> bool { + // No action, just a bracketing node. + context.node_stack().Push(parse_node); + return true; +} + +auto HandleVariableInitializer(Context& context, Parse::Node parse_node) + -> bool { + // No action, just a bracketing node. + context.node_stack().Push(parse_node); + return true; +} + auto HandleVariableDecl(Context& context, Parse::Node parse_node) -> bool { // Handle the optional initializer. auto init_id = SemIR::InstId::Invalid; @@ -32,17 +52,21 @@ auto HandleVariableDecl(Context& context, Parse::Node parse_node) -> bool { value_id = bind_name->value_id; } + // Pop the `returned` specifier if present. + context.node_stack() + .PopAndDiscardSoloParseNodeIf(); + // If there was an initializer, assign it to the storage. if (has_init) { - if (context.insts().Get(value_id).Is()) { + if (context.GetCurrentScopeAs()) { + // TODO: In a class scope, we should instead save the initializer + // somewhere so that we can use it as a default. + context.TODO(parse_node, "Field initializer"); + } else { init_id = Initialize(context, parse_node, value_id, init_id); // TODO: Consider using different instruction kinds for assignment versus // initialization. context.AddInst(SemIR::Assign{parse_node, value_id, init_id}); - } else { - // TODO: In a class scope, we should instead save the initializer - // somewhere so that we can use it as a default. - context.TODO(parse_node, "Field initializer"); } } @@ -52,18 +76,4 @@ auto HandleVariableDecl(Context& context, Parse::Node parse_node) -> bool { return true; } -auto HandleVariableIntroducer(Context& context, Parse::Node parse_node) - -> bool { - // No action, just a bracketing node. - context.node_stack().Push(parse_node); - return true; -} - -auto HandleVariableInitializer(Context& context, Parse::Node parse_node) - -> bool { - // No action, just a bracketing node. - context.node_stack().Push(parse_node); - return true; -} - } // namespace Carbon::Check diff --git a/toolchain/check/node_stack.h b/toolchain/check/node_stack.h index fece0099c822..3ae98745a109 100644 --- a/toolchain/check/node_stack.h +++ b/toolchain/check/node_stack.h @@ -100,6 +100,17 @@ class NodeStack { PopForSoloParseNode(); } + // Pops the top of the stack if it is the given kind. Returns `true` if a node + // was popped. + template + auto PopAndDiscardSoloParseNodeIf() -> bool { + if (!PeekIs()) { + return false; + } + PopForSoloParseNode(); + return true; + } + // Pops an expression from the top of the stack and returns the parse_node and // the ID. auto PopExprWithParseNode() -> std::pair { @@ -327,7 +338,9 @@ class NodeStack { case Parse::NodeKind::ParameterListStart: case Parse::NodeKind::ParenExprOrTupleLiteralStart: case Parse::NodeKind::QualifiedDecl: + case Parse::NodeKind::ReturnedSpecifier: case Parse::NodeKind::ReturnStatementStart: + case Parse::NodeKind::ReturnVarSpecifier: case Parse::NodeKind::SelfValueName: case Parse::NodeKind::StructLiteralOrStructTypeLiteralStart: case Parse::NodeKind::VariableInitializer: diff --git a/toolchain/check/return.cpp b/toolchain/check/return.cpp new file mode 100644 index 000000000000..696ad090cd7c --- /dev/null +++ b/toolchain/check/return.cpp @@ -0,0 +1,180 @@ +// 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 + +#include "toolchain/check/context.h" +#include "toolchain/check/convert.h" +#include "toolchain/parse/tree.h" +#include "toolchain/sem_ir/inst.h" + +namespace Carbon::Check { + +// Gets the function that lexically encloses the current location. +static auto GetCurrentFunction(Context& context) -> SemIR::Function& { + CARBON_CHECK(!context.return_scope_stack().empty()) + << "Handling return but not in a function"; + auto function_id = context.insts() + .GetAs( + context.return_scope_stack().back().decl_id) + .function_id; + return context.functions().Get(function_id); +} + +// Gets the currently in scope `returned var`, if any, that would be returned +// by a `return var;`. +static auto GetCurrentReturnedVar(Context& context) -> SemIR::InstId { + CARBON_CHECK(!context.return_scope_stack().empty()) + << "Handling return but not in a function"; + return context.return_scope_stack().back().returned_var; +} + +// Produces a note that the given function has no explicit return type. +static auto NoteNoReturnTypeProvided(Context& context, + Context::DiagnosticBuilder& diag, + const SemIR::Function& function) { + CARBON_DIAGNOSTIC(ReturnTypeOmittedNote, Note, + "There was no return type provided."); + diag.Note(context.insts().Get(function.declaration_id).parse_node(), + ReturnTypeOmittedNote); +} + +// Produces a note describing the return type of the given function. +static auto NoteReturnType(Context& context, Context::DiagnosticBuilder& diag, + const SemIR::Function& function) { + // TODO: This is the location of the `fn` keyword. Find the location of the + // return type. + auto type_parse_node = + context.insts().Get(function.declaration_id).parse_node(); + CARBON_DIAGNOSTIC(ReturnTypeHereNote, Note, + "Return type of function is `{0}`.", std::string); + diag.Note(type_parse_node, ReturnTypeHereNote, + context.sem_ir().StringifyType(function.return_type_id, true)); +} + +// Produces a note pointing at the currently in scope `returned var`. +static auto NoteReturnedVar(Context& context, Context::DiagnosticBuilder& diag, + SemIR::InstId returned_var_id) { + CARBON_DIAGNOSTIC(ReturnedVarHere, Note, "`returned var` was declared here."); + diag.Note(context.insts().Get(returned_var_id).parse_node(), ReturnedVarHere); +} + +auto CheckReturnedVar(Context& context, Parse::Node returned_node, + Parse::Node name_node, SemIR::NameId name_id, + Parse::Node type_node, SemIR::TypeId type_id) + -> SemIR::InstId { + // A `returned var` requires an explicit return type. + auto& function = GetCurrentFunction(context); + if (!function.return_type_id.is_valid()) { + CARBON_DIAGNOSTIC(ReturnedVarWithNoReturnType, Error, + "Cannot declare a `returned var` in this function."); + auto diag = + context.emitter().Build(returned_node, ReturnedVarWithNoReturnType); + NoteNoReturnTypeProvided(context, diag, function); + diag.Emit(); + return SemIR::InstId::BuiltinError; + } + + // The declared type of the var must match the return type of the function. + if (function.return_type_id != type_id) { + CARBON_DIAGNOSTIC(ReturnedVarWrongType, Error, + "Type `{0}` of `returned var` does not match " + "return type of enclosing function.", + std::string); + auto diag = + context.emitter().Build(type_node, ReturnedVarWrongType, + context.sem_ir().StringifyType(type_id, true)); + NoteReturnType(context, diag, function); + diag.Emit(); + return SemIR::InstId::BuiltinError; + } + + // The variable aliases the return slot if there is one. If not, it has its + // own storage. + if (function.return_slot_id.is_valid()) { + return function.return_slot_id; + } + return context.AddInst(SemIR::VarStorage{name_node, type_id, name_id}); +} + +auto RegisterReturnedVar(Context& context, SemIR::InstId bind_id) -> void { + auto existing_id = context.SetReturnedVarOrGetExisting(bind_id); + if (existing_id.is_valid()) { + CARBON_DIAGNOSTIC(ReturnedVarShadowed, Error, + "Cannot declare a `returned var` in the scope of " + "another `returned var`."); + auto diag = context.emitter().Build( + context.insts().Get(bind_id).parse_node(), ReturnedVarShadowed); + NoteReturnedVar(context, diag, existing_id); + diag.Emit(); + } +} + +auto BuildReturnWithNoExpr(Context& context, Parse::Node parse_node) -> void { + const auto& function = GetCurrentFunction(context); + + if (function.return_type_id.is_valid()) { + CARBON_DIAGNOSTIC(ReturnStatementMissingExpr, Error, + "Missing return value.", std::string); + auto diag = context.emitter().Build( + parse_node, ReturnStatementMissingExpr, + context.sem_ir().StringifyType(function.return_type_id)); + NoteReturnType(context, diag, function); + diag.Emit(); + } + + context.AddInst(SemIR::Return{parse_node}); +} + +auto BuildReturnWithExpr(Context& context, Parse::Node parse_node, + SemIR::InstId expr_id) -> void { + const auto& function = GetCurrentFunction(context); + auto returned_var_id = GetCurrentReturnedVar(context); + + if (!function.return_type_id.is_valid()) { + CARBON_DIAGNOSTIC( + ReturnStatementDisallowExpr, Error, + "No return expression should be provided in this context."); + auto diag = + context.emitter().Build(parse_node, ReturnStatementDisallowExpr); + NoteNoReturnTypeProvided(context, diag, function); + diag.Emit(); + expr_id = SemIR::InstId::BuiltinError; + } else if (returned_var_id.is_valid()) { + CARBON_DIAGNOSTIC( + ReturnExprWithReturnedVar, Error, + "Can only `return var;` in the scope of a `returned var`."); + auto diag = context.emitter().Build(parse_node, ReturnExprWithReturnedVar); + NoteReturnedVar(context, diag, returned_var_id); + diag.Emit(); + expr_id = SemIR::InstId::BuiltinError; + } else if (function.return_slot_id.is_valid()) { + expr_id = Initialize(context, parse_node, function.return_slot_id, expr_id); + } else { + expr_id = ConvertToValueOfType(context, parse_node, expr_id, + function.return_type_id); + } + + context.AddInst(SemIR::ReturnExpr{parse_node, expr_id}); +} + +auto BuildReturnVar(Context& context, Parse::Node parse_node) -> void { + const auto& function = GetCurrentFunction(context); + auto returned_var_id = GetCurrentReturnedVar(context); + + if (!returned_var_id.is_valid()) { + CARBON_DIAGNOSTIC(ReturnVarWithNoReturnedVar, Error, + "`return var;` with no `returned var` in scope."); + context.emitter().Emit(parse_node, ReturnVarWithNoReturnedVar); + returned_var_id = SemIR::InstId::BuiltinError; + } + + if (!function.return_slot_id.is_valid()) { + // If we don't have a return slot, we're returning by value. Convert to a + // value expression. + returned_var_id = ConvertToValueExpr(context, returned_var_id); + } + + context.AddInst(SemIR::ReturnExpr{parse_node, returned_var_id}); +} + +} // namespace Carbon::Check diff --git a/toolchain/check/return.h b/toolchain/check/return.h new file mode 100644 index 000000000000..b00306d7eb1b --- /dev/null +++ b/toolchain/check/return.h @@ -0,0 +1,36 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_CHECK_RETURN_H_ +#define CARBON_TOOLCHAIN_CHECK_RETURN_H_ + +#include "toolchain/check/context.h" +#include "toolchain/parse/tree.h" +#include "toolchain/sem_ir/inst.h" + +namespace Carbon::Check { + +// Checks a `returned var` binding and returns the location of the return +// value storage that the name should bind to. +auto CheckReturnedVar(Context& context, Parse::Node returned_node, + Parse::Node name_node, SemIR::NameId name_id, + Parse::Node type_node, SemIR::TypeId type_id) + -> SemIR::InstId; + +// Registers the given binding as the current `returned var` in this scope. +auto RegisterReturnedVar(Context& context, SemIR::InstId bind_id) -> void; + +// Checks and builds SemIR for a `return;` statement. +auto BuildReturnWithNoExpr(Context& context, Parse::Node parse_node) -> void; + +// Checks and builds SemIR for a `return ;` statement. +auto BuildReturnWithExpr(Context& context, Parse::Node parse_node, + SemIR::InstId expr_id) -> void; + +// Checks and builds SemIR for a `return var;` statement. +auto BuildReturnVar(Context& context, Parse::Node parse_node) -> void; + +} // namespace Carbon::Check + +#endif // CARBON_TOOLCHAIN_CHECK_RETURN_H_ diff --git a/toolchain/check/testdata/return/fail_return_var_no_returned_var.carbon b/toolchain/check/testdata/return/fail_return_var_no_returned_var.carbon new file mode 100644 index 000000000000..e50661ba6a48 --- /dev/null +++ b/toolchain/check/testdata/return/fail_return_var_no_returned_var.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 +// +// AUTOUPDATE + +fn Procedure() -> i32 { + // CHECK:STDERR: fail_return_var_no_returned_var.carbon:[[@LINE+3]]:13: ERROR: `return var;` with no `returned var` in scope. + // CHECK:STDERR: return var; + // CHECK:STDERR: ^ + return var; +} + +// CHECK:STDOUT: file "fail_return_var_no_returned_var.carbon" { +// CHECK:STDOUT: %Procedure: = fn_decl @Procedure +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Procedure() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/return/fail_return_with_returned_var.carbon b/toolchain/check/testdata/return/fail_return_with_returned_var.carbon new file mode 100644 index 000000000000..b0c9410e534a --- /dev/null +++ b/toolchain/check/testdata/return/fail_return_with_returned_var.carbon @@ -0,0 +1,81 @@ +// 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 + +fn F() -> i32 { + returned var v: i32 = 0; + // CHECK:STDERR: fail_return_with_returned_var.carbon:[[@LINE+6]]:11: ERROR: Can only `return var;` in the scope of a `returned var`. + // CHECK:STDERR: return 1; + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_return_with_returned_var.carbon:[[@LINE-4]]:16: `returned var` was declared here. + // CHECK:STDERR: returned var v: i32 = 0; + // CHECK:STDERR: ^ + return 1; +} + +class C { var a: i32; var b: i32; } +fn G() -> C { + returned var c: C = {.a = 1, .b = 2}; + // CHECK:STDERR: fail_return_with_returned_var.carbon:[[@LINE+6]]:11: ERROR: Can only `return var;` in the scope of a `returned var`. + // CHECK:STDERR: return c; + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_return_with_returned_var.carbon:[[@LINE-4]]:16: `returned var` was declared here. + // CHECK:STDERR: returned var c: C = {.a = 1, .b = 2}; + // CHECK:STDERR: ^ + return c; +} + +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc18_35.1: type = struct_type {.a: i32, .b: i32} +// CHECK:STDOUT: %.loc18_35.2: type = ptr_type {.a: i32, .b: i32} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file "fail_return_with_returned_var.carbon" { +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: class_decl @C, () +// CHECK:STDOUT: %C: type = class_type @C +// CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %.loc18_16.1: type = unbound_field_type C, i32 +// CHECK:STDOUT: %.loc18_16.2: = field a, member0 +// CHECK:STDOUT: %a: = bind_name a, %.loc18_16.2 +// CHECK:STDOUT: %.loc18_28.1: type = unbound_field_type C, i32 +// CHECK:STDOUT: %.loc18_28.2: = field b, member1 +// CHECK:STDOUT: %b: = bind_name b, %.loc18_28.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .a = %a +// CHECK:STDOUT: .b = %b +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %v.var: ref i32 = var v +// CHECK:STDOUT: %v: ref i32 = bind_name v, %v.var +// CHECK:STDOUT: %.loc8: i32 = int_literal 0 +// CHECK:STDOUT: assign %v.var, %.loc8 +// CHECK:STDOUT: %.loc15: i32 = int_literal 1 +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() -> %return: C { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %C.ref: type = name_reference C, file.%C +// CHECK:STDOUT: %c: ref C = bind_name c, %return +// CHECK:STDOUT: %.loc20_29: i32 = int_literal 1 +// CHECK:STDOUT: %.loc20_37: i32 = int_literal 2 +// CHECK:STDOUT: %.loc20_38.1: {.a: i32, .b: i32} = struct_literal (%.loc20_29, %.loc20_37) +// CHECK:STDOUT: %.loc20_38.2: ref i32 = class_field_access %return, member0 +// CHECK:STDOUT: %.loc20_38.3: init i32 = initialize_from %.loc20_29 to %.loc20_38.2 +// CHECK:STDOUT: %.loc20_38.4: ref i32 = class_field_access %return, member1 +// CHECK:STDOUT: %.loc20_38.5: init i32 = initialize_from %.loc20_37 to %.loc20_38.4 +// CHECK:STDOUT: %.loc20_38.6: init C = class_init (%.loc20_38.3, %.loc20_38.5), %return +// CHECK:STDOUT: %.loc20_38.7: init C = converted %.loc20_38.1, %.loc20_38.6 +// CHECK:STDOUT: assign %return, %.loc20_38.7 +// CHECK:STDOUT: %c.ref: ref C = name_reference c, %c +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/return/fail_returned_var_no_return_type.carbon b/toolchain/check/testdata/return/fail_returned_var_no_return_type.carbon new file mode 100644 index 000000000000..c813dc525368 --- /dev/null +++ b/toolchain/check/testdata/return/fail_returned_var_no_return_type.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 + +fn Procedure() { + // CHECK:STDERR: fail_returned_var_no_return_type.carbon:[[@LINE+6]]:3: ERROR: Cannot declare a `returned var` in this function. + // CHECK:STDERR: returned var v: () = (); + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_returned_var_no_return_type.carbon:[[@LINE-4]]:1: There was no return type provided. + // CHECK:STDERR: fn Procedure() { + // CHECK:STDERR: ^ + returned var v: () = (); + return; +} + +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc14: type = tuple_type () +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file "fail_returned_var_no_return_type.carbon" { +// CHECK:STDOUT: %Procedure: = fn_decl @Procedure +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Procedure() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc14_20.1: () = tuple_literal () +// CHECK:STDOUT: %.loc14_20.2: type = converted %.loc14_20.1, constants.%.loc14 +// CHECK:STDOUT: %v: () = bind_name v, +// CHECK:STDOUT: %.loc14_25: () = tuple_literal () +// CHECK:STDOUT: assign , +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/return/fail_returned_var_shadow.carbon b/toolchain/check/testdata/return/fail_returned_var_shadow.carbon new file mode 100644 index 000000000000..f2b3054b588e --- /dev/null +++ b/toolchain/check/testdata/return/fail_returned_var_shadow.carbon @@ -0,0 +1,89 @@ +// 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 + +fn SameScope() -> i32 { + if (true) { + returned var v: i32 = 0; + // CHECK:STDERR: fail_returned_var_shadow.carbon:[[@LINE+6]]:18: ERROR: Cannot declare a `returned var` in the scope of another `returned var`. + // CHECK:STDERR: returned var w: i32 = 1; + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_returned_var_shadow.carbon:[[@LINE-4]]:18: `returned var` was declared here. + // CHECK:STDERR: returned var v: i32 = 0; + // CHECK:STDERR: ^ + returned var w: i32 = 1; + } + return 0; +} + +fn DifferentScopes() -> i32 { + if (true) { + returned var v: i32 = 0; + if (true) { + // CHECK:STDERR: fail_returned_var_shadow.carbon:[[@LINE+6]]:20: ERROR: Cannot declare a `returned var` in the scope of another `returned var`. + // CHECK:STDERR: returned var w: i32 = 1; + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_returned_var_shadow.carbon:[[@LINE-5]]:18: `returned var` was declared here. + // CHECK:STDERR: returned var v: i32 = 0; + // CHECK:STDERR: ^ + returned var w: i32 = 1; + } + } + return 0; +} + +// CHECK:STDOUT: file "fail_returned_var_shadow.carbon" { +// CHECK:STDOUT: %SameScope: = fn_decl @SameScope +// CHECK:STDOUT: %DifferentScopes: = fn_decl @DifferentScopes +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @SameScope() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc8: bool = bool_literal true +// CHECK:STDOUT: if %.loc8 br !if.then else br !if.else +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then: +// CHECK:STDOUT: %v.var: ref i32 = var v +// CHECK:STDOUT: %v: ref i32 = bind_name v, %v.var +// CHECK:STDOUT: %.loc9: i32 = int_literal 0 +// CHECK:STDOUT: assign %v.var, %.loc9 +// CHECK:STDOUT: %w.var: ref i32 = var w +// CHECK:STDOUT: %w: ref i32 = bind_name w, %w.var +// CHECK:STDOUT: %.loc16: i32 = int_literal 1 +// CHECK:STDOUT: assign %w.var, %.loc16 +// CHECK:STDOUT: br !if.else +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else: +// CHECK:STDOUT: %.loc18: i32 = int_literal 0 +// CHECK:STDOUT: return %.loc18 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @DifferentScopes() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc22: bool = bool_literal true +// CHECK:STDOUT: if %.loc22 br !if.then.loc22 else br !if.else.loc22 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then.loc22: +// CHECK:STDOUT: %v.var: ref i32 = var v +// CHECK:STDOUT: %v: ref i32 = bind_name v, %v.var +// CHECK:STDOUT: %.loc23: i32 = int_literal 0 +// CHECK:STDOUT: assign %v.var, %.loc23 +// CHECK:STDOUT: %.loc24: bool = bool_literal true +// CHECK:STDOUT: if %.loc24 br !if.then.loc24 else br !if.else.loc24 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then.loc24: +// CHECK:STDOUT: %w.var: ref i32 = var w +// CHECK:STDOUT: %w: ref i32 = bind_name w, %w.var +// CHECK:STDOUT: %.loc31: i32 = int_literal 1 +// CHECK:STDOUT: assign %w.var, %.loc31 +// CHECK:STDOUT: br !if.else.loc24 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else.loc24: +// CHECK:STDOUT: br !if.else.loc22 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else.loc22: +// CHECK:STDOUT: %.loc34: i32 = int_literal 0 +// CHECK:STDOUT: return %.loc34 +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/return/fail_returned_var_type.carbon b/toolchain/check/testdata/return/fail_returned_var_type.carbon new file mode 100644 index 000000000000..952e9ef1f334 --- /dev/null +++ b/toolchain/check/testdata/return/fail_returned_var_type.carbon @@ -0,0 +1,28 @@ +// 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 + +fn Mismatch() -> i32 { + // CHECK:STDERR: fail_returned_var_type.carbon:[[@LINE+6]]:19: ERROR: Type `f64` of `returned var` does not match return type of enclosing function. + // CHECK:STDERR: returned var v: f64 = 0.0; + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_returned_var_type.carbon:[[@LINE-4]]:1: Return type of function is `i32`. + // CHECK:STDERR: fn Mismatch() -> i32 { + // CHECK:STDERR: ^ + returned var v: f64 = 0.0; + return var; +} + +// CHECK:STDOUT: file "fail_returned_var_type.carbon" { +// CHECK:STDOUT: %Mismatch: = fn_decl @Mismatch +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Mismatch() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %v: f64 = bind_name v, +// CHECK:STDOUT: %.loc14: f64 = real_literal 0e-1 +// CHECK:STDOUT: assign , +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/return/fail_value_disallowed.carbon b/toolchain/check/testdata/return/fail_value_disallowed.carbon index da6570ee069d..b24ffd62f330 100644 --- a/toolchain/check/testdata/return/fail_value_disallowed.carbon +++ b/toolchain/check/testdata/return/fail_value_disallowed.carbon @@ -21,5 +21,5 @@ fn Main() { // CHECK:STDOUT: fn @Main() { // CHECK:STDOUT: !entry: // CHECK:STDOUT: %.loc14: i32 = int_literal 0 -// CHECK:STDOUT: return %.loc14 +// CHECK:STDOUT: return // CHECK:STDOUT: } diff --git a/toolchain/check/testdata/return/fail_value_missing.carbon b/toolchain/check/testdata/return/fail_value_missing.carbon index 53777ce7caf4..539a60fbb8a7 100644 --- a/toolchain/check/testdata/return/fail_value_missing.carbon +++ b/toolchain/check/testdata/return/fail_value_missing.carbon @@ -5,9 +5,12 @@ // AUTOUPDATE fn Main() -> i32 { - // CHECK:STDERR: fail_value_missing.carbon:[[@LINE+3]]:9: ERROR: Must return a i32. + // CHECK:STDERR: fail_value_missing.carbon:[[@LINE+6]]:9: ERROR: Missing return value. // CHECK:STDERR: return; // CHECK:STDERR: ^ + // CHECK:STDERR: fail_value_missing.carbon:[[@LINE-4]]:1: Return type of function is `i32`. + // CHECK:STDERR: fn Main() -> i32 { + // CHECK:STDERR: ^ return; } diff --git a/toolchain/check/testdata/return/returned_var.carbon b/toolchain/check/testdata/return/returned_var.carbon new file mode 100644 index 000000000000..029bf8e8b60d --- /dev/null +++ b/toolchain/check/testdata/return/returned_var.carbon @@ -0,0 +1,72 @@ +// 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 + +class C { + var a: i32; + var b: i32; +} + +fn F() -> C { + returned var result: C = {.a = 1, .b = 2}; + return var; +} + +fn G() -> i32 { + returned var result: i32 = 0; + return var; +} + +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %.loc10_1.1: type = struct_type {.a: i32, .b: i32} +// CHECK:STDOUT: %.loc10_1.2: type = ptr_type {.a: i32, .b: i32} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file "returned_var.carbon" { +// CHECK:STDOUT: class_decl @C, () +// CHECK:STDOUT: %C: type = class_type @C +// CHECK:STDOUT: %F: = fn_decl @F +// CHECK:STDOUT: %G: = fn_decl @G +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @C { +// CHECK:STDOUT: %.loc8_8.1: type = unbound_field_type C, i32 +// CHECK:STDOUT: %.loc8_8.2: = field a, member0 +// CHECK:STDOUT: %a: = bind_name a, %.loc8_8.2 +// CHECK:STDOUT: %.loc9_8.1: type = unbound_field_type C, i32 +// CHECK:STDOUT: %.loc9_8.2: = field b, member1 +// CHECK:STDOUT: %b: = bind_name b, %.loc9_8.2 +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .a = %a +// CHECK:STDOUT: .b = %b +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> %return: C { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %C.ref: type = name_reference C, file.%C +// CHECK:STDOUT: %result: ref C = bind_name result, %return +// CHECK:STDOUT: %.loc13_34: i32 = int_literal 1 +// CHECK:STDOUT: %.loc13_42: i32 = int_literal 2 +// CHECK:STDOUT: %.loc13_43.1: {.a: i32, .b: i32} = struct_literal (%.loc13_34, %.loc13_42) +// CHECK:STDOUT: %.loc13_43.2: ref i32 = class_field_access %return, member0 +// CHECK:STDOUT: %.loc13_43.3: init i32 = initialize_from %.loc13_34 to %.loc13_43.2 +// CHECK:STDOUT: %.loc13_43.4: ref i32 = class_field_access %return, member1 +// CHECK:STDOUT: %.loc13_43.5: init i32 = initialize_from %.loc13_42 to %.loc13_43.4 +// CHECK:STDOUT: %.loc13_43.6: init C = class_init (%.loc13_43.3, %.loc13_43.5), %return +// CHECK:STDOUT: %.loc13_43.7: init C = converted %.loc13_43.1, %.loc13_43.6 +// CHECK:STDOUT: assign %return, %.loc13_43.7 +// CHECK:STDOUT: return %result +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %result.var: ref i32 = var result +// CHECK:STDOUT: %result: ref i32 = bind_name result, %result.var +// CHECK:STDOUT: %.loc18_30: i32 = int_literal 0 +// CHECK:STDOUT: assign %result.var, %.loc18_30 +// CHECK:STDOUT: %.loc18_16: i32 = bind_value %result +// CHECK:STDOUT: return %.loc18_16 +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/return/returned_var_scope.carbon b/toolchain/check/testdata/return/returned_var_scope.carbon new file mode 100644 index 000000000000..d578dfaad2d3 --- /dev/null +++ b/toolchain/check/testdata/return/returned_var_scope.carbon @@ -0,0 +1,79 @@ +// 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 + +fn UnrelatedScopes() -> i32 { + if (true) { + returned var v: i32 = 0; + } + if (true) { + returned var w: i32 = 1; + } + return 0; +} + +fn EnclosingButAfter(b: bool) -> i32 { + if (b) { + returned var v: i32 = 0; + return var; + } + returned var w: i32 = 1; + return var; +} + +// CHECK:STDOUT: file "returned_var_scope.carbon" { +// CHECK:STDOUT: %UnrelatedScopes: = fn_decl @UnrelatedScopes +// CHECK:STDOUT: %EnclosingButAfter: = fn_decl @EnclosingButAfter +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @UnrelatedScopes() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc8: bool = bool_literal true +// CHECK:STDOUT: if %.loc8 br !if.then.loc8 else br !if.else.loc8 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then.loc8: +// CHECK:STDOUT: %v.var: ref i32 = var v +// CHECK:STDOUT: %v: ref i32 = bind_name v, %v.var +// CHECK:STDOUT: %.loc9: i32 = int_literal 0 +// CHECK:STDOUT: assign %v.var, %.loc9 +// CHECK:STDOUT: br !if.else.loc8 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else.loc8: +// CHECK:STDOUT: %.loc11: bool = bool_literal true +// CHECK:STDOUT: if %.loc11 br !if.then.loc11 else br !if.else.loc11 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then.loc11: +// CHECK:STDOUT: %w.var: ref i32 = var w +// CHECK:STDOUT: %w: ref i32 = bind_name w, %w.var +// CHECK:STDOUT: %.loc12: i32 = int_literal 1 +// CHECK:STDOUT: assign %w.var, %.loc12 +// CHECK:STDOUT: br !if.else.loc11 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else.loc11: +// CHECK:STDOUT: %.loc14: i32 = int_literal 0 +// CHECK:STDOUT: return %.loc14 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @EnclosingButAfter(%b: bool) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %b.ref: bool = name_reference b, %b +// CHECK:STDOUT: if %b.ref br !if.then else br !if.else +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then: +// CHECK:STDOUT: %v.var: ref i32 = var v +// CHECK:STDOUT: %v: ref i32 = bind_name v, %v.var +// CHECK:STDOUT: %.loc19_27: i32 = int_literal 0 +// CHECK:STDOUT: assign %v.var, %.loc19_27 +// CHECK:STDOUT: %.loc19_18: i32 = bind_value %v +// CHECK:STDOUT: return %.loc19_18 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else: +// CHECK:STDOUT: %w.var: ref i32 = var w +// CHECK:STDOUT: %w: ref i32 = bind_name w, %w.var +// CHECK:STDOUT: %.loc22_25: i32 = int_literal 1 +// CHECK:STDOUT: assign %w.var, %.loc22_25 +// CHECK:STDOUT: %.loc22_16: i32 = bind_value %w +// CHECK:STDOUT: return %.loc22_16 +// CHECK:STDOUT: } diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index e90762251dfe..cb7697f786bc 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -70,6 +70,7 @@ CARBON_DIAGNOSTIC_KIND(ExpectedParenAfter) CARBON_DIAGNOSTIC_KIND(ExpectedExprSemi) CARBON_DIAGNOSTIC_KIND(ExpectedStatementSemi) CARBON_DIAGNOSTIC_KIND(ExpectedStructLiteralField) +CARBON_DIAGNOSTIC_KIND(ExpectedVarAfterReturned) CARBON_DIAGNOSTIC_KIND(ExpectedVariableDecl) CARBON_DIAGNOSTIC_KIND(ExpectedVariableName) CARBON_DIAGNOSTIC_KIND(OperatorRequiresParentheses) @@ -162,8 +163,15 @@ CARBON_DIAGNOSTIC_KIND(StructInitMissingFieldInLiteral) CARBON_DIAGNOSTIC_KIND(StructInitMissingFieldInConversion) CARBON_DIAGNOSTIC_KIND(TupleIndexIntegerLiteral) CARBON_DIAGNOSTIC_KIND(TupleInitElementCountMismatch) +CARBON_DIAGNOSTIC_KIND(ReturnedVarHere) +CARBON_DIAGNOSTIC_KIND(ReturnedVarShadowed) +CARBON_DIAGNOSTIC_KIND(ReturnedVarWithNoReturnType) +CARBON_DIAGNOSTIC_KIND(ReturnedVarWrongType) +CARBON_DIAGNOSTIC_KIND(ReturnExprWithReturnedVar) +CARBON_DIAGNOSTIC_KIND(ReturnVarWithNoReturnedVar) CARBON_DIAGNOSTIC_KIND(ReturnStatementDisallowExpr) -CARBON_DIAGNOSTIC_KIND(ReturnStatementImplicitNote) +CARBON_DIAGNOSTIC_KIND(ReturnTypeHereNote) +CARBON_DIAGNOSTIC_KIND(ReturnTypeOmittedNote) CARBON_DIAGNOSTIC_KIND(ReturnStatementMissingExpr) CARBON_DIAGNOSTIC_KIND(ImplicitAsConversionFailure) CARBON_DIAGNOSTIC_KIND(ExplicitAsConversionFailure) diff --git a/toolchain/lower/testdata/return/return_var.carbon b/toolchain/lower/testdata/return/return_var.carbon new file mode 100644 index 000000000000..4e94d07d2243 --- /dev/null +++ b/toolchain/lower/testdata/return/return_var.carbon @@ -0,0 +1,27 @@ +// 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 + +class C { + var data: i32; + var next: C*; +} + +fn Make() -> C { + returned var c: C; + c = {.data = 42, .next = &c}; + return var; +} + +// CHECK:STDOUT: ; ModuleID = 'return_var.carbon' +// CHECK:STDOUT: source_filename = "return_var.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: define void @Make(ptr sret({ i32, ptr }) %return) { +// CHECK:STDOUT: %data = getelementptr inbounds { i32, ptr }, ptr %return, i32 0, i32 0 +// CHECK:STDOUT: store i32 42, ptr %data, align 4 +// CHECK:STDOUT: %next = getelementptr inbounds { i32, ptr }, ptr %return, i32 0, i32 1 +// CHECK:STDOUT: store ptr %return, ptr %next, align 8 +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } diff --git a/toolchain/lower/testdata/return/return_var_byval.carbon b/toolchain/lower/testdata/return/return_var_byval.carbon new file mode 100644 index 000000000000..d811453a49e9 --- /dev/null +++ b/toolchain/lower/testdata/return/return_var_byval.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 + +fn Main() -> i32 { + returned var x: i32 = 0; + return var; +} + +// CHECK:STDOUT: ; ModuleID = 'return_var_byval.carbon' +// CHECK:STDOUT: source_filename = "return_var_byval.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: define i32 @Main() { +// CHECK:STDOUT: %x = alloca i32, align 4 +// CHECK:STDOUT: store i32 0, ptr %x, align 4 +// CHECK:STDOUT: %1 = load i32, ptr %x, align 4 +// CHECK:STDOUT: ret i32 %1 +// CHECK:STDOUT: } diff --git a/toolchain/parse/handle_declaration_scope_loop.cpp b/toolchain/parse/handle_declaration_scope_loop.cpp index 1685bf06da06..f53fe1818e07 100644 --- a/toolchain/parse/handle_declaration_scope_loop.cpp +++ b/toolchain/parse/handle_declaration_scope_loop.cpp @@ -78,7 +78,7 @@ auto HandleDeclScopeLoop(Context& context) -> void { break; } case Lex::TokenKind::Var: { - context.PushState(State::VarAsSemicolon); + context.PushState(State::VarAsDecl); break; } case Lex::TokenKind::Let: { diff --git a/toolchain/parse/handle_statement.cpp b/toolchain/parse/handle_statement.cpp index 74abd58e8f4a..40e41eb23dd0 100644 --- a/toolchain/parse/handle_statement.cpp +++ b/toolchain/parse/handle_statement.cpp @@ -38,8 +38,12 @@ auto HandleStatement(Context& context) -> void { context.PushState(State::StatementReturn); break; } + case Lex::TokenKind::Returned: { + context.PushState(State::VarAsReturned); + break; + } case Lex::TokenKind::Var: { - context.PushState(State::VarAsSemicolon); + context.PushState(State::VarAsDecl); break; } case Lex::TokenKind::While: { @@ -179,8 +183,15 @@ auto HandleStatementReturn(Context& context) -> void { context.PushState(state); context.AddLeafNode(NodeKind::ReturnStatementStart, context.Consume()); - if (!context.PositionIs(Lex::TokenKind::Semi)) { + + if (auto var_token = context.ConsumeIf(Lex::TokenKind::Var)) { + // `return var;` + context.AddLeafNode(NodeKind::ReturnVarSpecifier, *var_token); + } else if (!context.PositionIs(Lex::TokenKind::Semi)) { + // `return ;` context.PushState(State::Expr); + } else { + // `return;` } } diff --git a/toolchain/parse/handle_var.cpp b/toolchain/parse/handle_var.cpp index 3d8e031d5d7d..cf1e6c492912 100644 --- a/toolchain/parse/handle_var.cpp +++ b/toolchain/parse/handle_var.cpp @@ -6,22 +6,44 @@ namespace Carbon::Parse { -// Handles VarAs(Semicolon|For). -static auto HandleVar(Context& context, State finish_state) -> void { - context.PopAndDiscardState(); +// Handles VarAs(Decl|For). +static auto HandleVar(Context& context, State finish_state, + Lex::Token returned_token = Lex::Token::Invalid) -> void { + auto state = context.PopState(); + + // The finished variable declaration will start at the `var` or `returned`. + state.state = finish_state; + context.PushState(state); - // These will start at the `var`. - context.PushState(finish_state); context.PushState(State::VarAfterPattern); context.AddLeafNode(NodeKind::VariableIntroducer, context.Consume()); + if (returned_token.is_valid()) { + context.AddLeafNode(NodeKind::ReturnedSpecifier, returned_token); + } - // This will start at the pattern. context.PushState(State::PatternAsVariable); } -auto HandleVarAsSemicolon(Context& context) -> void { - HandleVar(context, State::VarFinishAsSemicolon); +auto HandleVarAsDecl(Context& context) -> void { + HandleVar(context, State::VarFinishAsDecl); +} + +auto HandleVarAsReturned(Context& context) -> void { + auto returned_token = context.Consume(); + + if (!context.PositionIs(Lex::TokenKind::Var)) { + CARBON_DIAGNOSTIC(ExpectedVarAfterReturned, Error, + "Expected `var` after `returned`."); + context.emitter().Emit(*context.position(), ExpectedVarAfterReturned); + auto semi = context.SkipPastLikelyEnd(returned_token); + context.AddLeafNode(NodeKind::EmptyDecl, semi ? *semi : returned_token, + /*has_error=*/true); + context.PopAndDiscardState(); + return; + } + + HandleVar(context, State::VarFinishAsDecl, returned_token); } auto HandleVarAsFor(Context& context) -> void { @@ -44,7 +66,7 @@ auto HandleVarAfterPattern(Context& context) -> void { } } -auto HandleVarFinishAsSemicolon(Context& context) -> void { +auto HandleVarFinishAsDecl(Context& context) -> void { auto state = context.PopState(); auto end_token = state.token; diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index bad9906aa5bb..417a0ed60ea8 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -223,8 +223,9 @@ CARBON_PARSE_NODE_KIND_BRACKET(LetDecl, LetIntroducer, CARBON_TOKEN(Semi) CARBON_IF_ERROR(CARBON_TOKEN(Let))) -// `var`: +// `var` and `returned var`: // VariableIntroducer +// _optional_ ReturnedSpecifier // _external_: PatternBinding // _optional_ VariableInitializer // _optional_ _external_: expression @@ -232,11 +233,15 @@ CARBON_PARSE_NODE_KIND_BRACKET(LetDecl, LetIntroducer, // // The VariableInitializer and following expression are paired: either both will // be present, or neither will. -CARBON_PARSE_NODE_KIND_CHILD_COUNT(VariableIntroducer, 0, CARBON_TOKEN(Var)) +CARBON_PARSE_NODE_KIND_CHILD_COUNT(VariableIntroducer, 0, + CARBON_TOKEN(Var) + CARBON_IF_ERROR(CARBON_TOKEN(Returned))) +CARBON_PARSE_NODE_KIND_CHILD_COUNT(ReturnedSpecifier, 0, CARBON_TOKEN(Returned)) CARBON_PARSE_NODE_KIND_CHILD_COUNT(VariableInitializer, 0, CARBON_TOKEN(Equal)) CARBON_PARSE_NODE_KIND_BRACKET(VariableDecl, VariableIntroducer, CARBON_TOKEN(Semi) - CARBON_IF_ERROR(CARBON_TOKEN(Var))) + CARBON_IF_ERROR(CARBON_TOKEN(Var) + CARBON_TOKEN(Returned))) // An expression statement: // _external_: expression @@ -262,10 +267,11 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(ContinueStatement, 1, // `return`: // ReturnStatementStart -// _optional_ _external_: expression +// _optional_ ReturnVarSpecifier or _external_: expression // ReturnStatement CARBON_PARSE_NODE_KIND_CHILD_COUNT(ReturnStatementStart, 0, CARBON_TOKEN(Return)) +CARBON_PARSE_NODE_KIND_CHILD_COUNT(ReturnVarSpecifier, 0, CARBON_TOKEN(Var)) CARBON_PARSE_NODE_KIND_BRACKET(ReturnStatement, ReturnStatementStart, CARBON_TOKEN(Semi) CARBON_IF_ERROR(CARBON_TOKEN(Return))) diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index 39e57fa454b9..8258ae53125b 100644 --- a/toolchain/parse/state.def +++ b/toolchain/parse/state.def @@ -229,7 +229,7 @@ CARBON_PARSE_STATE(DeclNameAndParamsAfterImplicit) // If `Semi`: // 1. DeclScopeLoop // If `Var`: -// 1. VarAsSemicolon +// 1. VarAsDecl // 2. DeclScopeLoop // If `Let`: // 1. Let @@ -568,7 +568,9 @@ CARBON_PARSE_STATE_VARIANTS2(PatternFinish, Generic, Regular) // If `Return`: // 1. StatementReturn // If `Var`: -// 1. VarAsSemicolon +// 1. VarAsDecl +// If `Returned`: +// 1. VarAsReturned // If `While`: // 1. StatementWhile // Else: @@ -660,6 +662,8 @@ CARBON_PARSE_STATE(StatementIfElseBlockFinish) // // If `Semi`: // 1. StatementReturnFinish +// If `Var`: +// 1. StatementReturnFinish // Else: // 1. Expr // 2. StatementReturnFinish @@ -725,13 +729,17 @@ CARBON_PARSE_STATE_VARIANTS3(TypeIntroducer, Class, Interface, NamedConstraint) // (state done) CARBON_PARSE_STATE_VARIANTS3(TypeAfterParams, Class, Interface, NamedConstraint) -// Handles the start of a `var`. +// Handles the start of a `var` or `returned var`. // -// Always: +// On error: +// (state done) +// Else: // 1. PatternAsVariable // 2. VarAfterPattern -// 3. VarFinishAs(Semicolon|For) -CARBON_PARSE_STATE_VARIANTS2(Var, Semicolon, For) +// 3. VarFinishAs(Decl|For) +// +// VarAsReturned uses VarFinishAsDecl. +CARBON_PARSE_STATE_VARIANTS3(Var, Decl, Returned, For) // Handles `var` after the pattern, either followed by an initializer or the // semicolon. @@ -746,7 +754,7 @@ CARBON_PARSE_STATE(VarAfterPattern) // // Always: // (state done) -CARBON_PARSE_STATE_VARIANTS2(VarFinish, Semicolon, For) +CARBON_PARSE_STATE_VARIANTS2(VarFinish, Decl, For) // Handles the start of a `let`. // diff --git a/toolchain/parse/testdata/for/fail_returned_var.carbon b/toolchain/parse/testdata/for/fail_returned_var.carbon new file mode 100644 index 000000000000..f9186f902bf6 --- /dev/null +++ b/toolchain/parse/testdata/for/fail_returned_var.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 + +fn foo() -> i32 { + // TODO: Should we allow this? + // CHECK:STDERR: fail_returned_var.carbon:[[@LINE+3]]:8: ERROR: Expected `var` declaration. + // CHECK:STDERR: for (returned var x: i32 in y) { + // CHECK:STDERR: ^ + for (returned var x: i32 in y) { + return var; + } +} + +// CHECK:STDOUT: - filename: fail_returned_var.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'Name', text: 'foo'}, +// CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ParameterList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'Literal', text: 'i32'}, +// CHECK:STDOUT: {kind: 'ReturnType', text: '->', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'ForHeaderStart', text: '('}, +// CHECK:STDOUT: {kind: 'NameExpr', text: 'y'}, +// CHECK:STDOUT: {kind: 'ForHeader', text: ')', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'CodeBlockStart', text: '{'}, +// CHECK:STDOUT: {kind: 'ReturnStatementStart', text: 'return'}, +// CHECK:STDOUT: {kind: 'ReturnVarSpecifier', text: 'var'}, +// CHECK:STDOUT: {kind: 'ReturnStatement', text: ';', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'CodeBlock', text: '}', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ForStatement', text: 'for', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 17}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/return/fail_returned_no_var.carbon b/toolchain/parse/testdata/return/fail_returned_no_var.carbon new file mode 100644 index 000000000000..ceba1cf7ee11 --- /dev/null +++ b/toolchain/parse/testdata/return/fail_returned_no_var.carbon @@ -0,0 +1,27 @@ +// 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 + +fn F() -> String { + // CHECK:STDERR: fail_returned_no_var.carbon:[[@LINE+3]]:12: ERROR: Expected `var` after `returned`. + // CHECK:STDERR: returned fn G() -> i32; + // CHECK:STDERR: ^ + returned fn G() -> i32; +} + +// CHECK:STDOUT: - filename: fail_returned_no_var.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'Name', text: 'F'}, +// CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ParameterList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'Literal', text: 'String'}, +// CHECK:STDOUT: {kind: 'ReturnType', text: '->', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'EmptyDecl', text: ';', has_error: yes}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/return/fail_var_no_semi.carbon b/toolchain/parse/testdata/return/fail_var_no_semi.carbon new file mode 100644 index 000000000000..c5a73048e0d2 --- /dev/null +++ b/toolchain/parse/testdata/return/fail_var_no_semi.carbon @@ -0,0 +1,27 @@ +// 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 + +fn F() { + return var +// CHECK:STDERR: fail_var_no_semi.carbon:[[@LINE+3]]:1: ERROR: `return` statements must end with a `;`. +// CHECK:STDERR: } +// CHECK:STDERR: ^ +} + +// CHECK:STDOUT: - filename: fail_var_no_semi.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'Name', text: 'F'}, +// CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ParameterList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'ReturnStatementStart', text: 'return'}, +// CHECK:STDOUT: {kind: 'ReturnVarSpecifier', text: 'var'}, +// CHECK:STDOUT: {kind: 'ReturnStatement', text: 'return', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/return/returned_var.carbon b/toolchain/parse/testdata/return/returned_var.carbon new file mode 100644 index 000000000000..dc6e8da45354 --- /dev/null +++ b/toolchain/parse/testdata/return/returned_var.carbon @@ -0,0 +1,35 @@ +// 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 + +fn F() -> String { + returned var s: String = "hello"; + return var; +} + +// CHECK:STDOUT: - filename: returned_var.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'Name', text: 'F'}, +// CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ParameterList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'Literal', text: 'String'}, +// CHECK:STDOUT: {kind: 'ReturnType', text: '->', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'}, +// CHECK:STDOUT: {kind: 'ReturnedSpecifier', text: 'returned'}, +// CHECK:STDOUT: {kind: 'Name', text: 's'}, +// CHECK:STDOUT: {kind: 'Literal', text: 'String'}, +// CHECK:STDOUT: {kind: 'PatternBinding', text: ':', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, +// CHECK:STDOUT: {kind: 'Literal', text: '"hello"'}, +// CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'ReturnStatementStart', text: 'return'}, +// CHECK:STDOUT: {kind: 'ReturnVarSpecifier', text: 'var'}, +// CHECK:STDOUT: {kind: 'ReturnStatement', text: ';', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 19}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/sem_ir/file.h b/toolchain/sem_ir/file.h index d376fa45532f..9ed613b4b5fe 100644 --- a/toolchain/sem_ir/file.h +++ b/toolchain/sem_ir/file.h @@ -37,6 +37,8 @@ struct Function : public Printable { // The function name. NameId name_id; + // The first declaration of the function. This is a FunctionDeclaration. + InstId declaration_id = InstId::Invalid; // The definition, if the function has been defined or is currently being // defined. This is a FunctionDecl. InstId definition_id = InstId::Invalid;