# C# Language Design Notes for Jul 16, 2018

## Agenda

1. Null-coalescing assignment
   1. User-defined operators
   1. Unconstrained type parameters
   1. Throw expression the right-hand side
1. Nullable await
1. Nullable pointer access
1. Non-nullable reference types feature flag follow-up

# Null-coalescing assignment

This feature has the syntax `??=` and only assigns the left-hand side of the
assignment if the left-hand side evaluates to null.

## User-defined operators

The proposal reads, `if (x == null) x = y;`

**Conclusion**

This should instead read `if (x is null) x = y;`. The difference is in the
operators: null-coalescing assignment, like the null-coalescing operator,
does not consider user-defined operators like `==`. The spec also states that
the semantics are equivalent to `x = x ?? y`, but that should instead be `(x
?? (x = y))`. If `x` is not null, the assignment is never performed.


## Result type of the expression

Other assignments have the type of the left-hand side e.g., `(x = y)` has the
type of `x`, not `y`. This has the convenient property that any assignment
expression can be replaced with the variable being assigned. However, there
may be some value in "transforming" the type to non-nullable when possible.
For instance, `(x ??= y)` could evaluate to a non-null type if `y` is
non-null, since we know that the null-coalescing assignment cannot produce
null. This is how the existing null-coalescing operator works. The
null-coalescing operator also specifies that, in `x ?? y`, the conversion to
a non-nullable version of `x` is preferred if it is available.

**Conclusion**

Let's favor the semantics of assignment. The return type should be the type of `x`.

## Null-coalescing assignment on unconstrained type parameters

Null coalescing currently isn't allowed on type parameters, so the proposal
currently doesn't allow it either.

**Conclusion**

This is a hole in null-coalescing as well. Both features should be allowed
on unconstrained type parameters.

## Throw expression on the right-hand side

Should the following be allowed: `a ??= throw new Exception`? `throw`
expressions are only allowed in certain places right now, so we would have to
explicitly add support. It makes more sense if the feature is equivalent to
`a = a ?? b` where `b` is a `throw`, than `a ?? (a = b)`.

**Conclusion**

Not supported

# Logical boolean compound assignment

Also proposed are the features `&&=` and `||=`. Should these features live
in the same proposal or be split out on their own?

**Conclusion**

Split them out. These features are potentially much more complicated because
user-defined conversions for `true`, `false`, `&`, and `|` could come into play.

# Null-conditional await, foreach

`await?` is proposed, which would only await the task-like target if it is
non-null and evaluate to null otherwise. A new syntax would be needed here
because the result produces a different type. `await` will currently always
have the "unwrapped" type from the task-like target, whereas `await?` would
need to make that type nullable in the case that it is not already. An
analogous feature is proposed for `foreach?`.

In favor of the feature, it may be more useful with nullable reference types
and more nullable Task-like things. On the other hand, it could lead to more
use of nulls in places that some of the design team considers code smells,
like widespread use of null Tasks or IEnumerable.

**Conclusion**

Probably not in C# 8, but maybe later. It may still have value, but probably
not as much as other features.


# Nullable pointer access expressions

The proposal includes `?->` and `?[]`. It purposefully leaves out `*?` as
being confusing as a prefix operator.

**Conclusion**

We don't love it. Not planned for any release. We may consider an external
contribution.

# Non-nullable reference type feature flag follow-up

We followed-up on the feature flag design from [last Wednesday's
LDM](LDM-2018-07-11.md). The main concern was making the language semantics
affected by a non-language option, namely a compiler flag. It was proposed
that the NonNullTypes attribute could effectively cover the same information
as the feature flag and the attribute together.

Proposal:

* `[NonNullTypes(true)]`: Warnings are on and unannotated types are non-nullable
* `[NonNullTypes(false)]`: Warnings are on but unannotated types are oblivious
* `<no attribute>`: Warnings are off and unannotated types are oblivious

The biggest benefit: if the warning opt-in is in source code, the nullable
warnings are not a breaking change. It's also noted
that this is similar to how CLS compliance already works with the
CLSCompliant attribute.

One downside is that there is no way to turn the warnings off once there is a
`NonNullTypes` attribute anywhere in the code. A solution is to provide a
`Warnings` property on the type which lets the user configure warnings. This
property would be true by default, but the user could override it e.g.,
`[NonNullTypes(true, Warnings = false)]`. Also, it's unclear how this would
work in environments like scripting. In normal compilations it's easy to find
somewhere to put an attribute, but that's not necessarily the case in
scripts.

**Conclusion**

Accepted with some notes:

* `[NonNullTypes]` means `[NonNullTypes(true)]`
* To prevent upgrading from C# 7 to C# 8 being a breaking change, we should
poison the `[NonNullTypes]` attribute using `ObsoleteAttribute` (like `ref`
structs), but provide a better message
* `?` in a context where there is no attribute or [NonNullTypes(false)] should be
a warning since the user is probably misunderstanding how to use the feature. `!`
is still useful even if unannotated types are oblivious, so it is only a warning
if there is no `NonNullType` present at all
