Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions standard/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

An expression is a sequence of operators and operands. This clause defines the syntax, order of evaluation of operands and operators, and meaning of expressions.

An expression *E* is said to ***directly contain*** a subexpression *E₁* if it is not subject to a user-defined conversion [§10.5](conversions.md#105-user-defined-conversions) whose parameter is not of a non-nullable value type, and one of the following conditions holds:

- *E* is *E₁*.
- If *E* is a parenthesized expression `(E₂)`, and *E₂* directly contains *E₁*.
- If *E* is a null-forgiving operator expression `E₂!`, and *E₂* directly contains *E₁*.
- If *E* is a cast expression `(T)E₂`, and the cast does not subject *E₂* to a non-lifted user-defined conversion whose parameter is not of a non-nullable value type, and *E₂* directly contains *E₁*.

Comment on lines +7 to +13
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MS proposal had this text in the new section, 9.4.4.3x ?.expressions, but as this text is general, and not specific to that expression form, I thought it did not belong there. Short of creating a new section just for this text, I put it in 12.1. Is that OK?

## 12.2 Expression classifications

### 12.2.1 General
Expand Down Expand Up @@ -2238,8 +2245,8 @@
A *null_conditional_invocation_expression* is syntactically either a *null_conditional_member_access* ([§12.8.8](expressions.md#1288-null-conditional-member-access)) or *null_conditional_element_access* ([§12.8.13](expressions.md#12813-null-conditional-element-access)) where the final *dependent_access* is an invocation expression ([§12.8.10](expressions.md#12810-invocation-expressions)).

A *null_conditional_invocation_expression* occurs within the context of a *statement_expression* ([§13.7](statements.md#137-expression-statements)), *anonymous_function_body* ([§12.22.1](expressions.md#12221-general)), or *method_body* ([§15.6.1](classes.md#1561-general)).

Check warning on line 2248 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/expressions.md#L2248

MDC032::Line length 84 > maximum 81
Unlike the syntactically equivalent *null_conditional_member_access* or *null_conditional_element_access*, a *null_conditional_invocation_expression* may be classified as nothing.

Check warning on line 2249 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/expressions.md#L2249

MDC032::Line length 85 > maximum 81

```ANTLR
null_conditional_invocation_expression
Expand Down Expand Up @@ -2319,7 +2326,7 @@
- The *primary_expression* has compile-time type `dynamic`.
- At least one expression of the *argument_list* has compile-time type `dynamic`.

In this case the compile-time type of the *element_access* depends on the compile-time type of its *primary_expression*: if it has an array type then the compile-time type is the element type of that array type; otherwise the compile-time type is `dynamic` and the *element_access* is classified as a value of type `dynamic`. The rules below to determine the meaning of the *element_access* are then applied at run-time, using the run-time type instead of the compile-time type of those of the *primary_expression* and *argument_list* expressions which have the compile-time type `dynamic`. If the *primary_expression* does not have compile-time type `dynamic`, then the element access undergoes a limited compile-time check as described in [§12.6.5](expressions.md#1265-compile-time-checking-of-dynamic-member-invocation).

Check warning on line 2329 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/expressions.md#L2329

MDC032::Line length 82 > maximum 81

> *Example*:
>
Expand Down Expand Up @@ -3464,7 +3471,7 @@
- one of the following value types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `bool`; or
- any enumeration type.

### 12.8.22 Stack allocation

Check warning on line 3474 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/expressions.md#L3474

MDC032::Line length 86 > maximum 81

A stack allocation expression allocates a block of memory from the execution stack. The ***execution stack*** is an area of memory where local variables are stored. The execution stack is not part of the managed heap. The memory used for local variable storage is automatically recovered when the current function returns.

Expand Down Expand Up @@ -3932,8 +3939,8 @@
All non-positional properties being changed shall have both set and init accessors.

This expression is evaluated as follows:

Check warning on line 3942 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/expressions.md#L3942

MDC032::Line length 84 > maximum 81
- The receiver's clone method ([§15.16.3](classes.md#15163-copy-and-clone-members)) is invoked, and its result is converted to the receiver’s type.

Check warning on line 3943 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/expressions.md#L3943

MDC032::Line length 85 > maximum 81
- Each `member_initializer` is processed the same way as an assignment to
a field or property access of the result of the conversion. Assignments are processed in lexical order. If *member_initializer_list* is omitted, no members are changed.

Expand Down Expand Up @@ -5374,11 +5381,11 @@
- Otherwise, `y` is evaluated and converted to the type of the conditional expression, and this becomes the result of the conditional expression.

## 12.22 Anonymous function expressions

Check warning on line 5384 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/expressions.md#L5384

MDC032::Line length 92 > maximum 81
### 12.22.1 General

An ***anonymous function*** is an expression that represents an “in-line” method definition. An anonymous function does not have a value or type in and of itself, but is convertible to a compatible delegate or expression-tree type. The evaluation of an anonymous-function conversion depends on the target type of the conversion: If it is a delegate type, the conversion evaluates to a delegate value referencing the method that the anonymous function defines. If it is an expression-tree type, the conversion evaluates to an expression tree that represents the structure of the method as an object structure.

Check warning on line 5388 in standard/expressions.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/expressions.md#L5388

MDC032::Line length 90 > maximum 81
> *Note*: For historical reasons, there are two syntactic flavors of anonymous functions, namely *lambda_expression*s and *anonymous_method_expression*s. For almost all purposes, *lambda_expression*s are more concise and expressive than *anonymous_method_expression*s, which remain in the language for backwards compatibility. *end note*

```ANTLR
Expand Down
162 changes: 162 additions & 0 deletions standard/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -847,8 +847,20 @@ For an expression *expr* of the form:
- The definite-assignment state of *v* before *expr_second* is the same as the definite-assignment state of *v* after *expr_first*.
- The definite-assignment statement of *v* after *expr* is determined by:
- If *expr_first* is a constant expression ([§12.26](expressions.md#1226-constant-expressions)) with value `null`, then the state of *v* after *expr* is the same as the state of *v* after *expr_second*.
- If *expr_first* directly contains ([§12.1](expressions.md#121-general)) a null-conditional expression *E*, and *v* is definitely assigned after the non-conditional counterpart *E₀* (§qdot-expressions), then the definite assignment state of *v* after *expr* is the same as the definite assignment state of *v* after *expr_second*.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The instructions in the MS spec for including the new bullet are unclear. They appear to suggest adding the new bullet at the end of the level-2 bullet list. However, that would make it come after the currently final “Otherwise” bullet, which doesn’t seem right. So, I inserted the new bullet before that bullet. Please check if that makes sense.

- Otherwise, the state of *v* after *expr* is the same as the definite-assignment state of *v* after *expr_first*.

> *Note*: The rule above formalizes that for an expression like `a?.M(out x) ?? (x = false)`, either the `a?.M(out x)` was fully evaluated and produced a non-null value, in which case `x` was assigned, or the `x = false` was evaluated, in which case `x` was also assigned. Therefore `x` is always assigned after this expression.
>
> This also handles the `dict?.TryGetValue(key, out var value) ?? false` scenario, by observing that *v* is definitely assigned after `dict.TryGetValue(key, out var value)`, and *v* is “definitely assigned when true” after `false`, and concluding that *v* must be “definitely assigned when true.”
>
> The more general formulation also allows the handling of some more unusual scenarios, such as:
>
> - `if (x?.M(out y) ?? (b && z.M(out y))) y.ToString();`
> - `if (x?.M(out y) ?? z?.M(out y) ?? false) y.ToString();`
>
> *end note*

#### 9.4.4.30 ?: expressions

For an expression *expr* of the form:
Expand All @@ -865,6 +877,24 @@ For an expression *expr* of the form:
- Otherwise, if *expr_cond* is a constant expression ([§12.26](expressions.md#1226-constant-expressions)) with value `false` then the state of *v* after *expr* is the same as the state of *v* after *expr_false*.
- Otherwise, if the state of *v* after *expr_true* is definitely assigned and the state of *v* after *expr_false* is definitely assigned, then the state of *v* after *expr* is definitely assigned.
- Otherwise, the state of *v* after *expr* is not definitely assigned.
- If the state of *v* after *expr_true* is “definitely assigned when true,” and the state of *v* after *expr_false* is “definitely assigned when true,” then the state of *v* after *expr* is “definitely assigned when true.”
- If the state of *v* after *expr_true* is “definitely assigned when false,” and the state of *v* after *expr_false* is “definitely assigned when false,” then the state of *v* after *expr* is “definitely assigned when false.”
Comment on lines +880 to +881
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The instructions in the MS spec for including the new bullets are unclear. They appear to suggest adding the new bullets at the end of the level-2 bullet list. However, that would make them come after the currently final “Otherwise” bullet, which doesn’t seem right. But that’s where I put them. Please check if that makes sense.


> *Note*: When both arms of a conditional expression result in a conditional state, the corresponding conditional states are joined and propagated out instead of unsplitting the state and allowing the final state to be non-conditional. This enables scenarios like the following:
>
> ```csharp
> bool b = true;
> object x = null;
> int y;
> if (b ? x != null && Set(out y) : x != null && Set(out y))
> {
> y.ToString();
> }
>
> bool Set(out int x) { x = 0; return true; }
> ```
>
> *end note*

#### 9.4.4.31 Anonymous functions

Expand Down Expand Up @@ -998,6 +1028,138 @@ For an expression *expr* of the form:
- If the variable ‘v’ is declared in *pattern*, then the definite-assignment state of ‘v’ after *expr* is “definitely assigned when true”.
- Otherwise the definite assignment state of ‘v’ after *expr* is the same as the definite assignment state of ‘v’ after *expr_operand*.

#### §qdot-expressions ?. expressions

For an expression *E* of the form:

```csharp
«primary_expression» ?. «null_conditional_operation»
```

let *E₀* be the expression obtained by textually removing the leading `?` from each of the *null_conditional_operation*s of *E* that have one. (*E₀* is referred to as the ***non-conditional counterpart*** to the null-conditional expression.)

- The definite assignment state of *v* at any point within *E* is the same as the definite assignment state at the corresponding point within *E₀*.
- The definite assignment state of *v* after *E* is the same as the definite assignment state of *v* after *primary_expression*.

> *Note*: *null_conditional_operation* is not actually a grammar rule; rather, it represents any form permitted by the grammar at that location. It is used here for convenience. *end note*
<!-- markdownlint-disable MD028 -->

<!-- markdownlint-enable MD028 -->
> *Note*: The concept of “directly contains” allows skipping over relatively simple “wrapper” expressions when analyzing conditional accesses that are compared to other values. For example, in general, `((a?.b(out x))!) == true` is expected to result in the same flow state as `a?.b == true`.
>
> The intent is to allow analysis to function in the presence of a number of possible conversions on a conditional access. Propagating out “state when not null” is not possible when the conversion is user-defined, though, since one can't count on user-defined conversions to honor the constraint that the output is non-null only if the input is non-null. The only exception to this is when the user-defined conversion’s input is a non-nullable value type. For example:
>
> ```csharp
> public struct S1 { }
> public struct S2 { public static implicit operator S2?(S1 s1) => null; }
> ```
>
> This also includes lifted conversions like the following:
>
> ```csharp
> string x;
>
> S1? s1 = null;
> _ = s1?.M1(x = "a") ?? s1.Value.M2(x = "a");
>
> x.ToString(); // ok
>
> public struct S1
> {
> public S1 M1(object obj) => this;
> public S2 M2(object obj) => new S2();
> }
> public struct S2
> {
> public static implicit operator S2(S1 s1) => null;
> }
> ```
>
> When it is considered whether a variable is assigned at a given point within a null-conditional expression, it can simply be assumed that any preceding null-conditional operations within the same null-conditional expression succeeded.
>
> For example, given a conditional expression `a?.b(out x)?.c(x)`, the non-conditional counterpart is `a.b(out x).c(x)`. If the definite assignment state of `x` before `?.c(x)` is to be determined, for example, a “hypothetical” analysis of `a.b(out x)` can be performed and the resulting state can be used as an input to `?.c(x)`. *end note*

#### §boolean-constant-expressions Boolean constant expressions

For an expression *expr*, where *expr* is a constant expression with a `bool` value, the definite assignment state of *v* after *expr* is determined, as follows:

- If *expr* is a constant expression with value *true*, and the state of *v* before *expr* is “not definitely assigned,” then the state of *v* after *expr* is “definitely assigned when false.”
- If *expr* is a constant expression with value *false*, and the state of *v* before *expr* is “not definitely assigned,” then the state of *v* after *expr* is “definitely assigned when true.”

> *Note*: It is assumed that if an expression has a constant value bool `false`, that it's impossible to reach any branch that requires the expression to return `true`. Therefore, variables are assumed to be definitely assigned in such branches.
>
> Being in a conditional state *before* visiting a constant expression, is never expected, so there is no need to account for scenarios such as “*expr* is a constant expression with value *true* and the state of *v* before *expr* is definitely assigned when true.” *end note*

#### §relational-equality-expressions ==/!= expressions

For an expression *expr* of the form:

```csharp
«expr_first» == «expr_second»
```

where `==` is a predefined comparison operator ([§12.15](expressions.md#1215-relational-and-type-testing-operators)) or a lifted operator ([§12.4.8](expressions.md#1248-lifted-operators)), the definite assignment state of *v* after *expr* is determined by:

- If *expr_first* directly contains ([§12.1](expressions.md#121-general)) a null-conditional expression *E* and *expr_second* is a constant expression with value *null*, and the state of *v* after the non-conditional counterpart *E₀* is “definitely assigned,” then the state of *v* after *expr* is “definitely assigned when false.”
- If *expr_first* directly contains a null-conditional expression *E* and *expr_second* is an expression of a non-nullable value type, or a constant expression with a non-null value, and the state of *v* after the non-conditional counterpart *E₀* is “definitely assigned,” then the state of *v* after *expr* is “definitely assigned when true.”
- If *expr_first* is of type `bool`, and *expr_second* is a constant expression with value `true`, then the definite assignment state after *expr* is the same as the definite assignment state after *expr_first*.
- If *expr_first* is of type `bool`, and *expr_second* is a constant expression with value `false`, then the definite assignment state after *expr* is the same as the definite assignment state of *v* after the logical negation expression `!expr_first`.

For an expression *expr* of the form:

```csharp
«expr_first» != «expr_second»
```

where `!=` is a predefined comparison operator or a lifted operator, the definite assignment state of *v* after *expr* is determined by:

- If *expr_first* directly contains a null-conditional expression *E* and *expr_second* is a constant expression with value `null`, and the state of *v* after the non-conditional counterpart *E₀* is “definitely assigned,” then the state of *v* after *expr* is “definitely assigned when true.”
- If *expr_first* directly contains a null-conditional expression *E* and *expr_second* is an expression of a non-nullable value type, or a constant expression with a non-null value, and the state of *v* after the non-conditional counterpart *E₀* is “definitely assigned.” then the state of *v* after *expr* is “definitely assigned when false.”
- If *expr_first* is of type `bool`, and *expr_second* is a constant expression with value `true`, then the definite assignment state after *expr* is the same as the definite assignment state of *v* after the logical negation expression `!`*expr_first*.
- If *expr_first* is of type `bool`, and *expr_second* is a constant expression with value `false`, then the definite assignment state after *expr* is the same as the definite assignment state after *expr_first*.

All of the above rules are commutative.

> *Note*: The general idea expressed by these rules is:
>
> - if a conditional access is compared to `null`, then the operations definitely occurred if the result of the comparison is `false`.
> - if a conditional access is compared to a non-nullable value type or a non-null constant, then the operations definitely occurred if the result of the comparison is `true`.
> - since user-defined operators can’t be trusted to provide reliable answers where initialization safety is concerned, the new rules only apply when a predefined `==`/`!=` operator is in use.
>
> Some consequences of these rules are:
>
> - `if (a?.b(out var x) == true)) x() else x();` will error in the 'else' branch
> - `if (a?.b(out var x) == 42)) x() else x();` will error in the 'else' branch
> - `if (a?.b(out var x) == false)) x() else x();` will error in the 'else' branch
> - `if (a?.b(out var x) == null)) x() else x();` will error in the 'then' branch
> - `if (a?.b(out var x) != true)) x() else x();` will error in the 'then' branch
> - `if (a?.b(out var x) != 42)) x() else x();` will error in the 'then' branch
> - `if (a?.b(out var x) != false)) x() else x();` will error in the 'then' branch
> - `if (a?.b(out var x) != null)) x() else x();` will error in the 'else' branch
>
> *end note*

#### §isop-ispattern-expressions is operator and is pattern expressions

For an expression *expr* of the form:

```csharp
«E» is «T»
```

where *T* is any type or pattern:

- The definite assignment state of *v* before *E* is the same as the definite assignment state of *v* before *expr*.
- The definite assignment state of *v* after *expr* is determined by:

- If *E* directly contains ([§12.1](expressions.md#121-general)) a null-conditional expression, and the state of *v* after the non-conditional counterpart *E₀* is “definitely assigned,” and `T` is any type or a pattern that does not match a `null` input, then the state of *v* after *expr* is “definitely assigned when true.”
- If *E* directly contains a null-conditional expression, and the state of *v* after the non-conditional counterpart *E₀* is “definitely assigned,” and `T` is a pattern that matches a `null` input, then the state of *v* after *expr* is “definitely assigned when false.”
- If *E* is of type `bool` and `T` is a pattern that only matches a `true` input, then the definite assignment state of *v* after *expr* is the same as the definite assignment state of *v* after *E*.
- If *E* is of type `bool` and `T` is a pattern that only matches a `false` input, then the definite assignment state of *v* after *expr* is the same as the definite assignment state of *v* after the logical negation expression `!`*expr*.
- Otherwise, if the definite assignment state of *v* after *E* is "definitely assigned," then the definite assignment state of *v* after *expr* is "definitely assigned."

> *Note*: This subclause addresses similar scenarios as §relational-equality-expressions. It does not, however, address recursive patterns; e.g., `(a?.b(out x), c?.d(out y)) is (object, object)`. *end note*

## 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.
Expand Down
Loading