-
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
Typeless expressions should contribute nullability to lambda return #47581
Conversation
b4be2e5
to
3de7f21
Compare
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.
Done with review pass (iteration 1).
@@ -6477,12 +6473,10 @@ private bool HasTopLevelNullabilityConversion(TypeWithAnnotations source, TypeWi | |||
resultState = NullableFlowState.NotNull; | |||
break; | |||
|
|||
case ConversionKind.ObjectCreation: | |||
case ConversionKind.SwitchExpression: |
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.
Is there any significance to the reordering of these case labels? #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.
SetResultType(node, TypeWithState.Create(expr.Type, NullableFlowState.NotNull)); | ||
// This method is only involved in method inference with unbound lambdas. | ||
// The diagnostics on arguments are reported by VisitObjectCreationExpression. | ||
SetResultType(node, TypeWithState.Create(null, NullableFlowState.NotNull)); |
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.
Do we need to make a pass over usages of ResultType to determine if we might try to access a null Type off of it? #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.
TypeWithState
can handle null type well (TypeSymbol? Type
already).
But TypeWithAnnotations
is a mixed bag. Will see if I can annotate it.
In reply to: 486453187 [](ancestors = 486453187)
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 tried annotating TypeWithAnnotations
(and in particular its .Type
), but that cascades to a lot of binding code. That will be for a separate PR ;-)
So instead, I reviewed the uses of TypeWithAnnotations.Type
in NullableWalker
and also traced the uses of TypeWithAnnotations
coming from _visitResult
. Most of those types come straight out of symbols (ie. have types) and the rest handle or check for null
.
In reply to: 486561434 [](ancestors = 486561434,486453187)
@@ -136114,27 +136536,31 @@ static void F<T>(Func<T> f) | |||
{ | |||
F(() => | |||
{ | |||
if (b) return default; // 1 | |||
if (b) return default; |
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.
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.
Indeed this resolves the first issue (since it involves unbound lambdas during method type inference).
But it does not resolve the second (since it doesn't involve lambdas). Solving that second issue needs to go MethodTypeInferer
. The method IsReallyAType
used in MakeExplicitParameterTypeInferences
discards typeless symbols. (note that there is a bug whereby default
is not treated as typeless during re-inference, so we hit this bug for null
but not for default
)
In reply to: 486460034 [](ancestors = 486460034)
SetResultType(node, TypeWithState.Create(expr.Type, NullableFlowState.NotNull)); | ||
// This method is only involved in method inference with unbound lambdas. | ||
// The diagnostics on arguments are reported by VisitObjectCreationExpression. | ||
SetResultType(node, TypeWithState.Create(null, NullableFlowState.NotNull)); |
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.
TypeWithState.Create(null, NullableFlowState.NotNull) [](start = 32, length = 53)
Does this create a TypeWithState
with Type == null
and State == NotNull
? Should we pass defaultState: MaybeNull
instead? #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.
Yes, that creates a TypeWithState
with no type but NoteNull
state.
Here we want to pass NotNull
because we know the value is not null
(object creation always results in an instance) even if we don't know the type.
In reply to: 486468093 [](ancestors = 486468093)
@@ -14,11 +15,17 @@ internal static class BestTypeInferrer | |||
{ | |||
public static NullableAnnotation GetNullableAnnotation(ArrayBuilder<TypeWithAnnotations> types) | |||
{ | |||
#if DEBUG | |||
var example = types.Where(t => t.HasType).FirstOrDefault(); |
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.
types.Where(t => t.HasType).FirstOrDefault() [](start = 26, length = 44)
Can use types.FirstOrDefault(t => t.HasType)
. #Pending
Debug.Assert(type.HasType); | ||
Debug.Assert(type.Equals(types[0], TypeCompareKind.AllIgnoreOptions)); | ||
#if DEBUG | ||
Debug.Assert(!type.HasType || (example.HasType && type.Equals(example, TypeCompareKind.AllIgnoreOptions))); |
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.
example.HasType && [](start = 47, length = 19)
example.HasType
is not needed. #Pending
Visit(expr); | ||
SetResultType(node, TypeWithState.Create(expr.Type, NullableFlowState.NotNull)); | ||
// This method is only involved in method inference with unbound lambdas. | ||
// The diagnostics on arguments are reported by VisitObjectCreationExpression. |
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 comment worth a few days reading the nullable walker
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.
Your comment made my day! :-)
map.GetOrAdd("""", _ => null); // 2 | ||
|
||
map.GetOrAdd("""", _ => { if (b) return default; return """"; }); // 3 | ||
map.GetOrAdd("""", _ => { if (b) return null; return """"; }); // 4 |
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.
map.GetOrAdd("""", _ => { if (b) return null; return """"; }); // 4 [](start = 8, length = 67)
Consider also testing with _ => { if (b) return ""; 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.
Minor comments only.
* upstream/master: (114 commits) Remove langversion restriction for source generators. (dotnet#47714) Adjust disambiguation rules for type pattern in a switch expression. (dotnet#47756) Delete decommissioned benchview tool scripts (dotnet#47752) Emit conversions between native integers and pointers directly (dotnet#47708) Typeless expressions should contribute nullability to lambda return (dotnet#47581) Use a distinct diagnostic ID when an exhaustiveness report uses an unnamed enum value. (dotnet#47693) [master] Update dependencies from dotnet/arcade (dotnet#46586) Change `Location` of record's primary constructor to point to record's identifier. (dotnet#47715) Add public API test for extended partial methods (dotnet#47727) Rename in CheckValidNullableMethodOverride (dotnet#47718) Update docs Add more doc comments Add comments and doc comments for ExternalErrorDiagnosticUpdateSource Add documentation remarks for syntax kinds (dotnet#43646) Disable TestCancellation (dotnet#47725) Classify function pointer punctuation (dotnet#47668) Disable flaky optprof test Handle NotNullIfNotNull in delegate creation and overrides (dotnet#47572) Adjust QuickInfo on record BaseType syntax (dotnet#47656) Don't exclude events for NameOf context ...
Fixes #46954
Fixes #44339
NullableWalker assumed that we would not encounter unconverted conditional or switch expressions. This resulted in a crash because such expressions can lack type.
We can encounter such expressions when those expressions are target-typed and we're in a lambda while trying to figure out the return type of the lambda (which will then become the target-type).
So we need to analyze such typeless expressions before they become target-typed (switch, conditional, null, default, lambdas, ...). They should contribute nullability information, but no type (so we should avoid giving them an error type for this purpose).