Skip to content
Closed
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
87 changes: 85 additions & 2 deletions standard/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2013,11 +2013,18 @@ member_declarator_list
: member_declarator (',' member_declarator)*
;

null_conditional_member_access
: primary_expression null_conditional_operations? '?' '.' identifier type_argument_list?
| primary_expression null_conditional_operations '.' identifier type_argument_list?
;

member_declarator
: simple_name
| member_access
| base_access
| null_conditional_member_access
| Identifier '=' expression
| null_conditional_member_access
;
```

Expand Down Expand Up @@ -2065,15 +2072,15 @@ Within the same program, two anonymous object initializers that specify a sequen

The `Equals` and `GetHashcode` methods on anonymous types override the methods inherited from `object`, and are defined in terms of the `Equals` and `GetHashcode` of the properties, so that two instances of the same anonymous type are equal if and only if all their properties are equal.

A member declarator can be abbreviated to a simple name ([§12.7.3](expressions.md#1273-simple-names)), a member access ([§12.7.5](expressions.md#1275-member-access)) or a base access ([§12.7.9](expressions.md#1279-base-access)). This is called a ***projection initializer*** and is shorthand for a declaration of and assignment to a property with the same name. Specifically, member declarators of the forms
A member declarator can be abbreviated to a simple name ([§12.7.3](expressions.md#1273-simple-names)), a member access ([§12.7.5](expressions.md#1275-member-access)), a base access ([§12.7.9](expressions.md#1279-base-access)), or a *null-conditional member access*. This is called a ***projection initializer*** and is shorthand for a declaration of and assignment to a property with the same name. Specifically, member declarators of the forms

`«Identifier»` and `«expr» . «Identifier»`

are precisely equivalent to the following, respectively:

`«identifer» = «Identifier»` and `«Identifier» = «expr» . «Identifier»`

Thus, in a projection initializer the identifier selects both the value and the field or property to which the value is assigned. Intuitively, a projection initializer projects not just a value, but also the name of the value.
Thus, in a projection initializer the identifier selects both the value and the field or property to which the value is assigned. Intuitively, a projection initializer projects not just a value, but also the name of the value. A null-conditional expression is only allowed if it ends with an (optionally null-conditional) member access.

### 12.7.12 The typeof operator

Expand Down Expand Up @@ -2327,6 +2334,7 @@ The `+`, `-`, `!`, `~`, `++`, `--`, cast, and `await` operators are called the u
```ANTLR
unary_expression
: primary_expression
| null_conditional_expression
Copy link
Contributor

Choose a reason for hiding this comment

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

Not having read a spec for this feature (https://tinyurl.com/seyughk is not sufficient) this grammar is curious. I might have expected to see null_condition_member_access alongside member_access (and likewise for element access) as an alternative in primary_expression. Are the sentences the approach here allows/disallows which placing the new operators in primary_expression would disallow/allow? TIA

Copy link
Contributor

Choose a reason for hiding this comment

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

Nigel, I'm struggling to understand your final sentence here. Could you rephrase it? (Or just explain it in the meeting.)

Copy link
Contributor

@Nigel-Ecma Nigel-Ecma Mar 10, 2021

Choose a reason for hiding this comment

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

There is a typo it should have been:

Are there sentences the approach here allows/disallows which placing the new operators in primary_expression would disallow/allow?

Why I'm asking: it seems null_conditional_member_access could logically sit alongside member_access (and ditto for the element access variant) primary_expression (more precisely primary_no_array_creation_expression). I'm trying to figure out if the decision to place these two operators in unary_expression and introduce productions rules for them which mimic a subset of primary_expression was a choice or a requirement to describe the required syntax and semantics?

There appear to be parts of the proposed changes which only need to exist because these new constructs are not part of primary_expression, and having them there would simply the description of the semantics. But would it also complicate the description in other areas? Right now I don't know.

I'm also unsure there is any need to describe different equivalent code for when these operators occur in statement expressions, it has the feel of inserting particular/irrelevant implementation details. (Consider for example that a boolean expression might be compiled differently if its result is stored in a variable or used as the predicate in an if – one produces a value to store the other branching code – but we don't say that in the Standard! Is this a similar case?)

Hope that makes a little more sense now, but its late (in NZ) so maybe it doesn't... We can figure that out tomorrow.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, that makes more sense - we can discuss it all in the meeting. Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

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

Tagging @MadsTorgersen

I think this discussion is at the heart of Nigel's alternative PR, and the discussion on ?. being one token or two. Not making edits at this time based on this comment. I believe it accurately reflects this path toward specing the feature.

| '+' unary_expression
| '-' unary_expression
| '!' unary_expression
Expand All @@ -2340,6 +2348,81 @@ unary_expression

If the operand of a *unary_expression* has the compile-time type `dynamic`, it is dynamically bound ([§12.3.3](expressions.md#1233-dynamic-binding)). In this case, the compile-time type of the *unary_expression* is `dynamic`, and the resolution described below will take place at run-time using the run-time type of the operand.

### §null-conditional-operator Null-conditional operator

#### §null-conditional-operator-general General

The null-conditional operator applies a list of operations to its operand only if that operand is non-`null`. Otherwise the result of applying the operator is `null`.

```ANTLR
null_conditional_expression
: primary_expression null_conditional_operations
;

null_conditional_operations
: null_conditional_operations? '?' '.' identifier type_argument_list?
| null_conditional_operations? '?' '[' argument_list ']'
Copy link
Contributor

Choose a reason for hiding this comment

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

Does not this alternative along with the one below on line 2360 remove the restrictions that any element access starts with a primary_no_array_creation_expression and not a primary_expression?

Copy link
Member Author

Choose a reason for hiding this comment

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

@Nigel-Ecma I think your concern here is addressed by the latest changes. As @jskeet pointed out two areas weren't integrated, but were written as stand alone feature changes.

| null_conditional_operations '.' identifier type_argument_list?
| null_conditional_operations '[' argument_list ']'
| null_conditional_operations '(' argument_list? ')'
;
```

The list of operations can include member access and element access operations (which may themselves be null-conditional), as well as invocation.
> *Example* : The expression `a.b?[0]?.c()` is a *null_conditional_expression* with a *primary_expression* `a.b` and *null_conditional_operations* `?[0]` (null-conditional element access), `?.c` (null-conditional member access) and `()` (invocation). *end example*

For a *null_conditional_expression* `E` with a *primary_expression* `P`, let `E₀` be the expression obtained by textually removing the leading `?` from each of the *null_conditional_operations* of `E` that have one. Conceptually, `E₀` is the expression that will be evaluated if none of the null checks represented by the `?`s do find a `null`.

Also, let `E₁` be the expression obtained by textually removing the leading `?` from just the first of the *null_conditional_operations* in `E`. This may lead to a *primary-expression* (if there was just one `?`) or to another *null_conditional_expression*.

> *Example* : If `E` is the expression `a.b?[0]?.c()`, then `E₀` is the expression `a.b[0].c()` and `E₁` is the expression `a.b[0]?.c()`. *end example*

If `E₀` is classified as nothing, then `E` is classified as nothing. Otherwise `E` is classified as a value.
`E₀` and `E₁` are used to determine the meaning of `E`:
- If `E` occurs as a *statement_expression* the meaning of `E` is the same as the statement
```csharp
if ((object)P != null) E₁;
```
except that `P` is evaluated only once.
- Otherwise, if `E₀` is classified as nothing a compile-time error occurs.
- Otherwise, let `T₀` be the type of `E₀`.
- If `T₀` is a type parameter that is not known to be a reference type or a non-nullable value type, a compile-time error occurs.
- If `T₀` is a non-nullable value type, then the type of `E` is `T₀?`, and the meaning of `E` is the same as
```csharp
((object)P == null) ? (T₀?)null : E₁
```
except that `P` is evaluated only once.
- Otherwise the type of `E` is `T₀`, and the meaning of `E` is the same as
```csharp
((object)P == null) ? null : E₁
```
except that `P` is evaluated only once.

If `E₁` is itself a *null_conditional_expression*, then these rules are applied again, nesting the tests for `null` until there are no further `?`'s, and the expression has been reduced all the way down to the primary-expression `E₀`.

> *Example* : If the expression `a.b?[0]?.c()` occurs as a statement-expression, as in the statement:
> ```csharp
> a.b?[0]?.c();
> ```
> its meaning is equivalent to:
> ```csharp
> if (a.b != null) a.b[0]?.c();
> ```
> which again is equivalent to:
> ```csharp
> if (a.b != null) if (a.b[0] != null) a.b[0].c();
> ```
> except that `a.b` and `a.b[0]` are evaluated only once.
> If it occurs in a context where its value is used, as in:
> ```csharp
> var x = a.b?[0]?.c();
> ```
> and assuming that the type of the final invocation is not a non-nullable value type, its meaning is equivalent to:
> ```csharp
> var x = (a.b == null) ? null : (a.b[0] == null) ? null : a.b[0].c();
> ```
> except that `a.b` and `a.b[0]` are evaluated only once. *end example*

### 12.8.2 Unary plus operator

For an operation of the form `+x`, unary operator overload resolution ([§12.4.4](expressions.md#1244-unary-operator-overload-resolution)) is applied to select a specific operator implementation. The operand is converted to the parameter type of the selected operator, and the type of the result is the return type of the operator. The predefined unary plus operators are:
Expand Down
12 changes: 11 additions & 1 deletion standard/statements.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,19 +359,29 @@ expression_statement
: statement_expression ';'
;

null_conditional_invocation_expression
: primary_expression null_conditional_operations '(' argument_list? ')'
;

statement_expression
Copy link
Contributor

Choose a reason for hiding this comment

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

@BillWagner - Same as for member_declarator, here the latest change appears to replace statement_expression with only null-conditional options rather than add them to it. Has something gone awry or am I misreading this?

: invocation_expression
| null_conditional_invocation_expression
| object_creation_expression
| assignment
| post_increment_expression
| post_decrement_expression
| pre_increment_expression
| pre_decrement_expression
| await_expression
| null_conditional_invocation_expression
;

null_conditional_invocation_expression
: primary_expression null_conditional_operations '(' argument_list? ')'
;
```

Not all expressions are permitted as statements.
Not all expressions are permitted as statements. A null-conditional expression is only allowed as a *statement_expression* if it ends with an invocation.

> *Note*: In particular, expressions such as `x + y` and `x == 1`, that merely compute a value (which will be discarded), are not permitted as statements. *end note*

Expand Down