From 089b2aad0076dd62702fb512a23cd6e28bed54f2 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Fri, 21 Jul 2023 17:58:10 +0300 Subject: [PATCH 01/16] Add spec for type inference in ref/in/out lambda params --- proposals/ref-out-lambda-params.md | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 proposals/ref-out-lambda-params.md diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md new file mode 100644 index 0000000000..758297d0b3 --- /dev/null +++ b/proposals/ref-out-lambda-params.md @@ -0,0 +1,32 @@ +# Declaration of `in`/`ref`/`out` lambda parameters without type name + +## Summary + +Allow lambda parameter declarations with `in`/`ref`/`out` to be declared without requiring their type names. +```cs +// Given this delegate +delegate bool TryParse(string text, out T result); + +// Allow this simplified parameter declaration +TryParse parse1 = (text, out result) => Int32.TryParse(text, out result); + +// Currently only this is valid +TryParse parse2 = (string text, out int result) => Int32.TryParse(text, out result); +``` + +## Detailed design + +### Parameter declaration + +Parameter declarations in lambda expressions now permit a single identifier after an `in`/`ref`/`out` modifier on the parameter. + +The change in the spec will require that, in [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general), the `implicit_anonymous_function_parameter` rule must be adjusted as follows: + +```diff + implicit_anonymous_function_parameter +- : identifier ++ : anonymous_function_parameter_modifier? identifier + ; +``` + +The type of the parameters matches the type of the parameter in the target delegate type, including the by-reference modifiers. From ecfe27f571fdef5970c659decf647eec730abb47 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Sat, 22 Jul 2023 12:35:55 +0300 Subject: [PATCH 02/16] Minor clarity updates --- proposals/ref-out-lambda-params.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index 758297d0b3..e9feaea766 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -3,14 +3,19 @@ ## Summary Allow lambda parameter declarations with `in`/`ref`/`out` to be declared without requiring their type names. + +Given this delegate: ```cs -// Given this delegate delegate bool TryParse(string text, out T result); +``` -// Allow this simplified parameter declaration +Allow this simplified parameter declaration: +```cs TryParse parse1 = (text, out result) => Int32.TryParse(text, out result); +``` -// Currently only this is valid +Currently only this is valid: +```cs TryParse parse2 = (string text, out int result) => Int32.TryParse(text, out result); ``` @@ -30,3 +35,5 @@ The change in the spec will require that, in [the grammar for lambda expressions ``` The type of the parameters matches the type of the parameter in the target delegate type, including the by-reference modifiers. + +Attributes on the parameters will not be affected in any way. Similarly, `async` lambdas will also not be affected from this change. From 5fe5f9f01481df6a444cc4e60f0804f47aef0137 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Sat, 22 Jul 2023 12:50:31 +0300 Subject: [PATCH 03/16] Add information about more cases --- proposals/ref-out-lambda-params.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index e9feaea766..5f0de42833 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -37,3 +37,25 @@ The change in the spec will require that, in [the grammar for lambda expressions The type of the parameters matches the type of the parameter in the target delegate type, including the by-reference modifiers. Attributes on the parameters will not be affected in any way. Similarly, `async` lambdas will also not be affected from this change. + +If the lambda expression is not assigned to an expression with a type, the type cannot be inferred from usage. For example, the following is illegal: +```csharp +var d = (in a, ref b, out c) => +{ + Method(in a, ref b, out c); +} + +void Method(in int a, ref int b, out int c) +{ + c = a; + b = c; +} +``` + +This remains illegal as the current behavior for implicit-typed parameters without modifiers does not infer the type of the parameters through usage inside the body of the lambda expression. For example, the following is illegal: +```csharp +// Error: The delegate type could not be inferred +var dd = (a, b) => Method2(a, b); + +int Method2(int a, int b) => a + b; +``` From bce67a11ab3800244ee5a8b0d2d3bc8be4be2b6e Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Sat, 22 Jul 2023 18:34:37 +0300 Subject: [PATCH 04/16] Reduce to parenthesized lambda parameters --- proposals/ref-out-lambda-params.md | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index 5f0de42833..c89de79c3f 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -23,17 +23,39 @@ TryParse parse2 = (string text, out int result) => Int32.TryParse(text, out ### Parameter declaration -Parameter declarations in lambda expressions now permit a single identifier after an `in`/`ref`/`out` modifier on the parameter. +Parameter declarations in lambda expressions with parenthesized parameters now permit a single identifier after an `in`/`ref`/`out` modifier on the parameter. This does not apply to lambda expressions with a single parameter with omitted parentheses. -The change in the spec will require that, in [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general), the `implicit_anonymous_function_parameter` rule must be adjusted as follows: +For example, +```csharp +SelfReturnerIn f = in x => x; +SelfReturnerRef g = ref x => x; +SelfReturnerOut h = out x => x; + +delegate T SelfReturnerIn(in T t); +delegate T SelfReturnerRef(ref T t); +delegate T SelfReturnerOut(out T t); +``` + +are all illegal, due to ambiguity with taking the reference of the returned expression in the `ref` case. For consistency, `in` and `out` are also left unsupported and illegal. + +The change in the spec will require that, [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general) must be adjusted as follows: ```diff - implicit_anonymous_function_parameter -- : identifier + implicit_anonymous_function_parameter_list +- : implicit_anonymous_function_parameter +- (',' implicit_anonymous_function_parameter)* ++ : implicit_parenthesized_anonymous_function_parameter ++ (',' implicit_parenthesized_anonymous_function_parameter)* + ; + ++ implicit_parenthesized_anonymous_function_parameter ++ : identifier + : anonymous_function_parameter_modifier? identifier ; ``` +The new grammar rule is added so that this change specifically affects parenthesized lambda expression parameters, leaving lambda expressions with an unparenthesized parameter unsupported. + The type of the parameters matches the type of the parameter in the target delegate type, including the by-reference modifiers. Attributes on the parameters will not be affected in any way. Similarly, `async` lambdas will also not be affected from this change. From 2f53f3ea578929350708eebabd45da2fc53f9528 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Sat, 22 Jul 2023 19:27:15 +0300 Subject: [PATCH 05/16] Remove superfluous rule + paragraph --- proposals/ref-out-lambda-params.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index c89de79c3f..313bcd878f 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -49,13 +49,10 @@ The change in the spec will require that, [the grammar for lambda expressions](h ; + implicit_parenthesized_anonymous_function_parameter -+ : identifier + : anonymous_function_parameter_modifier? identifier ; ``` -The new grammar rule is added so that this change specifically affects parenthesized lambda expression parameters, leaving lambda expressions with an unparenthesized parameter unsupported. - The type of the parameters matches the type of the parameter in the target delegate type, including the by-reference modifiers. Attributes on the parameters will not be affected in any way. Similarly, `async` lambdas will also not be affected from this change. From d8a76186ab11a88fccb481e3b31161b179f4d5a7 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Mon, 24 Jul 2023 21:28:35 +0300 Subject: [PATCH 06/16] Add support for `ref readonly` --- proposals/ref-out-lambda-params.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index 313bcd878f..c5df23c5c0 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -1,8 +1,8 @@ -# Declaration of `in`/`ref`/`out` lambda parameters without type name +# Declaration of lambda parameters with by-reference modifiers without type name ## Summary -Allow lambda parameter declarations with `in`/`ref`/`out` to be declared without requiring their type names. +Allow lambda parameter declarations with by-reference modifiers (`in` / `ref` / `out` / `ref readonly`) to be declared without requiring their type names. Given this delegate: ```cs @@ -23,20 +23,22 @@ TryParse parse2 = (string text, out int result) => Int32.TryParse(text, out ### Parameter declaration -Parameter declarations in lambda expressions with parenthesized parameters now permit a single identifier after an `in`/`ref`/`out` modifier on the parameter. This does not apply to lambda expressions with a single parameter with omitted parentheses. +Parameter declarations in lambda expressions with parenthesized parameters now permit a single identifier after a modifier on the parameter. This does not apply to lambda expressions with a single parameter with omitted parentheses. For example, ```csharp -SelfReturnerIn f = in x => x; -SelfReturnerRef g = ref x => x; -SelfReturnerOut h = out x => x; +SelfReturnerIn fin = in x => x; +SelfReturnerRef fref = ref x => x; +SelfReturnerOut fout = out x => x; +SelfReturnerRefReadonly frr = ref readonly x => x; delegate T SelfReturnerIn(in T t); delegate T SelfReturnerRef(ref T t); delegate T SelfReturnerOut(out T t); +delegate T SelfReturnerRefReadonly(ref readonly T t); ``` -are all illegal, due to ambiguity with taking the reference of the returned expression in the `ref` case. For consistency, `in` and `out` are also left unsupported and illegal. +are all illegal, due to ambiguity with taking the reference of the returned expression in the `ref` case. For consistency, `in`, `out` and `ref readonly` are also left unsupported and illegal. The change in the spec will require that, [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general) must be adjusted as follows: @@ -49,8 +51,8 @@ The change in the spec will require that, [the grammar for lambda expressions](h ; + implicit_parenthesized_anonymous_function_parameter -+ : anonymous_function_parameter_modifier? identifier - ; ++ : anonymous_function_parameter_modifier? identifier + ; ``` The type of the parameters matches the type of the parameter in the target delegate type, including the by-reference modifiers. From d2aeb260671f10395ea5c3f47103c46f43b637ab Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Fri, 28 Jul 2023 21:16:33 +0300 Subject: [PATCH 07/16] Apply suggestions and relax modifier eligibility --- proposals/ref-out-lambda-params.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index c5df23c5c0..f2ba2fa6fc 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -1,8 +1,8 @@ -# Declaration of lambda parameters with by-reference modifiers without type name +# Declaration of lambda parameters with modifiers without type name ## Summary -Allow lambda parameter declarations with by-reference modifiers (`in` / `ref` / `out` / `ref readonly`) to be declared without requiring their type names. +Allow lambda parameter declarations with modifiers (`in` / `ref` / `out` / `ref readonly` / `scoped` / `scoped ref` / `params`) to be declared without requiring their type names. Given this delegate: ```cs @@ -31,16 +31,22 @@ SelfReturnerIn fin = in x => x; SelfReturnerRef fref = ref x => x; SelfReturnerOut fout = out x => x; SelfReturnerRefReadonly frr = ref readonly x => x; +SelfReturnerScoped frr = scoped x => x; +SelfReturnerScopedRef frr = scoped ref x => x; +SelfReturnerParams frr = params x => x; delegate T SelfReturnerIn(in T t); delegate T SelfReturnerRef(ref T t); delegate T SelfReturnerOut(out T t); delegate T SelfReturnerRefReadonly(ref readonly T t); +delegate T SelfReturnerScoped(scoped T t); +delegate T SelfReturnerScopedRef(scoped ref T t); +delegate T SelfReturnerParams(params T[] t); ``` -are all illegal, due to ambiguity with taking the reference of the returned expression in the `ref` case. For consistency, `in`, `out` and `ref readonly` are also left unsupported and illegal. +are all illegal, due to ambiguity with taking the reference of the returned expression in the `ref` case. For consistency, `in`, `out` and `ref readonly`, `scoped`, `scoped ref`, `params` are also left unsupported and illegal. -The change in the spec will require that, [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general) must be adjusted as follows: +The change in the spec will require that [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general) be adjusted as follows: ```diff implicit_anonymous_function_parameter_list @@ -51,13 +57,15 @@ The change in the spec will require that, [the grammar for lambda expressions](h ; + implicit_parenthesized_anonymous_function_parameter -+ : anonymous_function_parameter_modifier? identifier ++ : anonymous_function_parameter_modifier? implicit_anonymous_function_parameter ; ``` -The type of the parameters matches the type of the parameter in the target delegate type, including the by-reference modifiers. +The type of the parameters matches the type of the parameter in the target delegate type, including the modifiers. -Attributes on the parameters will not be affected in any way. Similarly, `async` lambdas will also not be affected from this change. +Attributes on the parameters will not be affected in any way. + +It will still be illegal for `async` lambdas to contain by-ref parameters, since it is illegal to have by-ref parameters in async methods. If the lambda expression is not assigned to an expression with a type, the type cannot be inferred from usage. For example, the following is illegal: ```csharp From e555f344681f307398484bca6c890130f8190ecc Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Sun, 30 Jul 2023 15:28:49 +0300 Subject: [PATCH 08/16] Minor wording improvement --- proposals/ref-out-lambda-params.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index f2ba2fa6fc..26dfcfc08d 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -44,7 +44,7 @@ delegate T SelfReturnerScopedRef(scoped ref T t); delegate T SelfReturnerParams(params T[] t); ``` -are all illegal, due to ambiguity with taking the reference of the returned expression in the `ref` case. For consistency, `in`, `out` and `ref readonly`, `scoped`, `scoped ref`, `params` are also left unsupported and illegal. +are all illegal, due to ambiguity with taking the reference of the returned expression in the `ref` case. For consistency, all other modifiers are also left unsupported and illegal. The change in the spec will require that [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general) be adjusted as follows: From fc881e6095db72e526d0f642ff9ba562350f6bb9 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Mon, 31 Jul 2023 21:46:44 +0300 Subject: [PATCH 09/16] Improve wording and support discards --- proposals/ref-out-lambda-params.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index 26dfcfc08d..6ed39c7b5d 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -65,9 +65,18 @@ The type of the parameters matches the type of the parameter in the target deleg Attributes on the parameters will not be affected in any way. +Discard identifiers will be supported, as long as the modifiers are properly and correctly provided for the respective parameters, matching the target delegate type. This means that a parameter simply declared as `_` will not match a parameter declared `ref int x`, since the discard parameter needs to be accompanied by the `ref` modifier to match. + +More specifically, a proper lambda declaration involving discarded parameter names would be: +```csharp +delegate void Test(ref int x, scoped ref int y, params int[] p); + +Test t = (ref _, scoped ref _, params _) => { }; +``` + It will still be illegal for `async` lambdas to contain by-ref parameters, since it is illegal to have by-ref parameters in async methods. -If the lambda expression is not assigned to an expression with a type, the type cannot be inferred from usage. For example, the following is illegal: +If the lambda expression is not assigned to an expression with an explicit type, the type cannot be inferred from usage. For example, the following is illegal: ```csharp var d = (in a, ref b, out c) => { From b0d1f3f3b2d898d90d050799ee0362fdb143132e Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Mon, 31 Jul 2023 21:53:59 +0300 Subject: [PATCH 10/16] Exclude `scoped` from implicit param modifiers --- proposals/ref-out-lambda-params.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index 6ed39c7b5d..cdd87b89eb 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -46,6 +46,8 @@ delegate T SelfReturnerParams(params T[] t); are all illegal, due to ambiguity with taking the reference of the returned expression in the `ref` case. For consistency, all other modifiers are also left unsupported and illegal. +Using the `scoped` modifier alone is unsupported, because `scoped` is currently parsed as a type identifier, thus resolving to an explicit lambda parameter declaration. + The change in the spec will require that [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general) be adjusted as follows: ```diff From a107d847d1b4498d28a646d87f9f7e6248e86625 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Tue, 1 Aug 2023 10:22:50 +0300 Subject: [PATCH 11/16] Add more information about scoped --- proposals/ref-out-lambda-params.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index cdd87b89eb..ed9aeff3cd 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -31,9 +31,9 @@ SelfReturnerIn fin = in x => x; SelfReturnerRef fref = ref x => x; SelfReturnerOut fout = out x => x; SelfReturnerRefReadonly frr = ref readonly x => x; -SelfReturnerScoped frr = scoped x => x; -SelfReturnerScopedRef frr = scoped ref x => x; -SelfReturnerParams frr = params x => x; +SelfReturnerScoped fs = scoped x => x; +SelfReturnerScopedRef fsr = scoped ref x => x; +SelfReturnerParams fp = params x => x; delegate T SelfReturnerIn(in T t); delegate T SelfReturnerRef(ref T t); @@ -46,7 +46,15 @@ delegate T SelfReturnerParams(params T[] t); are all illegal, due to ambiguity with taking the reference of the returned expression in the `ref` case. For consistency, all other modifiers are also left unsupported and illegal. -Using the `scoped` modifier alone is unsupported, because `scoped` is currently parsed as a type identifier, thus resolving to an explicit lambda parameter declaration. +Using the `scoped` modifier alone is unsupported, because `scoped` is currently parsed as a type identifier, thus resolving to an explicit lambda parameter declaration. For example, + +```csharp +ScopedParameter d = (scoped x) => { } + +delegate void ScopedParameter(scoped T t); +``` + +is invalid, as `scoped` resolves to a type identifier, and not as a parameter modifier. Adjusting this behavior is a breaking change (see open question). The change in the spec will require that [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general) be adjusted as follows: @@ -99,3 +107,7 @@ var dd = (a, b) => Method2(a, b); int Method2(int a, int b) => a + b; ``` + +# Open Questions + +- [ ] Do we consider making a breaking change supporting `scoped` as the parameter modifier in implicitly-typed parameters? From f1ce98046532043da85259ab27f052a71096000d Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Wed, 9 Aug 2023 22:34:15 +0300 Subject: [PATCH 12/16] Lift `scoped` restriction --- proposals/ref-out-lambda-params.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index cdd87b89eb..4f59622fee 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -46,7 +46,15 @@ delegate T SelfReturnerParams(params T[] t); are all illegal, due to ambiguity with taking the reference of the returned expression in the `ref` case. For consistency, all other modifiers are also left unsupported and illegal. -Using the `scoped` modifier alone is unsupported, because `scoped` is currently parsed as a type identifier, thus resolving to an explicit lambda parameter declaration. +Using the `scoped` modifier alone is supported, since it was explicitly ruled out as a type name without the presence of `@` before the identifier in C# 11. This means that the following code will work: + +```csharp +SelfReturnerScoped frr = (scoped x) => x; + +delegate T SelfReturnerScoped(scoped T t); +``` + +with `x` being resolved as an implicitly-typed lambda parameter with the `scoped` modifier. The change in the spec will require that [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general) be adjusted as follows: From a273cc644e0d8ccbf349d47498bf4a8c510d7fff Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Thu, 10 Aug 2023 00:18:29 +0300 Subject: [PATCH 13/16] Declare open question about breaking change --- proposals/ref-out-lambda-params.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index 2e097175d3..f17f397fa7 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -110,6 +110,28 @@ int Method2(int a, int b) => a + b; # Open Questions -- [x] Do we consider making a breaking change supporting `scoped` as the parameter modifier in implicitly-typed parameters? +- [x] Question: Do we consider making a breaking change supporting `scoped` as the parameter modifier in implicitly-typed parameters? - Answer: [Yes](https://github.com/dotnet/csharplang/pull/7369#issuecomment-1670155767) - \ No newline at end of file + +- [ ] Question: + +By always considering `scoped` as a parameter modifier in implicitly-typed lambda parameters, do we also extend this breaking change onto explicitly-typed lambda parameters? This would change the existing snippet's meaning: +```csharp +public class C +{ + public void M(scoped sc, scoped scoped scsc) + { + MSc mSc = M; + MSc mSc1 = (scoped sc, scoped scoped scsc) => { }; + } + + public delegate void MSc(scoped sc, scoped scoped scsc); +} + +public ref struct @scoped { } +``` +`sc` in `mSc1` would now mean an implicitly-typed parameter with the `scoped` modifier, causing an error about not specifying its type explicitly, given the presence of `scsc` in that lambda. As of C# 11, `scoped` in this case refers to the type of the lambda parameter. The delegate and method declarations are unaffected from this behavior adjustment. + +Without this breaking change, we have to special-case implicitly-typed lambda parameters over explicitly-typed ones, and adjust the parsing behavior accordingly, which sounds like too much work for supporting a pathogenic case. + +An even further step is to introduce this breaking change in parameters in all signatures, methods, delegates and anonymous functions. However, other than consistency and alignment with the breaking changes around `scoped`, there is no real reason. From 390d5258083f5a92775601a250e0dc1cab4a323d Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Tue, 30 Jan 2024 01:02:55 +0200 Subject: [PATCH 14/16] Update rules + improve wording Co-Authored-By: Cyrus Najmabadi <4564579+CyrusNajmabadi@users.noreply.github.com> --- proposals/ref-out-lambda-params.md | 76 +++++++++++++++++------------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index f17f397fa7..944cee9470 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -25,55 +25,30 @@ TryParse parse2 = (string text, out int result) => Int32.TryParse(text, out Parameter declarations in lambda expressions with parenthesized parameters now permit a single identifier after a modifier on the parameter. This does not apply to lambda expressions with a single parameter with omitted parentheses. -For example, +For example, these would not be legal: ```csharp SelfReturnerIn fin = in x => x; SelfReturnerRef fref = ref x => x; SelfReturnerOut fout = out x => x; -SelfReturnerRefReadonly frr = ref readonly x => x; -SelfReturnerScoped fs = scoped x => x; -SelfReturnerScopedRef fsr = scoped ref x => x; -SelfReturnerParams fp = params x => x; delegate T SelfReturnerIn(in T t); delegate T SelfReturnerRef(ref T t); delegate T SelfReturnerOut(out T t); -delegate T SelfReturnerRefReadonly(ref readonly T t); -delegate T SelfReturnerScoped(scoped T t); -delegate T SelfReturnerScopedRef(scoped ref T t); -delegate T SelfReturnerParams(params T[] t); ``` -are all illegal, due to ambiguity with taking the reference of the returned expression in the `ref` case. For consistency, all other modifiers are also left unsupported and illegal. +All the above examples are not legal due to ambiguity with taking the reference of the returned expression in the `ref` case. For consistency, all other modifiers are also left unsupported and illegal. -Using the `scoped` modifier alone is supported, since it was explicitly ruled out as a type name without the presence of `@` before the identifier in C# 11. This means that the following code will work: +Using the `scoped` modifier alone is supported, since it was explicitly ruled out as a type name without the presence of `@` before the identifier in C# 11. This means that the following code will resolve `x` as an implicitly-typed lambda parameter with the `scoped` modifier: ```csharp -SelfReturnerScoped frr = (scoped x) => x; +SelfReturnerScoped fs = (scoped x) => x; delegate T SelfReturnerScoped(scoped T t); ``` -with `x` being resolved as an implicitly-typed lambda parameter with the `scoped` modifier. - -The change in the spec will require that [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general) be adjusted as follows: - -```diff - implicit_anonymous_function_parameter_list -- : implicit_anonymous_function_parameter -- (',' implicit_anonymous_function_parameter)* -+ : implicit_parenthesized_anonymous_function_parameter -+ (',' implicit_parenthesized_anonymous_function_parameter)* - ; - -+ implicit_parenthesized_anonymous_function_parameter -+ : anonymous_function_parameter_modifier? implicit_anonymous_function_parameter - ; -``` - The type of the parameters matches the type of the parameter in the target delegate type, including the modifiers. -Attributes on the parameters will not be affected in any way. +Implicitly-typed parameters with modifiers can be assigned attributes. Implicitly-typed parameters with modifiers will be eligible for being assigned a default value. Currently, only `scoped` ref-struct parameters can have default values. Discard identifiers will be supported, as long as the modifiers are properly and correctly provided for the respective parameters, matching the target delegate type. This means that a parameter simply declared as `_` will not match a parameter declared `ref int x`, since the discard parameter needs to be accompanied by the `ref` modifier to match. @@ -108,6 +83,43 @@ var dd = (a, b) => Method2(a, b); int Method2(int a, int b) => a + b; ``` +### Grammar + +The change in the spec will require that [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general) be adjusted as follows: + +```diff + implicit_anonymous_function_signature + : '(' implicit_anonymous_function_parameter_list? ')' + | implicit_anonymous_function_parameter + ; + + implicit_anonymous_function_parameter_list +- : implicit_anonymous_function_parameter +- (',' implicit_anonymous_function_parameter)* ++ : implicit_parenthesized_anonymous_function_parameter ++ (',' implicit_parenthesized_anonymous_function_parameter)* + ; + ++ implicit_parenthesized_anonymous_function_parameter ++ : attributes? anonymous_function_parameter_modifier* identifier default_argument? + ; +``` + +We take into account the fact that the `anonymous_function_parameter_modifier` rule is declared as follows: + +```antlr +anonymous_function_parameter_modifier + : 'ref' + | 'out' + | 'in' + | 'scoped' + | 'readonly' + | 'params' + ; +``` + +Should the above production rule be updated to only reflect the valid combinations of modifiers, the `implicit_parenthesized_anonymous_function_parameter` rule is updated accordingly to reflect the applicable modifiers. + # Open Questions - [x] Question: Do we consider making a breaking change supporting `scoped` as the parameter modifier in implicitly-typed parameters? @@ -132,6 +144,6 @@ public ref struct @scoped { } ``` `sc` in `mSc1` would now mean an implicitly-typed parameter with the `scoped` modifier, causing an error about not specifying its type explicitly, given the presence of `scsc` in that lambda. As of C# 11, `scoped` in this case refers to the type of the lambda parameter. The delegate and method declarations are unaffected from this behavior adjustment. -Without this breaking change, we have to special-case implicitly-typed lambda parameters over explicitly-typed ones, and adjust the parsing behavior accordingly, which sounds like too much work for supporting a pathogenic case. +Without this breaking change, we have to special-case implicitly-typed lambda parameters over explicitly-typed ones, and adjust the parsing behavior accordingly, which sounds like too much work for supporting a pathological case. -An even further step is to introduce this breaking change in parameters in all signatures, methods, delegates and anonymous functions. However, other than consistency and alignment with the breaking changes around `scoped`, there is no real reason. +An even further step is to introduce this breaking change in parameters in all signatures, methods, delegates and anonymous functions. However, there is no motivation other than consistency and alignment with the breaking changes around `scoped`. From 0d7c534cccca1715ecf783b67677acfe1e917d0e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 20 Feb 2024 11:49:33 -0800 Subject: [PATCH 15/16] Update ref-out-lambda-params.md --- proposals/ref-out-lambda-params.md | 88 +++++++++--------------------- 1 file changed, 25 insertions(+), 63 deletions(-) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index 944cee9470..8ae2288252 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -23,7 +23,31 @@ TryParse parse2 = (string text, out int result) => Int32.TryParse(text, out ### Parameter declaration -Parameter declarations in lambda expressions with parenthesized parameters now permit a single identifier after a modifier on the parameter. This does not apply to lambda expressions with a single parameter with omitted parentheses. +Parameter declarations in lambda expressions with parenthesized parameters now permit a single identifier after a modifier on the parameter. + +### Grammar + +The change in the spec will require that [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general) be adjusted as follows: + +```diff + implicit_anonymous_function_signature + : '(' implicit_anonymous_function_parameter_list? ')' + | implicit_anonymous_function_parameter + ; + + implicit_anonymous_function_parameter_list +- : implicit_anonymous_function_parameter +- (',' implicit_anonymous_function_parameter)* ++ : implicit_parenthesized_anonymous_function_parameter ++ (',' implicit_parenthesized_anonymous_function_parameter)* + ; + ++ implicit_parenthesized_anonymous_function_parameter ++ : attributes? anonymous_function_parameter_modifier* identifier default_argument? + ; +``` + +Note: This does not apply to lambda expressions with a single parameter with omitted parentheses. For example, these would not be legal: ```csharp @@ -83,67 +107,5 @@ var dd = (a, b) => Method2(a, b); int Method2(int a, int b) => a + b; ``` -### Grammar - -The change in the spec will require that [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general) be adjusted as follows: - -```diff - implicit_anonymous_function_signature - : '(' implicit_anonymous_function_parameter_list? ')' - | implicit_anonymous_function_parameter - ; - - implicit_anonymous_function_parameter_list -- : implicit_anonymous_function_parameter -- (',' implicit_anonymous_function_parameter)* -+ : implicit_parenthesized_anonymous_function_parameter -+ (',' implicit_parenthesized_anonymous_function_parameter)* - ; - -+ implicit_parenthesized_anonymous_function_parameter -+ : attributes? anonymous_function_parameter_modifier* identifier default_argument? - ; -``` - -We take into account the fact that the `anonymous_function_parameter_modifier` rule is declared as follows: - -```antlr -anonymous_function_parameter_modifier - : 'ref' - | 'out' - | 'in' - | 'scoped' - | 'readonly' - | 'params' - ; -``` Should the above production rule be updated to only reflect the valid combinations of modifiers, the `implicit_parenthesized_anonymous_function_parameter` rule is updated accordingly to reflect the applicable modifiers. - -# Open Questions - -- [x] Question: Do we consider making a breaking change supporting `scoped` as the parameter modifier in implicitly-typed parameters? - - Answer: [Yes](https://github.com/dotnet/csharplang/pull/7369#issuecomment-1670155767) - -- [ ] Question: - -By always considering `scoped` as a parameter modifier in implicitly-typed lambda parameters, do we also extend this breaking change onto explicitly-typed lambda parameters? This would change the existing snippet's meaning: -```csharp -public class C -{ - public void M(scoped sc, scoped scoped scsc) - { - MSc mSc = M; - MSc mSc1 = (scoped sc, scoped scoped scsc) => { }; - } - - public delegate void MSc(scoped sc, scoped scoped scsc); -} - -public ref struct @scoped { } -``` -`sc` in `mSc1` would now mean an implicitly-typed parameter with the `scoped` modifier, causing an error about not specifying its type explicitly, given the presence of `scsc` in that lambda. As of C# 11, `scoped` in this case refers to the type of the lambda parameter. The delegate and method declarations are unaffected from this behavior adjustment. - -Without this breaking change, we have to special-case implicitly-typed lambda parameters over explicitly-typed ones, and adjust the parsing behavior accordingly, which sounds like too much work for supporting a pathological case. - -An even further step is to introduce this breaking change in parameters in all signatures, methods, delegates and anonymous functions. However, there is no motivation other than consistency and alignment with the breaking changes around `scoped`. From a16f8e8e27e6459a92c94a98aa615d3f0815b89e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 20 Feb 2024 12:09:37 -0800 Subject: [PATCH 16/16] Update ref-out-lambda-params.md --- proposals/ref-out-lambda-params.md | 106 ++++++++++++----------------- 1 file changed, 42 insertions(+), 64 deletions(-) diff --git a/proposals/ref-out-lambda-params.md b/proposals/ref-out-lambda-params.md index 8ae2288252..176454a404 100644 --- a/proposals/ref-out-lambda-params.md +++ b/proposals/ref-out-lambda-params.md @@ -23,89 +23,67 @@ TryParse parse2 = (string text, out int result) => Int32.TryParse(text, out ### Parameter declaration -Parameter declarations in lambda expressions with parenthesized parameters now permit a single identifier after a modifier on the parameter. - ### Grammar -The change in the spec will require that [the grammar for lambda expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12191-general) be adjusted as follows: - ```diff - implicit_anonymous_function_signature - : '(' implicit_anonymous_function_parameter_list? ')' - | implicit_anonymous_function_parameter - ; - - implicit_anonymous_function_parameter_list -- : implicit_anonymous_function_parameter -- (',' implicit_anonymous_function_parameter)* -+ : implicit_parenthesized_anonymous_function_parameter -+ (',' implicit_parenthesized_anonymous_function_parameter)* - ; - -+ implicit_parenthesized_anonymous_function_parameter -+ : attributes? anonymous_function_parameter_modifier* identifier default_argument? - ; +implicit_anonymous_function_signature + : '(' implicit_anonymous_function_parameter_list? ')' + | implicit_anonymous_function_parameter + ; + +implicit_anonymous_function_parameter_list +- : implicit_anonymous_function_parameter (',' implicit_anonymous_function_parameter)* ++ : implicit_anonymous_function_parameter_ex (',' implicit_anonymous_function_parameter_ex)* + ; + +implicit_anonymous_function_parameter + : identifier + ; + +implicit_anonymous_function_parameter_ex + : anonymous_function_parameter_modifier? identifier + ; ``` -Note: This does not apply to lambda expressions with a single parameter with omitted parentheses. - -For example, these would not be legal: -```csharp -SelfReturnerIn fin = in x => x; -SelfReturnerRef fref = ref x => x; -SelfReturnerOut fout = out x => x; +Notes -delegate T SelfReturnerIn(in T t); -delegate T SelfReturnerRef(ref T t); -delegate T SelfReturnerOut(out T t); -``` +1. This does not apply lambda without a parameter list. e.g. `x => x.ToString()`. +2. A lambda parameter list cannot mix `implicit_anonymous_function_parameter_ex` and `explicit_anonymous_function_parameter` parameters. +3. An implicit lambda with a parameter list cannot have attributes (open question on if we want to allow that though). +4. An implicit lambda with a parameter list cannot have a default value. -All the above examples are not legal due to ambiguity with taking the reference of the returned expression in the `ref` case. For consistency, all other modifiers are also left unsupported and illegal. +The type of the parameters matches the type of the parameter in the target delegate type, including the modifiers. -Using the `scoped` modifier alone is supported, since it was explicitly ruled out as a type name without the presence of `@` before the identifier in C# 11. This means that the following code will resolve `x` as an implicitly-typed lambda parameter with the `scoped` modifier: +### Semantics -```csharp -SelfReturnerScoped fs = (scoped x) => x; +https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12192-anonymous-function-signatures is updated as follows: -delegate T SelfReturnerScoped(scoped T t); ``` +If an anonymous function has an explicit_anonymous_function_signature, then the set of compatible delegate types and expression tree types is restricted to those that have the same parameter types and modifiers in the same order (§10.7). In contrast to method group conversions (§10.8), contra-variance of anonymous function parameter types is not supported. If an anonymous function does not have an anonymous_function_signature, then the set of compatible delegate types and expression tree types is restricted to those that have no out parameters. -The type of the parameters matches the type of the parameter in the target delegate type, including the modifiers. ++ If an anonymous function contains an `implicit_anonymous_function_parameter_ex` with modifiers, then the set of compatible delegate types and expression tree types is restricted to those that have the same modifiers in the +same order (§10.7). +``` -Implicitly-typed parameters with modifiers can be assigned attributes. Implicitly-typed parameters with modifiers will be eligible for being assigned a default value. Currently, only `scoped` ref-struct parameters can have default values. +### Open questions -Discard identifiers will be supported, as long as the modifiers are properly and correctly provided for the respective parameters, matching the target delegate type. This means that a parameter simply declared as `_` will not match a parameter declared `ref int x`, since the discard parameter needs to be accompanied by the `ref` modifier to match. +1. Should attributes be allowed as well? +2. Should default parameter values be allowed? -More specifically, a proper lambda declaration involving discarded parameter names would be: -```csharp -delegate void Test(ref int x, scoped ref int y, params int[] p); +Both seem viable, and may be worth it if we're doing the rest of this work. With this formalization, we would instead say that: -Test t = (ref _, scoped ref _, params _) => { }; +```diff +explicit_anonymous_function_parameter +- : attributes anonymous_function_parameter_modifier? type identifier default_argument? ++ : attributes anonymous_function_parameter_modifier? type? identifier default_argument? + ; ``` -It will still be illegal for `async` lambdas to contain by-ref parameters, since it is illegal to have by-ref parameters in async methods. +With a rule that all parameters would have to supply a type, or eschew a type. -If the lambda expression is not assigned to an expression with an explicit type, the type cannot be inferred from usage. For example, the following is illegal: -```csharp -var d = (in a, ref b, out c) => -{ - Method(in a, ref b, out c); -} +We would also update the semantic specification to say: -void Method(in int a, ref int b, out int c) -{ - c = a; - b = c; -} ``` - -This remains illegal as the current behavior for implicit-typed parameters without modifiers does not infer the type of the parameters through usage inside the body of the lambda expression. For example, the following is illegal: -```csharp -// Error: The delegate type could not be inferred -var dd = (a, b) => Method2(a, b); - -int Method2(int a, int b) => a + b; +- The parameters of an anonymous function in the form of a lambda_expression can be explicitly or implicitly typed. In an explicitly typed parameter list, the type of each parameter is explicitly stated. In an implicitly typed parameter list, the types of the parameters are inferred from the context in which the anonymous function occurs—specifically, when the anonymous function is converted to a compatible delegate type or expression tree type, that type provides the parameter types (§10.7). ++ The parameters of an anonymous function in the form of a lambda_expression can be explicitly or implicitly typed. In an `anonymous_function_signature` whose parameters have a provided `type`, the type of each parameter is explicitly stated. In all other signatures the types of the parameters are inferred from the context in which the anonymous function occurs—specifically, when the anonymous function is converted to a compatible delegate type or expression tree type, that type provides the parameter types (§10.7). ``` - - -Should the above production rule be updated to only reflect the valid combinations of modifiers, the `implicit_parenthesized_anonymous_function_parameter` rule is updated accordingly to reflect the applicable modifiers.