Skip to content

Use default parameter values in flow analysis#38788

Merged
RikkiGibson merged 19 commits intodotnet:masterfrom
RikkiGibson:get-effective-arguments
Oct 15, 2019
Merged

Use default parameter values in flow analysis#38788
RikkiGibson merged 19 commits intodotnet:masterfrom
RikkiGibson:get-effective-arguments

Conversation

@RikkiGibson
Copy link
Member

Closes #37903

/cc @jcouv. This flows data around in a somewhat disjunct way but I felt that adding lots of additional return values (i.e. ImmutableArray<BoundExpression> arguments, ImmutableArray<BoundExpression> argumentsNoConversions, ImmutableArray<Conversion> conversions) which "augment" the original arrays with the default parameter values was also kind of clumsy.

@RikkiGibson RikkiGibson requested a review from a team as a code owner September 20, 2019 23:13
@jcouv jcouv self-assigned this Sep 20, 2019
@RikkiGibson
Copy link
Member Author

RikkiGibson commented Sep 25, 2019

This change seems to consistently make the build hang. I don't understand why, though, because it seems to build fine locally. #Closed

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Sep 25, 2019

This change seems to consistently make the build hang. I don't understand why, though, because it seems to build fine locally.

That is probably because CI builds everything with compiler that includes the change. Try build -restore -bootstrap locally #Closed

@RikkiGibson
Copy link
Member Author

RikkiGibson commented Sep 25, 2019

So, the on-the-fly creation of default parameter values is causing analysis to run an indefinite number of times on certain methods. It will probably be necessary to refactor to do something like create the effective arguments on demand on the BoundCall and reuse them. #Closed

@RikkiGibson RikkiGibson reopened this Sep 25, 2019
if (parameter.IsOptional && !visitedParameters.Contains(parameter))
{
var annotations = GetParameterAnnotations(parameter);
if (!_defaultValues.TryGetValue((syntax, parameter), out var argument))
Copy link
Member

@jcouv jcouv Sep 27, 2019

Choose a reason for hiding this comment

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

syntax [](start = 57, length = 6)

Why do we need the syntax as part of the key? I would think that ParameterSymbol -> BoundExpression would be a sufficient map, no?
If we don't need it, then we don't need to pass syntax into VisitArgumentsEvaluate too. #Closed

Copy link
Member Author

@RikkiGibson RikkiGibson Sep 27, 2019

Choose a reason for hiding this comment

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

The LocalRewriter.GetDefaultParameterValue uses the provided Syntax to get the actual values when CallerMemberName, CallerLineNumber, etc. attributes are in use. The produced BoundExpression uses the containing invocation such as BoundCall for its Syntax property.

It's possible we could make it work without actually providing syntax to the BoundExpression for the default argument but I'm not sure what would break/need to change as a result. #Closed

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense. May be worth a comment


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

Copy link
Member

Choose a reason for hiding this comment

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

Also, could you add a test with CallerExpression or some other attribute, if we don't have one already?


In reply to: 329250289 [](ancestors = 329250289,329248471)

Copy link
Member Author

Choose a reason for hiding this comment

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

Adding additional comments to the declaration of _defaultValues


In reply to: 329250289 [](ancestors = 329250289,329248471)

_updatedSymbolMapOpt = updatedSymbolMapOpt;
_returnTypesOpt = returnTypesOpt;
_snapshotBuilderOpt = snapshotBuilderOpt;
_defaultValues = defaultValues;
Copy link
Member

@jcouv jcouv Sep 27, 2019

Choose a reason for hiding this comment

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

defaultValues [](start = 29, length = 13)

from our discussion, is it possible to initialize the empty dictionary here? When do we need a dictionary passed in across analyses? #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.

It seems that we don't, I somehow got the impression that we did when I was troubleshooting the bootstrap build


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

{
method = InferMethodTypeArguments(node, method, GetArgumentsForMethodTypeInference(argumentsNoConversions, results));
parameters = method.Parameters;
var binder = (node as BoundCall)?.BinderOpt ?? (node as BoundCollectionElementInitializer)?.BinderOpt ?? throw ExceptionUtilities.UnexpectedValue(node);
Copy link
Member

@jcouv jcouv Sep 27, 2019

Choose a reason for hiding this comment

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

BoundCall [](start = 42, length = 9)

Looking at the different kinds of bound nodes that have BinderOpt I noticed BoundIndexerAccess. and BoundObjectCreation. Should they be handled too? #Closed

Copy link
Member Author

@RikkiGibson RikkiGibson Sep 27, 2019

Choose a reason for hiding this comment

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

Perhaps, I only did these because the overload of InferMethodTypeArguments that I deleted would switch on these two types and reject any other types. #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.

The reason I only handled these two is that the InferMethodTypeArguments overload that I deleted only handled these two.


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

Copy link
Member

Choose a reason for hiding this comment

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

Could you add a test with indexer and default parameter?


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

Copy link
Member Author

@RikkiGibson RikkiGibson Oct 9, 2019

Choose a reason for hiding this comment

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

I will add the test with the expectation that NotNullIfNotNull will not have an effect and that we won't get the UnexpectedValue exception.


In reply to: 329269700 [](ancestors = 329269700,329251239)

GetConversionIfApplicable(argument, argumentNoConversion),
argumentNoConversion,
conversions.IsDefault ? Conversion.Identity : conversions[i],
conversions.IsDefault || i >= conversions.Length ? Conversion.Identity : conversions[i],
Copy link
Member

@jcouv jcouv Sep 27, 2019

Choose a reason for hiding this comment

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

i >= conversions.Length [](start = 49, length = 23)

I didn't understand this. Are we using the right index? #Closed

Copy link
Member Author

@RikkiGibson RikkiGibson Sep 27, 2019

Choose a reason for hiding this comment

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

This conversions array was not resized as part of VisitArgumentsEvaluate. #Closed

Copy link
Member

Choose a reason for hiding this comment

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

I see. The important thing is that the extended arrays start the same as the original arrays, and only have some items appended at the end.
Thanks


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

}
var builder = ArrayBuilder<VisitArgumentResult>.GetInstance(n);

var visitedParameters = PooledHashSet<ParameterSymbol>.GetInstance();
Copy link
Member

@jcouv jcouv Sep 27, 2019

Choose a reason for hiding this comment

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

() [](start = 78, length = 2)

can use n as size hint? #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.

This seems reasonable, as long as it doesn't cause a bad behavior when we have a call with a large params array.


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

Copy link
Member Author

Choose a reason for hiding this comment

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

Scratch that, PooledHashSet.GetInstance does not actually accept a size hint.


In reply to: 329263552 [](ancestors = 329263552,329248589)

Copy link
Member

Choose a reason for hiding this comment

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

Ah, good point


In reply to: 329263995 [](ancestors = 329263995,329263552,329248589)

}

var previousDisableNullabilityAnalysis = _disableNullabilityAnalysis;
_disableNullabilityAnalysis = true;
Copy link
Member

@jcouv jcouv Sep 27, 2019

Choose a reason for hiding this comment

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

true [](start = 46, length = 4)

consider adding a comment too #Closed

/// </summary>
private VisitResult _currentConditionalReceiverVisitResult;

/// <summary>
Copy link
Member

@jcouv jcouv Sep 27, 2019

Choose a reason for hiding this comment

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

summary [](start = 13, length = 7)

May be worth adding a note as to why we save/cache these #Closed

var annotations = GetParameterAnnotations(parameter);
if (!_defaultValues.TryGetValue((syntax, parameter), out var argument))
{
_defaultValues[(syntax, parameter)] = argument = LocalRewriter.GetDefaultParameterValue(syntax, parameter, ThreeState.Unknown, localRewriter: null, _binder, Diagnostics);
Copy link
Member

@jcouv jcouv Sep 27, 2019

Choose a reason for hiding this comment

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

Unknown [](start = 146, length = 7)

Should this be ThreeState.True instead? #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.

I think so, because we know we are in an invocation. It seems that passing Unknown makes it examine the syntax to decide if the default argument is contained in a kind of syntax where caller info makes sense.


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

}

if (!parametersOpt.IsDefaultOrEmpty)
{
Copy link
Member

@jcouv jcouv Sep 27, 2019

Choose a reason for hiding this comment

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

{ [](start = 12, length = 1)

It may also be good to leave a comment explaining that it's okay to analyze the optional arguments after the real arguments because they won't have any side-effects (constants only) #Closed

CompileAndVerify(comp2);
}

[Fact]
Copy link
Member

@jcouv jcouv Sep 27, 2019

Choose a reason for hiding this comment

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

Fact [](start = 9, length = 4)

Please add a test with attribute on a local function. It will fail for now, but will pass sometime soon ;-) #Closed

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.

Done with review pass (iteration 10)

@RikkiGibson RikkiGibson mentioned this pull request Oct 14, 2019
@RikkiGibson
Copy link
Member Author

@jcouv @dotnet/roslyn-compiler sorry that I neglected this PR for a bit, I have addressed review comments and have the bootstrap build working again.

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 16)

@RikkiGibson RikkiGibson added this to the 16.4.P3 milestone Oct 14, 2019
@RikkiGibson RikkiGibson requested a review from a team October 14, 2019 22:05
_analyzedNullabilityMapOpt = analyzedNullabilityMapOpt;
_returnTypesOpt = returnTypesOpt;
_snapshotBuilderOpt = snapshotBuilderOpt;
_defaultValues = new Dictionary<(SyntaxNode, ParameterSymbol), BoundExpression>();
Copy link
Contributor

@cston cston Oct 15, 2019

Choose a reason for hiding this comment

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

new Dictionary<(SyntaxNode, ParameterSymbol), BoundExpression>() [](start = 29, length = 64)

Consider using PooledDictionary<,> and delaying the allocation until the first use, similar to other dictionary fields. #Closed

visitedParameters.Add(parameter);
}

if (!parametersOpt.IsDefaultOrEmpty)
Copy link
Contributor

@cston cston Oct 15, 2019

Choose a reason for hiding this comment

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

) [](start = 47, length = 1)

Can we skip this block (and avoid allocating new arrays for arguments, etc.) if visitedParameters.Count == parametersOpt.Length? #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.

This is the scenario where we know every parameter was visited, right? Maybe we can change the condition to !parametersOpt.IsDefaultOrEmpty && parametersOpt.Length != visitedParameters.Count

_ = M1(p1: null, p2: ""hello"").ToString(); // 3
_ = M1(p2: null, p1: ""hello"").ToString();
}
}
Copy link
Contributor

@cston cston Oct 15, 2019

Choose a reason for hiding this comment

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

Are we testing cases where the optional value is null?

using System.Diagnostics.CodeAnalysis;
public class C
{
    [return: NotNullIfNotNull("p1")]
    string? M1(string? p1 = null, string? p2 = null) => p1;

    void M2()
    {
        _ = M1(p2: "a").ToString(); // 1
        _ = M1(p1: "b").ToString();
        _ = M1(p1: "a", p2: null).ToString();
        _ = M1(p2: "a", p1: null).ToString(); // 2
        _ = M1(p1: null, p2: "b").ToString(); // 3
        _ = M1(p2: null, p1: "b").ToString();
    }
}
``` #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.

Looks like no, will add the test

Copy link
Contributor

@cston cston Oct 15, 2019

Choose a reason for hiding this comment

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

Dictionary [](start = 47, length = 10)

PooledDictionary #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.

Oops, pushed too soon.

@cston
Copy link
Contributor

cston commented Oct 15, 2019

    {

_defaultValues?.Free(); #Closed


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

@RikkiGibson RikkiGibson force-pushed the get-effective-arguments branch from a320fcb to 35699ab Compare October 15, 2019 21:23
@RikkiGibson RikkiGibson merged commit 3484227 into dotnet:master Oct 15, 2019
@RikkiGibson RikkiGibson deleted the get-effective-arguments branch October 15, 2019 23:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Roslyn incorrectly handles optional parameters when processing [NotNullIfNotNull] annotation

4 participants