From 1c69fe344771bd6a21e4d744e64851ffd13cbd4f Mon Sep 17 00:00:00 2001 From: David Blaikie Date: Wed, 14 Oct 2026 23:19:16 +0000 Subject: [PATCH] Support `-> val T` and `-> var T` return forms (#7912) Proposal p5434 specifies `-> val T`, `-> ref T`, and `-> var T` as return forms. `-> ref T` is parsed as a `RefPrimitiveForm`; extend that to parse a leading `val` or `var` after `->` as a `ValPrimitiveForm` or `VarPrimitiveForm`, reusing the nodes produced by `form(val T)` and `form(var T)`, and check them as forms. --------- Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com> --- toolchain/check/convert.h | 6 +- toolchain/check/handle_function.cpp | 11 +- .../declaration/val_var_return.carbon | 304 ++++++++++++++++++ toolchain/parse/handle_function.cpp | 6 +- toolchain/parse/state.def | 6 +- .../testdata/function/declaration.carbon | 89 +++++ 6 files changed, 414 insertions(+), 8 deletions(-) create mode 100644 toolchain/check/testdata/function/declaration/val_var_return.carbon diff --git a/toolchain/check/convert.h b/toolchain/check/convert.h index 02519a79cc2d..676d8b5ee3cc 100644 --- a/toolchain/check/convert.h +++ b/toolchain/check/convert.h @@ -247,9 +247,13 @@ auto ExprAsType(Context& context, SemIR::LocId loc_id, SemIR::InstId value_id, // Converts an expression in a form position for use as a form. // +// Form positions include return-forms (following `->?`) and form binding +// patterns. +// // Note that the right-hand side of a `->` return type declaration is normally // a type expression, not a form, and should be handled by `ReturnExprAsForm`. -// It's only a form if it begins with a primitive form keyword such as `ref`. +// It's only a form if it begins with a primitive form keyword: `val`, `ref`, +// or `var`. // // `diagnose` has the same effect as in `ExprAsType`. auto FormExprAsForm(Context& context, SemIR::LocId loc_id, diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index fb86f2829cd1..4b4acc393bba 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -55,11 +55,14 @@ static auto HandleReturnDecl(Context& context, Parse::AnyReturnDeclId node_id) -> bool { auto [expr_node_id, expr_inst_id] = context.node_stack().PopExprWithNodeId(); Context::FormExpr form_expr = [&] { - // `-> T` provides a type expression, whereas `-> ref T` and `->? F` - // provide a form. + // `-> T` provides a type expression, whereas `-> val T`, `-> ref T`, + // `-> var T`, and `->? F` provide a form. + auto expr_kind = context.parse_tree().node_kind(expr_node_id); + bool is_primitive_form = expr_kind == Parse::ValPrimitiveFormId::Kind || + expr_kind == Parse::RefPrimitiveFormId::Kind || + expr_kind == Parse::VarPrimitiveFormId::Kind; if (context.parse_tree().node_kind(node_id) == Parse::ReturnTypeId::Kind && - context.parse_tree().node_kind(expr_node_id) != - Parse::RefPrimitiveFormId::Kind) { + !is_primitive_form) { return ReturnExprAsForm(context, expr_node_id, expr_inst_id); } else { return FormExprAsForm(context, expr_node_id, expr_inst_id); diff --git a/toolchain/check/testdata/function/declaration/val_var_return.carbon b/toolchain/check/testdata/function/declaration/val_var_return.carbon new file mode 100644 index 000000000000..64c014ed461b --- /dev/null +++ b/toolchain/check/testdata/function/declaration/val_var_return.carbon @@ -0,0 +1,304 @@ +// 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-FILE: toolchain/testing/testdata/min_prelude/int.carbon +// EXTRA-ARGS: --dump-sem-ir-ranges=only +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/function/declaration/val_var_return.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/function/declaration/val_var_return.carbon + +// --- val_return.carbon + +library "[[@TEST_NAME]]"; + +//@include-in-dumps + +fn F() -> val i32; + +fn G() { + let unused x: i32 = F(); +} + +// --- var_return.carbon + +library "[[@TEST_NAME]]"; + +//@include-in-dumps + +fn F() -> var i32; + +fn G() { + let unused x: i32 = F(); +} + +// --- val_return_definition.carbon + +library "[[@TEST_NAME]]"; + +//@include-in-dumps + +fn F() -> val i32 { + return 1; +} + +// --- var_return_definition.carbon + +library "[[@TEST_NAME]]"; + +fn F() -> var i32 { + return 1; +} + +// --- var_return_returned_var.carbon + +library "[[@TEST_NAME]]"; + +fn F() -> var i32 { + returned var v: i32 = 1; + return var; +} + +// --- fail_val_return_returned_var.carbon + +library "[[@TEST_NAME]]"; + +fn F() -> val i32 { + // CHECK:STDERR: fail_val_return_returned_var.carbon:[[@LINE+7]]:3: error: `returned var` declaration in function with non-initializing return form [ReturnedVarNotInit] + // CHECK:STDERR: returned var v: i32 = 1; + // CHECK:STDERR: ^~~~~~~~ + // CHECK:STDERR: fail_val_return_returned_var.carbon:[[@LINE-4]]:11: note: return form declared here [ReturnFormHereNote] + // CHECK:STDERR: fn F() -> val i32 { + // CHECK:STDERR: ^~~~~~~ + // CHECK:STDERR: + returned var v: i32 = 1; + return var; +} + +// --- val_return_redecl.carbon + +library "[[@TEST_NAME]]"; + +fn F() -> val i32; +fn F() -> val i32 { + return 1; +} + +// --- todo_fail_val_return_redecl_mismatch.carbon + +library "[[@TEST_NAME]]"; + +// TODO: This should be diagnosed as a redeclaration with a different return +// form. Redeclaration checking doesn't currently compare return forms (the same +// is true for `-> ref T` versus `-> T`). +fn F() -> val i32; +fn F() -> var i32 { + return 1; +} + +// CHECK:STDOUT: --- val_return.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: type: type = facet_type [concrete] +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete] +// CHECK:STDOUT: %Int.type: type = parameterized_class_name_type @Int [concrete] +// CHECK:STDOUT: %Int.generic: %Int.type = struct_value () [concrete] +// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete] +// CHECK:STDOUT: %.acd: Core.Form = value_form %i32 [concrete] +// CHECK:STDOUT: %pattern_type.d94: type = pattern_type %i32 [concrete] +// CHECK:STDOUT: %.45b: %pattern_type.d94 = value_return_pattern [concrete] +// CHECK:STDOUT: %return.patt: %pattern_type.d94 = return_slot_pattern %.45b, %i32 [concrete] +// CHECK:STDOUT: %F.type: type = fn_type @F [concrete] +// CHECK:STDOUT: %F: %F.type = struct_value () [concrete] +// CHECK:STDOUT: %G.type: type = fn_type @G [concrete] +// CHECK:STDOUT: %G: %G.type = struct_value () [concrete] +// CHECK:STDOUT: %x.patt: %pattern_type.d94 = value_binding_pattern x [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [concrete] { +// CHECK:STDOUT: .Int = %Core.Int +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.Int: %Int.type = import_ref Core//prelude/parts/int, Int, loaded [concrete = constants.%Int.generic] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [concrete] { +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .F = %F.decl +// CHECK:STDOUT: .G = %G.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] { +// CHECK:STDOUT: %.loc6_11.1: %pattern_type.d94 = value_return_pattern [concrete = constants.%.45b] +// CHECK:STDOUT: %return.patt: %pattern_type.d94 = return_slot_pattern %.loc6_11.1, %.loc6_11.3 [concrete = constants.%return.patt] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %i32: type = type_literal constants.%i32 [concrete = constants.%i32] +// CHECK:STDOUT: %.loc6_11.2: Core.Form = value_form %i32 [concrete = constants.%.acd] +// CHECK:STDOUT: %.loc6_11.3: type = type_component_of %.loc6_11.2 [concrete = constants.%i32] +// CHECK:STDOUT: %.loc6_11.4: %i32 = value_return +// CHECK:STDOUT: %return: %i32 = return_slot %.loc6_11.4 +// CHECK:STDOUT: } +// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> val %i32; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref: %F.type = name_ref F, file.%F.decl [concrete = constants.%F] +// CHECK:STDOUT: %F.call: %i32 = call %F.ref() +// CHECK:STDOUT: %i32: type = type_literal constants.%i32 [concrete = constants.%i32] +// CHECK:STDOUT: %x: %i32 = wrapper_binding x, %F.call +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %x.patt: %pattern_type.d94 = value_binding_pattern x [concrete = constants.%x.patt] +// CHECK:STDOUT: } +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- var_return.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: type: type = facet_type [concrete] +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete] +// CHECK:STDOUT: %Int.type: type = parameterized_class_name_type @Int [concrete] +// CHECK:STDOUT: %Int.generic: %Int.type = struct_value () [concrete] +// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete] +// CHECK:STDOUT: %.0b3: Core.Form = init_form %i32 [concrete] +// CHECK:STDOUT: %pattern_type.d94: type = pattern_type %i32 [concrete] +// CHECK:STDOUT: %return.param_patt: %pattern_type.d94 = out_param_pattern [concrete] +// CHECK:STDOUT: %return.patt: %pattern_type.d94 = return_slot_pattern %return.param_patt, %i32 [concrete] +// CHECK:STDOUT: %F.type: type = fn_type @F [concrete] +// CHECK:STDOUT: %F: %F.type = struct_value () [concrete] +// CHECK:STDOUT: %G.type: type = fn_type @G [concrete] +// CHECK:STDOUT: %G: %G.type = struct_value () [concrete] +// CHECK:STDOUT: %x.patt: %pattern_type.d94 = value_binding_pattern x [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [concrete] { +// CHECK:STDOUT: .Int = %Core.Int +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.Int: %Int.type = import_ref Core//prelude/parts/int, Int, loaded [concrete = constants.%Int.generic] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [concrete] { +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .F = %F.decl +// CHECK:STDOUT: .G = %G.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] { +// CHECK:STDOUT: %return.param_patt: %pattern_type.d94 = out_param_pattern [concrete = constants.%return.param_patt] +// CHECK:STDOUT: %return.patt: %pattern_type.d94 = return_slot_pattern %return.param_patt, %.loc6_11.2 [concrete = constants.%return.patt] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %i32: type = type_literal constants.%i32 [concrete = constants.%i32] +// CHECK:STDOUT: %.loc6_11.1: Core.Form = init_form %i32 [concrete = constants.%.0b3] +// CHECK:STDOUT: %.loc6_11.2: type = type_component_of %.loc6_11.1 [concrete = constants.%i32] +// CHECK:STDOUT: %return.param: ref %i32 = out_param call_param0 +// CHECK:STDOUT: %return: ref %i32 = return_slot %return.param +// CHECK:STDOUT: } +// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> out %return.param: %i32; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref: %F.type = name_ref F, file.%F.decl [concrete = constants.%F] +// CHECK:STDOUT: %F.call: init %i32 = call %F.ref() +// CHECK:STDOUT: %.loc9_25.1: %i32 = value_of_initializer %F.call +// CHECK:STDOUT: %.loc9_25.2: %i32 = converted %F.call, %.loc9_25.1 +// CHECK:STDOUT: %i32: type = type_literal constants.%i32 [concrete = constants.%i32] +// CHECK:STDOUT: %x: %i32 = wrapper_binding x, %.loc9_25.2 +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %x.patt: %pattern_type.d94 = value_binding_pattern x [concrete = constants.%x.patt] +// CHECK:STDOUT: } +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- val_return_definition.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: type: type = facet_type [concrete] +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete] +// CHECK:STDOUT: %Int.type: type = parameterized_class_name_type @Int [concrete] +// CHECK:STDOUT: %Int.generic: %Int.type = struct_value () [concrete] +// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete] +// CHECK:STDOUT: %.acd: Core.Form = value_form %i32 [concrete] +// CHECK:STDOUT: %pattern_type.d94: type = pattern_type %i32 [concrete] +// CHECK:STDOUT: %.45b: %pattern_type.d94 = value_return_pattern [concrete] +// CHECK:STDOUT: %return.patt.f8b: %pattern_type.d94 = return_slot_pattern %.45b, %i32 [concrete] +// CHECK:STDOUT: %F.type: type = fn_type @F [concrete] +// CHECK:STDOUT: %F: %F.type = struct_value () [concrete] +// CHECK:STDOUT: %int_1.5b8: Core.IntLiteral = int_value 1 [concrete] +// CHECK:STDOUT: %ImplicitAs.type.0ff: type = generic_interface_type @ImplicitAs [concrete] +// CHECK:STDOUT: %ImplicitAs.generic: %ImplicitAs.type.0ff = struct_value () [concrete] +// CHECK:STDOUT: %ImplicitAs.type.6b0: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete] +// CHECK:STDOUT: %To: Core.IntLiteral = symbolic_binding To, 0 [symbolic] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.3c6: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%To) [symbolic] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.6f2: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.3c6 = struct_value () [symbolic] +// CHECK:STDOUT: %ImplicitAs.impl_witness.d73: = impl_witness imports.%ImplicitAs.impl_witness_table.f53, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.766: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.f60: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.766 = struct_value () [concrete] +// CHECK:STDOUT: %ImplicitAs.facet: %ImplicitAs.type.6b0 = facet_value Core.IntLiteral, (%ImplicitAs.impl_witness.d73) [concrete] +// CHECK:STDOUT: %ImplicitAs.WithSelf.Convert.type.58f: type = fn_type @ImplicitAs.WithSelf.Convert, @ImplicitAs.WithSelf(%i32, %ImplicitAs.facet) [concrete] +// CHECK:STDOUT: %.33a: type = fn_type_with_self_type %ImplicitAs.WithSelf.Convert.type.58f, %ImplicitAs.facet [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound: = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.f60 [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.f60, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete] +// CHECK:STDOUT: %bound_method: = bound_method %int_1.5b8, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete] +// CHECK:STDOUT: %int_1.68a: %i32 = int_value 1 [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [concrete] { +// CHECK:STDOUT: .Int = %Core.Int +// CHECK:STDOUT: .ImplicitAs = %Core.ImplicitAs +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/... +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.Int: %Int.type = import_ref Core//prelude/parts/int, Int, loaded [concrete = constants.%Int.generic] +// CHECK:STDOUT: %Core.ImplicitAs: %ImplicitAs.type.0ff = import_ref Core//prelude/parts/as, ImplicitAs, loaded [concrete = constants.%ImplicitAs.generic] +// CHECK:STDOUT: %Core.import_ref.e47: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.3c6) = import_ref Core//prelude/parts/int, loc{{\d+_\d+}}, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.6f2)] +// CHECK:STDOUT: %ImplicitAs.impl_witness_table.f53 = impl_witness_table (%Core.import_ref.e47), @Core.IntLiteral.as.ImplicitAs.impl [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [concrete] { +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .F = %F.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] { +// CHECK:STDOUT: %.loc6_11.1: %pattern_type.d94 = value_return_pattern [concrete = constants.%.45b] +// CHECK:STDOUT: %return.patt: %pattern_type.d94 = return_slot_pattern %.loc6_11.1, %.loc6_11.3 [concrete = constants.%return.patt.f8b] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %i32: type = type_literal constants.%i32 [concrete = constants.%i32] +// CHECK:STDOUT: %.loc6_11.2: Core.Form = value_form %i32 [concrete = constants.%.acd] +// CHECK:STDOUT: %.loc6_11.3: type = type_component_of %.loc6_11.2 [concrete = constants.%i32] +// CHECK:STDOUT: %.loc6_11.4: %i32 = value_return +// CHECK:STDOUT: %return: %i32 = return_slot %.loc6_11.4 +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> val %i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %int_1: Core.IntLiteral = int_value 1 [concrete = constants.%int_1.5b8] +// CHECK:STDOUT: %impl.elem0: %.33a = impl_witness_access constants.%ImplicitAs.impl_witness.d73, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.f60] +// CHECK:STDOUT: %bound_method.loc7_11.1: = bound_method %int_1, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound] +// CHECK:STDOUT: %specific_fn: = specific_function %impl.elem0, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn] +// CHECK:STDOUT: %bound_method.loc7_11.2: = bound_method %int_1, %specific_fn [concrete = constants.%bound_method] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc7_11.2(%int_1) [concrete = constants.%int_1.68a] +// CHECK:STDOUT: %.loc7_11.1: %i32 = value_of_initializer %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_1.68a] +// CHECK:STDOUT: %.loc7_11.2: %i32 = converted %int_1, %.loc7_11.1 [concrete = constants.%int_1.68a] +// CHECK:STDOUT: return %.loc7_11.2 +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/parse/handle_function.cpp b/toolchain/parse/handle_function.cpp index a3aadaa1017b..3fb808bccaa9 100644 --- a/toolchain/parse/handle_function.cpp +++ b/toolchain/parse/handle_function.cpp @@ -35,8 +35,10 @@ auto HandleFunctionAfterParams(Context& context) -> void { auto HandleReturnTypeOrFormExpr(Context& context) -> void { context.PopAndDiscardState(); - // `-> ref T` specifies a primitive return form. - if (context.PositionIs(Lex::TokenKind::Ref)) { + // `-> val T`, `-> ref T`, and `-> var T` specify a primitive return form. + if (context.PositionIs(Lex::TokenKind::Val) || + context.PositionIs(Lex::TokenKind::Ref) || + context.PositionIs(Lex::TokenKind::Var)) { context.PushState(StateKind::PrimitiveFormFinish, context.Consume()); } context.PushStateForExpr(PrecedenceGroup::ForType()); diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index 03c7dcbee8ec..87fe87e08945 100644 --- a/toolchain/parse/state.def +++ b/toolchain/parse/state.def @@ -883,10 +883,14 @@ CARBON_PARSE_STATE(LambdaBodyFinish) CARBON_PARSE_STATE(FunctionAfterParams) // Handles the expression following a `->` in a function or lambda, which may -// begin with `ref` to specify a primitive return form. +// begin with `val`, `ref`, or `var` to specify a primitive return form. // +// -> val expr ... +// ^~~ // -> ref expr ... // ^~~ +// -> var expr ... +// ^~~ // 1. Expr // 2. PrimitiveFormFinish // diff --git a/toolchain/parse/testdata/function/declaration.carbon b/toolchain/parse/testdata/function/declaration.carbon index 05d481127ec6..e335f71261b8 100644 --- a/toolchain/parse/testdata/function/declaration.carbon +++ b/toolchain/parse/testdata/function/declaration.carbon @@ -197,6 +197,22 @@ let a: auto = fn -> ref u32 => 0; // CHECK:STDERR: fn RefReturn() -> ref; +// --- val_var_return.carbon + +fn ValReturn() -> val u32; +fn VarReturn() -> var u32; +fn ComplexValReturn() -> val X.Y(Z); +let a: auto = fn -> val u32 => 0; +let b: auto = fn -> var u32 => 0; + +// --- fail_val_return_missing_type.carbon + +// CHECK:STDERR: fail_val_return_missing_type.carbon:[[@LINE+4]]:22: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fn ValReturn() -> val; +// CHECK:STDERR: ^ +// CHECK:STDERR: +fn ValReturn() -> val; + // CHECK:STDOUT: - filename: basic.carbon // CHECK:STDOUT: ╭─FileStart '' // CHECK:STDOUT: │ ╭─FunctionIntroducer 'fn' @@ -570,3 +586,76 @@ fn RefReturn() -> ref; // CHECK:STDOUT: ├─FunctionDecl ';' // CHECK:STDOUT: ├─FileEnd '' // CHECK:STDOUT: (root) +// CHECK:STDOUT: - filename: val_var_return.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─FunctionIntroducer 'fn' +// CHECK:STDOUT: │ ├─IdentifierNameMaybeBeforeSignature 'ValReturn' +// CHECK:STDOUT: │ │ ╭─ExplicitParamListStart '(' +// CHECK:STDOUT: │ ├─ExplicitParamList ')' +// CHECK:STDOUT: │ │ ╭─UnsignedIntTypeLiteral 'u32' +// CHECK:STDOUT: │ │ ╭─ValPrimitiveForm 'val' +// CHECK:STDOUT: │ ├─ReturnType '->' +// CHECK:STDOUT: ├─FunctionDecl ';' +// CHECK:STDOUT: │ ╭─FunctionIntroducer 'fn' +// CHECK:STDOUT: │ ├─IdentifierNameMaybeBeforeSignature 'VarReturn' +// CHECK:STDOUT: │ │ ╭─ExplicitParamListStart '(' +// CHECK:STDOUT: │ ├─ExplicitParamList ')' +// CHECK:STDOUT: │ │ ╭─UnsignedIntTypeLiteral 'u32' +// CHECK:STDOUT: │ │ ╭─VarPrimitiveForm 'var' +// CHECK:STDOUT: │ ├─ReturnType '->' +// CHECK:STDOUT: ├─FunctionDecl ';' +// CHECK:STDOUT: │ ╭─FunctionIntroducer 'fn' +// CHECK:STDOUT: │ ├─IdentifierNameMaybeBeforeSignature 'ComplexValReturn' +// CHECK:STDOUT: │ │ ╭─ExplicitParamListStart '(' +// CHECK:STDOUT: │ ├─ExplicitParamList ')' +// CHECK:STDOUT: │ │ ╭─IdentifierNameExpr 'X' +// CHECK:STDOUT: │ │ ├─IdentifierNameNotBeforeSignature 'Y' +// CHECK:STDOUT: │ │ ╭─MemberAccessExpr '.' +// CHECK:STDOUT: │ │ ╭─CallExprStart '(' +// CHECK:STDOUT: │ │ ├─IdentifierNameExpr 'Z' +// CHECK:STDOUT: │ │ ╭─CallExpr ')' +// CHECK:STDOUT: │ │ ╭─ValPrimitiveForm 'val' +// CHECK:STDOUT: │ ├─ReturnType '->' +// CHECK:STDOUT: ├─FunctionDecl ';' +// CHECK:STDOUT: │ ╭─LetIntroducer 'let' +// CHECK:STDOUT: │ │ ╭─IdentifierNameNotBeforeSignature 'a' +// CHECK:STDOUT: │ │ ├─BindingPatternTypeStart ':' +// CHECK:STDOUT: │ │ ├─AutoTypeLiteral 'auto' +// CHECK:STDOUT: │ ├─LetBindingPattern ':' +// CHECK:STDOUT: │ ├─LetInitializer '=' +// CHECK:STDOUT: │ │ ╭─LambdaIntroducer 'fn' +// CHECK:STDOUT: │ │ │ ╭─UnsignedIntTypeLiteral 'u32' +// CHECK:STDOUT: │ │ │ ╭─ValPrimitiveForm 'val' +// CHECK:STDOUT: │ │ ├─ReturnType '->' +// CHECK:STDOUT: │ │ ╭─LambdaDefinitionStart '=>' +// CHECK:STDOUT: │ │ ├─IntLiteral '0' +// CHECK:STDOUT: │ ├─Lambda 'fn' +// CHECK:STDOUT: ├─LetDecl ';' +// CHECK:STDOUT: │ ╭─LetIntroducer 'let' +// CHECK:STDOUT: │ │ ╭─IdentifierNameNotBeforeSignature 'b' +// CHECK:STDOUT: │ │ ├─BindingPatternTypeStart ':' +// CHECK:STDOUT: │ │ ├─AutoTypeLiteral 'auto' +// CHECK:STDOUT: │ ├─LetBindingPattern ':' +// CHECK:STDOUT: │ ├─LetInitializer '=' +// CHECK:STDOUT: │ │ ╭─LambdaIntroducer 'fn' +// CHECK:STDOUT: │ │ │ ╭─UnsignedIntTypeLiteral 'u32' +// CHECK:STDOUT: │ │ │ ╭─VarPrimitiveForm 'var' +// CHECK:STDOUT: │ │ ├─ReturnType '->' +// CHECK:STDOUT: │ │ ╭─LambdaDefinitionStart '=>' +// CHECK:STDOUT: │ │ ├─IntLiteral '0' +// CHECK:STDOUT: │ ├─Lambda 'fn' +// CHECK:STDOUT: ├─LetDecl ';' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root) +// CHECK:STDOUT: - filename: fail_val_return_missing_type.carbon +// CHECK:STDOUT: ╭─FileStart '' +// CHECK:STDOUT: │ ╭─FunctionIntroducer 'fn' +// CHECK:STDOUT: │ ├─IdentifierNameMaybeBeforeSignature 'ValReturn' +// CHECK:STDOUT: │ │ ╭─ExplicitParamListStart '(' +// CHECK:STDOUT: │ ├─ExplicitParamList ')' +// CHECK:STDOUT: │ │ ╭─InvalidParse ';' has_error +// CHECK:STDOUT: │ │ ╭─ValPrimitiveForm 'val' has_error +// CHECK:STDOUT: │ ├─ReturnType '->' +// CHECK:STDOUT: ├─FunctionDecl ';' +// CHECK:STDOUT: ├─FileEnd '' +// CHECK:STDOUT: (root)