Skip to content
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

Null-forgiving operator #1195

Draft
wants to merge 5 commits into
base: draft-v8
Choose a base branch
from
Draft
Changes from 2 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
23 changes: 17 additions & 6 deletions standard/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ The precedence of an operator is established by the definition of its associated
>
> | **Subclause** | **Category** | **Operators** |
> | ----------------- | ------------------------------- | -------------------------------------------------------|
> | [§12.8](expressions.md#128-primary-expressions) | Primary | `x.y` `x?.y` `f(x)` `a[x]` `a?[x]` `x++` `x--` `new` `typeof` `default` `checked` `unchecked` `delegate` `stackalloc` |
> | [§12.8](expressions.md#128-primary-expressions) | Primary | `x.y` `x?.y` `f(x)` `a[x]` `a?[x]` `x++` `x--` `x!` `new` `typeof` `default` `checked` `unchecked` `delegate` `stackalloc` |
> | [§12.9](expressions.md#129-unary-operators) | Unary | `+` `-` `!` `~` `++x` `--x` `(T)x` `await x` |
> | [§12.10](expressions.md#1210-arithmetic-operators) | Multiplicative | `*` `/` `%` |
> | [§12.10](expressions.md#1210-arithmetic-operators) | Additive | `+` `-` |
Expand Down Expand Up @@ -1273,7 +1273,6 @@ Primary expressions include the simplest forms of expressions.
primary_expression
: primary_no_array_creation_expression
| array_creation_expression
| null_forgiving_expression
;

primary_no_array_creation_expression
Expand All @@ -1291,6 +1290,7 @@ primary_no_array_creation_expression
| base_access
| post_increment_expression
| post_decrement_expression
| null_forgiving_expression
| object_creation_expression
| delegate_creation_expression
| anonymous_object_creation_expression
Expand All @@ -1307,7 +1307,7 @@ primary_no_array_creation_expression
;
```

> *Note*: These grammar rules are not ANTLR-ready as they are part of a set of mutually left-recursive rules (`primary_expression`, `primary_no_array_creation_expression`, `member_access`, `invocation_expression`, `element_access`, `post_increment_expression`, `post_decrement_expression`, `pointer_member_access` and `pointer_element_access`) which ANTLR does not handle. Standard techniques can be used to transform the grammar to remove the mutual left-recursion. This has not been done as not all parsing strategies require it (e.g. an LALR parser would not) and doing so would obfuscate the structure and description. *end note*
> *Note*: These grammar rules are not ANTLR-ready as they are part of a set of mutually left-recursive rules (`primary_expression`, `primary_no_array_creation_expression`, `member_access`, `invocation_expression`, `element_access`, `post_increment_expression`, `post_decrement_expression`, `null_forgiving_expression`, `pointer_member_access` and `pointer_element_access`) which ANTLR does not handle. Standard techniques can be used to transform the grammar to remove the mutual left-recursion. This has not been done as not all parsing strategies require it (e.g. an LALR parser would not) and doing so would obfuscate the structure and description. *end note*

*pointer_member_access* ([§23.6.3](unsafe-code.md#2363-pointer-member-access)) and *pointer_element_access* ([§23.6.4](unsafe-code.md#2364-pointer-element-access)) are only available in unsafe code ([§23](unsafe-code.md#23-unsafe-code)).

Expand Down Expand Up @@ -1824,18 +1824,29 @@ A *null_conditional_projection_initializer* is a restriction of *null_conditiona

### 12.8.9 Null-forgiving expressions

This operator sets the null state ([§8.9.5](types.md#895-nullabilities-and-null-states)) of the operand to “not null”.
The null-forgiving operator sets the null state ([§8.9.5](types.md#895-nullabilities-and-null-states)) of the operand to “not null”.

```ANTLR
null_forgiving_expression
: primary_no_array_creation_expression suppression
: primary_expression null_forgiving_operator
;

suppression
null_forgiving_operator
: '!'
;
```

The *primary_expression* must not be known to have a value type.
Copy link
Contributor

Choose a reason for hiding this comment

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

Was this restriction relaxed after C# 8? Here's what I see at SharpLab:

class C {
    static void M() {
        (string, string?) t1 = ("", null);
        (string?, string) t2;

        t2 = t1; // warning CS8619: Nullability of reference types in value of type '(string, string?)' doesn't match target type '(string?, string)'.
        t2 = t1!; // no warning, although (string, string?) is a value type
    }

    static void N() {
        (int, int) t1 = (0, 0);
        (int, int) t2;

        t2 = t1!; // no warning, although (int, int) is a value type
    }

    static void O() {
        int t1 = 0;
        int t2;

        t2 = t1!; // no warning, although int is a value type
    }
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Roslyn started allowing e! on value types in dotnet/roslyn#32090, which is included in tag Visual-Studio-2019-Version-16.0-Preview-2. So Visual Studio 2019 version 16.0 already had this change in its C# 8 support. Visual Studio 2019 version 16.0 release notes for C#

According to dotnet/roslyn#57142 (comment) on Oct 20, 2021, "the spec" was also changed to allow ! on value types. That change was perhaps dotnet/csharplang@e8ddd37 in proposals/csharp-9.0/nullable-reference-types-specification.md, making it look like e! on value types is only allowed starting from C# 9. But AFAIK no C# 8 compiler was released without this feature.

I suppose the committee still has the authority to omit that feature from the C# 8 standard, even if compilers support it.


It is an error to apply the null-forgiving operator more than once to the same expression, intervening parenetheses notwithstanding.
Nigel-Ecma marked this conversation as resolved.
Show resolved Hide resolved

> *Example*: the following are all invalid:
>
> ```csharp
> var p = q!!; // error: cannot apply the null_forgiving_operator more than once
> var s = ( ( m(t) ! ) )! // error: null_forgiving_operator applied twice to m(t)
> ```

This operator has no runtime effect; it evaluates to the result of its operand, and that result retains that operand’s classification.

The null-forgiving operator is used to declare that an expression not known to be a value type is not null.
Expand Down
Loading