-
Notifications
You must be signed in to change notification settings - Fork 93
C# 6.0 feature: specification for null-conditional-operator #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
; | ||
``` | ||
|
||
|
@@ -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 | ||
|
||
|
@@ -2327,6 +2334,7 @@ The `+`, `-`, `!`, `~`, `++`, `--`, cast, and `await` operators are called the u | |
```ANTLR | ||
unary_expression | ||
: primary_expression | ||
| null_conditional_expression | ||
| '+' unary_expression | ||
| '-' unary_expression | ||
| '!' unary_expression | ||
|
@@ -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 ']' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
BillWagner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
> *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: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -359,19 +359,29 @@ expression_statement | |
: statement_expression ';' | ||
; | ||
|
||
null_conditional_invocation_expression | ||
: primary_expression null_conditional_operations '(' argument_list? ')' | ||
; | ||
|
||
statement_expression | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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* | ||
|
||
|
There was a problem hiding this comment.
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
alongsidemember_access
(and likewise for element access) as an alternative inprimary_expression
. Are the sentences the approach here allows/disallows which placing the new operators inprimary_expression
would disallow/allow? TIAThere was a problem hiding this comment.
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.)
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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:
Why I'm asking: it seems
null_conditional_member_access
could logically sit alongsidemember_access
(and ditto for the element access variant)primary_expression
(more preciselyprimary_no_array_creation_expression
). I'm trying to figure out if the decision to place these two operators inunary_expression
and introduce productions rules for them which mimic a subset ofprimary_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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.