From 5bc6b79d36af7b1e986e78f976cf8bdc08467f66 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 23 Oct 2020 15:32:28 -0400 Subject: [PATCH 1/4] specification for null-conditional-operator Add the sections and grammar for the `?.` null conditional operator. --- standard/expressions.md | 104 +++++++++++++++++++++++++++++++++++++++- standard/statements.md | 5 ++ 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/standard/expressions.md b/standard/expressions.md index caa00f4ec..4739d2066 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -2017,6 +2017,7 @@ member_declarator : simple_name | member_access | base_access + | null_conditional_member_access | Identifier '=' expression ; ``` @@ -2065,7 +2066,7 @@ 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 (§null-conditional-operator-initializer). 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»` @@ -2327,6 +2328,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 +2342,106 @@ 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 ']' + | 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* + +#### §null-conditional-operator-initializer Null-conditional expressions as projection initializers + +A null-conditional expression is only allowed as a *member_declarator* in an *anonymous_object_creation_expression* ([§12.7.11.7](expressions.md#127117-anonymous-object-creation-expressions)) if it ends with an (optionally null-conditional) member access. Grammatically, this requirement can be expressed as: + +```antlr +null_conditional_member_access + : primary_expression null_conditional_operations? '?' '.' identifier type_argument_list? + | primary_expression null_conditional_operations '.' identifier type_argument_list? + ; +``` + +This is a special case of the grammar for *null_conditional_expression* above. The production for *member_declarator* in [§12.7.11.7](expressions.md#127117-anonymous-object-creation-expressions) then includes only *null_conditional_member_access*. + +#### §null-conditional-operator-statement Null-conditional expressions as statement expressions + +A null-conditional expression is only allowed as a *statement_expression* ([§13.7](statements.md#137-expression-statements)) if it ends with an invocation. Grammatically, this requirement can be expressed as: + +```antlr +null_conditional_invocation_expression + : primary_expression null_conditional_operations '(' argument_list? ')' + ; +``` + +This is a special case of the grammar for *null_conditional_expression* above. The production for *statement_expression* in [§13.7](statements.md#137-expression-statements) then includes only *null_conditional_invocation_expression*. + ### 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: diff --git a/standard/statements.md b/standard/statements.md index 54c8a5484..cb3bc70f2 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -361,6 +361,7 @@ expression_statement statement_expression : invocation_expression + | null_conditional_invocation_expression | object_creation_expression | assignment | post_increment_expression @@ -1092,6 +1093,10 @@ exception_specifier : '(' type Identifier? ')' ; +finally_clause + : 'finally' block + ; + finally_clause : 'finally' block ; From 571710d7719e345669278e47d84a8682e2065d5f Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Mon, 30 Nov 2020 17:07:27 -0500 Subject: [PATCH 2/4] revert an edit for another feature. An edit from await in catch and finally clauses (#3) snuck in. --- standard/expressions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/standard/expressions.md b/standard/expressions.md index 4739d2066..9f24b972a 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -2605,6 +2605,7 @@ await_expression An *await_expression* is only allowed in the body of an async function ([§15.15](classes.md#1515-async-functions)). Within the nearest enclosing async function, an *await_expression* shall not occur in these places: - Inside a nested (non-async) anonymous function +- In a `catch` or `finally` block of a *try_statement* - Inside the block of a *lock_statement* - In an anonymous function conversion to an expression tree type ([§11.7.3](conversions.md#1173-evaluation-of-anonymous-function-conversions-to-expression-tree-types)) - In an unsafe context From a3eb7d8a94e815757500a48421b83aed60c35d93 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 12 May 2021 15:51:07 -0400 Subject: [PATCH 3/4] Respond to comments --- standard/expressions.md | 41 +++++++---------------------------------- standard/statements.md | 20 ++++++-------------- 2 files changed, 13 insertions(+), 48 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index 9f24b972a..f0c96eabd 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -2014,14 +2014,13 @@ member_declarator_list ; member_declarator - : simple_name - | member_access - | base_access - | null_conditional_member_access - | Identifier '=' expression + : primary_expression null_conditional_operations? '?' '.' identifier type_argument_list? + | primary_expression null_conditional_operations '.' identifier type_argument_list? ; ``` +This is a special case of the grammar for *null_conditional_expression* above. The production for *member_declarator* in [§12.7.11.7](expressions.md#127117-anonymous-object-creation-expressions) then includes only *null_conditional_member_access*. + An anonymous object initializer declares an anonymous type and returns an instance of that type. An anonymous type is a nameless class type that inherits directly from `object`. The members of an anonymous type are a sequence of read-only properties inferred from the anonymous object initializer used to create an instance of the type. Specifically, an anonymous object initializer of the form `new {` *p₁* `=` *e₁* `,` *p₂* `=` *e₂* `,` ... *pᵥ* `=` *eᵥ* `}` @@ -2066,7 +2065,7 @@ 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)), a base access ([§12.7.9](expressions.md#1279-base-access)), or a null-conditional member access (§null-conditional-operator-initializer). 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»` @@ -2074,7 +2073,7 @@ 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 @@ -2348,7 +2347,7 @@ If the operand of a *unary_expression* has the compile-time type `dynamic`, it i 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 +```ANTLR null_conditional_expression : primary_expression null_conditional_operations ; @@ -2417,31 +2416,6 @@ If `E₁` is itself a *null_conditional_expression*, then these rules are applie > ``` > except that `a.b` and `a.b[0]` are evaluated only once. *end example* -#### §null-conditional-operator-initializer Null-conditional expressions as projection initializers - -A null-conditional expression is only allowed as a *member_declarator* in an *anonymous_object_creation_expression* ([§12.7.11.7](expressions.md#127117-anonymous-object-creation-expressions)) if it ends with an (optionally null-conditional) member access. Grammatically, this requirement can be expressed as: - -```antlr -null_conditional_member_access - : primary_expression null_conditional_operations? '?' '.' identifier type_argument_list? - | primary_expression null_conditional_operations '.' identifier type_argument_list? - ; -``` - -This is a special case of the grammar for *null_conditional_expression* above. The production for *member_declarator* in [§12.7.11.7](expressions.md#127117-anonymous-object-creation-expressions) then includes only *null_conditional_member_access*. - -#### §null-conditional-operator-statement Null-conditional expressions as statement expressions - -A null-conditional expression is only allowed as a *statement_expression* ([§13.7](statements.md#137-expression-statements)) if it ends with an invocation. Grammatically, this requirement can be expressed as: - -```antlr -null_conditional_invocation_expression - : primary_expression null_conditional_operations '(' argument_list? ')' - ; -``` - -This is a special case of the grammar for *null_conditional_expression* above. The production for *statement_expression* in [§13.7](statements.md#137-expression-statements) then includes only *null_conditional_invocation_expression*. - ### 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: @@ -2605,7 +2579,6 @@ await_expression An *await_expression* is only allowed in the body of an async function ([§15.15](classes.md#1515-async-functions)). Within the nearest enclosing async function, an *await_expression* shall not occur in these places: - Inside a nested (non-async) anonymous function -- In a `catch` or `finally` block of a *try_statement* - Inside the block of a *lock_statement* - In an anonymous function conversion to an expression tree type ([§11.7.3](conversions.md#1173-evaluation-of-anonymous-function-conversions-to-expression-tree-types)) - In an unsafe context diff --git a/standard/statements.md b/standard/statements.md index cb3bc70f2..0a7377190 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -360,19 +360,15 @@ expression_statement ; statement_expression - : 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* @@ -1093,10 +1089,6 @@ exception_specifier : '(' type Identifier? ')' ; -finally_clause - : 'finally' block - ; - finally_clause : 'finally' block ; From 6f74f3df49fcdecf3b073e812166a4403199b399 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 2 Jun 2021 14:29:45 -0400 Subject: [PATCH 4/4] fix grammar After discussions, I went back to the text from the Microsoft C# 6 draft. The C# 6 draft put all changes in one new clause for conditional expressions. When we went to move the two grammars for anonymous object creation and member invocation, those edits introduced grammar production errors. The only concern now is that the anonymous object creation expression does reference the null conditional expression that is defined later in the Expressions clause. --- standard/expressions.md | 13 ++++++++++--- standard/statements.md | 15 ++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/standard/expressions.md b/standard/expressions.md index f0c96eabd..e77dde436 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -2013,13 +2013,20 @@ member_declarator_list : member_declarator (',' 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? ; -``` -This is a special case of the grammar for *null_conditional_expression* above. The production for *member_declarator* in [§12.7.11.7](expressions.md#127117-anonymous-object-creation-expressions) then includes only *null_conditional_member_access*. +member_declarator + : simple_name + | member_access + | base_access + | null_conditional_member_access + | Identifier '=' expression + | null_conditional_member_access + ; +``` An anonymous object initializer declares an anonymous type and returns an instance of that type. An anonymous type is a nameless class type that inherits directly from `object`. The members of an anonymous type are a sequence of read-only properties inferred from the anonymous object initializer used to create an instance of the type. Specifically, an anonymous object initializer of the form diff --git a/standard/statements.md b/standard/statements.md index 0a7377190..4b717e044 100644 --- a/standard/statements.md +++ b/standard/statements.md @@ -359,8 +359,21 @@ expression_statement : statement_expression ';' ; +null_conditional_invocation_expression + : primary_expression null_conditional_operations '(' argument_list? ')' + ; + statement_expression - : null_conditional_invocation_expression + : 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