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

Collection expressions: nullable analysis of spread element expression #74686

Merged
merged 11 commits into from
Oct 18, 2024

Conversation

cston
Copy link
Member

@cston cston commented Aug 8, 2024

From #68786:

  • Spread analysis

Fixes #74667

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Aug 8, 2024
@cston cston force-pushed the 74667 branch 4 times, most recently from 5eb7963 to 9dc9b7f Compare August 8, 2024 22:12
@cston cston marked this pull request as ready for review August 8, 2024 23:03
@cston cston requested a review from a team as a code owner August 8, 2024 23:03
@cston cston requested a review from a team as a code owner August 9, 2024 02:28
@cston
Copy link
Member Author

cston commented Aug 9, 2024

/azp run roslyn-CI

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@cston cston requested a review from a team August 10, 2024 01:14
@cston
Copy link
Member Author

cston commented Aug 20, 2024

@dotnet/roslyn-compiler, please review.

@RikkiGibson RikkiGibson self-assigned this Aug 20, 2024
return base.VisitCollectionExpression(node);
// See NullableWalker.VisitCollectionExpression.getCollectionDetails() which
// does not have an element type for the ImplementsIEnumerable case.
var hasElementType = node.CollectionTypeKind is not (CollectionExpressionTypeKind.None or CollectionExpressionTypeKind.ImplementsIEnumerable);
Copy link
Contributor

@RikkiGibson RikkiGibson Aug 21, 2024

Choose a reason for hiding this comment

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

That would be the non-generic IEnumerable case, right? So that's like converting an object~ element type to the target element type, there's no nullability check that needs to be done there? #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.

CollectionExpressionTypeKind.ImplementsIEnumerable represents any collection initializer type where the type implements IEnumerable and elements are added through Add().

This PR does not handle nullability analysis for those cases. There is a separate item in #68786.

  • Checking conversion of elements to iteration type for collection initializer types

}
if (hasElementType)
{
Visit(((BoundExpressionStatement?)spread.IteratorBody)?.Expression);
Copy link
Contributor

@RikkiGibson RikkiGibson Aug 21, 2024

Choose a reason for hiding this comment

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

Do we actually need to dig into the expression here, instead of just visiting the statement, for the verifier to pick it up? #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.

NullableWalker only visits the expression within the BoundExpressionStatement of spread.IteratorBody. (The containing BoundExpressionStatement is only there to allow sharing code in binding between foreach and .. since the foreach infrastructure expects the body to be a statement.)

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, sounds like if we visited the statement here, then we would fail verification because NullableWalker did not also visit that statement.

var itemResult = spread.EnumeratorInfoOpt == null ? default : _visitResult;
var iteratorBody = ((BoundExpressionStatement)spread.IteratorBody).Expression;
AddPlaceholderReplacement(spread.ElementPlaceholder, expression: null, itemResult);
var completion = VisitOptionalImplicitConversion(iteratorBody, elementType,
Copy link
Contributor

@RikkiGibson RikkiGibson Aug 21, 2024

Choose a reason for hiding this comment

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

It looks like we are assuming the iteratorBody is something that would be convertible to the elementType? But I thought it could represent a call to a void-returning Add method, for example. #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.

The Add() cases are not handled in this PR. For those cases, elementType.HasType == false so we shouldn't reach this code path.

Copy link
Contributor

@RikkiGibson RikkiGibson Oct 18, 2024

Choose a reason for hiding this comment

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

I don't find the connection between elementType.HasType and the shape of the spread IteratorBody straightforward to follow. It seems like there are several places where we dig into the IteratorBody and expect to get an expression which meets certain assumptions. Perhaps it would be helpful to add some extension(s) to BoundCollectionExpressionSpreadElement which implies and verifies the assumption the developer is making.

Also, it feels like using a name like targetElementType instead of elementType would make this code a little easier to follow, to be clear we are not talking about the element type of the spread operand itself.

That said, none of the above changes need to happen in this PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks. I've renamed elementType to targetElementType. We can consider adding methods to BoundCollectionExpressionSpreadElement as needed in separate PRs.

var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics();
comp.VerifyEmitDiagnostics(
// (10,44): warning CS8619: Nullability of reference types in value of type 'IEnumerable<string?>' doesn't match target type 'IEnumerable<object>'.
Copy link
Contributor

Choose a reason for hiding this comment

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

What is being analyzed to yield this warning? Are we actually converting the b to IEnumerable<object> before enumerating in the bound tree and warning on that?

Copy link
Member Author

Choose a reason for hiding this comment

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

It looks like the comment refers to ab = [..a, ..b] in TypeInference_Spread_08.

In that test, b has type IEnumerable<string?>[] and the target collection has type IEnumerable<object!>[].

NullableWalker.VisitCollectionExpression is reporting the warning from the implicit conversion of the element of the spread element expression, with type IEnumerable<string?>, to the element type IEnumerable<object!> of the target collection.

@jaredpar jaredpar added this to the 17.12 milestone Sep 5, 2024
@cston cston requested review from jcouv and a team October 17, 2024 15:47
@jcouv jcouv self-assigned this Oct 17, 2024
CreateCompilation(src).VerifyEmitDiagnostics(
// (5,19): warning CS8602: Dereference of a possibly null reference.
// string[] y1 = [.. x1];
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x1").WithLocation(5, 19));
Copy link
Member

@jcouv jcouv Oct 17, 2024

Choose a reason for hiding this comment

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

Parking comment here, but it relates to a test below. I think we can remove the comment in SpreadNullability_SplitExpression. #Closed

static void Main()
{
IEnumerable<string?> x = [null];
IEnumerable<object> y = [..x];
Copy link
Member

@jcouv jcouv Oct 17, 2024

Choose a reason for hiding this comment

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

Consider adding the converse scenario, where the elements are not-null and the target collection allows for null elements:

                        IEnumerable<string> x = [""];
                        IEnumerable<object?> y = [..x]; // no warning
``` #Closed

""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics();
}
Copy link
Member

@jcouv jcouv Oct 17, 2024

Choose a reason for hiding this comment

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

Do we have a test where the spread operand is more than a simple local? For instance M(x), which could produce nullability warnings of its own. #Closed

Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like the operand in test Spread_Nullable_09 is not producing warnings of its own. It sounded like Julien was asking for an F() which warns about a possible null argument, for example.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added case.

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (iteration 8)

@cston cston requested a review from RikkiGibson October 18, 2024 15:53
@@ -3767,6 +3779,34 @@ static NullableFlowState getResultState(BoundCollectionExpression node, Collecti
}
}

public override BoundNode? VisitCollectionExpressionSpreadElement(BoundCollectionExpressionSpreadElement node)
{
VisitRvalue(node.Expression);
Copy link
Contributor

@RikkiGibson RikkiGibson Oct 18, 2024

Choose a reason for hiding this comment

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

It looks like a case like int[] x = [..(int[]?)null] is handled by the VisitForEachExpression itself? Since we would be visiting a loop like foreach (var elem in (int[]?)null) { ... }? #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.

Yes, the top-level nullability of the expression being spread is handled in VisitForEachExpression(). For an example test, see CollectionExpressionTests.SpreadNullability.

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (iteration 10)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers Feature - Collection Expressions untriaged Issues and PRs which have not yet been triaged by a lead VSCode
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Missing nullability warning for spreads of maybe-null collections
6 participants