Skip to content

Enforce nullability contract in lambda body#56590

Merged
jcouv merged 11 commits intodotnet:release/dev17.0from
jcouv:lambda-nullability
Sep 28, 2021
Merged

Enforce nullability contract in lambda body#56590
jcouv merged 11 commits intodotnet:release/dev17.0from
jcouv:lambda-nullability

Conversation

@jcouv
Copy link
Member

@jcouv jcouv commented Sep 21, 2021

Fixes #52827 (Enforce nullability attributes in lambda body)
Fixes #47896 (Enforce nullability attributes in local function body)
Fixes #45814 (Enforce DoesNotReturn in local function body)
Fixes #52598 (Enforce DoesNotReturn in local function body)
Fixes #55649 (Roslyn fails to verify nullable attributes on lambda expressions in delegate conversions)

Per discussion with Jared and Chuck, I'm pulling out the part of the change that relates to conversions. Opened #56668 to track this change in 17.1.

Relates to test plan #52192

@jcouv jcouv added this to the 17.0 milestone Sep 21, 2021
@jcouv jcouv self-assigned this Sep 21, 2021
@ghost ghost added the Area-Compilers label Sep 21, 2021
@jcouv jcouv force-pushed the lambda-nullability branch 2 times, most recently from 40a2b89 to 2c450c7 Compare September 22, 2021 02:14
}
}

void enforceNotNullWhenForPendingReturn(PendingBranch pendingReturn, BoundReturnStatement returnStatement)
Copy link
Member Author

@jcouv jcouv Sep 22, 2021

Choose a reason for hiding this comment

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

📝 The local functions were extracted mostly without modification (except for naming changes and tweak to location reported for WRN_ParameterDisallowsNull)

@jcouv jcouv force-pushed the lambda-nullability branch from 7bec213 to c082359 Compare September 23, 2021 04:08
Debug.Assert(!useDelegateInvokeReturnType || delegateInvokeMethod is object);

var oldSymbol = this.CurrentSymbol;
var oldSymbol = this._symbol;
Copy link
Member Author

@jcouv jcouv Sep 23, 2021

Choose a reason for hiding this comment

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

📝 _symbol drives various APIs, such as MethodParameters #Resolved

Copy link
Member

Choose a reason for hiding this comment

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

Did you consider modifying the relevant APIs to instead check CurrentSymbol?

Copy link
Member Author

Choose a reason for hiding this comment

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

Took another look, but the comment on _symbol says its primary purpose is to provide method parameters and return type. So I think this is correct.

@jcouv jcouv force-pushed the lambda-nullability branch from c082359 to 4220574 Compare September 23, 2021 04:12
@jcouv jcouv force-pushed the lambda-nullability branch from 56a06e4 to d5b9c77 Compare September 23, 2021 06:00
@jcouv jcouv force-pushed the lambda-nullability branch from d5b9c77 to 0f4fdb7 Compare September 23, 2021 07:14
@jcouv jcouv requested review from a team, RikkiGibson and cston September 23, 2021 15:31
@jcouv jcouv marked this pull request as ready for review September 23, 2021 16:53
@RikkiGibson RikkiGibson self-assigned this Sep 23, 2021
}

[Fact, WorkItem(52827, "https://github.com/dotnet/roslyn/issues/52827")]
public void FunctionTypeConversion_NullableAttributes()
Copy link
Contributor

@cston cston Sep 23, 2021

Choose a reason for hiding this comment

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

FunctionTypeConversion

Are these function type conversions or method group conversions? #Closed

/// 'MethodParameters', 'MethodThisParameter' and 'AnalyzeOutParameters(...)' should be used
/// instead. _symbol is null during speculative binding.
/// </summary>
protected readonly Symbol _symbol;
Copy link
Contributor

@cston cston Sep 23, 2021

Choose a reason for hiding this comment

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

readonly Symbol _symbol

We already have a CurrentSymbol field that is mutable. Do we need both fields to be mutable? #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, I think so, as it drives separate APIs, like MethodParameters which are used by existing helpers for nullability checks.

}

void reportParameterIfBadConditionalState(SyntaxNode syntax, ParameterSymbol parameter, bool sense, LocalState stateWhen)
void EnforceParameterNotNullOnExit(SyntaxNode? syntaxOpt, LocalState state)
Copy link
Contributor

@cston cston Sep 23, 2021

Choose a reason for hiding this comment

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

void

private for each of these. #Closed

}
}

void EnforceNotNullWhenForPendingReturn(PendingBranch pendingReturn, BoundReturnStatement returnStatement)
Copy link
Contributor

@cston cston Sep 23, 2021

Choose a reason for hiding this comment

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

EnforceNotNullWhenForPendingReturn

Consider moving this before EnforceParameterNotNullOnExit() since it looks like that might reduce the differences in this section. #Closed

{
if (!unboundLambda.HasExplicitlyTypedParameterList)
MethodSymbol targetInvokeMethod = delegateType.DelegateInvokeMethod;
MethodSymbol sourceInvokeMethod = (MethodSymbol)lambda.ExpressionSymbol;
Copy link
Contributor

@cston cston Sep 23, 2021

Choose a reason for hiding this comment

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

(MethodSymbol)lambda.ExpressionSymbol

The cast can be avoided with lambda.Symbol. #Resolved

compilation,
sourceInvokeMethod,
targetInvokeMethod,
new BindingDiagnosticBag(Diagnostics),
Copy link
Contributor

@cston cston Sep 23, 2021

Choose a reason for hiding this comment

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

new BindingDiagnosticBag(Diagnostics)

Can we re-use the instance from the previous call? #Resolved

Func<object?> f9 = [return: NotNull] () => null; // 5
D1 f10 = [return: NotNull] () => null;
D2 f11 = [return: NotNull] () => null; // 6
D3 f12 = [return: NotNull] () => null;
Copy link
Contributor

@cston cston Sep 23, 2021

Choose a reason for hiding this comment

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

Are these cases distinct from f6, f7, f8? #Closed

_ = new Func<object?>([return: NotNull] () => null); // 5
_ = new D1([return: NotNull] () => null);
_ = new D2([return: NotNull] () => null); // 6
_ = new D3([return: NotNull] () => null);
Copy link
Contributor

@cston cston Sep 23, 2021

Choose a reason for hiding this comment

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

Are these cases distinct from the three above? #Closed

Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "([NotNullWhen(true)] out object? obj) =>").WithArguments("obj", "lambda expression", "D").WithLocation(10, 15),
// (13,17): warning CS8762: Parameter 'obj' must have a non-null value when exiting with 'true'.
// return true;
Diagnostic(ErrorCode.WRN_ParameterConditionallyDisallowsNull, "return true;").WithArguments("obj", "true").WithLocation(13, 17)
Copy link
Contributor

@cston cston Sep 23, 2021

Choose a reason for hiding this comment

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

ErrorCode.WRN_ParameterConditionallyDisallowsNull

Should this be reported for the previous lambda as well? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

One lambda returns true with obj=null and the other returns false. So only one of them reports this warning.

@jcouv jcouv requested a review from cston September 24, 2021 00:38
@jcouv
Copy link
Member Author

jcouv commented Sep 27, 2021

@RikkiGibson Gentle ping to take a look. Thanks

}
}";
// Wrong lambda conversion warnings tracked by https://github.com/dotnet/roslyn/issues/56668
// We're expecting WRN_NullabilityMismatchInReturnTypeOfTargetDelegate for each commented line
Copy link
Member

@RikkiGibson RikkiGibson Sep 27, 2021

Choose a reason for hiding this comment

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

I'm not 100% sure I understood. Does this mean that this baseline is missing a single diagnostic for each // n comment above? And the commented lines are not meant to indicate the entire set of expected diagnostics? #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.

That's correct. We should have one WRN_NullabilityMismatchInReturnTypeOfTargetDelegate for each commented line (and only those lines). Other lines have different warnings which are not the focus on this test.

Copy link
Member

Choose a reason for hiding this comment

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

Or is it more that the "check nullable attributes in lambda conversion" was implemented, the commented lines were added, then the conversion check was punted out, and the commented lines were intentionally not updated?

Copy link
Member Author

Choose a reason for hiding this comment

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

Your description is also correct (another way to say the same thing).

// (9,28): warning CS8621: Nullability of reference types in return type of 'lambda expression' doesn't match the target delegate 'Func<string?>' (possibly because of nullability attributes).
// Func<string?> f3 = string () => default!;
Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOfTargetDelegate, "string () => default!").WithArguments("lambda expression", "System.Func<string?>").WithLocation(9, 28));
Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOfTargetDelegate, "string () =>").WithArguments("lambda expression", "System.Func<string?>").WithLocation(9, 28));
Copy link
Member

@RikkiGibson RikkiGibson Sep 27, 2021

Choose a reason for hiding this comment

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

Removing the lambda body from the diagnostic span seems like a good change. #Resolved

assert(o2 is not null);
o2.ToString();

// Note: no enforcement on method body for DoesNotReturnIf
Copy link
Member

@RikkiGibson RikkiGibson Sep 27, 2021

Choose a reason for hiding this comment

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

Was this limitation intentional? Or the functionality was punted out? #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.

I think we failed to find a design for that enforcement (if I recall correctly it's not possible for a reasonable cost).

}
}
";
var comp = CreateCompilation(new[] { source, AllowNullAttributeDefinition });
Copy link
Member

@RikkiGibson RikkiGibson Sep 27, 2021

Choose a reason for hiding this comment

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

Should we also have a comment here that the missing warning will be handled in a follow-up? #Resolved

var comp = CreateCompilation(source);
comp.VerifyDiagnostics(
// (11,14): warning CS8622: Nullability of reference types in type of parameter 'o' of 'lambda expression' doesn't match the target delegate 'Action<object>' (possibly because of nullability attributes).
// a1 = (object? o) => { }; // warning
Copy link
Member

@RikkiGibson RikkiGibson Sep 27, 2021

Choose a reason for hiding this comment

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

nit: it looks like // warning comments are present in this test baseline but not in the source code. #Resolved

Copy link
Member

@RikkiGibson RikkiGibson left a comment

Choose a reason for hiding this comment

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

LGTM. Would be nice if we could address CurrentSymbol vs _symbol in this PR but can be punted if necessary.

@jcouv jcouv enabled auto-merge (squash) September 27, 2021 19:22
@jcouv jcouv disabled auto-merge September 28, 2021 00:04
@jcouv jcouv changed the base branch from main to release/dev17.0 September 28, 2021 00:04
@jcouv
Copy link
Member Author

jcouv commented Sep 28, 2021

Re-targeted to release/dev17.0 branch

@jcouv jcouv enabled auto-merge (squash) September 28, 2021 00:07
@jcouv jcouv merged commit 3a4b369 into dotnet:release/dev17.0 Sep 28, 2021
@jcouv jcouv deleted the lambda-nullability branch September 28, 2021 02:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment