Skip to content

Latest commit

 

History

History
106 lines (74 loc) · 4.29 KB

LDM-2019-04-03.md

File metadata and controls

106 lines (74 loc) · 4.29 KB

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

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!.