
# C# Language Design Meeting for Sep. 11, 2019

## Agenda

## Discussion

### Nullable attributes and flow state interaction

We started this discussion with an email with a proposal based on follow-up research from the
previous meeting.

### Attribute interaction proposal

> Allowed inputs and outputs:
> 
> First of all, I’ll try to make rules based only on “allowed inputs” and “allowed outputs” of a
> property. I’ll use shorthands ?, ! and T for “nullable”, “nonnullable” and “unknown – depends on
> T” respectively.
> 
> For all the different sensible combinations of attributes, here are the “allowed inputs” and “allowed outputs”:
	

> |                            | Allowed input | Allowed output |
> |----------------------------|---------------|----------------|
> | string                     | !             | !              |
> | [AllowNull]string          | ?             | !              |
> | [NotNull]string?           | ?             | !              |
> | [MaybeNull]string          | !             | ?              |
> | [DisallowNull]string?      | !             | ?              |
> | string?                    | ?             | ?              |
> | [DisallowNull][NotNull]T   | !             | !              |
> | [NotNull]T                 | T             | !              |
> | [AllowNull][NotNull]T      | ?             | !              |
> | [DisallowNull]T            | !             | T              |
> | T                          | T             | T              |
> | [AllowNull]T               | ?             | T              |
> | [DisallowNull][MaybeNull]T | !             | ?              |
> | [MaybeNull]T               | T             | ?              |
> | [AllowNull][MaybeNull]T    | ?             | ?              |

> There should be no surprises to anyone there. Now let’s use these to define the different behaviors around properties:
> Ordering of states:  T is stricter than ? and ! is stricter than both T and ?.
> This is a measure of relative permissiveness of states.

> Initial state: The initial state of a property is its allowed output.
> This corresponds to us knowing nothing about the property yet, beyond what it tells us through a combination of its type and its postconditions.

> State after null check:
> 
> - On the non-null branch of a null check the state of the property is !.
> - On the null branch of a pure null check the state of the property is ?.
> - Elsewhere the state of the property is unchanged.
> 
> These rules reflect the general benefit of a null check, as well as the overriding effect of a pure null check even of a nonnull property.

Note that this rules out "dangerous" properties, meaning properties that may change outside the
scope of the nullable analysis, as in fields which may be changed by a different thread, and thus
the null check is unreliable. We don't consider this scenario to be in scope of our current
design and if we decide to address this, we must create a new attribute or some other mechanism.
 
There's also some problem with the state after null checks, namely that the type may not support
the `?` state. For instance, in the following unconstrained generic,

```C# 
T M<T>(T t)
{
    if (t != null)
        t.ToString();
    return t;
}
```

we should not produce a warning on the return, since the state should match the legal state of
`T`, which is `T`, not `?`. Similarly, for non-Nullable value types, the state cannot be `?`
after a null check, since the value cannot be null. The rule should use the `T` state.

> State after assignment:
> 
> The state of the property after an assignment is
> 
>  - Its initial state if the state of the assigned value is at least as strict as the allowed input, but no stricter than the allowed output
>  - The state of the assigned value otherwise
> 
> This rule reflects that a property is expected to “take care of things” when the state of an assigned value is valid as input but not as output. It does so by assuming that the resulting state in such situations is something that’s valid as output.
> 
> This is probably the only rule that would differ from the rules for fields, which would continue to always use the state of the assigned value.

> Warnings on assignment: A warning is yielded if the state of the assigned value is less strict than the allowed input.
> This is the same rule as all other input positions.


We like this new "state after assignment" rule and think it can be implemented now.

However, when looking into the solution, we found that this is the current behavior for properties when annotated:

```C#
using System;
using System.Diagnostics.CodeAnalysis;

#nullable enable

class C<T> where T : class?
{
    public C(T x) => f = x;
    T f;
                T P1 { get => f;        set => f = value; }
    [AllowNull] T P2 { get => f;        set => f = value ?? throw new ArgumentNullException(); }
    [MaybeNull] T P3 { get => default!; set => f = value; }

    void M()
    {
        P1 = null; // Warning
        P2 = null; // No warning
        P3 = null; // Warning

        f = P1;    // No warning
        f = P2;    // No warning
        f = P3;    // BUG?: No warning!
    }
}
```

That last line does not look right. Similar to `default(T)`, you could be producing a
potentially nullable value, when the substituted type may not permit it. We think
the three state domain outlined above will solve the problem, but that would
be too extensive to change to perform in such a short period of time.

Alternative: whenever you introduce a value (by calling
a property or a method), that produces a generic type annotated with `[MaybeNull]`
in a substituted generic method, that would produce a warning. This
matches our current behavior for `default(T)`.

For example,

```C#
T M<T>()
{
    _ = (new List<T>).FirstOrDefault(); // this would now produce a warning
}
```

**Conclusion**

Let's implement the "state after assignment" rule as defined and implement the
"Alternative" proposal outlined above. We will consider updating to use the
"three state domain" above later, which may have some further changes. A sample
of the expected behavior follows:

Non-Generic

```C#

using System;
using System.Diagnostics.CodeAnalysis;

class Widget {
    string _description = string.Empty;

    [AllowNull]
    string Description {
        get => _description;
        set => _description = value ?? string.Empty;
    }

    static void Test(Widget w) {
        w.Description = null; // ok
        Console.WriteLine(w.Description.ToUpper()); // ok

        if (w.Description == null) {
            Console.WriteLine(w.Description.ToUpper()); // warning
        }
    }
}
```

Generic

```C#
using System;
using System.Diagnostics.CodeAnalysis;

class Box<T> {
    T _value;

    [AllowNull]
    T Value {
        get => _value;
        set {
            if (value != null) {
                _value = value;
            }
        }
    }
    
    static void TestConstrained<U>(Box<U> box) where U : class {
        box.Value = null; // ok
        Console.WriteLine(box.Value.ToString()); // ok

        if (box.Value == null) {
            Console.WriteLine(box.Value.ToString()); // warning
        }
    }

    static void TestUnconstrained<U>(Box<U> box, U value) {
        box.Value = default(U); // 'default(U)' always produces a warning when U could be a non-nullable reference type
        Console.WriteLine(box.Value.ToString()); // ok

        box.Value = value; // ok
        Console.WriteLine(box.Value.ToString()); // ok

        if (box.Value == null) {
            Console.WriteLine(box.Value.ToString()); // warning
        }
    }
}
```



## More triage

### Top-level statements and member declarations

We have a variety of different use cases and experimental products (C# Interactive Window,
Jupyter projects, try.net, etc) that use the current "C# scripting" language, which is already
effectively a dialect of C#. There's a fair amount of concern that if adoption continues, we may produce a fracturing of the C# language.

However, adding top-level statements and reconciling scripting in C# proper would be an expensive
feature, in both design and implementation. It also doesn't directly impact many of the designs
we're currently considering.

But there is also significant cost to doing nothing. We have not considered the semantic for many
features in C# 8, or even if they should work in scripting (`using` declarations, notably). There
is a significant ongoing cost here, either in considering all our designs for the scripting
dialect, or in risk that not doing design/implementation work will cause bad experiences for
products using the C# scripting code.

**Conclusion**

We'll schedule this for 9.0, to at least examine options.

### Primary constructors

This occupies the same design space as records, which is scheduled for 9.0, so
we at least need to consider this feature while implementing records.

**Conclusion**

Moving to 9.0.

### Negated-condition if statement

Issue #882

This overlaps significantly with a "is not" pattern. We're not confident this
feature has significant value, after the "is not" pattern is implemented.

**Conclusion**

Move to X.X to consider after "is not" has shipped and see if there are significant
use cases that are not addressed by the "is not" pattern.

### Allow `default` in deconstruction

Issue #1394

The primary use case is `(x, y, z) = default;` instead of naming each variable
individually. There are some issues around the written specification, specifically
on what target typing `default` has.

**Conclusion**

From a consistency perspective it seems like this should work, regardless of the
complexity in details of the specification. We'll take this Any Time whenever we have a solid
specification and implementation.

### Partial type inference

Issue #1349

Nothing that's related to type inference is a tiny feature, but this is pretty
small as type inference changes are concerned. We think the hardest problem will
be agreeing on the syntax. Agreed that it could be useful, though.

**Conclusion**

We'll take this Any Time.

### Declaration expressions

Issue #973

Somewhat related is "sequence expressions". This is useful for declaring variables inline in an
expression. There are places where statements are not possible, and this requires
refactoring.

**Conclusion**

We don't think there's value in half measures here. We think going all the way to sequence
expressions may have value, but then declaration expressions do not.
