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

Nullable analysis of yield return #31072

Merged
merged 9 commits into from
Nov 13, 2018
Merged

Conversation

jcouv
Copy link
Member

@jcouv jcouv commented Nov 9, 2018

Implements NullableWalker.VisitYieldReturnStatement and use TSWA for MethodSymbol.IteratorElementType (and machinery to produce it).

Fixes #23701

@jcouv jcouv added this to the 16.0.P2 milestone Nov 9, 2018
@jcouv jcouv self-assigned this Nov 9, 2018
@jcouv jcouv requested a review from a team as a code owner November 9, 2018 19:42
}
}

#nullable disable
Copy link
Member Author

@jcouv jcouv Nov 9, 2018

Choose a reason for hiding this comment

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

📝 This code was moved from the async-streams tests, but I added a #nullable disable here to avoid nullable warnings in types that we only privately depend on. #Resolved

@agocke
Copy link
Member

agocke commented Nov 9, 2018

@jcouv I have a dumb question. In LDM we've basically decided that we're going to treat nullable as though it were an analyzer or layer on top of the type system. Seeing every little piece of code flowing nullable in Binding seems error prone and difficult to follow.

Is it possible to move this out of Binding entirely and just rely on the nullable walker pass to create an augmented tree?

Edit: never mind this is a bad idea. We really need to keep track of nullability at the symbol level, but there's no tree to rewrite (except for creating an entirely new symbol table).

This is unfortunate. #Resolved

agocke
agocke previously approved these changes Nov 9, 2018
Copy link
Member

@agocke agocke left a comment

Choose a reason for hiding this comment

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

LGTM except for missing test

{
_iteratorElementType = TypeMap.SubstituteType(BaseMethod.IteratorElementType).TypeSymbol;
if (BaseMethod.IteratorElementType.IsNull)
Copy link
Member

@agocke agocke Nov 9, 2018

Choose a reason for hiding this comment

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

Does TypeMap.SubstituteType not take a TypeSymbolWithAnnotations overload? #Resolved

@@ -347,7 +353,7 @@ await foreach (var s in M())
ref struct S
{
}";
var comp = CreateCompilationWithTasksExtensions(new[] { source, s_common }, options: TestOptions.DebugExe);
var comp = CreateCompilationWithTasksExtensions(new[] { source, AsyncStreamsTypes }, options: TestOptions.DebugExe);
Copy link
Member

@agocke agocke Nov 9, 2018

Choose a reason for hiding this comment

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

All of these could use the helper I created CreateCompilationWithAsyncIterator. Up to you, though. #Resolved

Diagnostic(ErrorCode.WRN_NullReferenceReturn, "null").WithLocation(25, 26)
);
}

[Fact]
Copy link
Member

@agocke agocke Nov 9, 2018

Choose a reason for hiding this comment

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

I'd expect a yield break positive test just to verify it generates no warnings. #Resolved

@jcouv
Copy link
Member Author

jcouv commented Nov 9, 2018

@agocke Thanks
I rebased (to solve the CI build issue) and addressed your feedback. #Resolved

@jcouv
Copy link
Member Author

jcouv commented Nov 9, 2018

@agocke From discussion with Chuck, I think your original point was valid. Maybe I don't need to make IteratorElementType a TSWA.
Initially, I thought it was exposed by a public API, so I went down that road. I'll undo and explore your proposal. #Resolved

@@ -946,7 +946,7 @@ protected override BoundNode VisitReturnStatementNoAdjust(BoundReturnStatement n
Debug.Assert(!IsConditionalState);

BoundExpression expr = node.ExpressionOpt;
if (expr == null)
if (expr == null || expr.HasErrors)
Copy link
Member

@cston cston Nov 9, 2018

Choose a reason for hiding this comment

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

expr.HasErrors [](start = 32, length = 14)

expr.HasErrors [](start = 32, length = 14)

We should report nullable issues within expr, even if the outer BoundExpression has errors. #Resolved

return method.IsGenericTaskReturningAsync(compilation) ?
((NamedTypeSymbol)returnType.TypeSymbol).TypeArgumentsNoUseSiteDiagnostics.Single() :
returnType;
((NamedTypeSymbol)returnType.TypeSymbol).TypeArgumentsNoUseSiteDiagnostics.Single() :
Copy link
Member

@cston cston Nov 9, 2018

Choose a reason for hiding this comment

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

           [](start = 0, length = 15)
           [](start = 0, length = 15)

Indenting looks incorrect. #Resolved

public override BoundNode VisitYieldReturnStatement(BoundYieldReturnStatement node)
{
BoundExpression expr = node.Expression;
if (expr == null || expr.HasErrors)
Copy link
Member

@cston cston Nov 9, 2018

Choose a reason for hiding this comment

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

expr.HasErrors [](start = 32, length = 14)

expr.HasErrors [](start = 32, length = 14)

Report warnings within expr even if expr.HasErrors. #Resolved

@jcouv
Copy link
Member Author

jcouv commented Nov 10, 2018

Solution without changes to binding works well. Thanks!
Rebased again to deal with latest conflicts. #Resolved

@jcouv
Copy link
Member Author

jcouv commented Nov 10, 2018

@jaredpar Please approve for preview2. Thanks

if (expr == null)
{
return null;
}
Copy link
Member

@cston cston Nov 12, 2018

Choose a reason for hiding this comment

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

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

Please add a test where expr.HasErrors and also results in nullable warnings. #Resolved

Copy link
Member Author

@jcouv jcouv Nov 12, 2018

Choose a reason for hiding this comment

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

Sorry, it looks like I had forgotten to push the commit updating tests. There are already tests for this.
Please take another look.

I much prefer the behavior that avoids cascading diagnostics. We should discuss more. I filed a follow-up issue: #31121 #Resolved

return ((NamedTypeSymbol)returnType).TypeArgumentsNoUseSiteDiagnostics[0];
}

if (returnType.OriginalDefinition == compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T))
Copy link
Member

@333fred 333fred Nov 12, 2018

Choose a reason for hiding this comment

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

System_Collections_Generic_IAsyncEnumerable_T [](start = 96, length = 45)

More general question: do we want to make this iterator type a SpecialType, like the other iterators? And does async enumerable have an equivalent enumerator type we need to worry about here? #Resolved

Copy link
Member Author

@jcouv jcouv Nov 12, 2018

Choose a reason for hiding this comment

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

From my understanding, "special type" is like a "well-know type", except that it must live in the same assembly as object (ie. in the core library).
We haven't added a "special type" in a long time (the entire GitHub history).

Yes, there is a corresponding IAsyncEnumerator<T> type. Async-iterator methods can't yet return such enumerators yet. That will be in a PR later this week (#31114). I added a note there to update this code too. Good catch


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

return ((NamedTypeSymbol)returnType).TypeArgumentsNoUseSiteDiagnostics[0];
}

Debug.Assert(false);
Copy link
Member

@333fred 333fred Nov 12, 2018

Choose a reason for hiding this comment

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

Consider throwing ExceptionUtilities.Unreachable? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

Had to adjust this further for regressions caught by CI. Now return default;. Sorry for the churn.
The scenario is that you put a yield return in a regular method.


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

VisitOptionalImplicitConversion(expr, getIteratorElementType(method.ReturnType.TypeSymbol), useLegacyWarnings: false, AssignmentKind.Return);
return null;

TypeSymbolWithAnnotations getIteratorElementType(TypeSymbol returnType)
Copy link
Member

Choose a reason for hiding this comment

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

Looks like we have a helper GetIteratorElementTypeFromReturnType that already exists and is almost identical to this function. Can we share?

Copy link
Member Author

@jcouv jcouv Nov 12, 2018

Choose a reason for hiding this comment

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

I considered and tried it, but the code ended up worse in my opinion.
To make a static method, we need to pass two extra parameters (binder and compilation). Trying to only use the compilation and not the binder is possible but was trouble.
Then the two methods also differ in their handling of errors. The original method uses a GetSpecialType that requires a diagnostic bag (I could pass a dummy one I suppose).

Let me know what you think.

Copy link
Member Author

Choose a reason for hiding this comment

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

FWIW, here's what that looks like:
jcouv@e81acf5

@agocke agocke dismissed their stale review November 12, 2018 22:22

Obsolete review

Copy link
Member

@agocke agocke left a comment

Choose a reason for hiding this comment

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

LGTM

{
if (refKind == RefKind.None && returnType.Kind == SymbolKind.NamedType)
{
switch (returnType.OriginalDefinition.SpecialType)
{
case SpecialType.System_Collections_IEnumerable:
case SpecialType.System_Collections_IEnumerator:
return GetSpecialType(SpecialType.System_Object, diagnostics, errorLocationNode);
var objectType = diagnostics is null ? compilation.GetSpecialType(SpecialType.System_Object) : binder.GetSpecialType(SpecialType.System_Object, diagnostics, errorLocationNode);
Copy link
Member

Choose a reason for hiding this comment

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

Maybe split the ternary over three lines?

@jcouv
Copy link
Member Author

jcouv commented Nov 13, 2018

@jaredpar Please approve for preview2. Thanks

@jaredpar
Copy link
Member

Approved

@jcouv jcouv merged commit ff8e1c7 into dotnet:dev16.0-preview2 Nov 13, 2018
@jcouv jcouv deleted the yield-return branch November 13, 2018 18:03
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.

5 participants