Skip to content

Allow overriding members with nullable variance#34718

Merged
agocke merged 8 commits intodotnet:masterfrom
agocke:override-signature-variance-classes
May 4, 2019
Merged

Allow overriding members with nullable variance#34718
agocke merged 8 commits intodotnet:masterfrom
agocke:override-signature-variance-classes

Conversation

@agocke
Copy link
Copy Markdown
Member

@agocke agocke commented Apr 3, 2019

Implements a design change where overrides are allowed to change the
type of the member as long as there is an implicit nullable reference
conversion from the overriding type to the overridden type according to
the nullable variance rules.

Fixes #23268
Fixes #30958

@agocke agocke force-pushed the override-signature-variance-classes branch from 959c234 to 418e01a Compare April 3, 2019 20:21
@agocke agocke changed the title Allow overriding members with nullable conversions Allow overriding members with nullable covariance Apr 3, 2019
@agocke agocke marked this pull request as ready for review April 3, 2019 20:23
@agocke agocke requested a review from a team as a code owner April 3, 2019 20:23
@agocke agocke force-pushed the override-signature-variance-classes branch from 418e01a to 6c4cee4 Compare April 3, 2019 21:53
@agocke agocke changed the title Allow overriding members with nullable covariance Allow overriding members with nullable variance Apr 3, 2019
@agocke
Copy link
Copy Markdown
Member Author

agocke commented Apr 3, 2019

@dotnet/roslyn-compiler for review #Closed

@agocke
Copy link
Copy Markdown
Member Author

agocke commented Apr 4, 2019

@dotnet/roslyn-compiler ping for review #Closed

gafter
gafter previously approved these changes Apr 5, 2019
Copy link
Copy Markdown
Member

@gafter gafter left a comment

Choose a reason for hiding this comment

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

:shipit:

@agocke
Copy link
Copy Markdown
Member Author

agocke commented Apr 5, 2019

@cston Did you have any more comments? #Closed

@AlekseyTs
Copy link
Copy Markdown
Contributor

AlekseyTs commented Apr 8, 2019

        var overriddenMembers = overriddenOrHiddenMembers.OverriddenMembers;

What is the overriddenMembers here? Is this a base override, if any, or always the initial declaration of the member in the top-most base type?

It looks like https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-01-07.md suggest that we would want to check nullability against base override.


Refers to: src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs:703 in d9ddb61. [](commit_id = d9ddb61, deletion_comment = False)

@AlekseyTs
Copy link
Copy Markdown
Contributor

AlekseyTs commented Apr 8, 2019

public override System.Action<string?> Oblivious2(System.Action<string?> x) => throw null!; // warn 3 and 4 // https://github.com/dotnet/roslyn/issues/29851: Should not warn

Can we remove this comment now and resolve the issue once this PR is merged? #Closed


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:11206 in d9ddb61. [](commit_id = d9ddb61, deletion_comment = False)

Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs Apr 8, 2019

Choose a reason for hiding this comment

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

)); [](start = 131, length = 3)

)); [](start = 131, length = 3)

Consider preserving original formatting #Closed

@AlekseyTs
Copy link
Copy Markdown
Contributor

AlekseyTs commented Apr 8, 2019

Done with review pass (iteration 6) #Closed

@agocke agocke force-pushed the override-signature-variance-classes branch from 2c79032 to 2c8a461 Compare April 15, 2019 21:21
@jcouv jcouv added this to the 16.1.P3 milestone Apr 18, 2019
@AlekseyTs
Copy link
Copy Markdown
Contributor

AlekseyTs commented Apr 18, 2019

@agocke Could you please resolve merge conflicts and figure out what is wrong with CI failures? #Closed

Implements a design change where overrides are allowed to change the
type of the member as long as there is an implicit nullable reference
conversion from the overriding type to the overridden type according to
the nullable variance rules.

Fixes dotnet#23268
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

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

LGTM (iteration 5)

@agocke
Copy link
Copy Markdown
Member Author

agocke commented Apr 26, 2019

@gafter You looked at an earlier iteration but I've made substantial changes since then. Can you take another look? #Closed

@agocke
Copy link
Copy Markdown
Member Author

agocke commented Apr 29, 2019

ping @gafter #Closed

@gafter
Copy link
Copy Markdown
Member

gafter commented Apr 30, 2019

Looking #Closed

overridingMethod is null ||
compilation is null ||
((CSharpParseOptions)overridingMemberLocation.SourceTree?.Options)
?.IsFeatureEnabled(MessageID.IDS_FeatureNullableReferenceTypes) != true)
Copy link
Copy Markdown
Member

@gafter gafter Apr 30, 2019

Choose a reason for hiding this comment

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

This can be compilation.IsFeatureEnabled(MessageID.IDS_FeatureNullableReferenceTypes) since all parse trees are checked that they have the same version and features. #Resolved

if (!isValidNullableConversion(
conversions,
overridingMethod.RefKind,
overridingMethod.ReturnTypeWithAnnotations,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

overridingMethod.ReturnTypeWithAnnotations [](start = 24, length = 42)

Has one of these been alpha-renamed to match the other, e.g. in case the method is generic and returns T?

for (int i = 0; i < overriddenMethod.ParameterCount; i++)
{
var overriddenParameterType = overriddenParameters[i].TypeWithAnnotations;
var overridingParameterType = overridingParameters[i].TypeWithAnnotations;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

overridingParameters[i] [](start = 50, length = 23)

Same question: have the methods been alpha-renamed to match each other?

Copy link
Copy Markdown
Member

@gafter gafter left a comment

Choose a reason for hiding this comment

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

This needs tests showing the behavior for overriding generic methods, methods in generic classes.

@gafter gafter dismissed their stale review April 30, 2019 21:59

Needs tests for overriding generic methods with variance.


if (overriddenMember.Kind == SymbolKind.Method && (overriddenMethod = (MethodSymbol)overriddenMember).IsGenericMethod)
{
overriddenParameters = overriddenMethod.Construct(((MethodSymbol)overridingMember).TypeArgumentsWithAnnotations).Parameters;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

overriddenParameters = overriddenMethod.Construct(((MethodSymbol)overridingMember).TypeArgumentsWithAnnotations).Parameters; [](start = 32, length = 124)

It looks like @gafter is correct, there is a problem with overriding generic methods and that is because this logic is not preserved.

Copy link
Copy Markdown
Member Author

@agocke agocke May 1, 2019

Choose a reason for hiding this comment

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

As I mentioned offline, I think this logic is performed on line 955 above, but this code was using overriddenMember for checking, instead of the variable overriddenMethod, which contains the constructed member.

@agocke
Copy link
Copy Markdown
Member Author

agocke commented May 1, 2019

@gafter Anything else? #Closed


return;

bool isOrContainsErrorType(TypeSymbol typeSymbol)
Copy link
Copy Markdown
Member

@jcouv jcouv May 3, 2019

Choose a reason for hiding this comment

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

bool [](start = 12, length = 4)

nit: not related to this PR, static too? #Closed


return;

static bool isValidNullableConversion(
Copy link
Copy Markdown
Member

@jcouv jcouv May 3, 2019

Choose a reason for hiding this comment

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

isValidNullableConversion [](start = 28, length = 25)

If you don't mind, could we move this local function out one level, to avoid nested local functions? #Closed

// (17,29): warning CS8610: Nullability of reference types in type of parameter 't' doesn't match overridden member.
// public override List<T> M<T>(List<T> t) => null!;
Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOnOverride, "M").WithArguments("t").WithLocation(17, 29));
}
Copy link
Copy Markdown
Member

@jcouv jcouv May 3, 2019

Choose a reason for hiding this comment

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

} [](start = 8, length = 1)

Some test ideas:

I think we should cover some generic cases as well:

class C : Base<object_> // where object_ is object? or object! or object~
{
   override object_ M() { ... } // ditto
}

We should also cover some scenarios with multiple layers of overrides (with various combination of ?, ! and ~ for string_:

class A
{
   virtual string_ Property { get; }
}
class B : A
{
   override string_ Property { get; }
}
class C : B
{
  override string_ Property { get; }
}

Could you also add some tests verifying the behavior for callers?

new Derived().Property.ToString(); // warning depends on overriding member, not overridden member

It would also be good to cover a sublety in isValidNullableConversion: it compares ignoring all differences, except nullability.

class Base
{
  virtual object? M() ...
}
class Derived : Base
{
  override dynamic M() ...
}

@jcouv
Copy link
Copy Markdown
Member

jcouv commented May 3, 2019

public override event System.Action<string> E2; // 2

consider removing or updating the numbered comments


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:11156 in 0c532f5. [](commit_id = 0c532f5, deletion_comment = False)

@jcouv
Copy link
Copy Markdown
Member

jcouv commented May 3, 2019

public override event System.Action<string?> E1 {add {} remove{}}

Feels like there should be a mismatch warning here.
There are multiple ways to interact with an event: add, remove, invocation and copy. The copy seems dangerous in override scenarios.

using System;
public class Derived : Base {
    override event Action<string?> Event;
    public void M() {
        Action<string?> x = Event; // is Event abiding by the contract of Base or of C? Is it safe to give it a null argument?
    }
}

#Closed


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:11148 in 0c532f5. [](commit_id = 0c532f5, deletion_comment = False)

@jcouv
Copy link
Copy Markdown
Member

jcouv commented May 3, 2019

    var e2 = E2;

It would be good to verify the result type that NullableWalker gets for E2 and similar override scenarios (also for properties, method groups, etc).
You could do _ = E2 /*T:System.Action<string>*/; and call comp.VerifyTypes();.


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:11456 in 0c532f5. [](commit_id = 0c532f5, deletion_comment = False)

@AlekseyTs
Copy link
Copy Markdown
Contributor

public override event System.Action<string?> E1 {add {} remove{}}

There are multiple ways to interact with an event: add, remove, invocation and copy. The copy seems dangerous in override scenarios.

There is no concept of copy for events. Also, it looks like there is a warning for this line:

                // (18,50): warning CS8610: Nullability of reference types in type of parameter 'value' doesn't match overridden member.
                //     public override event System.Action<string?> E1 {add {} remove{}}
                Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOnOverride, "E1").WithArguments("value").WithLocation(18, 50),

In reply to: 489261259 [](ancestors = 489261259)


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:11148 in 0c532f5. [](commit_id = 0c532f5, deletion_comment = False)

@jcouv
Copy link
Copy Markdown
Member

jcouv commented May 3, 2019

public override event System.Action<string?> E1 {add {} remove{}}

There is no concept of copy for events.

I'm not sure what you're arguing (terminology or use case). Action<string> x = Event; is valid code.

it looks like there is a warning for this line

Ah, thanks.
I don't understand why that warning is produced by current implementation, then. override void Add(Action<string?>) seems a valid override for virtual void Add(Action<string>).


In reply to: 489263473 [](ancestors = 489263473,489261259)


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:11148 in 0c532f5. [](commit_id = 0c532f5, deletion_comment = False)

Copy link
Copy Markdown
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

Done with review pass (iteration 6)

@jcouv
Copy link
Copy Markdown
Member

jcouv commented May 3, 2019

public override event System.Action<string?> E1 {add {} remove{}}

Resolved offline.
I now understand why the warning is produced on the add method :-) Thanks!


In reply to: 489266871 [](ancestors = 489266871,489263473,489261259)


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:11148 in 0c532f5. [](commit_id = 0c532f5, deletion_comment = False)

@agocke
Copy link
Copy Markdown
Member Author

agocke commented May 4, 2019

@jcouv Added tests for overriding a constructed type. The test for dynamic/object? is superseded by the test we have in the dynamic tests for object/dynamic. The multiple levels of overrides I have additional tests for.

Copy link
Copy Markdown
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (iteration 8)

@agocke agocke merged commit 472276a into dotnet:master May 4, 2019
@agocke agocke deleted the override-signature-variance-classes branch May 4, 2019 07:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Contravariant parameter types with respect to nullable reference types Covariant return types with respect to nullable reference types

7 participants