Skip to content

Refactor VisitLambda to not create a child walker#45527

Merged
RikkiGibson merged 8 commits intodotnet:masterfrom
RikkiGibson:lambda-reuse-walker
Jul 10, 2020
Merged

Refactor VisitLambda to not create a child walker#45527
RikkiGibson merged 8 commits intodotnet:masterfrom
RikkiGibson:lambda-reuse-walker

Conversation

@RikkiGibson
Copy link
Member

@RikkiGibson RikkiGibson commented Jun 29, 2020

Fixes #44411
Fixes #45795

I had hoped to outright remove the _useDelegateInvokeParameterTypes field and ctor parameter, as well as the delegateInvokeMethodOpt ctor parameter, but I cannot prove to my satisfaction that they will never make a difference in the one scenario where we create a NullableWalker specifically from a lambda, which is UnboundLambda.GetInferredReturnType. There are no tests which fail when the delegateInvokeMethodOpt is forced to null in this context.

I also found that after this change, we were throwing on ImmutableDictionary creation in the bootstrap build, because dictionary values of type TypeWithAnnotations were being compared for equality. This is solved by manually passing a value equality comparer when creating the dictionary.

@RikkiGibson RikkiGibson force-pushed the lambda-reuse-walker branch from 0d4d671 to 9f032bd Compare July 1, 2020 16:30
@RikkiGibson RikkiGibson marked this pull request as ready for review July 1, 2020 17:19
@RikkiGibson RikkiGibson requested a review from a team as a code owner July 1, 2020 17:19
}

LeaveParameters(localFuncSymbol.Parameters, localFunc.Syntax, location);
LeaveParameters(lambdaOrFunctionSymbol.Parameters, lambdaOrFunction.Syntax, location);
Copy link
Member

Choose a reason for hiding this comment

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

Why do we condition EnterParameters but not LeaveParameters?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question! I found that the IsCompilerGenerated check went by unremarked when it was introduced: https://github.com/dotnet/roslyn/pull/40422/files#diff-e3717bc6a837e218d79277c0e45f213aR1620

One thing I did notice is that LeaveParameters does absolutely nothing in NullableWalker, it only has an effect in DefiniteAssignment.

private void EnterParameters()
{
if (!(_symbol is MethodSymbol methodSymbol))
if (!(CurrentSymbol is MethodSymbol methodSymbol))
Copy link
Contributor

Choose a reason for hiding this comment

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

CurrentSymbol [](start = 18, length = 13)

There are other references to _symbol that are not updated. Are those other references correct?

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 made a pass over existing usages of _symbol and changed the ones that seemed wrong.

_variableSlot.ToImmutableDictionary(),
ImmutableArray.Create(variableBySlot, start: 0, length: nextVariableSlot),
_variableTypes.ToImmutableDictionary(),
_variableTypes.ToImmutableDictionary(_variableTypes.Comparer, TypeWithAnnotations.EqualsComparer.ConsiderEverythingComparer),
Copy link
Contributor

Choose a reason for hiding this comment

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

_variableTypes.Comparer, TypeWithAnnotations.EqualsComparer.ConsiderEverythingComparer [](start = 53, length = 86)

Is this change needed? (The key comparer is the default, and the value comparer doesn't appear to be used.)

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, without this change, the bootstrap build crashes because it attempts to call TypeWithAnnotations.Equals on the dictionary values.

Copy link
Contributor

Choose a reason for hiding this comment

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

Where are we calling Equals on the dictionary values?


In reply to: 451162769 [](ancestors = 451162769)

Copy link
Member Author

Choose a reason for hiding this comment

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

ToImmutableDictionary() may call Equals on dictionary values internally when constructing the immutable dictionary. This is something we might want to examine in greater detail. It's not obvious to me why sharing the _variableTypes between a lambda and its containing method would make this behavior start to surface in the bootstrap build.

// (15,17): warning CS8602: Dereference of a possibly null reference.
// _ = x.ToString(); // 1
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 17));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

} [](start = 8, length = 1)

Please add a similar test where the local function is passed as a delegate.

private void EnforceDoesNotReturn(SyntaxNode? syntaxOpt)
{
if (_symbol is MethodSymbol method &&
// DoesNotReturn is only supported in member methods
Copy link
Contributor

Choose a reason for hiding this comment

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

DoesNotReturn [](start = 15, length = 13)

Can [DoesNotReturn] be applied to a local function?

Copy link
Member Author

@RikkiGibson RikkiGibson Jul 8, 2020

Choose a reason for hiding this comment

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

We don't complain if it is applied, but we do not change analysis when it is present.

I actually expected us to mark regions unreachable in NullableWalker when calling a local function that does not return, and thus give no warnings in those regions--definite assignment already works such that the following scenario gives no errors. Unfortunately this does not seem to be the case. It's an inconsistency we might want to follow up on. Once we start doing this, it should not be necessary to use [DoesNotReturn] or [DoesNotReturnIf] on local functions.

string s;
local1();
s.ToString();

void local1() => throw null!;
string? s = null;
local1();
s.ToString(); // this warns, but shouldn't

void local1() => throw null!;

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 will try and ensure that DoesNotReturn does not impact analysis of local functions for the moment, and file a follow-up issue to confirm that this is expected.


[Theory]
[InlineData("")]
[InlineData("static ")]
Copy link
Contributor

@cston cston Jul 8, 2020

Choose a reason for hiding this comment

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

Does [DoesNotReturn] analysis depend on whether the local function is static?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, I pushed this by accident, I believe this function should be deleted.

Copy link
Member Author

Choose a reason for hiding this comment

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

Simplified the test down to a [Fact] to simplify, since 'static' differences are not expected to impact the test.

[DoesNotReturn]
" + modifiers + @"void boom()
{
}
Copy link
Contributor

@cston cston Jul 8, 2020

Choose a reason for hiding this comment

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

} [](start = 8, length = 1)

Should report WRN_ShouldNotReturn. Not blocking this PR.

using System.Diagnostics.CodeAnalysis;
class D
{
void Main(object? o)
Copy link
Contributor

Choose a reason for hiding this comment

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

object? o [](start = 14, length = 9)

Not used.

@333fred
Copy link
Member

333fred commented Jul 9, 2020

            if (_symbol is MethodSymbol method)

Why should this not be CurrentSymbol?


Refers to: src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs:476 in c5b1eec. [](commit_id = c5b1eec, deletion_comment = False)

@333fred
Copy link
Member

333fred commented Jul 9, 2020

            if (_symbol is MethodSymbol method)

Why should this not be CurrentSymbol?


Refers to: src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs:524 in c5b1eec. [](commit_id = c5b1eec, deletion_comment = False)

@333fred
Copy link
Member

333fred commented Jul 9, 2020

            if (_symbol is MethodSymbol method)

Why should this not be CurrentSymbol?


Refers to: src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs:568 in c5b1eec. [](commit_id = c5b1eec, deletion_comment = False)

@333fred
Copy link
Member

333fred commented Jul 9, 2020

                        var method = getTopLevelMethod(_symbol as MethodSymbol);

It seems like _symbol here should always be the top level method? Is there a time when it's not? If there is, the docs on _symbol are probably wrong.


Refers to: src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs:1218 in c5b1eec. [](commit_id = c5b1eec, deletion_comment = False)

@RikkiGibson
Copy link
Member Author

                        var method = getTopLevelMethod(_symbol as MethodSymbol);

It seems like _symbol here should always be the top level method? Is there a time when it's not? If there is, the docs on _symbol are probably wrong.

Refers to: src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs:1218 in c5b1eec. [](commit_id = c5b1eec, deletion_comment = False)

_symbol is not the top level method when we are reinferring a lambda return type.

@RikkiGibson
Copy link
Member Author

            if (_symbol is MethodSymbol method)

Why should this not be CurrentSymbol?

Refers to: src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs:476 in c5b1eec. [](commit_id = c5b1eec, deletion_comment = False)

I would prefer to address these cases in a follow-up PR which resolves #45821

}

public override BoundNode? VisitLocalFunctionStatement(BoundLocalFunctionStatement localFunc)
public override BoundNode? VisitLocalFunctionStatement(BoundLocalFunctionStatement node)
Copy link
Member

Choose a reason for hiding this comment

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

Alright, so I think speculative analysis in local functions was already broken in current master: we're never calling snapshotBuilderOpt?.EnterNewWalker() on the local function symbol, so state saving and restore will be broken.

Copy link
Member

Choose a reason for hiding this comment

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

This should reproduce in that variables declared in the local function don't have correct state when restoring for semantic analysis (if previous statements in the local function checked them for null, for example.


In reply to: 451908958 [](ancestors = 451908958)

Copy link
Member

Choose a reason for hiding this comment

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

Opened #45825 to track this. I think that in order to not introduce this bug for lambdas in this PR you're going to have to fix this one as well.

@RikkiGibson
Copy link
Member Author

RikkiGibson commented Jul 9, 2020

It appears that tests already verify that we respect [DoesNotReturn] on local functions. Therefore I will revert the behavior and delete my contradictory test. I think we should revisit this when we decide 1. whether to respect/check DoesNotReturn on local functions and 2. whether to change nullable analysis to understand that a local function may never return and to make regions unreachable as a result. #45814

SetUpdatedSymbol(node, node.Symbol, delegateTypeOpt);
}

Analyze(
Copy link
Member

@333fred 333fred Jul 9, 2020

Choose a reason for hiding this comment

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

By no longer calling this analyze overload, we're not entering a new scope for the lambda, so speculation will be broken. We need to restore the calls to snapshotManager?.EnterNewWalker with the new symbol. And exiting the walker after lambda analysis is done.

@333fred
Copy link
Member

333fred commented Jul 9, 2020

Done review pass (commit 7). I didn't look at tests.

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

Explicitly blocking since there are 2 approvals, want to make sure this doesn't accidentally go in.

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

LGTM (commit 8)

@RikkiGibson RikkiGibson merged commit ffb3ca7 into dotnet:master Jul 10, 2020
@ghost ghost added this to the Next milestone Jul 10, 2020
@RikkiGibson RikkiGibson deleted the lambda-reuse-walker branch July 10, 2020 01:01
@JoeRobich JoeRobich modified the milestones: Next, 16.8.P1 Jul 20, 2020
cston added a commit to cston/roslyn that referenced this pull request Dec 5, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

5 participants