Add actual nullable analysis support for with(...) elements.#81978
Conversation
| """; | ||
|
|
||
| // We're missing a diagnostic for the `where T : notnull` constraint on the Create method | ||
| // Tracked by https://github.com/dotnet/roslyn/issues/68786 |
There was a problem hiding this comment.
yaay. one bug down.
| """; | ||
|
|
||
| // We're missing a diagnostic for the `where T : notnull` constraint on the Create method | ||
| // Tracked by https://github.com/dotnet/roslyn/issues/68786 |
| expectedOutput: IncludeExpectedOutput(""" | ||
| 42 | ||
| 0, 1 | ||
| """)).VerifyDiagnostics().VerifyIL("Program.<Main>d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """ |
There was a problem hiding this comment.
i can remove the IL if desired. but i thought it usefful to show that everything is happening correctly.
There was a problem hiding this comment.
Let's assert runtime async code instead. Much shorter and easier to verify.
| public void ConstructorNonNullParameterPassedNull_Inference1() | ||
| { | ||
| // PROTOTYPE: Currently, inference on Goo is producing `Goo<string>` even though it should be `Goo<string!>`. | ||
| // This is problematic as it then means T is oblivious and we don't properly report a warning on 'null'. |
There was a problem hiding this comment.
Think we can add MemberNotNull tests for collection builders; this would be for another static member on the builder type. Should also be able to test DoesNotReturn and DoesNotReturnIf.
There was a problem hiding this comment.
Note: membernotnull doesn't work. i added a prototype comment. However, this is a limtation that existed prior to my change. It's also very esoteric. So i don't think this shuold be blocking, and we can track this with an issue. LMK if that's sufficient for you @333fred .
| expectedOutput: IncludeExpectedOutput(""" | ||
| 42 | ||
| 0, 1 | ||
| """)).VerifyDiagnostics().VerifyIL("Program.<Main>d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """ |
There was a problem hiding this comment.
Let's assert runtime async code instead. Much shorter and easier to verify.
| // take the final collection type and use its type arguments to re-construct the creation method. | ||
|
|
||
| var allTypeArguments = ((NamedTypeSymbol)collectionFinalType).GetAllTypeArgumentsNoUseSiteDiagnostics(); | ||
| Debug.Assert(allTypeArguments.Length == call.Method.Arity, "Guaranteed by GetCollectionBuilderMethods"); |
There was a problem hiding this comment.
Are there tests of the form:
// Collection builder method
public static MyCollection<TList> Create<TList, TOther>(TOther arg, ReadOnlySpan<TList> elements) { ... }
MyCollection<int> m = [with(1), 2];To verify that such cases don't go through here in error recovery modes?
|
Runtime async done in: c2c780c. Much nicer. |
|
@333fred for another pass. |
|
@RikkiGibson ptal :) |
| // Walk into the arguments of the object creation, passing in 'delayCompletionForTargetMember: true' | ||
| // so that we reprocess the nullability of the arguments when we have the target-type for the | ||
| // collection expression. | ||
| var (_, argumentResults, _, completion) = this.VisitArguments( |
There was a problem hiding this comment.
@jcouv I think your change to introduce a named return type for some of these may cause conflicts and/or build failures when merging the branches. I think they will be simple to resolve, just, thought I would give a heads up.
RikkiGibson
left a comment
There was a problem hiding this comment.
LGTM, comments are not blocking.
| var allTypeArguments = ((NamedTypeSymbol)collectionFinalType).GetAllTypeArgumentsNoUseSiteDiagnostics(); | ||
| Debug.Assert(allTypeArguments.Length == call.Method.Arity, "Guaranteed by GetCollectionBuilderMethods"); | ||
|
|
||
| var constructed = call.Method.Arity == 0 ? call.Method : call.Method.ConstructedFrom.Construct(allTypeArguments); |
There was a problem hiding this comment.
I am wondering if there is an impact to using 'ConstructedFrom' versus 'OriginalDefinition' here.
There was a problem hiding this comment.
The difference is in whether the containing type is generic. I'd expect OriginalDefinition to return the original method in the original type, while ConstructedFrom returns the original method in the constructed type.
There was a problem hiding this comment.
Correct.
Because the containing type must be a normal-non-generic type, this ends up not mattering (since .OriginalDef and .ConstructedFrom will be the same.
But, conceptually .ConstructedFrom makes the most sense here as we would not be reinferring the type args on the containing type, just reconstructing the method again, off of whatever type it came from.
So going to leave this as is.
| // of the arguments passed into them. The other kinds are: | ||
| // | ||
| // 1. Arrays/Spans. These can't have CollectionCreation expressions at all. | ||
| // 2. Array interfaces (e.g. IList<T>). These only have two supported constructors that are called |
There was a problem hiding this comment.
It might be good to translate this list into assertions, so that when conditions change in the future (new collection kinds, etc.), we could find out sooner that a change is also needed here.
| { | ||
| var count = 0; | ||
| for (var current = symbol; current is not null; current = current.ContainingType) | ||
| count += current.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Length; |
There was a problem hiding this comment.
nit: Arity? This isn't blocking.
There was a problem hiding this comment.
i could switch it to that... but this feels more clearly correct, as it is preallocating the size for exactly what we know we will be adding.
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| var v = true ? [with(""), null] : new MyCollection<string>(""); |
There was a problem hiding this comment.
Do we have a case like M(true ? [with(null), ""] : [with(""), ""]), with void M<T>(MyCollection<T> arg)? e.g. where nullability is "pushed down" to the other branch and we show there are no warnings (both paths are inferred as nullable). I am assuming we do.
There was a problem hiding this comment.
i will add teest in followup. In this case, because the elements are all non-null, i would expect this to be inferred as string and the with(null) wouold warn.
It was part of the design that the with(...) call itself doesn't contribute to inference. but we can def have a few variants on this to show what's going on. will incclude in followup PR.
| } | ||
| """; | ||
|
|
||
| // PROTOTYPE: MemberNotNull analysis does not flow across calls to collection builders yet. This is probably |
There was a problem hiding this comment.
It might be good to ensure that a MemberNotNull on a create method, doesn't update a member of the collection type.
It seems also reasonable to test the analogous cases with constructors, if the attribute can be applied to those.
There was a problem hiding this comment.
It seems also reasonable to test the analogous cases with constructors, if the attribute can be applied to those.
It can't be applied to constructors. Only methods and properties.
There was a problem hiding this comment.
Yup. intentional that this was only for builders, as it's not legal on constructors (unlike many of the other attributes).
| { | ||
| string sourceA = """ | ||
| var source = """ | ||
| #nullable enable |
There was a problem hiding this comment.
Do we have nullability tests verifying the symbols post nullability analysis?
I'd expect GetSymbolInfo on with to reflect the re-inferred symbols.
There was a problem hiding this comment.
I will add in followup PR (i have two coming) :)
48270ee
into
dotnet:features/collection-expression-arguments
Fixes #72142
Relates to test plan #80613