Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track additional state for "maybe null even if not nullable" #39778

Merged
merged 14 commits into from
Nov 21, 2019

Conversation

cston
Copy link
Member

@cston cston commented Nov 12, 2019

Track additional flow state to improve analysis of [MaybeNull]T values.

The change includes:

  • Additional flow state NullableFlowState.MaybeDefault for values of type parameters that are not constrained to be not nullable (types that cannot be annotated).
  • [AllowNull] and [return: MaybeNull] on containing method signature now affect analysis within the method body.
  • No W-warnings for local assignment or explicit cast for type parameter types that cannot be annotated.
  • No warnings for expressions (of type parameter types that cannot be annotated) that may produce a null value. Instead, the value is simply tracked as NullableFlowState.MaybeDefault.

Examples:

[return: MaybeNull] static T Default<T>()
{
    return default(T); // ok
}

static T F<T>(IEnumerable<T> e)
{
    T t = e.FirstOrDefault(); // ok
    return t;                 // warning: maybe null
}

See dotnet/csharplang#2946.

Fixes #38638.
Fixes #37362.

@cston cston force-pushed the MaybeNullT branch 7 times, most recently from d045f46 to 9588a81 Compare November 14, 2019 22:07
@cston cston marked this pull request as ready for review November 14, 2019 22:15
@cston cston requested a review from a team as a code owner November 14, 2019 22:15
@cston
Copy link
Member Author

cston commented Nov 15, 2019

@dotnet/roslyn-compiler please review.

@RikkiGibson
Copy link
Contributor

RikkiGibson commented Nov 15, 2019

}

Does this PR partially enable #36039? #Closed


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

@RikkiGibson
Copy link
Contributor

RikkiGibson commented Nov 15, 2019

                Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterConstraint, "M1<object?>").WithArguments("Test1<object>.M1<S>(S)", "object", "S", "object?").WithLocation(7, 9),

Please delete the comments in source such as // 2, // 5 for the diagnostics which are no longer produced. (no need to renumber the existing ones IMO) #Closed


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:78879 in b027ec9. [](commit_id = b027ec9, deletion_comment = True)

@RikkiGibson
Copy link
Contributor

RikkiGibson commented Nov 15, 2019

    T1 t1 = (T1)NullableObject(); // warn: T1 may be non-null

This comment doesn't seem accurate any more and probably others in this test, please update them #Closed


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:82264 in b027ec9. [](commit_id = b027ec9, deletion_comment = True)

@RikkiGibson
Copy link
Contributor

RikkiGibson commented Nov 15, 2019

static U F17<T, U>(T t) where U : T => (U)t;        // W on cast

W on return? #Closed


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:82595 in b027ec9. [](commit_id = b027ec9, deletion_comment = True)

@RikkiGibson
Copy link
Contributor

RikkiGibson commented Nov 15, 2019

static U F20<T, U>(T t) where U : T, new() => (U)t; // W on cast

W on return? #Closed


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:82598 in b027ec9. [](commit_id = b027ec9, deletion_comment = True)

@RikkiGibson
Copy link
Contributor

RikkiGibson commented Nov 15, 2019

static U F21<T, U>(T t) => (U)(object)t;            // W on cast, W on cast

W on cast, W on return? #Closed


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:82599 in b027ec9. [](commit_id = b027ec9, deletion_comment = True)

@RikkiGibson
Copy link
Contributor

RikkiGibson commented Nov 15, 2019

    T x1 = default; // 2

Please delete #Closed


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:82810 in b027ec9. [](commit_id = b027ec9, deletion_comment = True)

@RikkiGibson
Copy link
Contributor

RikkiGibson commented Nov 15, 2019

    y = default; // 3

Please delete #Closed


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:87829 in b027ec9. [](commit_id = b027ec9, deletion_comment = True)

@RikkiGibson
Copy link
Contributor

RikkiGibson commented Nov 15, 2019

    y = x; // 1

Please delete the markers for diagnostics that are no longer produced #Closed


Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:90223 in b027ec9. [](commit_id = b027ec9, deletion_comment = True)

@RikkiGibson
Copy link
Contributor

RikkiGibson commented Nov 15, 2019

        // PROTOTYPE: Should not report warning for F02 or F10. (We should treat [MaybeNull]T as T? when T is not nullable.)

Is this going to happen for all reference types, not just generics constrained to class? #Closed


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

@RikkiGibson
Copy link
Contributor

RikkiGibson commented Nov 15, 2019

[MaybeNull]T F = default!;

Is this ! necessary? I thought we had some nice behavior fall out of MaybeNull returns, where we don't warn if the return value has a maybe-null flow state. #Closed


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

@RikkiGibson
Copy link
Contributor

[MaybeNull]T F = default!;

Obviously it's not the same thing here, but I wondered if it might fall out in a similar way.


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


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

@RikkiGibson
Copy link
Contributor

RikkiGibson commented Nov 15, 2019

    return Identity(F<T>()); // 1

Does this Identity function provide the expected flow state for other combinations of constraints? e.g. M<T> where T : one of unconstrained, class, class?, struct. #Closed


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

@RikkiGibson
Copy link
Contributor

[MaybeNull]T F = default!;

Seems like the answer is yes, it's needed (based on a later test)


In reply to: 554561397 [](ancestors = 554561397,554561256)


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

@RikkiGibson
Copy link
Contributor

RikkiGibson commented Nov 15, 2019

static T F1<T>(T t) => t ??= default(T);

It feels like we should also test ?? default. I would expect such a test to warn in the same places. #Closed


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

@@ -1043,46 +1041,70 @@ private enum AssignmentKind
Conversion conversion = default,
Location location = null)
{
// PROTOTYPE: Fix callers so we don't have cases where valueType and targetType differ.
Copy link
Contributor

@RikkiGibson RikkiGibson Nov 15, 2019

Choose a reason for hiding this comment

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

In which scenarios do they differ? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

The types may differ in binary logical operators and deconstruction. See #39867.


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

Copy link
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.

I think there are three substantive comments remaining:

  1. Discards should allow null without warning. Possibly just file a separate issue for that.
  2. TypeWithState.Create should handle a type parameter constrained by int?, which can occur by substitution. Currently it is treated the same as an unconstrained type parameter, which doesn't know if it can contain null or not. Such type parameters do have null in their domain.
  3. SetStateAndTrackForFinally should use Join when incorporating new state information.

}
finally
{
F0(t2); // 1
Copy link
Contributor

@RikkiGibson RikkiGibson Nov 19, 2019

Choose a reason for hiding this comment

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

I'm a little confused by this-- is it expected that we could throw out of the 'try' block at any point and then run the 'finally' block, so we have to be as pessimistic as possible here? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, we assume an exception could have been thrown between the assignments above, although perhaps we could do better for those simple assignments.


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

verify(exprs[0], PublicNullableAnnotation.NotAnnotated, PublicNullableFlowState.MaybeNull);
verify(exprs[1], PublicNullableAnnotation.Annotated, PublicNullableFlowState.MaybeNull);

void verify(DefaultExpressionSyntax expr, PublicNullableAnnotation expectedAnnotation, PublicNullableFlowState expectedState)
Copy link
Contributor

@RikkiGibson RikkiGibson Nov 19, 2019

Choose a reason for hiding this comment

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

consider putting expected values first or labeling in order to match the conventions of the xunit Assert methods. #ByDesign

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll leave as is since this matches similar local functions in this class.


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

@cston
Copy link
Member Author

cston commented Nov 19, 2019

    return r2; // 2

Ideally we wouldn't warn for this case but we do not handle this case currently. I'll add a comment.


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

@RikkiGibson
Copy link
Contributor

    return Identity(F<T>()); // 1

Just curious, was this ever addressed by additional tests or comments elsewhere?


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


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

@RikkiGibson
Copy link
Contributor

static T F1<T>(T t) => t ??= default(T);

It doesn't look like any test was added in response to this, did I miss it by accident, or maybe miss a relevant existing test?


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


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

@cston
Copy link
Member Author

cston commented Nov 20, 2019

static T F1<T>(T t) => t ??= default(T);

Yes, F0 in MaybeNullT_16.


In reply to: 555771498 [](ancestors = 555771498,554565234)


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

@cston
Copy link
Member Author

cston commented Nov 20, 2019

}

Yes, for [AllowNull] and [MaybeNull].


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


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

@cston
Copy link
Member Author

cston commented Nov 20, 2019

@RikkiGibson, @333fred, @gafter, all feedback has been addressed, thanks.

Copy link
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:

using System.Diagnostics.CodeAnalysis;
class Program
{
static T F0<T>(T t) => t ??= default;
Copy link
Contributor

@RikkiGibson RikkiGibson Nov 20, 2019

Choose a reason for hiding this comment

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

This appears to still be using ??= #Closed

Copy link
Contributor

@RikkiGibson RikkiGibson left a comment

Choose a reason for hiding this comment

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

LGTM modulo a minor bit of test coverage

@333fred
Copy link
Member

333fred commented Nov 20, 2019

static T F1(T t)

Nit: unused parameter #Resolved


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

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

LGTM (commit 13)

/// <summary>
/// Maybe null (type may be not nullable).
/// </summary>
MaybeDefault = 0b11,
Copy link
Member

@jaredpar jaredpar Nov 20, 2019

Choose a reason for hiding this comment

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

Why 0b11 and not 0b10? #ByDesign

Copy link
Member Author

Choose a reason for hiding this comment

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

So we can (continue to) use bit-wise | and & for Join() and Meet().


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

@jaredpar
Copy link
Member

jaredpar commented Nov 20, 2019

    public void MaybeNullT_09()

Is there a version of this test where the method is invoked, stored into a local and then the local is returned? Essentially verifying that the state transfers through locals as well. #Resolved


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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants