From 32f8a88addcd844b12884bb6c98dcf94e82f0e92 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 27 Dec 2020 14:07:34 -0500 Subject: [PATCH 01/27] Update statements.md --- standard/statements.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/standard/statements.md b/standard/statements.md index e8dd905cc..a255f30e0 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -270,12 +270,13 @@ In addition to the reachability provided by normal flow of control, a labeled st ### 12.6.1 General -A *declaration_statement* declares a local variable or constant. Declaration statements are permitted in blocks, but are not permitted as embedded statements. +A *declaration_statement* declares a local variable, constant, or function. Declaration statements are permitted in blocks, but are not permitted as embedded statements. ```ANTLR declaration_statement : local_variable_declaration ';' | local_constant_declaration ';' + | local_function_declaration ; ``` @@ -414,6 +415,34 @@ The scope of a local constant is the block in which the declaration occurs. It i A local constant declaration that declares multiple constants is equivalent to multiple declarations of single constants with the same type. +### §local-function-declarations-new-clause Local function declarations + +A *local_function_declaration* declares a local function. + +```ANTLR +local_function_declaration + : local_function_header local_function_body + ; + +local_function_header + : local_function_modifiers? return_type identifier type_parameter_list? + ( formal_parameter_list? ) type_parameter_constraints_clause* + ; + +local_function_modifiers + : async + ; + +local_function_body + : block + | '=>' expression ';' + ; +``` + +> TBD - Detailed spec goes here (and might require edits elsewhere) + +> TBD - Examples go here + ## 12.7 Expression statements An *expression_statement* evaluates a given expression. The value computed by the expression, if any, is discarded. From 6e64ec299f45610b61a578fc9ee539230b4c70fb Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Sun, 27 Dec 2020 14:12:03 -0500 Subject: [PATCH 02/27] Update unsafe-code.md --- standard/unsafe-code.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/standard/unsafe-code.md b/standard/unsafe-code.md index a1ec49d4e..da272d01b 100644 --- a/standard/unsafe-code.md +++ b/standard/unsafe-code.md @@ -18,11 +18,12 @@ An implementation that does not support unsafe code is required to diagnose any ## 22.2 Unsafe contexts -The unsafe features of C# are available only in unsafe contexts. An unsafe context is introduced by including an `unsafe` modifier in the declaration of a type or member, or by employing an *unsafe_statement*: +The unsafe features of C# are available only in unsafe contexts. An unsafe context is introduced by including an `unsafe` modifier in the declaration of a type, member, or local function, or by employing an *unsafe_statement*: - A declaration of a class, struct, interface, or delegate may include an `unsafe` modifier, in which case, the entire textual extent of that type declaration (including the body of the class, struct, or interface) is considered an unsafe context. > *Note*: If the *type_declaration* is partial, only that part is an unsafe context. *end note* - A declaration of a field, method, property, event, indexer, operator, instance constructor, finalizer, or static constructor may include an `unsafe` modifier, in which case, the entire textual extent of that member declaration is considered an unsafe context. +- A declaration of a local function may include an `unsafe` modifier, in which case, the entire textual extent of that local function is considered an unsafe context. - An *unsafe_statement* enables the use of an unsafe context within a *block*. The entire textual extent of the associated *block* is considered an unsafe context. The associated grammar extensions are shown below and in subsequent subclauses. @@ -35,6 +36,11 @@ unsafe_modifier unsafe_statement : 'unsafe' block ; + +local_function_modifiers + : ... + | 'unsafe' + ; ``` > *Example*: In the following code From 917d9a541f10031a0cdfe0496b8ba24384931353 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 19 Apr 2021 11:18:18 -0400 Subject: [PATCH 03/27] Add semantics and example for local functions v7.0 feature --- standard/statements.md | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/standard/statements.md b/standard/statements.md index a255f30e0..6b2115261 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -270,7 +270,7 @@ In addition to the reachability provided by normal flow of control, a labeled st ### 12.6.1 General -A *declaration_statement* declares a local variable, constant, or function. Declaration statements are permitted in blocks, but are not permitted as embedded statements. +A *declaration_statement* declares a local variable, local constant, or local function. Declaration statements are permitted in blocks, but are not permitted as embedded statements. ```ANTLR declaration_statement @@ -280,6 +280,8 @@ declaration_statement ; ``` +A local variable is declared using a *local_variable_declaration* ([§13.6.2](statements.md#1362-local-variable-declarations)). A local constant is declared using a *local_constant_declaration* ([§13.6.3](statements.md#1362-local-constant-declarations)). A local function is declared using a *local_function_declaration* (§local-function-declarations-new-clause). + ### 12.6.2 Local variable declarations A *local_variable_declaration* declares one or more local variables. @@ -430,7 +432,8 @@ local_function_header ; local_function_modifiers - : async + : 'async' + | 'unsafe' ; local_function_body @@ -439,9 +442,37 @@ local_function_body ; ``` -> TBD - Detailed spec goes here (and might require edits elsewhere) +Unless specified otherwise below, the semantics of all grammar elements is the same as for *method_declaration* ([15.6.1](classes.md#1561-general), read in the context of a local function instead of a method. + +A *local_function_declaration* may include one `async` ([§15.15](classes.md#1515-async-functions)) modifier and one `unsafe` ([§23.1](unsafe_code.md#231-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§15.15.1](classes.md#15151-general)). + +A local function is declared at block scope, and that function may capture variables from the enclosing scope. Each call of the function requires captured variables to be definitely assigned before the call. The compiler shall determine which variables are definitely assigned on return. + +A local function may be called from a lexical point prior to its definition. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable it wishes to capture. -> TBD - Examples go here +It is a compile-time error for a local function to declare a parameter or local variable with the same name as one declared in the enclosing scope. + +Local function declaration statements are always reachable. + +> *Example*: There are two common use cases for local functions: public iterator methods and public async methods. In iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. In async methods, any exceptions are only observed when the returned Task is awaited. The following example demonstrates separating parameter validation from the iterator implementation using a local function: +> ```csharp +> public static IEnumerable AlphabetSubset(char start, char end) +> { +> if (start < 'a' || start > 'z') +> throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter"); +> if (end < 'a' || end > 'z') +> throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); +> if (end <= start) +> throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}"); +> return alphabetSubsetImplementation(); +> IEnumerable alphabetSubsetImplementation() +> { +> for (var c = start; c < end; c++) +> yield return c; +> } +> } +> ``` +> *end example* ## 12.7 Expression statements From 202efa2004c6f1f9641db681db778126f9b3a124 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Mon, 19 Apr 2021 11:24:00 -0400 Subject: [PATCH 04/27] removed local function unsafe grammar extension With the consolidation of the unsafe grammar in PR #233, this text is no longer needed. --- standard/unsafe-code.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/standard/unsafe-code.md b/standard/unsafe-code.md index da272d01b..ff5d2f269 100644 --- a/standard/unsafe-code.md +++ b/standard/unsafe-code.md @@ -36,11 +36,6 @@ unsafe_modifier unsafe_statement : 'unsafe' block ; - -local_function_modifiers - : ... - | 'unsafe' - ; ``` > *Example*: In the following code From 0c6149d2c4b39885693d86239b6069a1eda10a61 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 23 Apr 2021 11:17:33 -0400 Subject: [PATCH 05/27] include support for local functions --- standard/basic-concepts.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/standard/basic-concepts.md b/standard/basic-concepts.md index 8171860fc..2eee8e04e 100644 --- a/standard/basic-concepts.md +++ b/standard/basic-concepts.md @@ -60,12 +60,13 @@ There are several different types of declaration spaces, as described in the fol - Within all compilation units of a program, *namespace_member_declaration*s with no enclosing *namespace_declaration* are members of a single combined declaration space called the ***global declaration space***. - Within all compilation units of a program, *namespace_member_declaration*s within *namespace_declaration*s that have the same fully qualified namespace name are members of a single combined declaration space. -- Each *compilation_unit* and *namespace_body* has an ***alias declaration space***. Each *extern_alias_directive* and *using_alias_directive* of the *compilation_unit* or *namespace_body* contributes a member to the alias declaration space ([§13.5.2](namespaces.md#1352-using-alias-directives)). -- Each non-partial class, struct, or interface declaration creates a new declaration space. Each partial class, struct, or interface declaration contributes to a declaration space shared by all matching parts in the same program ([§15.2.3](structs.md#1523-partial-modifier)).Names are introduced into this declaration space through *class_member_declaration*s, *struct_member_declaration*s, *interface_member_declaration*s, or *type_parameter*s. Except for overloaded instance constructor declarations and static constructor declarations, a class or struct cannot contain a member declaration with the same name as the class or struct. A class, struct, or interface permits the declaration of overloaded methods and indexers. Furthermore, a class or struct permits the declaration of overloaded instance constructors and operators. For example, a class, struct, or interface may contain multiple method declarations with the same name, provided these method declarations differ in their signature ([§7.6](basic-concepts.md#76-signatures-and-overloading)). Note that base classes do not contribute to the declaration space of a class, and base interfaces do not contribute to the declaration space of an interface. Thus, a derived class or interface is allowed to declare a member with the same name as an inherited member. Such a member is said to ***hide*** the inherited member. +- Each *compilation_unit* and *namespace_body* has an ***alias declaration space***. Each *extern_alias_directive* and *using_alias_directive* of the *compilation_unit* or *namespace_body* contributes a member to the alias declaration space ([§14.5.2](namespaces.md#1452-using-alias-directives)). +- Each non-partial class, struct, or interface declaration creates a new declaration space. Each partial class, struct, or interface declaration contributes to a declaration space shared by all matching parts in the same program ([§16.2.3](structs.md#1623-partial-modifier)).Names are introduced into this declaration space through *class_member_declaration*s, *struct_member_declaration*s, *interface_member_declaration*s, or *type_parameter*s. Except for overloaded instance constructor declarations and static constructor declarations, a class or struct cannot contain a member declaration with the same name as the class or struct. A class, struct, or interface permits the declaration of overloaded methods and indexers. Furthermore, a class or struct permits the declaration of overloaded instance constructors and operators. For example, a class, struct, or interface may contain multiple method declarations with the same name, provided these method declarations differ in their signature ([§8.6](basic-concepts.md#86-signatures-and-overloading)). Note that base classes do not contribute to the declaration space of a class, and base interfaces do not contribute to the declaration space of an interface. Thus, a derived class or interface is allowed to declare a member with the same name as an inherited member. Such a member is said to ***hide*** the inherited member. - Each delegate declaration creates a new declaration space. Names are introduced into this declaration space through formal parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. - Each enumeration declaration creates a new declaration space. Names are introduced into this declaration space through *enum_member_declarations*. -- Each method declaration, property declaration, property accessor declaration, indexer declaration, indexer accessor declaration, operator declaration, instance constructor declaration and anonymous function creates a new declaration space called a ***local variable declaration space***. Names are introduced into this declaration space through formal parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. The set accessor for a property or an indexer introduces the name `value` as a formal parameter. The body of the function member or anonymous function, if any, is considered to be nested within the local variable declaration space. It is an error for a local variable declaration space and a nested local variable declaration space to contain elements with the same name. Thus, within a nested declaration space it is not possible to declare a local variable or constant with the same name as a local variable or constant in an enclosing declaration space. It is possible for two declaration spaces to contain elements with the same name as long as neither declaration space contains the other. -- Each *block* or *switch_block*, as well as a `for`, `foreach`, and `using` statement, creates a local variable declaration space for local variables and local constants. Names are introduced into this declaration space through *local_variable_declaration*s and *local_constant_declaration*s. Note that blocks that occur as or within the body of a function member or anonymous function are nested within the local variable declaration space declared by those functions for their parameters. Thus, it is an error to have, for example, a method with a local variable and a parameter of the same name. +- Each method declaration, property declaration, property accessor declaration, indexer declaration, indexer accessor declaration, operator declaration, instance constructor declaration, anonymous function, and local function creates a new declaration space called a ***local variable declaration space***. Names are introduced into this declaration space through formal parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. The set accessor for a property or an indexer introduces the name `value` as a formal parameter. The body of the function member, anonymous function, or local function, if any, is considered to be nested within the local variable declaration space. It is an error for a local variable declaration space and a nested local variable declaration space to contain elements with the same name. Thus, within a nested declaration space it is not possible to declare a local variable or constant with the same name as a local variable or constant in an enclosing declaration space. It is possible for two declaration spaces to contain elements with the same name as long as neither declaration space contains the other. +- Each *block* or *switch_block*, as well as a `for`, `foreach`, and `using` statement, creates a local variable declaration space for local variables and local constants. Names are introduced into this declaration space through *local_variable_declaration*s and *local_constant_declaration*s. Note that blocks that occur as or within the body of a function member, anonymous function, or local function are nested within the local variable declaration space declared by those functions for their parameters. Thus, it is an error to have, for example, a method with a local variable and a parameter of the same name. + - Each *block* or *switch_block* creates a separate declaration space for labels. Names are introduced into this declaration space through *labeled_statement*s, and the names are referenced through *goto_statement*s. The ***label declaration space*** of a block includes any nested blocks. Thus, within a nested block it is not possible to declare a label with the same name as a label in an enclosing block. The textual order in which names are declared is generally of no significance. In particular, textual order is not significant for the declaration and use of namespaces, constants, methods, properties, events, indexers, operators, instance constructors, finalizers, static constructors, and types. Declaration order is significant in the following ways: From f10d5bb66926ea8442b82d46c3900bd1fd5103ad Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 23 Apr 2021 11:20:48 -0400 Subject: [PATCH 06/27] add support for local functions --- standard/variables.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/variables.md b/standard/variables.md index 658d681b8..3adbea1c4 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -82,7 +82,7 @@ For the purpose of definite-assignment checking, a value parameter is considered A parameter declared with a `ref` modifier is a ***reference parameter***. -A reference parameter does not create a new storage location. Instead, a reference parameter represents the same storage location as the variable given as the argument in the function member or anonymous function invocation. Thus, the value of a reference parameter is always the same as the underlying variable. +A reference parameter does not create a new storage location. Instead, a reference parameter represents the same storage location as the variable given as the argument in the function member, anonymous function, or local function invocation. Thus, the value of a reference parameter is always the same as the underlying variable. The following definite-assignment rules apply to reference parameters. @@ -106,7 +106,7 @@ The following definite-assignment rules apply to output parameters. - A variable need not be definitely assigned before it can be passed as an output parameter in a function member or delegate invocation. - Following the normal completion of a function member or delegate invocation, each variable that was passed as an output parameter is considered assigned in that execution path. - Within a function member or anonymous function, an output parameter is considered initially unassigned. -- Every output parameter of a function member or anonymous function shall be definitely assigned ([§9.4](variables.md#94-definite-assignment)) before the function member or anonymous function returns normally. +- Every output parameter of a function member, anonymous function, or local function shall be definitely assigned ([§9.4](variables.md#94-definite-assignment)) before the function member, anonymous function, or local function returns normally. Within an instance constructor of a struct type, the `this` keyword behaves exactly as an output or reference parameter of the struct type, depending on whether the constructor declaration includes a constructor initializer ([§11.7.12](expressions.md#11712-this-access)). From 5daeb7c7c528a57ff66cdf2803d3a3d396b320d2 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Sat, 2 Apr 2022 16:52:58 -0400 Subject: [PATCH 07/27] fix build issues --- standard/basic-concepts.md | 4 ++-- standard/statements.md | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/standard/basic-concepts.md b/standard/basic-concepts.md index 2eee8e04e..5b238f23a 100644 --- a/standard/basic-concepts.md +++ b/standard/basic-concepts.md @@ -60,8 +60,8 @@ There are several different types of declaration spaces, as described in the fol - Within all compilation units of a program, *namespace_member_declaration*s with no enclosing *namespace_declaration* are members of a single combined declaration space called the ***global declaration space***. - Within all compilation units of a program, *namespace_member_declaration*s within *namespace_declaration*s that have the same fully qualified namespace name are members of a single combined declaration space. -- Each *compilation_unit* and *namespace_body* has an ***alias declaration space***. Each *extern_alias_directive* and *using_alias_directive* of the *compilation_unit* or *namespace_body* contributes a member to the alias declaration space ([§14.5.2](namespaces.md#1452-using-alias-directives)). -- Each non-partial class, struct, or interface declaration creates a new declaration space. Each partial class, struct, or interface declaration contributes to a declaration space shared by all matching parts in the same program ([§16.2.3](structs.md#1623-partial-modifier)).Names are introduced into this declaration space through *class_member_declaration*s, *struct_member_declaration*s, *interface_member_declaration*s, or *type_parameter*s. Except for overloaded instance constructor declarations and static constructor declarations, a class or struct cannot contain a member declaration with the same name as the class or struct. A class, struct, or interface permits the declaration of overloaded methods and indexers. Furthermore, a class or struct permits the declaration of overloaded instance constructors and operators. For example, a class, struct, or interface may contain multiple method declarations with the same name, provided these method declarations differ in their signature ([§8.6](basic-concepts.md#86-signatures-and-overloading)). Note that base classes do not contribute to the declaration space of a class, and base interfaces do not contribute to the declaration space of an interface. Thus, a derived class or interface is allowed to declare a member with the same name as an inherited member. Such a member is said to ***hide*** the inherited member. +- Each *compilation_unit* and *namespace_body* has an ***alias declaration space***. Each *extern_alias_directive* and *using_alias_directive* of the *compilation_unit* or *namespace_body* contributes a member to the alias declaration space ([§13.5.2](namespaces.md#1352-using-alias-directives)). +- Each non-partial class, struct, or interface declaration creates a new declaration space. Each partial class, struct, or interface declaration contributes to a declaration space shared by all matching parts in the same program ([§15.2.3](structs.md#1523-partial-modifier)).Names are introduced into this declaration space through *class_member_declaration*s, *struct_member_declaration*s, *interface_member_declaration*s, or *type_parameter*s. Except for overloaded instance constructor declarations and static constructor declarations, a class or struct cannot contain a member declaration with the same name as the class or struct. A class, struct, or interface permits the declaration of overloaded methods and indexers. Furthermore, a class or struct permits the declaration of overloaded instance constructors and operators. For example, a class, struct, or interface may contain multiple method declarations with the same name, provided these method declarations differ in their signature ([§7.6](basic-concepts.md#76-signatures-and-overloading)). Note that base classes do not contribute to the declaration space of a class, and base interfaces do not contribute to the declaration space of an interface. Thus, a derived class or interface is allowed to declare a member with the same name as an inherited member. Such a member is said to ***hide*** the inherited member. - Each delegate declaration creates a new declaration space. Names are introduced into this declaration space through formal parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. - Each enumeration declaration creates a new declaration space. Names are introduced into this declaration space through *enum_member_declarations*. - Each method declaration, property declaration, property accessor declaration, indexer declaration, indexer accessor declaration, operator declaration, instance constructor declaration, anonymous function, and local function creates a new declaration space called a ***local variable declaration space***. Names are introduced into this declaration space through formal parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. The set accessor for a property or an indexer introduces the name `value` as a formal parameter. The body of the function member, anonymous function, or local function, if any, is considered to be nested within the local variable declaration space. It is an error for a local variable declaration space and a nested local variable declaration space to contain elements with the same name. Thus, within a nested declaration space it is not possible to declare a local variable or constant with the same name as a local variable or constant in an enclosing declaration space. It is possible for two declaration spaces to contain elements with the same name as long as neither declaration space contains the other. diff --git a/standard/statements.md b/standard/statements.md index 6b2115261..85a521ffc 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -280,7 +280,7 @@ declaration_statement ; ``` -A local variable is declared using a *local_variable_declaration* ([§13.6.2](statements.md#1362-local-variable-declarations)). A local constant is declared using a *local_constant_declaration* ([§13.6.3](statements.md#1362-local-constant-declarations)). A local function is declared using a *local_function_declaration* (§local-function-declarations-new-clause). +A local variable is declared using a *local_variable_declaration* ([§12.6.2](statements.md#1262-local-variable-declarations)). A local constant is declared using a *local_constant_declaration* ([§12.6.3](statements.md#1263-local-constant-declarations)). A local function is declared using a *local_function_declaration* (§local-function-declarations-new-clause). ### 12.6.2 Local variable declarations @@ -442,9 +442,9 @@ local_function_body ; ``` -Unless specified otherwise below, the semantics of all grammar elements is the same as for *method_declaration* ([15.6.1](classes.md#1561-general), read in the context of a local function instead of a method. +Unless specified otherwise below, the semantics of all grammar elements is the same as for *method_declaration* ([§14.6.1](classes.md#1461-general), read in the context of a local function instead of a method. -A *local_function_declaration* may include one `async` ([§15.15](classes.md#1515-async-functions)) modifier and one `unsafe` ([§23.1](unsafe_code.md#231-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§15.15.1](classes.md#15151-general)). +A *local_function_declaration* may include one `async` ([§14.15](classes.md#1415-async-functions)) modifier and one `unsafe` ([§22.1](unsafe-code.md#221-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§14.15.1](classes.md#14151-general)). A local function is declared at block scope, and that function may capture variables from the enclosing scope. Each call of the function requires captured variables to be definitely assigned before the call. The compiler shall determine which variables are definitely assigned on return. @@ -455,6 +455,7 @@ It is a compile-time error for a local function to declare a parameter or local Local function declaration statements are always reachable. > *Example*: There are two common use cases for local functions: public iterator methods and public async methods. In iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. In async methods, any exceptions are only observed when the returned Task is awaited. The following example demonstrates separating parameter validation from the iterator implementation using a local function: +> > ```csharp > public static IEnumerable AlphabetSubset(char start, char end) > { @@ -472,6 +473,7 @@ Local function declaration statements are always reachable. > } > } > ``` +> > *end example* ## 12.7 Expression statements From 8b4c9f28a557aba70d433ed1b5c965a44dfde50c Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 12 May 2022 10:41:41 -0400 Subject: [PATCH 08/27] Apply suggestions from code review Co-authored-by: KalleOlaviNiemitalo --- standard/statements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/statements.md b/standard/statements.md index 85a521ffc..ebd7f1ca3 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -452,7 +452,7 @@ A local function may be called from a lexical point prior to its definition. How It is a compile-time error for a local function to declare a parameter or local variable with the same name as one declared in the enclosing scope. -Local function declaration statements are always reachable. +Local function bodies are always reachable. The endpoint of a local function is reachable if the beginning point of the local function is reachable. > *Example*: There are two common use cases for local functions: public iterator methods and public async methods. In iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. In async methods, any exceptions are only observed when the returned Task is awaited. The following example demonstrates separating parameter validation from the iterator implementation using a local function: > From a54993b718663863d9d62694cd72d54412476915 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 12 May 2022 11:04:58 -0400 Subject: [PATCH 09/27] clarify definite assignment, add examples Addresses comments in committee meeting: - https://github.com/dotnet/csharpstandard/pull/104#discussion_r870744332 - https://github.com/dotnet/csharpstandard/pull/104#discussion_r870745137 --- standard/statements.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/standard/statements.md b/standard/statements.md index ebd7f1ca3..a619f1d3a 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -446,7 +446,42 @@ Unless specified otherwise below, the semantics of all grammar elements is the s A *local_function_declaration* may include one `async` ([§14.15](classes.md#1415-async-functions)) modifier and one `unsafe` ([§22.1](unsafe-code.md#221-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§14.15.1](classes.md#14151-general)). -A local function is declared at block scope, and that function may capture variables from the enclosing scope. Each call of the function requires captured variables to be definitely assigned before the call. The compiler shall determine which variables are definitely assigned on return. +A local function is declared at block scope, and that function may capture variables from the enclosing scope. Captured variables must be definitely assigned before read by the body of the local function in each call to the function. The compiler shall determine which variables are definitely assigned on return. + +> *Example*: The following example demonstrates definite assignment for captured variables in local functions. If a local function reads a captured variable before writing it, the captured variable must be definitely assigned before calling the local function. The local function `F1` reads `s` without assigning it. It is an error if `F1` is called before `s` is definitely assigned. `F2` assigns `i` before reading it. It may be called before `i` is definitely assigned. Furthermore, `F1` may be called after `F2` because `i` is definitely assigned in `F2`. +> +> ```csharp +> void M() +> { +> string s; +> int i; +> +> // Error: Use of unassigned local variable s: +> F1(); +> // OK, F2 assigns i before reading it. +> F2(); +> +> // OK, i is definitely assigned in the body of F2: +> s = i.ToString(); +> +> // OK. s is now definitely assigned. +> F1(); +> +> void F1() +> { +> Console.WriteLine(s); +> } +> +> void F2() +> { +> i = 5; +> // OK. i is definitely assigned. +> Console.WriteLine(i); +> } +> } +> ``` +> +> *end example* A local function may be called from a lexical point prior to its definition. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable it wishes to capture. From 5b75036f505af6f9064f79c31fafba6d4e00897b Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 7 Jun 2022 16:08:09 -0400 Subject: [PATCH 10/27] respond to feedback. --- standard/statements.md | 65 ++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/standard/statements.md b/standard/statements.md index a619f1d3a..bad4a275e 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -442,19 +442,49 @@ local_function_body ; ``` +> *Example*: There are two common use cases for local functions: public iterator methods and public async methods. In iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. In async methods, any exceptions are only observed when the returned Task is awaited. The following example demonstrates separating parameter validation from the iterator implementation using a local function: +> +> ```csharp +> public static IEnumerable AlphabetSubset(char start, char end) +> { +> if (start < 'a' || start > 'z') +> { +> throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter"); +> } +> if (end < 'a' || end > 'z') +> { +> throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); +> } +> if (end <= start) +> { +> throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}"); +> } +> return alphabetSubsetImplementation(); +> +> IEnumerable alphabetSubsetImplementation() +> { +> for (var c = start; c < end; c++) +> yield return c; +> } +> } +> ``` +> +> *end example* + Unless specified otherwise below, the semantics of all grammar elements is the same as for *method_declaration* ([§14.6.1](classes.md#1461-general), read in the context of a local function instead of a method. A *local_function_declaration* may include one `async` ([§14.15](classes.md#1415-async-functions)) modifier and one `unsafe` ([§22.1](unsafe-code.md#221-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§14.15.1](classes.md#14151-general)). A local function is declared at block scope, and that function may capture variables from the enclosing scope. Captured variables must be definitely assigned before read by the body of the local function in each call to the function. The compiler shall determine which variables are definitely assigned on return. -> *Example*: The following example demonstrates definite assignment for captured variables in local functions. If a local function reads a captured variable before writing it, the captured variable must be definitely assigned before calling the local function. The local function `F1` reads `s` without assigning it. It is an error if `F1` is called before `s` is definitely assigned. `F2` assigns `i` before reading it. It may be called before `i` is definitely assigned. Furthermore, `F1` may be called after `F2` because `i` is definitely assigned in `F2`. +> *Example*: The following example demonstrates definite assignment for captured variables in local functions. If a local function reads a captured variable before writing it, the captured variable must be definitely assigned before calling the local function. The local function `F1` reads `s` without assigning it. It is an error if `F1` is called before `s` is definitely assigned. `F2` assigns `i` before reading it. It may be called before `i` is definitely assigned. Furthermore, `F3` may be called after `F2` because `s2` is definitely assigned in `F2`. > > ```csharp > void M() > { > string s; > int i; +> string s2; > > // Error: Use of unassigned local variable s: > F1(); @@ -466,7 +496,10 @@ A local function is declared at block scope, and that function may capture varia > > // OK. s is now definitely assigned. > F1(); -> +> +> // OK, F3 reads s2, which is definitely assigned in F2. +> F3(); +> > void F1() > { > Console.WriteLine(s); @@ -477,6 +510,12 @@ A local function is declared at block scope, and that function may capture varia > i = 5; > // OK. i is definitely assigned. > Console.WriteLine(i); +> s2 = i.ToString(); +> } +> +> void F3() +> { +> Console.WriteLine(s2); > } > } > ``` @@ -489,28 +528,6 @@ It is a compile-time error for a local function to declare a parameter or local Local function bodies are always reachable. The endpoint of a local function is reachable if the beginning point of the local function is reachable. -> *Example*: There are two common use cases for local functions: public iterator methods and public async methods. In iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. In async methods, any exceptions are only observed when the returned Task is awaited. The following example demonstrates separating parameter validation from the iterator implementation using a local function: -> -> ```csharp -> public static IEnumerable AlphabetSubset(char start, char end) -> { -> if (start < 'a' || start > 'z') -> throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter"); -> if (end < 'a' || end > 'z') -> throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); -> if (end <= start) -> throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}"); -> return alphabetSubsetImplementation(); -> IEnumerable alphabetSubsetImplementation() -> { -> for (var c = start; c < end; c++) -> yield return c; -> } -> } -> ``` -> -> *end example* - ## 12.7 Expression statements An *expression_statement* evaluates a given expression. The value computed by the expression, if any, is discarded. From 3173218a146006be9bc2c87a6fa508ce675d7a16 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 7 Jun 2022 16:25:42 -0400 Subject: [PATCH 11/27] grammar updates --- standard/statements.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/standard/statements.md b/standard/statements.md index bad4a275e..1a25b0e5e 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -427,11 +427,10 @@ local_function_declaration ; local_function_header - : local_function_modifiers? return_type identifier type_parameter_list? + : local_function_modifier* return_type identifier type_parameter_list? ( formal_parameter_list? ) type_parameter_constraints_clause* ; - -local_function_modifiers +local_function_modifier : 'async' | 'unsafe' ; @@ -439,9 +438,12 @@ local_function_modifiers local_function_body : block | '=>' expression ';' + | '=>' null_conditional_invocation_expression ';' ; ``` +Grammar note: When recognising a *local_function_body* if both the *null_conditional_invocation_expression* and *expression* alternatives are applicable then the former shall be chosen. + > *Example*: There are two common use cases for local functions: public iterator methods and public async methods. In iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. In async methods, any exceptions are only observed when the returned Task is awaited. The following example demonstrates separating parameter validation from the iterator implementation using a local function: > > ```csharp @@ -473,7 +475,7 @@ local_function_body Unless specified otherwise below, the semantics of all grammar elements is the same as for *method_declaration* ([§14.6.1](classes.md#1461-general), read in the context of a local function instead of a method. -A *local_function_declaration* may include one `async` ([§14.15](classes.md#1415-async-functions)) modifier and one `unsafe` ([§22.1](unsafe-code.md#221-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§14.15.1](classes.md#14151-general)). +A *local_function_declaration* may include one `async` ([§14.15](classes.md#1415-async-functions)) modifier and one `unsafe` ([§22.1](unsafe-code.md#221-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§14.15.1](classes.md#14151-general)). It is a compile-time error for *type_parameter_list* or *formal_parameter_list* to contain *attributes*. A local function is declared at block scope, and that function may capture variables from the enclosing scope. Captured variables must be definitely assigned before read by the body of the local function in each call to the function. The compiler shall determine which variables are definitely assigned on return. From 9cab99ce1f929b9f98883335718ae6577845fa59 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 12 Jul 2022 16:02:15 -0400 Subject: [PATCH 12/27] Incorporate Andy's text In this commit, create the new clause for the definite assignment rules for local functions. Move the existing example to that clause. Add text from Andy's comments and notes. --- standard/statements.md | 51 ++++------------------------------------ standard/variables.md | 53 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 46 deletions(-) diff --git a/standard/statements.md b/standard/statements.md index 1a25b0e5e..7e605e362 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -475,54 +475,11 @@ Grammar note: When recognising a *local_function_body* if both the *null_conditi Unless specified otherwise below, the semantics of all grammar elements is the same as for *method_declaration* ([§14.6.1](classes.md#1461-general), read in the context of a local function instead of a method. -A *local_function_declaration* may include one `async` ([§14.15](classes.md#1415-async-functions)) modifier and one `unsafe` ([§22.1](unsafe-code.md#221-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§14.15.1](classes.md#14151-general)). It is a compile-time error for *type_parameter_list* or *formal_parameter_list* to contain *attributes*. +The *identifier* of a *local_function_declaration* must be unique in its declared block scope. In other words, overloaded *local_function_declaration*s are not allowed. -A local function is declared at block scope, and that function may capture variables from the enclosing scope. Captured variables must be definitely assigned before read by the body of the local function in each call to the function. The compiler shall determine which variables are definitely assigned on return. +A *local_function_declaration* may include one `async` ([§14.15](classes.md#1415-async-functions)) modifier and one `unsafe` ([§22.1](unsafe-code.md#221-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§14.15.1](classes.md#14151-general)). The `unsafe` modifier uses the containing lexical scope. The `async` modifier does not use the containing lexical scope. It is a compile-time error for *type_parameter_list* or *formal_parameter_list* to contain *attributes*. -> *Example*: The following example demonstrates definite assignment for captured variables in local functions. If a local function reads a captured variable before writing it, the captured variable must be definitely assigned before calling the local function. The local function `F1` reads `s` without assigning it. It is an error if `F1` is called before `s` is definitely assigned. `F2` assigns `i` before reading it. It may be called before `i` is definitely assigned. Furthermore, `F3` may be called after `F2` because `s2` is definitely assigned in `F2`. -> -> ```csharp -> void M() -> { -> string s; -> int i; -> string s2; -> -> // Error: Use of unassigned local variable s: -> F1(); -> // OK, F2 assigns i before reading it. -> F2(); -> -> // OK, i is definitely assigned in the body of F2: -> s = i.ToString(); -> -> // OK. s is now definitely assigned. -> F1(); -> -> // OK, F3 reads s2, which is definitely assigned in F2. -> F3(); -> -> void F1() -> { -> Console.WriteLine(s); -> } -> -> void F2() -> { -> i = 5; -> // OK. i is definitely assigned. -> Console.WriteLine(i); -> s2 = i.ToString(); -> } -> -> void F3() -> { -> Console.WriteLine(s2); -> } -> } -> ``` -> -> *end example* +A local function is declared at block scope, and that function may capture variables from the enclosing scope. Captured variables must be definitely assigned before read by the body of the local function in each call to the function. The compiler shall determine which variables are definitely assigned on return (§definite-assignment-rules-for-local-function). A local function may be called from a lexical point prior to its definition. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable it wishes to capture. @@ -530,6 +487,8 @@ It is a compile-time error for a local function to declare a parameter or local Local function bodies are always reachable. The endpoint of a local function is reachable if the beginning point of the local function is reachable. +Calls to local functions must be statically resolved. + ## 12.7 Expression statements An *expression_statement* evaluates a given expression. The value computed by the expression, if any, is discarded. diff --git a/standard/variables.md b/standard/variables.md index 3adbea1c4..1c36c328e 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -802,6 +802,59 @@ For a *lambda_expression* or *anonymous_method_expression* *expr* with a body (e > > *end example* +#### §definite-assighment-rules-for-local-function Rules for variables in local functions + +Local functions are analyzed in the context of their parent method. There are two new control flow paths that matter for local functions: calls and delegate conversions. Definite assignment for the body of each local function is defined separately for each call site. At each invocation, variables captured by the local function are considered definitely assigned if they were definitely assigned at the point of call. A control flow path also exists to the local function body at this point and is considered reachable. After a call to the local function, captured variables that were definitely assigned at every control point leaving the function (`return` statements, `yield` statements, `await` statements) are considered definitely assigned after the call location. + +Delegate conversions have a control flow path to the local function body and captured variables are also considered assigned for the body if they are assigned before the conversion, but variables assigned by the local function are not considered assigned after the conversion. + +Note: the above implies that bodies are re-analyzed for definite assignment at every local function invocation or delegate invocation. That's one way to achieve the required result, but for performance the compiler doesn't actually do so. The implementation should be equivalent, however. + +> *Example*: The following example demonstrates definite assignment for captured variables in local functions. If a local function reads a captured variable before writing it, the captured variable must be definitely assigned before calling the local function. The local function `F1` reads `s` without assigning it. It is an error if `F1` is called before `s` is definitely assigned. `F2` assigns `i` before reading it. It may be called before `i` is definitely assigned. Furthermore, `F3` may be called after `F2` because `s2` is definitely assigned in `F2`. +> +> ```csharp +> void M() +> { +> string s; +> int i; +> string s2; +> +> // Error: Use of unassigned local variable s: +> F1(); +> // OK, F2 assigns i before reading it. +> F2(); +> +> // OK, i is definitely assigned in the body of F2: +> s = i.ToString(); +> +> // OK. s is now definitely assigned. +> F1(); +> +> // OK, F3 reads s2, which is definitely assigned in F2. +> F3(); +> +> void F1() +> { +> Console.WriteLine(s); +> } +> +> void F2() +> { +> i = 5; +> // OK. i is definitely assigned. +> Console.WriteLine(i); +> s2 = i.ToString(); +> } +> +> void F3() +> { +> Console.WriteLine(s2); +> } +> } +> ``` +> +> *end example* + ## 9.5 Variable references A *variable_reference* is an *expression* that is classified as a variable. A *variable_reference* denotes a storage location that can be accessed both to fetch the current value and to store a new value. From 792668f161f1769104e785f0a8e0d1791978a867 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 12 Jul 2022 16:14:22 -0400 Subject: [PATCH 13/27] finish local functions and definite assignment. Incorporate the rules for definite assignment for captured variables in local functions. This is both for local function calls and for delegate conversion. --- standard/variables.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/standard/variables.md b/standard/variables.md index 1c36c328e..79bc9cd89 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -804,12 +804,16 @@ For a *lambda_expression* or *anonymous_method_expression* *expr* with a body (e #### §definite-assighment-rules-for-local-function Rules for variables in local functions -Local functions are analyzed in the context of their parent method. There are two new control flow paths that matter for local functions: calls and delegate conversions. Definite assignment for the body of each local function is defined separately for each call site. At each invocation, variables captured by the local function are considered definitely assigned if they were definitely assigned at the point of call. A control flow path also exists to the local function body at this point and is considered reachable. After a call to the local function, captured variables that were definitely assigned at every control point leaving the function (`return` statements, `yield` statements, `await` statements) are considered definitely assigned after the call location. +Local functions are analyzed in the context of their parent method. There are two control flow paths that matter for local functions: function calls and delegate conversions. -Delegate conversions have a control flow path to the local function body and captured variables are also considered assigned for the body if they are assigned before the conversion, but variables assigned by the local function are not considered assigned after the conversion. +Definite assignment for the body of each local function is defined separately for each call site. At each invocation, variables captured by the local function are considered definitely assigned if they were definitely assigned at the point of call. A control flow path also exists to the local function body at this point and is considered reachable. After a call to the local function, captured variables that were definitely assigned at every control point leaving the function (`return` statements, `yield` statements, `await` statements) are considered definitely assigned after the call location. -Note: the above implies that bodies are re-analyzed for definite assignment at every local function invocation or delegate invocation. That's one way to achieve the required result, but for performance the compiler doesn't actually do so. The implementation should be equivalent, however. +Delegate conversions have a control flow path to the local function body. Captured variables are definitely assigned for the body if they are definitely assigned before the conversion. Variables assigned by the local function are not considered assigned after the conversion. +> *note:* the above implies that bodies are re-analyzed for definite assignment at every local function invocation or delegate conversion. Compilers are not required to re-analyze the body of a local function at each invocation or delegate conversion. The implementation must produce results equivalent to that description. + + + > *Example*: The following example demonstrates definite assignment for captured variables in local functions. If a local function reads a captured variable before writing it, the captured variable must be definitely assigned before calling the local function. The local function `F1` reads `s` without assigning it. It is an error if `F1` is called before `s` is definitely assigned. `F2` assigns `i` before reading it. It may be called before `i` is definitely assigned. Furthermore, `F3` may be called after `F2` because `s2` is definitely assigned in `F2`. > > ```csharp From e36eb5add894e1a6a5f0fe9316adacc5d2d08e2f Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 12 Jul 2022 16:17:12 -0400 Subject: [PATCH 14/27] typo --- standard/variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/variables.md b/standard/variables.md index 79bc9cd89..50a658f28 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -802,7 +802,7 @@ For a *lambda_expression* or *anonymous_method_expression* *expr* with a body (e > > *end example* -#### §definite-assighment-rules-for-local-function Rules for variables in local functions +#### §definite-assignment-rules-for-local-function Rules for variables in local functions Local functions are analyzed in the context of their parent method. There are two control flow paths that matter for local functions: function calls and delegate conversions. From 070b067992aad40f7002d932112e8982d4717f61 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 7 Sep 2022 13:31:27 -0400 Subject: [PATCH 15/27] Apply suggestions from code review Co-authored-by: Nigel-Ecma --- standard/statements.md | 2 +- standard/variables.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/standard/statements.md b/standard/statements.md index 7e605e362..e046a68c1 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -473,7 +473,7 @@ Grammar note: When recognising a *local_function_body* if both the *null_conditi > > *end example* -Unless specified otherwise below, the semantics of all grammar elements is the same as for *method_declaration* ([§14.6.1](classes.md#1461-general), read in the context of a local function instead of a method. +Unless specified otherwise below, the semantics of all grammar elements is the same as for *method_declaration* ([§14.6.1](classes.md#1461-general)), read in the context of a local function instead of a method. The *identifier* of a *local_function_declaration* must be unique in its declared block scope. In other words, overloaded *local_function_declaration*s are not allowed. diff --git a/standard/variables.md b/standard/variables.md index 50a658f28..ba7ef1e67 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -810,7 +810,7 @@ Definite assignment for the body of each local function is defined separately fo Delegate conversions have a control flow path to the local function body. Captured variables are definitely assigned for the body if they are definitely assigned before the conversion. Variables assigned by the local function are not considered assigned after the conversion. -> *note:* the above implies that bodies are re-analyzed for definite assignment at every local function invocation or delegate conversion. Compilers are not required to re-analyze the body of a local function at each invocation or delegate conversion. The implementation must produce results equivalent to that description. +> *Note:* the above implies that bodies are re-analyzed for definite assignment at every local function invocation or delegate conversion. Compilers are not required to re-analyze the body of a local function at each invocation or delegate conversion. The implementation must produce results equivalent to that description. From 99d6a49204b1eebe7ba8284a16e0611ac22125a9 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 7 Sep 2022 13:32:01 -0400 Subject: [PATCH 16/27] Apply suggestions from code review --- standard/statements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/statements.md b/standard/statements.md index e046a68c1..67a6eb5e3 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -444,7 +444,7 @@ local_function_body Grammar note: When recognising a *local_function_body* if both the *null_conditional_invocation_expression* and *expression* alternatives are applicable then the former shall be chosen. -> *Example*: There are two common use cases for local functions: public iterator methods and public async methods. In iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. In async methods, any exceptions are only observed when the returned Task is awaited. The following example demonstrates separating parameter validation from the iterator implementation using a local function: +> *Example*: There are two common use cases for local functions: iterator methods and async methods. In iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. In async methods, any exceptions are only observed when the returned Task is awaited. The following example demonstrates separating parameter validation from the iterator implementation using a local function: > > ```csharp > public static IEnumerable AlphabetSubset(char start, char end) From 600c1e3720f1d47a030c54b686ed726a4603bdbf Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 7 Sep 2022 17:01:33 -0400 Subject: [PATCH 17/27] Update standard/statements.md Co-authored-by: Jon Skeet --- standard/statements.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/standard/statements.md b/standard/statements.md index 67a6eb5e3..17356f853 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -466,7 +466,9 @@ Grammar note: When recognising a *local_function_body* if both the *null_conditi > IEnumerable alphabetSubsetImplementation() > { > for (var c = start; c < end; c++) +> { > yield return c; +> } > } > } > ``` From ffc7c8006bd3119c1979299db22b158453ee959b Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Wed, 7 Sep 2022 22:02:40 +0100 Subject: [PATCH 18/27] Update standard/statements.md --- standard/statements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/statements.md b/standard/statements.md index 17356f853..67083e941 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -481,7 +481,7 @@ The *identifier* of a *local_function_declaration* must be unique in its declare A *local_function_declaration* may include one `async` ([§14.15](classes.md#1415-async-functions)) modifier and one `unsafe` ([§22.1](unsafe-code.md#221-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§14.15.1](classes.md#14151-general)). The `unsafe` modifier uses the containing lexical scope. The `async` modifier does not use the containing lexical scope. It is a compile-time error for *type_parameter_list* or *formal_parameter_list* to contain *attributes*. -A local function is declared at block scope, and that function may capture variables from the enclosing scope. Captured variables must be definitely assigned before read by the body of the local function in each call to the function. The compiler shall determine which variables are definitely assigned on return (§definite-assignment-rules-for-local-function). +A local function is declared at block scope, and that function may capture variables from the enclosing scope. Captured variables must be definitely assigned before being read by the body of the local function in each call to the function. The compiler shall determine which variables are definitely assigned on return (§definite-assignment-rules-for-local-function). A local function may be called from a lexical point prior to its definition. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable it wishes to capture. From 9792da11c14c19e7773d3baddacd8df745a85e2b Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Wed, 7 Sep 2022 22:03:00 +0100 Subject: [PATCH 19/27] Update standard/statements.md Co-authored-by: Neal Gafter --- standard/statements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/statements.md b/standard/statements.md index 67083e941..dac7cc12c 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -487,7 +487,7 @@ A local function may be called from a lexical point prior to its definition. How It is a compile-time error for a local function to declare a parameter or local variable with the same name as one declared in the enclosing scope. -Local function bodies are always reachable. The endpoint of a local function is reachable if the beginning point of the local function is reachable. +Local function bodies are always reachable. The endpoint of a local function declaration is reachable if the beginning point of the local function declaration is reachable. Calls to local functions must be statically resolved. From 0d3135a3595f2e2b4a3f11afc52f81cc63a41166 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Wed, 7 Sep 2022 22:03:27 +0100 Subject: [PATCH 20/27] Update standard/statements.md --- standard/statements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/statements.md b/standard/statements.md index dac7cc12c..d20d1df9f 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -479,7 +479,7 @@ Unless specified otherwise below, the semantics of all grammar elements is the s The *identifier* of a *local_function_declaration* must be unique in its declared block scope. In other words, overloaded *local_function_declaration*s are not allowed. -A *local_function_declaration* may include one `async` ([§14.15](classes.md#1415-async-functions)) modifier and one `unsafe` ([§22.1](unsafe-code.md#221-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§14.15.1](classes.md#14151-general)). The `unsafe` modifier uses the containing lexical scope. The `async` modifier does not use the containing lexical scope. It is a compile-time error for *type_parameter_list* or *formal_parameter_list* to contain *attributes*. +A *local_function_declaration* may include one `async` ([§14.15](classes.md#1415-async-functions)) modifier and one `unsafe` ([§22.1](unsafe-code.md#221-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§14.15.1](classes.md#14151-general)). The `unsafe` modifier uses the containing lexical scope. The `async` modifier does not use the containing lexical scope. It is a compile-time error for *type_parameter_list* or *formal_parameter_list* to contain *attributes*. A local function is declared at block scope, and that function may capture variables from the enclosing scope. Captured variables must be definitely assigned before being read by the body of the local function in each call to the function. The compiler shall determine which variables are definitely assigned on return (§definite-assignment-rules-for-local-function). From dbb1ee00add169a51381d9308419b1144fa7f70e Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Wed, 7 Sep 2022 22:29:03 +0100 Subject: [PATCH 21/27] Update standard/statements.md --- standard/statements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/statements.md b/standard/statements.md index d20d1df9f..4dacd5ff7 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -477,7 +477,7 @@ Grammar note: When recognising a *local_function_body* if both the *null_conditi Unless specified otherwise below, the semantics of all grammar elements is the same as for *method_declaration* ([§14.6.1](classes.md#1461-general)), read in the context of a local function instead of a method. -The *identifier* of a *local_function_declaration* must be unique in its declared block scope. In other words, overloaded *local_function_declaration*s are not allowed. +The *identifier* of a *local_function_declaration* must be unique in its declared block scope. One consequence of this is that overloaded *local_function_declaration*s are not allowed. A *local_function_declaration* may include one `async` ([§14.15](classes.md#1415-async-functions)) modifier and one `unsafe` ([§22.1](unsafe-code.md#221-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§14.15.1](classes.md#14151-general)). The `unsafe` modifier uses the containing lexical scope. The `async` modifier does not use the containing lexical scope. It is a compile-time error for *type_parameter_list* or *formal_parameter_list* to contain *attributes*. From 9cf2cb09d18373aba4f04b55316e5a7dec420490 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 27 Sep 2022 16:48:48 -0400 Subject: [PATCH 22/27] edits based on September committee meeting --- standard/statements.md | 10 +++++----- standard/unsafe-code.md | 5 ++--- standard/variables.md | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/standard/statements.md b/standard/statements.md index 4dacd5ff7..f017c9e11 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -437,12 +437,12 @@ local_function_modifier local_function_body : block - | '=>' expression ';' | '=>' null_conditional_invocation_expression ';' + | '=>' expression ';' ; ``` -Grammar note: When recognising a *local_function_body* if both the *null_conditional_invocation_expression* and *expression* alternatives are applicable then the former shall be chosen. +Grammar note: When recognising a *local_function_body* if both the *null_conditional_invocation_expression* and *expression* alternatives are applicable then the former shall be chosen. ([§14.6.1]) > *Example*: There are two common use cases for local functions: iterator methods and async methods. In iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. In async methods, any exceptions are only observed when the returned Task is awaited. The following example demonstrates separating parameter validation from the iterator implementation using a local function: > @@ -481,15 +481,15 @@ The *identifier* of a *local_function_declaration* must be unique in its declare A *local_function_declaration* may include one `async` ([§14.15](classes.md#1415-async-functions)) modifier and one `unsafe` ([§22.1](unsafe-code.md#221-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§14.15.1](classes.md#14151-general)). The `unsafe` modifier uses the containing lexical scope. The `async` modifier does not use the containing lexical scope. It is a compile-time error for *type_parameter_list* or *formal_parameter_list* to contain *attributes*. -A local function is declared at block scope, and that function may capture variables from the enclosing scope. Captured variables must be definitely assigned before being read by the body of the local function in each call to the function. The compiler shall determine which variables are definitely assigned on return (§definite-assignment-rules-for-local-function). +A local function is declared at block scope, and that function may capture variables from the enclosing scope. It is a compile time error if a captured variables is read by the body of the local function if it is not definitely assigned before each call to the function. The compiler shall determine which variables are definitely assigned on return (§definite-assignment-rules-for-local-function). -A local function may be called from a lexical point prior to its definition. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable it wishes to capture. +A local function may be called from a lexical point prior to its definition. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable used in the local function ([§7.7]). It is a compile-time error for a local function to declare a parameter or local variable with the same name as one declared in the enclosing scope. Local function bodies are always reachable. The endpoint of a local function declaration is reachable if the beginning point of the local function declaration is reachable. -Calls to local functions must be statically resolved. +If the argument to a local function is dynamic, the function to be called must be resolved at compile time, not runtime. ## 12.7 Expression statements diff --git a/standard/unsafe-code.md b/standard/unsafe-code.md index ff5d2f269..d0a4168ed 100644 --- a/standard/unsafe-code.md +++ b/standard/unsafe-code.md @@ -22,9 +22,8 @@ The unsafe features of C# are available only in unsafe contexts. An unsafe conte - A declaration of a class, struct, interface, or delegate may include an `unsafe` modifier, in which case, the entire textual extent of that type declaration (including the body of the class, struct, or interface) is considered an unsafe context. > *Note*: If the *type_declaration* is partial, only that part is an unsafe context. *end note* -- A declaration of a field, method, property, event, indexer, operator, instance constructor, finalizer, or static constructor may include an `unsafe` modifier, in which case, the entire textual extent of that member declaration is considered an unsafe context. -- A declaration of a local function may include an `unsafe` modifier, in which case, the entire textual extent of that local function is considered an unsafe context. -- An *unsafe_statement* enables the use of an unsafe context within a *block*. The entire textual extent of the associated *block* is considered an unsafe context. +- A declaration of a field, method, property, event, indexer, operator, instance constructor, finalizer, static constructor, or local function may include an `unsafe` modifier, in which case, the entire textual extent of that member declaration is considered an unsafe context. +- An *unsafe_statement* enables the use of an unsafe context within a *block*. The entire textual extent of the associated *block* is considered an unsafe context. A local function declared within an unsafe context is itself unsafe. The associated grammar extensions are shown below and in subsequent subclauses. diff --git a/standard/variables.md b/standard/variables.md index ba7ef1e67..70bc75a60 100644 --- a/standard/variables.md +++ b/standard/variables.md @@ -806,7 +806,7 @@ For a *lambda_expression* or *anonymous_method_expression* *expr* with a body (e Local functions are analyzed in the context of their parent method. There are two control flow paths that matter for local functions: function calls and delegate conversions. -Definite assignment for the body of each local function is defined separately for each call site. At each invocation, variables captured by the local function are considered definitely assigned if they were definitely assigned at the point of call. A control flow path also exists to the local function body at this point and is considered reachable. After a call to the local function, captured variables that were definitely assigned at every control point leaving the function (`return` statements, `yield` statements, `await` statements) are considered definitely assigned after the call location. +Definite assignment for the body of each local function is defined separately for each call site. At each invocation, variables captured by the local function are considered definitely assigned if they were definitely assigned at the point of call. A control flow path also exists to the local function body at this point and is considered reachable. After a call to the local function, captured variables that were definitely assigned at every control point leaving the function (`return` statements, `yield` statements, `await` expressions) are considered definitely assigned after the call location. Delegate conversions have a control flow path to the local function body. Captured variables are definitely assigned for the body if they are definitely assigned before the conversion. Variables assigned by the local function are not considered assigned after the conversion. From 3a4fc3d54c94cf4ab2b86c5c5a9ffe9f7c7419d3 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 27 Sep 2022 16:53:58 -0400 Subject: [PATCH 23/27] fix link text --- standard/statements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/statements.md b/standard/statements.md index f017c9e11..358a99834 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -483,7 +483,7 @@ A *local_function_declaration* may include one `async` ([§14.15](classes.md#141 A local function is declared at block scope, and that function may capture variables from the enclosing scope. It is a compile time error if a captured variables is read by the body of the local function if it is not definitely assigned before each call to the function. The compiler shall determine which variables are definitely assigned on return (§definite-assignment-rules-for-local-function). -A local function may be called from a lexical point prior to its definition. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable used in the local function ([§7.7]). +A local function may be called from a lexical point prior to its definition. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable used in the local function (§7.7). It is a compile-time error for a local function to declare a parameter or local variable with the same name as one declared in the enclosing scope. From d3574e16a6b7a3cbd650de0be8f041a76e16043f Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Sun, 2 Oct 2022 17:14:54 -0400 Subject: [PATCH 24/27] fix section renumbering issue --- standard/statements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/statements.md b/standard/statements.md index 358a99834..a277d1666 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -442,7 +442,7 @@ local_function_body ; ``` -Grammar note: When recognising a *local_function_body* if both the *null_conditional_invocation_expression* and *expression* alternatives are applicable then the former shall be chosen. ([§14.6.1]) +Grammar note: When recognising a *local_function_body* if both the *null_conditional_invocation_expression* and *expression* alternatives are applicable then the former shall be chosen. (§14.6.1) > *Example*: There are two common use cases for local functions: iterator methods and async methods. In iterator methods, any exceptions are observed only when calling code that enumerates the returned sequence. In async methods, any exceptions are only observed when the returned Task is awaited. The following example demonstrates separating parameter validation from the iterator implementation using a local function: > From ffac29346661ba55b7c45c284e97ef1810534f99 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 5 Oct 2022 18:34:45 -0400 Subject: [PATCH 25/27] Final edits We resolved these two discussions during the committee meeting. Edits reflect those decisions. --- standard/statements.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/standard/statements.md b/standard/statements.md index a277d1666..647cbff3e 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -461,9 +461,9 @@ Grammar note: When recognising a *local_function_body* if both the *null_conditi > { > throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}"); > } -> return alphabetSubsetImplementation(); +> return AlphabetSubsetImplementation(); > -> IEnumerable alphabetSubsetImplementation() +> IEnumerable AlphabetSubsetImplementation() > { > for (var c = start; c < end; c++) > { @@ -489,6 +489,26 @@ It is a compile-time error for a local function to declare a parameter or local Local function bodies are always reachable. The endpoint of a local function declaration is reachable if the beginning point of the local function declaration is reachable. +> *Example*: In the following example, the body of `L` is reachable even though the beginning point of `L` is not reachable. Because the beginning point of `L` isn't reachable, the statement following the endpoint of `L` is not reachable: +> +> ```csharp +> class C +> { +> int M() +> { +> L(); +> return 1; // Beginning of L is not reachable +> int L() +> { +> return 2; // The body of L is reachable +> } +> return 3; // Not reachable, because beginning point of L is not reachable +> } +> } +> ``` +> +> In other words, the location of a local function declaration doesn't affect the reachability of any statements in the containing function. *end example* + If the argument to a local function is dynamic, the function to be called must be resolved at compile time, not runtime. ## 12.7 Expression statements From 5e651228f5609e3d245e83579932166b5bb246e7 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 11 Oct 2022 07:29:06 -0400 Subject: [PATCH 26/27] updates from code review. --- standard/statements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/statements.md b/standard/statements.md index 647cbff3e..65cddc8f3 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -481,7 +481,7 @@ The *identifier* of a *local_function_declaration* must be unique in its declare A *local_function_declaration* may include one `async` ([§14.15](classes.md#1415-async-functions)) modifier and one `unsafe` ([§22.1](unsafe-code.md#221-general)) modifier. If the declaration includes the `async` modifier then the return type shall be `void` or a task type ([§14.15.1](classes.md#14151-general)). The `unsafe` modifier uses the containing lexical scope. The `async` modifier does not use the containing lexical scope. It is a compile-time error for *type_parameter_list* or *formal_parameter_list* to contain *attributes*. -A local function is declared at block scope, and that function may capture variables from the enclosing scope. It is a compile time error if a captured variables is read by the body of the local function if it is not definitely assigned before each call to the function. The compiler shall determine which variables are definitely assigned on return (§definite-assignment-rules-for-local-function). +A local function is declared at block scope, and that function may capture variables from the enclosing scope. It is a compile-time error if a captured variable is read by the body of the local function but is not definitely assigned before each call to the function. The compiler shall determine which variables are definitely assigned on return (§definite-assignment-rules-for-local-function). A local function may be called from a lexical point prior to its definition. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable used in the local function (§7.7). From a5d8436307f671e86a0a94b8523d4e8561027f77 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 11 Oct 2022 07:30:54 -0400 Subject: [PATCH 27/27] one more bit of feedback. --- standard/statements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/statements.md b/standard/statements.md index 65cddc8f3..1ecc414f5 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -483,7 +483,7 @@ A *local_function_declaration* may include one `async` ([§14.15](classes.md#141 A local function is declared at block scope, and that function may capture variables from the enclosing scope. It is a compile-time error if a captured variable is read by the body of the local function but is not definitely assigned before each call to the function. The compiler shall determine which variables are definitely assigned on return (§definite-assignment-rules-for-local-function). -A local function may be called from a lexical point prior to its definition. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable used in the local function (§7.7). +A local function may be called from a lexical point prior to its declaration. However, it is a compile-time error for the function to be declared lexically prior to the declaration of a variable used in the local function (§7.7). It is a compile-time error for a local function to declare a parameter or local variable with the same name as one declared in the enclosing scope.