-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Conversation
} | ||
} | ||
|
||
#nullable disable |
There was a problem hiding this comment.
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
@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 |
There was a problem hiding this 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) |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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
@agocke Thanks |
@agocke From discussion with Chuck, I think your original point was valid. Maybe I don't need to make |
@@ -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) |
There was a problem hiding this comment.
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() : |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
Solution without changes to binding works well. Thanks! |
@jaredpar Please approve for preview2. Thanks |
if (expr == null) | ||
{ | ||
return null; | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this 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); |
There was a problem hiding this comment.
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?
@jaredpar Please approve for preview2. Thanks |
Approved |
Implements
NullableWalker.VisitYieldReturnStatement
and use TSWA forMethodSymbol.IteratorElementType
(and machinery to produce it).Fixes #23701