Use default parameter values in flow analysis#38788
Use default parameter values in flow analysis#38788RikkiGibson merged 19 commits intodotnet:masterfrom
Conversation
|
This change seems to consistently make the build hang. I don't understand why, though, because it seems to build fine locally. #Closed |
That is probably because CI builds everything with compiler that includes the change. Try |
|
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 |
| if (parameter.IsOptional && !visitedParameters.Contains(parameter)) | ||
| { | ||
| var annotations = GetParameterAnnotations(parameter); | ||
| if (!_defaultValues.TryGetValue((syntax, parameter), out var argument)) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Adding additional comments to the declaration of _defaultValues
In reply to: 329250289 [](ancestors = 329250289,329248471)
| _updatedSymbolMapOpt = updatedSymbolMapOpt; | ||
| _returnTypesOpt = returnTypesOpt; | ||
| _snapshotBuilderOpt = snapshotBuilderOpt; | ||
| _defaultValues = defaultValues; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Could you add a test with indexer and default parameter?
In reply to: 329251239 [](ancestors = 329251239)
There was a problem hiding this comment.
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], |
There was a problem hiding this comment.
i >= conversions.Length [](start = 49, length = 23)
I didn't understand this. Are we using the right index? #Closed
There was a problem hiding this comment.
This conversions array was not resized as part of VisitArgumentsEvaluate. #Closed
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
() [](start = 78, length = 2)
can use n as size hint? #Closed
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Scratch that, PooledHashSet.GetInstance does not actually accept a size hint.
In reply to: 329263552 [](ancestors = 329263552,329248589)
There was a problem hiding this comment.
| } | ||
|
|
||
| var previousDisableNullabilityAnalysis = _disableNullabilityAnalysis; | ||
| _disableNullabilityAnalysis = true; |
There was a problem hiding this comment.
true [](start = 46, length = 4)
consider adding a comment too #Closed
| /// </summary> | ||
| private VisitResult _currentConditionalReceiverVisitResult; | ||
|
|
||
| /// <summary> |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Unknown [](start = 146, length = 7)
Should this be ThreeState.True instead? #Closed
There was a problem hiding this comment.
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) | ||
| { |
There was a problem hiding this comment.
{ [](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] |
There was a problem hiding this comment.
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
jcouv
left a comment
There was a problem hiding this comment.
Done with review pass (iteration 10)
|
@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. |
| _analyzedNullabilityMapOpt = analyzedNullabilityMapOpt; | ||
| _returnTypesOpt = returnTypesOpt; | ||
| _snapshotBuilderOpt = snapshotBuilderOpt; | ||
| _defaultValues = new Dictionary<(SyntaxNode, ParameterSymbol), BoundExpression>(); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
) [](start = 47, length = 1)
Can we skip this block (and avoid allocating new arrays for arguments, etc.) if visitedParameters.Count == parametersOpt.Length? #Closed
There was a problem hiding this comment.
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(); | ||
| } | ||
| } |
There was a problem hiding this comment.
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();
}
}
``` #ClosedThere was a problem hiding this comment.
Looks like no, will add the test
There was a problem hiding this comment.
Dictionary [](start = 47, length = 10)
PooledDictionary #Closed
There was a problem hiding this comment.
Oops, pushed too soon.
a320fcb to
35699ab
Compare
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.