
# C# Language Design Notes for Apr. 3, 2019

## Agenda

1. Ambiguous implementations/overrides with generic methods and NRTs
2. NRT and `dynamic`

## Discussion

### Ambiguous implementations/overrides with generic methods and NRTs

```C#
interface I
{
    void Foo<T>(T value) where T : class;
    void Foo<T>(T? value) where T : struct;
}

class C : I
{
    void I.Foo<T>(T? value) { }
}
```

This was valid code in C# 7 and would always match the Nullable
overload because T? could only mean Nullable\<T>. Now, however, `T?`
is ambiguous. The ambiguity also cannot be fixed because we do not
allow you to write the constraints.

Possible fixes:

1. The existing code means the same thing as before, namely that `T?` always means `Nullable<T>`.
If you would like to use the nullable class form, we allow exactly one constrain on the
override/implementation: `where T : class`.

2. Same as (1), but also allow the constraint `where T : struct`.

3. No new syntax, do matching in two passes. First, look if the old form (`where T : struct`) is
present and use it if present. Second, expand to look for both forms if no match was found.

These fixes would address the problem where we currently just choose the "first" one. Note that
this is not the only situation where you observe ordering dependence for OHI, but it also appears
that that ordering dependence appears in the CLR, so we're not considering this fix to address
all of the complexity here, just to not make things worse.


Option 4: When you are trying to match an implementation against two signatures, you prefer the
one that, for each type parameter, if it has any `T?` instantiations, each of those type
parameters has `struct` constraints. In other words, for each place where the overriding member
has a `?`, we look for a signature that has a struct type parameter in at least the same places
as every other candidate.

Follow-on question: why not allow implementations and overrides to specify constraints? The
biggest problem is that C# does not allow you to specify certain constraints when you actually
need to in order to correctly override. VB solves this problem by allowing anything, specifically
for OHI (overriding, hiding, and interface implementation). We could also allow a *subset* of
constraints to be specified, but we would have to define what we mean by subset.

The main problem we have with (4) is we're worried that the preference rule would be confusing.
Fundamentally, allowing a bare `T?` to mean either `Nullable<T>` or nullable reference based on
context of what is defined in the base is worrying.

**Conclusion**

We like option 2. This would maintain backwards compatibility and is a
simple explanation for what `T?` means in OHI.

### Dynamic invocation and nullable warnings

Do we want to report or track nullability for values of type `dynamic`?
In some dynamic scenarios we don't know the nullability, but the simple
assignment of null to a local of dynamic type must be null.

It seems like there could be value in providing at least some null tracking.
For a method which simply returns a `dynamic?` we could produce a maybe null
dynamic value. For invocations, we could always produce a `dynamic!` that
is not maybe null. This seems like a reasonable middle ground.

**Conclusion**

Let's track nullability for `dynamic`. Tracking dynamic values seems somewhat useful. We would do
the same value-based flow analysis as we do for other types. Some scenarios with `dynamic` would
not produce warnings, but if a warning is produced it seems likely to indicate a real problem.

### Dynamic invocation of static methods with dynamic arguments

For static methods, we currently do no type checking for the dynamic
arguments, but we do check if any of the candidates are possible. Would
we provide null checking in addition to this? For all argument types, including
the dynamic arguments?

**Conclusion**

Since we already do some validation for static invocations, and we can provide
some useful static information here, it sounds good to do it if possible.

### Nullability of return type of dynamic static invocations

We currently do no checking on the return type (the return type is always
dynamic, even if all the candidates return void). Adding nullability does not
seem useful given the lack of checking that we already do.

**Conclusion**

The return type is always `dynamic!`.