From ffb40d02f1707f379fb6a82170e3a84603ad48e1 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Thu, 24 Aug 2023 00:13:04 +0300 Subject: [PATCH 01/11] Create pattern-const-expressions.md --- proposals/pattern-const-expressions.md | 97 ++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 proposals/pattern-const-expressions.md diff --git a/proposals/pattern-const-expressions.md b/proposals/pattern-const-expressions.md new file mode 100644 index 0000000000..82137df33e --- /dev/null +++ b/proposals/pattern-const-expressions.md @@ -0,0 +1,97 @@ +# Constant `is` pattern expressions + +* [x] Proposed +* [ ] Prototype: None +* [ ] Implementation: None +* [ ] Specification: Started, below + +## Summary +[summary]: #summary + +Consider `is` pattern expressions as constant if the LHS is constant. + +## Motivation +[motivation]: #motivation + +When the pattern expressions are converted into simpler boolean expressions using equality and comparison operators, they are considered constant expressions if all parts of the expression are considered constant and can be evaluated during compilation. Also, the expressions returned from the ternary operator can also be considered constant if all sides are constant (condition, consequence and alternative). + +## Detailed design +[design]: #detailed-design + +### Spec +The [section](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1223-constant-expressions) in the spec needs to be updated accordingly, in the following segments: + +> Only the following constructs are permitted in constant expressions: +> - Literals (including the null literal). +> - References to const members of class and struct types. +> - References to members of enumeration types. +> - References to local constants. +> - Parenthesized subexpressions, which are themselves constant expressions. +> - Cast expressions. +> - checked and unchecked expressions. +> - nameof expressions. +> - The predefined +, –, !, and ~ unary operators. +> - The predefined +, –, *, /, %, <<, >>, &, |, ^, &&, ||, ==, !=, <, >, <=, and >= binary operators. +> - The ?: conditional operator. +> - **Pattern expressions.** +> - sizeof expressions, provided the unmanaged-type is one of the types specified in §23.6.9 for which sizeof returns a constant value. +> - Default value expressions, provided the type is one of the types listed above. + +### Grammar +The grammar remains untouched, as nothing changes syntactically for this change. + +### Semantics +A pattern expression with a constant LHS may be evaluated at compile time currently, but it cannot be assigned to a constant symbol. With this change, constant fields and locals of type `bool` may be assigned pattern expressions, as already evaluated during compilation. + +### Examples +The below example shows a pattern expression being assigned to a constant field. +```csharp +public const int A = 4; +public const bool B = A is 4; +public const bool C = A is not 3 and < 1 or 5; +``` + +Since `A` is constant, and pattern expressions require that the operands on the right are constant, all operands are constant, and the expression is thus evaluated during compilation. This result will then be assigned to the constant field. + +Another example, using type and `null` value checks: +```csharp +const int a = 4; +const bool b = false; +const long c = 4; +const string d = "hello"; + +const bool x = a is int; // always true +const bool y = a is long; // always false +const bool z = d is not null; // always true +const bool p = b is null; // always false +``` + +All the above are currently valid pattern matching expressions, that also emit warnings about their constant evaluation results, about them being always true or false. + +When assigning those expressions to a constant symbol, it would be preferrable to not report these warnings about the constant result of the expression. + +Expressions accessing properties and comparing their values are illegal, as property evaluation occurs at runtime: + +```csharp +const bool q = d is string { Length: 5 }; // Error: not a constant expression +``` + +## Drawbacks +[drawbacks]: #drawbacks + +None. + +## Alternatives +[alternatives]: #alternatives + +Currently, equality and comparison operators can be used to compare against other literals, including nullability of the objects (e.g. `x != null`, or `y == 4 || y < 3`). However, type checking cannot be currently peformed at compile time and assigned to a constant. + +## Unresolved questions +[unresolved]: #unresolved-questions + +- [ ] Requires LDM review + +## Design meetings +[meetings]: #design-meetings + +None. From d5e4d8b736115d912eca4005ab39316620333621 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Tue, 10 Oct 2023 02:43:30 +0300 Subject: [PATCH 02/11] Add design meeting from 2023-10-09 --- proposals/pattern-const-expressions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/pattern-const-expressions.md b/proposals/pattern-const-expressions.md index 82137df33e..3f709a092e 100644 --- a/proposals/pattern-const-expressions.md +++ b/proposals/pattern-const-expressions.md @@ -94,4 +94,4 @@ Currently, equality and comparison operators can be used to compare against othe ## Design meetings [meetings]: #design-meetings -None. +- Approval for Any Time milestone: [Here](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-09.md#is-expression-evaluating-const-expression-should-be-considered-constant) \ No newline at end of file From 2767290070c73f96adb63b5704cd416ed2e6b53f Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Tue, 10 Oct 2023 23:54:36 +0300 Subject: [PATCH 03/11] Update spec --- proposals/pattern-const-expressions.md | 48 +++++++++++++++++++------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/proposals/pattern-const-expressions.md b/proposals/pattern-const-expressions.md index 3f709a092e..da4ea102f2 100644 --- a/proposals/pattern-const-expressions.md +++ b/proposals/pattern-const-expressions.md @@ -13,12 +13,14 @@ Consider `is` pattern expressions as constant if the LHS is constant. ## Motivation [motivation]: #motivation -When the pattern expressions are converted into simpler boolean expressions using equality and comparison operators, they are considered constant expressions if all parts of the expression are considered constant and can be evaluated during compilation. Also, the expressions returned from the ternary operator can also be considered constant if all sides are constant (condition, consequence and alternative). +Certain pattern expressions that can be converted into simpler boolean expressions using equality and comparison operators are never considered constant expressions, even when all parts of the expression are considered constant and can be evaluated during compilation. However, the lowered versions of those expressions (that only involve equality and comparison operators) are always considered constant. This prohibits the ability to utilize `is` expressions for operations like comparing against a range (e.g. `x is >= 'a' and 'z'`), or checking against distinct values (e.g. `x is Values.A or Values.B or Values.C`). ## Detailed design [design]: #detailed-design ### Spec +[spec]: #spec + The [section](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1223-constant-expressions) in the spec needs to be updated accordingly, in the following segments: > Only the following constructs are permitted in constant expressions: @@ -33,47 +35,68 @@ The [section](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/ex > - The predefined +, –, !, and ~ unary operators. > - The predefined +, –, *, /, %, <<, >>, &, |, ^, &&, ||, ==, !=, <, >, <=, and >= binary operators. > - The ?: conditional operator. -> - **Pattern expressions.** +> - **Pattern expressions with only the following subpatterns:** +> - **Boolean literal patterns.** +> - **Numeric literal patterns.** +> - **Character literal patterns.** +> - **String literal patterns.** +> - **Relative numeric literal patterns.** +> - **Relative character literal patterns.** +> - **Null literal patterns.** +> - **Default literal patterns.** > - sizeof expressions, provided the unmanaged-type is one of the types specified in §23.6.9 for which sizeof returns a constant value. > - Default value expressions, provided the type is one of the types listed above. +The allowed subpatterns as shown in the list above are called "constant subpatterns", as they will be eligible for compile-time evaluation. + ### Grammar +[grammar]: #grammar The grammar remains untouched, as nothing changes syntactically for this change. ### Semantics -A pattern expression with a constant LHS may be evaluated at compile time currently, but it cannot be assigned to a constant symbol. With this change, constant fields and locals of type `bool` may be assigned pattern expressions, as already evaluated during compilation. +[semantics]: #semantics +A pattern expression only consisting of the above subpatterns with a constant LHS may be evaluated at compile time currently, but it cannot be assigned to a constant symbol. With this change, constant fields and locals of type `bool` may be assigned pattern expressions, as already evaluated during compilation. ### Examples -The below example shows a pattern expression being assigned to a constant field. +[examples]: #examples +The below example shows pattern expressions being assigned to a constant field. ```csharp public const int A = 4; public const bool B = A is 4; -public const bool C = A is not 3 and < 1 or 5; +public const bool C = A is not 3 and <= 4 or 6; + +// DeploymentEnvironment is an enum type +public const DeploymentEnvironment Environment = DeploymentEnvironment.Production; +public const bool D = Environment + is DeploymentEnvironment.Production + or DeploymentEnvironment.Test; ``` -Since `A` is constant, and pattern expressions require that the operands on the right are constant, all operands are constant, and the expression is thus evaluated during compilation. This result will then be assigned to the constant field. +Since `A` is constant, and all the operands on the right are constant, the entire expression only consists of constants, thus the expression is evaluated during compilation. This result will then be assigned to the constant field. Likewise, `Environment` is also constant as an enum value, and so are the other subpatterns of the expression. -Another example, using type and `null` value checks: +Another example, using `null` and default value checks: ```csharp const int a = 4; const bool b = false; const long c = 4; const string d = "hello"; -const bool x = a is int; // always true -const bool y = a is long; // always false +const bool x = a is default(int); // always false +const bool y = b is default(bool); // always true const bool z = d is not null; // always true const bool p = b is null; // always false ``` All the above are currently valid pattern matching expressions, that also emit warnings about their constant evaluation results, about them being always true or false. -When assigning those expressions to a constant symbol, it would be preferrable to not report these warnings about the constant result of the expression. +When assigning those expressions to a constant symbol, these warnings about the constant result of the expression will **not** be reported, as the user intends to capture the constant value of the expression. -Expressions accessing properties and comparing their values are illegal, as property evaluation occurs at runtime: +Pattern expressions containing non-constant subpatterns, like accessing properties, list patterns and var patterns, are **not** constant. In the below examples, all expressions will report compiler errors: ```csharp const bool q = d is string { Length: 5 }; // Error: not a constant expression +const bool r = d is [.. var prefix, 'l', 'o']; // Error: not a constant expression +const bool s = d is var someString; // Error: not a constant expression ``` ## Drawbacks @@ -84,12 +107,13 @@ None. ## Alternatives [alternatives]: #alternatives -Currently, equality and comparison operators can be used to compare against other literals, including nullability of the objects (e.g. `x != null`, or `y == 4 || y < 3`). However, type checking cannot be currently peformed at compile time and assigned to a constant. +Currently, equality and comparison operators can be used to compare against other literals, including nullability of the objects (e.g. `x != null`, or `y == 4 || y < 3`). ## Unresolved questions [unresolved]: #unresolved-questions - [ ] Requires LDM review +- [ ] Should we introduce a new error for non-constant subpatterns in order to isolate the root cause of the inability to consider the expression constant? ## Design meetings [meetings]: #design-meetings From 438900ccf06d48c06a95a9fdba3211fab6d07842 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Wed, 11 Oct 2023 01:10:06 +0300 Subject: [PATCH 04/11] Remove new error question --- proposals/pattern-const-expressions.md | 1 - 1 file changed, 1 deletion(-) diff --git a/proposals/pattern-const-expressions.md b/proposals/pattern-const-expressions.md index da4ea102f2..0570114956 100644 --- a/proposals/pattern-const-expressions.md +++ b/proposals/pattern-const-expressions.md @@ -113,7 +113,6 @@ Currently, equality and comparison operators can be used to compare against othe [unresolved]: #unresolved-questions - [ ] Requires LDM review -- [ ] Should we introduce a new error for non-constant subpatterns in order to isolate the root cause of the inability to consider the expression constant? ## Design meetings [meetings]: #design-meetings From d66f305414c7a9c6560828fbe08a5cbbfcaa4772 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Sat, 14 Oct 2023 03:23:41 +0300 Subject: [PATCH 05/11] Update pattern-const-expressions.md --- proposals/pattern-const-expressions.md | 49 +++++++++++++++++++------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/proposals/pattern-const-expressions.md b/proposals/pattern-const-expressions.md index 0570114956..c5dc2696d1 100644 --- a/proposals/pattern-const-expressions.md +++ b/proposals/pattern-const-expressions.md @@ -13,15 +13,36 @@ Consider `is` pattern expressions as constant if the LHS is constant. ## Motivation [motivation]: #motivation -Certain pattern expressions that can be converted into simpler boolean expressions using equality and comparison operators are never considered constant expressions, even when all parts of the expression are considered constant and can be evaluated during compilation. However, the lowered versions of those expressions (that only involve equality and comparison operators) are always considered constant. This prohibits the ability to utilize `is` expressions for operations like comparing against a range (e.g. `x is >= 'a' and 'z'`), or checking against distinct values (e.g. `x is Values.A or Values.B or Values.C`). +Certain pattern expressions that can be converted into simpler boolean expressions using equality and comparison operators are never considered constant expressions, even when all parts of the expression are considered constant and can be evaluated during compilation. However, the lowered versions of those expressions (that only involve equality and comparison operators) are constant. + +The above restriction prevents the ability to utilize `is` expressions for operations like comparing against a range (e.g. `x is >= 'a' and 'z'`), or checking against distinct values (e.g. `x is Values.A or Values.B or Values.C`), and instead relying on using traditional comparison operators like so for the last example: `x == Values.A || x == Values.B || x == Values.C`. ## Detailed design [design]: #detailed-design +A pattern expression is constant if all its contained subpatterns are all also constant. The following subpatterns are constant: +- Boolean literal (`true` or `false`) +- Numeric literal (e.g. `0`, `1f`, `3m`, etc.) +- Character literal (e.g. `'c'`, `'\0'`, etc.) +- String literal (e.g. `"Hello world."`, `""`, etc.) +- Relative numeric literal (e.g. `> 3`, `< 0.3m`, etc.) +- Relative character literal (e.g. `> 'a'`, `<= 'z'`, etc.) +- Null literal (`null`) +- Default literal (`default(T)`), but not `default` without a type +- And, or and not patterns (e.g. the *and* in `>= 'a' and <= 'z'`) +- Parenthesized patterns (e.g. the parentheses in `is (not (X or Y))`) +- Constant field (including enum fields) or local, e.g. + - `x is y` when y is a const field or local, or + - `x is SomeEnum.None` when `None` is an enum field + +The allowed subpatterns as shown in the list above are called "constant subpatterns", as they will be eligible for compile-time evaluation. + +If the `is` expression consists of a constant expression on the LHS, and is compared against a constant pattern on the right, the entire `is` expression is a constant expression. + ### Spec [spec]: #spec -The [section](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1223-constant-expressions) in the spec needs to be updated accordingly, in the following segments: +The [§12.23 section](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#1223-constant-expressions) in the spec needs to be updated accordingly, in the following segments: > Only the following constructs are permitted in constant expressions: > - Literals (including the null literal). @@ -35,7 +56,9 @@ The [section](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/ex > - The predefined +, –, !, and ~ unary operators. > - The predefined +, –, *, /, %, <<, >>, &, |, ^, &&, ||, ==, !=, <, >, <=, and >= binary operators. > - The ?: conditional operator. -> - **Pattern expressions with only the following subpatterns:** +> - sizeof expressions, provided the unmanaged-type is one of the types specified in §23.6.9 for which sizeof returns a constant value. +> - Default value expressions, provided the type is one of the types listed above. +> - **`is` expressions with only the following subpatterns:** > - **Boolean literal patterns.** > - **Numeric literal patterns.** > - **Character literal patterns.** @@ -44,22 +67,21 @@ The [section](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/ex > - **Relative character literal patterns.** > - **Null literal patterns.** > - **Default literal patterns.** -> - sizeof expressions, provided the unmanaged-type is one of the types specified in §23.6.9 for which sizeof returns a constant value. -> - Default value expressions, provided the type is one of the types listed above. - -The allowed subpatterns as shown in the list above are called "constant subpatterns", as they will be eligible for compile-time evaluation. +> - **And, or and not patterns.** +> - **Parenthesized patterns.** +> - **References to constant fields or locals inside constant patterns.** ### Grammar [grammar]: #grammar -The grammar remains untouched, as nothing changes syntactically for this change. +The grammar remains untouched, as nothing changes syntactically for this feature. ### Semantics [semantics]: #semantics -A pattern expression only consisting of the above subpatterns with a constant LHS may be evaluated at compile time currently, but it cannot be assigned to a constant symbol. With this change, constant fields and locals of type `bool` may be assigned pattern expressions, as already evaluated during compilation. +A pattern expression only consisting of constant subpatterns with a constant LHS may be evaluated at compile time currently, but it is not considered as constant. With this change, the following adjustments enable constant pattern expressions to be used in constant contexts, like assigning a constant field or local, a default parameter, the condition in a constant conditional expression, etc. ### Examples [examples]: #examples -The below example shows pattern expressions being assigned to a constant field. +The below example shows pattern expressions being assigned to constant fields: ```csharp public const int A = 4; public const bool B = A is 4; @@ -72,7 +94,9 @@ public const bool D = Environment or DeploymentEnvironment.Test; ``` -Since `A` is constant, and all the operands on the right are constant, the entire expression only consists of constants, thus the expression is evaluated during compilation. This result will then be assigned to the constant field. Likewise, `Environment` is also constant as an enum value, and so are the other subpatterns of the expression. +In the expression assigned to `C`, since `A` is constant, and all the subpatterns on the pattern are constant, the entire expression only consists of constants. This result will be evaluated during compile-time and then be assigned to the constant field. + +Likewise, `Environment` is constant as an enum value, and so are the other subpatterns of the expression assigned to `D`. Another example, using `null` and default value checks: ```csharp @@ -113,8 +137,9 @@ Currently, equality and comparison operators can be used to compare against othe [unresolved]: #unresolved-questions - [ ] Requires LDM review +- [ ] Should this be extended to `switch` expressions too? (Discussion [#7489](https://github.com/dotnet/csharplang/discussions/7489)) ## Design meetings [meetings]: #design-meetings -- Approval for Any Time milestone: [Here](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-09.md#is-expression-evaluating-const-expression-should-be-considered-constant) \ No newline at end of file +- Approval for Any Time milestone: [Here](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-09.md#is-expression-evaluating-const-expression-should-be-considered-constant) From 18906538b8a46ffef1ade46f85e4a9c257167dbd Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Wed, 29 Nov 2023 17:47:16 +0200 Subject: [PATCH 06/11] Remove `literal` word --- proposals/pattern-const-expressions.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/proposals/pattern-const-expressions.md b/proposals/pattern-const-expressions.md index c5dc2696d1..533d588650 100644 --- a/proposals/pattern-const-expressions.md +++ b/proposals/pattern-const-expressions.md @@ -59,14 +59,14 @@ The [§12.23 section](https://github.com/dotnet/csharpstandard/blob/standard-v7/ > - sizeof expressions, provided the unmanaged-type is one of the types specified in §23.6.9 for which sizeof returns a constant value. > - Default value expressions, provided the type is one of the types listed above. > - **`is` expressions with only the following subpatterns:** -> - **Boolean literal patterns.** -> - **Numeric literal patterns.** -> - **Character literal patterns.** -> - **String literal patterns.** -> - **Relative numeric literal patterns.** -> - **Relative character literal patterns.** -> - **Null literal patterns.** -> - **Default literal patterns.** +> - **Boolean patterns.** +> - **Numeric patterns.** +> - **Character patterns.** +> - **String patterns.** +> - **Relative numeric patterns.** +> - **Relative character patterns.** +> - **Null patterns.** +> - **Default patterns.** > - **And, or and not patterns.** > - **Parenthesized patterns.** > - **References to constant fields or locals inside constant patterns.** From 259d137b4bd7c1982301c6867b4121e67597f4dd Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Thu, 30 Nov 2023 02:37:06 +0200 Subject: [PATCH 07/11] Further clarifications on diagnostics --- proposals/pattern-const-expressions.md | 50 ++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/proposals/pattern-const-expressions.md b/proposals/pattern-const-expressions.md index 533d588650..b3aea6cdae 100644 --- a/proposals/pattern-const-expressions.md +++ b/proposals/pattern-const-expressions.md @@ -111,9 +111,54 @@ const bool z = d is not null; // always true const bool p = b is null; // always false ``` -All the above are currently valid pattern matching expressions, that also emit warnings about their constant evaluation results, about them being always true or false. +### Diagnostics -When assigning those expressions to a constant symbol, these warnings about the constant result of the expression will **not** be reported, as the user intends to capture the constant value of the expression. +All the above are currently valid pattern matching expressions, that also emit warnings about their constant evaluation results, being always true or false. + +When assigning those expressions in a constant context, these warnings about the constant result of the expression will **not** be reported, as the user intends to capture the constant value of the expression. Constant context involves: +- initializers for const symbols (fields or locals) +- attribute arguments +- parameter default value +- switch statement case labels +- switch expression arms + +Examples for the above: +```csharp +const int a = 4; +const bool b = false; +const long c = 4; +const string d = "hello"; +const Sign e = Sign.Negative; + +// local or field +const bool x = a is default(int); // always false, no warning/error + +// attribute argument +[assembly: Something(a is 4)] // always true, no warning/error + +// parameter default value +// always true, no warning/error +int Negate(int value, bool negate = e is Sign.Negative) { } + +// switch statement case label +switch (b) +{ + // always true, no warning/error + case a is c: + break; +} + +// switch expression arm +var p = b switch +{ + // always true, no warning/error + a is c => 1, +}; +``` + +**NOTE**: we do not introduce any breaking changes in the reported diagnostics. Currently, all the above cases are illegal reporting "CS0150: A constant value is expected". A warning about the values always evaluating to either true or false is also reported alongside the error. We remove the warnings from those places where `is` expressions are currently not permitted to be used due to the error. + +### Other patterns Pattern expressions containing non-constant subpatterns, like accessing properties, list patterns and var patterns, are **not** constant. In the below examples, all expressions will report compiler errors: @@ -143,3 +188,4 @@ Currently, equality and comparison operators can be used to compare against othe [meetings]: #design-meetings - Approval for Any Time milestone: [Here](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-09.md#is-expression-evaluating-const-expression-should-be-considered-constant) +- November 27th, 2023 [Discussion](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-27.md#making-patterns-constant-expressions) From 9234d417d2ff1b52e12f65ce807fd9465413f215 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Thu, 30 Nov 2023 19:13:25 +0200 Subject: [PATCH 08/11] Clarify constant context --- proposals/pattern-const-expressions.md | 39 ++++++++++++++++++++------ 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/proposals/pattern-const-expressions.md b/proposals/pattern-const-expressions.md index b3aea6cdae..12be0f5ce2 100644 --- a/proposals/pattern-const-expressions.md +++ b/proposals/pattern-const-expressions.md @@ -115,37 +115,58 @@ const bool p = b is null; // always false All the above are currently valid pattern matching expressions, that also emit warnings about their constant evaluation results, being always true or false. -When assigning those expressions in a constant context, these warnings about the constant result of the expression will **not** be reported, as the user intends to capture the constant value of the expression. Constant context involves: -- initializers for const symbols (fields or locals) -- attribute arguments -- parameter default value -- switch statement case labels -- switch expression arms +When assigning those expressions in a constant context, these warnings about the constant result of the expression will **not** be reported, as the user intends to capture the constant value of the expression. + +Constant context includes contexts where a constant expression is required, as indicated in the [§12.23 section](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#1223-constant-expressions), specifically: + +> Constant expressions are required in the contexts listed below and this is indicated in the grammar by using *constant_expression*. In these contexts, a compile-time error occurs if an expression cannot be fully evaluated at compile-time. +> +> - Constant declarations ([§15.4](classes.md#154-constants)) +> - Enumeration member declarations ([§19.4](enums.md#194-enum-members)) +> - Default arguments of formal parameter lists ([§15.6.2](classes.md#1562-method-parameters)) +> - `case` labels of a `switch` statement ([§13.8.3](statements.md#1383-the-switch-statement)). +> - `goto case` statements ([§13.10.4](statements.md#13104-the-goto-statement)) +> - Dimension lengths in an array creation expression ([§12.8.16.5](expressions.md#128165-array-creation-expressions)) that includes an initializer. +> - Attributes ([§22](attributes.md#22-attributes)) +> - In a *constant_pattern* ([§11.2.3](patterns.md#1123-constant-pattern)) + +Based on the above, we only filter constant contexts that accept `bool` values, namely: +- Constant declarations +- Default arguments of formal parameter lists +- Attribute arguments +- `case` labels of a `switch` statement +- `goto case` statements +- Switch expression arms Examples for the above: ```csharp const int a = 4; const bool b = false; -const long c = 4; +const int c = 4; const string d = "hello"; const Sign e = Sign.Negative; -// local or field +// constant declaration const bool x = a is default(int); // always false, no warning/error // attribute argument [assembly: Something(a is 4)] // always true, no warning/error -// parameter default value +// default argument of formal parameter list // always true, no warning/error int Negate(int value, bool negate = e is Sign.Negative) { } // switch statement case label +// + goto case statement switch (b) { // always true, no warning/error case a is c: break; + + default: + // always true, no warning/error + goto case a is c; } // switch expression arm From b065e8a0757395c9a979c8fd312ac571a76c837c Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Tue, 19 Dec 2023 17:19:00 +0200 Subject: [PATCH 09/11] Include switch expressions + rewording Co-Authored-By: Fred Silberberg <333fred@users.noreply.github.com> --- proposals/pattern-const-expressions.md | 50 +++++++++++++++++++++----- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/proposals/pattern-const-expressions.md b/proposals/pattern-const-expressions.md index 12be0f5ce2..f41b50f6d2 100644 --- a/proposals/pattern-const-expressions.md +++ b/proposals/pattern-const-expressions.md @@ -8,7 +8,7 @@ ## Summary [summary]: #summary -Consider `is` pattern expressions as constant if the LHS is constant. +Consider `is` pattern expressions as constant if both the RHS and the LHS are constant values, expressions, or patterns. ## Motivation [motivation]: #motivation @@ -112,12 +112,13 @@ const bool p = b is null; // always false ``` ### Diagnostics +[diagnostics]: #diagnostics All the above are currently valid pattern matching expressions, that also emit warnings about their constant evaluation results, being always true or false. When assigning those expressions in a constant context, these warnings about the constant result of the expression will **not** be reported, as the user intends to capture the constant value of the expression. -Constant context includes contexts where a constant expression is required, as indicated in the [§12.23 section](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#1223-constant-expressions), specifically: +Constant contexts are contexts where a constant expression is required, as indicated in the [§12.23 section](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#1223-constant-expressions), specifically: > Constant expressions are required in the contexts listed below and this is indicated in the grammar by using *constant_expression*. In these contexts, a compile-time error occurs if an expression cannot be fully evaluated at compile-time. > @@ -130,7 +131,7 @@ Constant context includes contexts where a constant expression is required, as i > - Attributes ([§22](attributes.md#22-attributes)) > - In a *constant_pattern* ([§11.2.3](patterns.md#1123-constant-pattern)) -Based on the above, we only filter constant contexts that accept `bool` values, namely: +Based on the above, the contexts that will accept constant `is` pattern expressions are constant contexts that accept `bool` values, namely: - Constant declarations - Default arguments of formal parameter lists - Attribute arguments @@ -150,7 +151,7 @@ const Sign e = Sign.Negative; const bool x = a is default(int); // always false, no warning/error // attribute argument -[assembly: Something(a is 4)] // always true, no warning/error +[assembly: Something(a is 3)] // always false, no warning/error // default argument of formal parameter list // always true, no warning/error @@ -160,13 +161,13 @@ int Negate(int value, bool negate = e is Sign.Negative) { } // + goto case statement switch (b) { - // always true, no warning/error - case a is c: + // always false, no warning/error + case a is not c: break; default: - // always true, no warning/error - goto case a is c; + // always false, no warning/error + goto case a is not c; } // switch expression arm @@ -180,6 +181,7 @@ var p = b switch **NOTE**: we do not introduce any breaking changes in the reported diagnostics. Currently, all the above cases are illegal reporting "CS0150: A constant value is expected". A warning about the values always evaluating to either true or false is also reported alongside the error. We remove the warnings from those places where `is` expressions are currently not permitted to be used due to the error. ### Other patterns +[other-patterns]: #other-patterns Pattern expressions containing non-constant subpatterns, like accessing properties, list patterns and var patterns, are **not** constant. In the below examples, all expressions will report compiler errors: @@ -189,6 +191,38 @@ const bool r = d is [.. var prefix, 'l', 'o']; // Error: not a constant expressi const bool s = d is var someString; // Error: not a constant expression ``` +### Switch expressions +[switch-expressions]: #switch-expressions + +When evaluating a constant value on a switch expression, we adjust the reported diagnostics based on the matching arms: +- If any subpattern is matched, we do **not** report a warning for the uncovered default case. +- If no subpattern is matched, we report an **error** instead of a warning about the unmatched subpattern, asking the user to either handle the specific value, or the default case. + +For example: +```csharp +const int a = 1; +const int b = 2; +const int c = 3; + +// no warning about the missing default arm, +// the expression always returns the value of b +int x = a switch +{ + a => b, + b => a, + c => a + b, +}; + +// the matching subpatterns are not all constant, +// therefore the user will be warned about missing +// the default arm +int y = [a, b] switch +{ + [0, 1] => 1, + [.., 3] => 3, +}; +``` + ## Drawbacks [drawbacks]: #drawbacks From 9eee89d40dfb83141afb571b4aef6e58e0acdbe7 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Sat, 6 Jan 2024 22:10:58 +0200 Subject: [PATCH 10/11] Adjust missing default arm diagnostics + fixes --- proposals/pattern-const-expressions.md | 38 +++++++++++++++++++------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/proposals/pattern-const-expressions.md b/proposals/pattern-const-expressions.md index f41b50f6d2..552306a997 100644 --- a/proposals/pattern-const-expressions.md +++ b/proposals/pattern-const-expressions.md @@ -173,8 +173,9 @@ switch (b) // switch expression arm var p = b switch { - // always true, no warning/error + // 'a is c' is always true, no warning/error about the expression a is c => 1, + _ => 2, }; ``` @@ -195,7 +196,7 @@ const bool s = d is var someString; // Error: not a constant expression [switch-expressions]: #switch-expressions When evaluating a constant value on a switch expression, we adjust the reported diagnostics based on the matching arms: -- If any subpattern is matched, we do **not** report a warning for the uncovered default case. +- If any subpattern is matched, we keep reporting the warning about the missing default arm. - If no subpattern is matched, we report an **error** instead of a warning about the unmatched subpattern, asking the user to either handle the specific value, or the default case. For example: @@ -204,7 +205,7 @@ const int a = 1; const int b = 2; const int c = 3; -// no warning about the missing default arm, +// we get a warning about the missing default arm, // the expression always returns the value of b int x = a switch { @@ -213,13 +214,30 @@ int x = a switch c => a + b, }; -// the matching subpatterns are not all constant, -// therefore the user will be warned about missing -// the default arm -int y = [a, b] switch +// we get a warning about the missing default arm, +// the expression results in xc being assigned the value of b +const int xc = a switch { - [0, 1] => 1, - [.., 3] => 3, + a => b, + b => a, + c => a + b, +}; + +// we get a warning about the missing default arm, +// as no subpattern matches the constant value, +// and the program will throw at runtime +int y = a switch +{ + b => a, + c => a + b, +}; + +// we get an error about the missing default arm, +// as no subpattern matches the constant value +const int yc = a switch +{ + b => a, + c => a + b, }; ``` @@ -237,7 +255,7 @@ Currently, equality and comparison operators can be used to compare against othe [unresolved]: #unresolved-questions - [ ] Requires LDM review -- [ ] Should this be extended to `switch` expressions too? (Discussion [#7489](https://github.com/dotnet/csharplang/discussions/7489)) +- [ ] Should we also consider `switch` expressions evaluating and returning constant expressions as constant too? (Discussion [#7489](https://github.com/dotnet/csharplang/discussions/7489)) ## Design meetings [meetings]: #design-meetings From f8d2b7a68380380a83e29a4f3f1555942acfbe64 Mon Sep 17 00:00:00 2001 From: Rekkonnect Date: Tue, 9 Jan 2024 23:51:34 +0200 Subject: [PATCH 11/11] Update constant contexts + default arm warnings --- proposals/pattern-const-expressions.md | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/proposals/pattern-const-expressions.md b/proposals/pattern-const-expressions.md index 552306a997..c20177103f 100644 --- a/proposals/pattern-const-expressions.md +++ b/proposals/pattern-const-expressions.md @@ -71,6 +71,19 @@ The [§12.23 section](https://github.com/dotnet/csharpstandard/blob/standard-v7/ > - **Parenthesized patterns.** > - **References to constant fields or locals inside constant patterns.** +We additionally define the concept of constant contexts, which are contexts where a constant expression is required, as indicated in the [§12.23 section](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#1223-constant-expressions), specifically: + +> Constant expressions are required in the contexts listed below and this is indicated in the grammar by using *constant_expression*. In these contexts, a compile-time error occurs if an expression cannot be fully evaluated at compile-time. +> +> - Constant declarations ([§15.4](classes.md#154-constants)) +> - Enumeration member declarations ([§19.4](enums.md#194-enum-members)) +> - Default arguments of formal parameter lists ([§15.6.2](classes.md#1562-method-parameters)) +> - `case` labels of a `switch` statement ([§13.8.3](statements.md#1383-the-switch-statement)). +> - `goto case` statements ([§13.10.4](statements.md#13104-the-goto-statement)) +> - Dimension lengths in an array creation expression ([§12.8.16.5](expressions.md#128165-array-creation-expressions)) that includes an initializer. +> - Attributes ([§22](attributes.md#22-attributes)) +> - In a *constant_pattern* ([§11.2.3](patterns.md#1123-constant-pattern)) + ### Grammar [grammar]: #grammar The grammar remains untouched, as nothing changes syntactically for this feature. @@ -118,20 +131,7 @@ All the above are currently valid pattern matching expressions, that also emit w When assigning those expressions in a constant context, these warnings about the constant result of the expression will **not** be reported, as the user intends to capture the constant value of the expression. -Constant contexts are contexts where a constant expression is required, as indicated in the [§12.23 section](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#1223-constant-expressions), specifically: - -> Constant expressions are required in the contexts listed below and this is indicated in the grammar by using *constant_expression*. In these contexts, a compile-time error occurs if an expression cannot be fully evaluated at compile-time. -> -> - Constant declarations ([§15.4](classes.md#154-constants)) -> - Enumeration member declarations ([§19.4](enums.md#194-enum-members)) -> - Default arguments of formal parameter lists ([§15.6.2](classes.md#1562-method-parameters)) -> - `case` labels of a `switch` statement ([§13.8.3](statements.md#1383-the-switch-statement)). -> - `goto case` statements ([§13.10.4](statements.md#13104-the-goto-statement)) -> - Dimension lengths in an array creation expression ([§12.8.16.5](expressions.md#128165-array-creation-expressions)) that includes an initializer. -> - Attributes ([§22](attributes.md#22-attributes)) -> - In a *constant_pattern* ([§11.2.3](patterns.md#1123-constant-pattern)) - -Based on the above, the contexts that will accept constant `is` pattern expressions are constant contexts that accept `bool` values, namely: +The contexts that will accept constant `is` pattern expressions are constant contexts that accept `bool` values, namely: - Constant declarations - Default arguments of formal parameter lists - Attribute arguments @@ -214,7 +214,7 @@ int x = a switch c => a + b, }; -// we get a warning about the missing default arm, +// we get no warning about the missing default arm, since we are in constant context // the expression results in xc being assigned the value of b const int xc = a switch {