Assert duplicate named arguments produce correct diagnostics#25730
Assert duplicate named arguments produce correct diagnostics#25730OmarTawfik merged 3 commits intodotnet:dev15.7.xfrom OmarTawfik:fix-200500-parameter-assert
Conversation
There was a problem hiding this comment.
Does this test hit MakeArgumentsInEvaluationOrder?
There was a problem hiding this comment.
No. The original assert mentioned in the comment was refactored out. I'll add an IOperations test as well per Julien's comment to cover more ground.
|
On second thought, the stack in the bug report shows the problem happens in |
Probably need to test IOperation scenarios too
|
I was actually able to repro a failure through tests (VS didn't). Will fix this PR and tag people shortly. #Closed |
|
@jcouv @cston after more inspection, we have two options:
I chose the first solution, and added tests to make sure that the node we add is marked as |
There was a problem hiding this comment.
IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsInvalid) (Syntax: 'format: """"') [](start = 6, length = 143)
IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: format) (OperationKind.Argument, Type: null, IsInvalid) (Syntax: 'format: """"') [](start = 6, length = 143)
I think this is wrong, we are not supposed to create IInvocationOperation when there are issues with mapping arguments to parameters. The IInvocationOperation can be created only for successful overload resolution case. #Closed
There was a problem hiding this comment.
CorrectOrder [](start = 75, length = 12)
CorrectOrder [](start = 75, length = 12)
I am not sure what "CorrectOrder" mean in this scenario #Closed
There was a problem hiding this comment.
Arguments are not ordered. I'll fix it to be more explicit.
In reply to: 177267989 [](ancestors = 177267989)
There was a problem hiding this comment.
Debug.Assert(!processedParameters.Contains(p)); [](start = 16, length = 47)
I think this assert should be put back. It catches real problems. #Resolved
There was a problem hiding this comment.
The method should not be called in scenarios outlined in the issue.
In reply to: 177268255 [](ancestors = 177268255)
AlekseyTs
left a comment
There was a problem hiding this comment.
The proposed fix violates an invariant that IInvocationOperation is created only when overload resolution succeeds.
|
Done with review pass (iteration 1) #Closed |
@AlekseyTs the compiler indeed generates For example, this is a true overload resolution failure: void N(int a, int b)
{
}
void M()
{
/*<bind>*/N(a: 1, a: 2)/*</bind>*/;
}We produce: And the overload resolution failure leads to this IOperations tree: Are you suggesting that additional duplicate arguments should fail overload resolution? #Closed |
I am not suggesting this, I am pretty sure overload resolution fails in this case. #Closed |
|
@OmarTawfik If you need more help with this issue, let's take this offline #Closed |
|
Discussed offline. Overload resolution actually succeeds with such arguments. I'll change it and update the PR. I'll tag the reviewers again when ready. #Closed |
|
@cston @jcouv @AlekseyTs comments addressed. #Closed |
| NoCorrespondingParameter, | ||
| FirstInvalid = NoCorrespondingParameter, | ||
| NoCorrespondingNamedParameter, | ||
| DuplicateNamedArguments, |
There was a problem hiding this comment.
DuplicateNamedArguments [](start = 8, length = 23)
Should we use singular as the other members, i.e. "DuplicateNamedArgument"? #Closed
There was a problem hiding this comment.
I chose the plural form as you cannot have one duplicate argument :)
In reply to: 178667114 [](ancestors = 178667114)
There was a problem hiding this comment.
I chose the plural form as you cannot have one duplicate argument
Of course I can, the second one is a duplicate, the first one is not.
In reply to: 178707798 [](ancestors = 178707798,178667114)
There was a problem hiding this comment.
| Debug.Assert(badArguments.Length != 0); | ||
| return new MemberAnalysisResult( | ||
| MemberResolutionKind.BadArguments, | ||
| MemberResolutionKind.BadArgumentConversion, |
There was a problem hiding this comment.
MemberResolutionKind.BadArgumentConversion, [](start = 15, length = 44)
Why was this change necessary? #Resolved
There was a problem hiding this comment.
| private void ReportDuplicateNamedArguments(MemberResolutionResult<TMember> result, DiagnosticBag diagnostics, AnalyzedArguments arguments) | ||
| { | ||
| Debug.Assert(result.Result.BadArgumentsOpt.Length == 1); | ||
| var name = arguments.Names[result.Result.BadArgumentsOpt[0]]; |
There was a problem hiding this comment.
var [](start = 12, length = 3)
Please spell out the type #Closed
| [Fact] | ||
| [CompilerTrait(CompilerFeature.IOperation)] | ||
| [WorkItem(20050, "https://github.com/dotnet/roslyn/issues/20050")] | ||
| public void BuildsArgumentsOperationsForDuplicateExplicitArguments_Repro() |
There was a problem hiding this comment.
BuildsArgumentsOperationsForDuplicateExplicitArguments_Repro [](start = 20, length = 60)
Please add similar tests for properties (indexers). #Closed
| Assert.Equal("M(x: 1, x: 2)", invocation.ToString()); | ||
| Assert.Equal("void C.M(params System.Int32[] x)", model.GetSymbolInfo(invocation).Symbol.ToTestDisplayString()); | ||
|
|
||
| var symbolInfo = model.GetSymbolInfo(invocation); |
There was a problem hiding this comment.
symbolInfo [](start = 16, length = 10)
Please add Assert.Null for symbolInfo.Symbol. #Closed
| // (8,38): error CS1740: Named argument 'x' cannot be specified multiple times | ||
| // System.Console.Write(c[x: 1, x: 2, 3]); | ||
| Diagnostic(ErrorCode.ERR_DuplicateNamedArgument, "x").WithArguments("x").WithLocation(8, 38), | ||
| // (8,32): error CS1739: The best overload for 'this' does not have a parameter named 'x' |
There was a problem hiding this comment.
// (8,32): error CS1739: The best overload for 'this' does not have a parameter named 'x' [](start = 16, length = 89)
It feels like this error is more useful than the duplicate name error. #Closed
| // (1) Is there any named argument used out-of-position and followed by unnamed arguments? | ||
| // (2) Is there any argument without a corresponding parameter? | ||
| // (3) Was there any named argument that specified a parameter that was already | ||
| // (1) Is there any named argument that were specified twice? |
There was a problem hiding this comment.
Is there any named argument that were specified twice? [](start = 19, length = 54)
It feels like we shouldn't start with this check. Consider adding it last. Usually, adding new checks last is a better approach, especially for condition that was not considered an error before. #Closed
|
Done with review pass (iteration 1) #Closed |
| const int noCorrespondingParameterPriority = 3; | ||
| const int badNonTrailingNamedArgument = 4; | ||
| var supportedInPriorityOrder = new MemberResolutionResult<TMember>[6]; // from highest to lowest priority | ||
| const int duplicateNamedArguments = 0; |
There was a problem hiding this comment.
const int duplicateNamedArguments = 0; [](start = 12, length = 38)
Here it is probably appropriate to treat the new failure as highest priority because before it wasn't considered a failure. #Closed
| const int nameUsedForPositionalPriority = 2; | ||
| const int noCorrespondingNamedParameterPriority = 3; | ||
| const int noCorrespondingParameterPriority = 4; | ||
| const int badNonTrailingNamedArgument = 5; |
There was a problem hiding this comment.
const int badNonTrailingNamedArgument = 5; [](start = 11, length = 43)
Curious: why do we use constants here to establish priority vs. giving the memwber of MemberResolutionKind an explicit numeric value and using that as priority? #Resolved
There was a problem hiding this comment.
Curious: why do we use constants here to establish priority vs. giving the memwber of MemberResolutionKind an explicit numeric value and using that as priority?
There are way more MemberResolutionKinds than priorities. This is the only place where we prioritize things. And we avoid spooky action at a distance. :-) #Resolved
| return null; | ||
| } | ||
|
|
||
| var alreadyDefined = PooledHashSet<string>.GetInstance(); |
There was a problem hiding this comment.
GetInstance(); [](start = 55, length = 14)
Should we pass in arguments.Names.Count as the initial capacity here? #Resolved
There was a problem hiding this comment.
ObjectPool does not consider initial capacity :( Maybe we can revise that design later.
In reply to: 178680788 [](ancestors = 178680788)
| [Fact] | ||
| [CompilerTrait(CompilerFeature.IOperation)] | ||
| [WorkItem(20050, "https://github.com/dotnet/roslyn/issues/20050")] | ||
| public void BuildsArgumentsOperationsForDuplicateExplicitArguments_CorrectArgumentsOrder_Lambdas() |
There was a problem hiding this comment.
Lambdas [](start = 97, length = 7)
I do not see lambdas in this tests scenario. There are delegates though. #Closed
AlekseyTs
left a comment
There was a problem hiding this comment.
LGTM (iteration 2), modulo a small issue around names for tests.
|
approved |
Customer scenario
Getting IOperations trees on call expressions that have duplicate explicitly named arguments, will cause a
Debug.Assert()to throw in the compiler. Example:Bugs this fixes
Fixes #20050
Workarounds, if any
None.
Risk
Low. One line fix.
Is this a regression from a previous update?
No.
Root cause analysis
It is a case where we didn't count for invalid arguments when constructing the IOperations tree.
How was the bug found?
Reported on GitHub.