-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ensure that we get a well formed child IOperation tree even for bound… #18976
Conversation
… nodes for whom IOperation support is not yet implemented This ensures that the analyzer driver/operation walker is able to find all the descendant IOperation nodes within these not yet implemented features. We should remove the IOperationWithChildren interface once we have designed all the IOperation APIs. TODO: Implement the VB part. Fixes dotnet#8884
Tagging @dotnet/analyzer-ioperation for review. |
Added the VB support and tests. Tagging @AlekseyTs @cston as well. |
var collectionInitializerExpresion = objectOrCollectionInitializer as BoundCollectionInitializerExpression; | ||
if (collectionInitializerExpresion != null) | ||
{ | ||
return collectionInitializerExpresion.Initializers; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return collectionInitializerExpresion.Initializers; [](start = 16, length = 51)
Is this new code path covered by tests? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added a skipped test for this. It still doesn't work as expected due to the fact that IObjectCreationExpression.Initializers is still returning ImmutableArray of ISymbolInitializers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not comfortable with changes that affect behavior of existing APIs and are not covered by active tests. I think we should either revert changes to IObjectCreationExpression.MemberInitializers implementation, or cover the changes in tests. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, let me revert the changes to IObjectCreationExpression.MemberInitializers implementation.
@@ -1001,6 +1016,8 @@ internal sealed partial class BoundDeconstructionAssignmentOperator : BoundExpre | |||
// TODO: implement IOperation for pattern-matching constructs (https://github.com/dotnet/roslyn/issues/8699) | |||
protected override OperationKind ExpressionKind => OperationKind.None; | |||
|
|||
protected override ImmutableArray<IOperation> Children => ImmutableArray.Create<IOperation>(this.Left, this.Right); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.Right [](start = 111, length = 10)
We might not want to expose the right node as is. For example, I believe it can contain special conversions that probably shouldn't be exposed to the outside world in that form. I think we might want to expose only the nodes that aren't special for deconstruction. CC @jcouv #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the IOperationWithChildren
API is just internal, which only the OperationWalker understands so we can walk down it's children when computing all operations to analyze in a method block. Public clients will still not be able to access any children nodes, we don't need to worry about exposing unwanted nodes here.
@@ -1413,6 +1420,8 @@ internal partial class BoundDynamicIndexerAccess | |||
{ | |||
protected override OperationKind ExpressionKind => OperationKind.None; | |||
|
|||
protected override ImmutableArray<IOperation> Children => this.Arguments.Add(this.ReceiverOpt).As<IOperation>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.ReceiverOpt [](start = 85, length = 16)
Receiver should probably be the first. We also probably don't want to add nulls to the list. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that the API is only internal, and the OperationWalker handles null child nodes inside Children, I felt it is better to add all nodes and the client handle null cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I felt it is better to add all nodes and the client handle null cases.
Then we should clearly document this on the API in the BoundNode and in the interface. #Closed
@@ -1488,6 +1503,8 @@ internal partial class BoundRangeVariable | |||
{ | |||
protected override OperationKind ExpressionKind => OperationKind.None; | |||
|
|||
protected override ImmutableArray<IOperation> Children => ImmutableArray.Create<IOperation>(this.Value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protected override ImmutableArray Children => ImmutableArray.Create(this.Value); [](start = 8, length = 104)
This doesn't feel right. It looks like the value doesn't correspond to anything explicitly present in source. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I will revert this.
@@ -1533,6 +1550,8 @@ internal partial class BoundQueryClause | |||
{ | |||
protected override OperationKind ExpressionKind => OperationKind.None; | |||
|
|||
protected override ImmutableArray<IOperation> Children => ImmutableArray.Create<IOperation>(this.Value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protected override ImmutableArray Children => ImmutableArray.Create(this.Value); [](start = 8, length = 104)
I am not sure if we want to expose the entire value here. I think it will include calls to the query operators, but at the design meeting I didn't get an impression that we would want to expose them right now. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we ensure that we make callbacks for any operation nodes for user code within query? I think the fact that we are returning compiler generated nodes is irrelevant to this approach or the IOperationWithChildren API.
|
||
internal partial class BoundDeclarationPattern | ||
{ | ||
protected override ImmutableArray<IOperation> Children => ImmutableArray.Create<IOperation>(this.VariableAccess, this.DeclaredType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protected override ImmutableArray Children => ImmutableArray.Create(this.VariableAccess, this.DeclaredType); [](start = 7, length = 133)
Should be an empty set, I think. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I can revert
@@ -1818,6 +1873,8 @@ internal partial class BoundDynamicInvocation | |||
{ | |||
protected override OperationKind ExpressionKind => OperationKind.None; | |||
|
|||
protected override ImmutableArray<IOperation> Children => this.Arguments.As<IOperation>().Add(this.Expression); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.Arguments.As().Add(this.Expression); [](start = 66, length = 53)
Expression should probably be first. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Order doesn't really matter as we are not exposing this publically.
@@ -1833,6 +1890,8 @@ internal partial class BoundArrayLength | |||
{ | |||
protected override OperationKind ExpressionKind => OperationKind.None; | |||
|
|||
protected override ImmutableArray<IOperation> Children => ImmutableArray.Create<IOperation>(this.Expression); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protected override ImmutableArray Children => ImmutableArray.Create(this.Expression); [](start = 8, length = 109)
I think we can skip this node, it is created only at lowering. #Closed
@@ -767,6 +795,8 @@ internal partial class BoundConditionalGoto | |||
{ | |||
protected override OperationKind StatementKind => OperationKind.None; | |||
|
|||
protected override ImmutableArray<IOperation> Children => ImmutableArray.Create<IOperation>(this.Condition); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protected override ImmutableArray Children => ImmutableArray.Create(this.Condition); [](start = 8, length = 108)
This is a lower level node. #Closed
{ | ||
builder.Add(section); | ||
} | ||
builder.Add(this.DefaultLabel); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
builder.Add(this.DefaultLabel); [](start = 16, length = 31)
It feels like one of the sections refers to this node and this member is just for compiler's convenience. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, in that case I can remove this statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be verified with a test. #Closed
@@ -58,6 +58,8 @@ public bool HasLocals | |||
|
|||
protected override OperationKind ExpressionKind => OperationKind.None; | |||
|
|||
protected override ImmutableArray<IOperation> Children => ImmutableArray.Create<IOperation>(this.Value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protected override ImmutableArray Children => ImmutableArray.Create(this.Value); [](start = 12, length = 104)
This is a lower node, I think we shouldn't touch it. #Closed
Children(2): IOperation: (OperationKind.None) (Syntax: 'var (x, y)') | ||
Children(2): ILocalReferenceExpression: x (OperationKind.LocalReferenceExpression, Type: System.Int32) (Syntax: 'x') | ||
ILocalReferenceExpression: y (OperationKind.LocalReferenceExpression, Type: System.Int32) (Syntax: 'y') | ||
IConversionExpression (ConversionKind.Invalid, Implicit) (OperationKind.ConversionExpression, Type: (System.Int32, System.Int32)) (Syntax: 'point') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IConversionExpression (ConversionKind.Invalid, Implicit) (OperationKind.ConversionExpression, Type: (System.Int32, System.Int32)) (Syntax: 'point') [](start = 4, length = 147)
This looks like a conversion that we might not want to expose. #Closed
} | ||
|
||
[Fact, WorkItem(8884, "https://github.com/dotnet/roslyn/issues/8884")] | ||
public void ParameterReference_PatternSwitchStatement() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ParameterReference_PatternSwitchStatement [](start = 20, length = 41)
It looks like we are missing a test with default label. #Closed
Protected Overrides Function ExpressionKind() As OperationKind | ||
Return OperationKind.None | ||
End Function | ||
|
||
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) [](start = 8, length = 79)
BoundArrayLength is lower node, there is no need to modify it. #Closed
Protected Overrides Function ExpressionKind() As OperationKind | ||
Return OperationKind.None | ||
End Function | ||
|
||
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) [](start = 8, length = 79)
It looks like BoundReferenceAssignment is a lower node, we don't need to modify it. #Closed
Protected Overrides Function ExpressionKind() As OperationKind | ||
Return OperationKind.None | ||
End Function | ||
|
||
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) | ||
Get | ||
Return ImmutableArray.Create(Of IOperation)(Me.OriginalArgument, Me.InConversion, Me.InPlaceholder, Me.OutConversion, Me.OutPlaceholder) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Me.InConversion, Me.InPlaceholder, Me.OutConversion, Me.OutPlaceholder [](start = 81, length = 70)
I think we shouldn't expose these nodes for now. Placeholders are going to be duplicated in conversions. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protected Overrides Function ExpressionKind() As OperationKind | ||
Return OperationKind.None | ||
End Function | ||
|
||
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) [](start = 8, length = 79)
BoundLateBoundArgumentSupportingAssignmentWithCapture looks like a lower node, no need to modify it. #Closed
Protected Overrides Function ExpressionKind() As OperationKind | ||
Return OperationKind.None | ||
End Function | ||
|
||
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) | ||
Get | ||
Return ImmutableArray.Create(Of IOperation)(Me.CapturedGroupOpt, Me.GroupPlaceholderOpt, Me.UnderlyingExpression) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Me.GroupPlaceholderOpt [](start = 81, length = 22)
I think GroupPlaceholderOpt should be excluded from the list, it simply provides identity. #Closed
Protected Overrides Function ExpressionKind() As OperationKind | ||
Return OperationKind.None | ||
End Function | ||
|
||
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) [](start = 8, length = 79)
BoundComplexConditionalAccessReceiver looks like a lower node, there is no need to modify it. #Closed
/// </summary> | ||
protected virtual ImmutableArray<IOperation> Children => ImmutableArray<IOperation>.Empty; | ||
|
||
public virtual void Accept(OperationVisitor visitor) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public virtual void Accept(OperationVisitor visitor) [](start = 8, length = 52)
Should this be abstract instead? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
visitor.VisitNoneOperation(this); | ||
} | ||
|
||
public virtual TResult Accept<TArgument, TResult>(OperationVisitor<TArgument, TResult> visitor, TArgument argument) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public virtual TResult Accept<TArgument, TResult>(OperationVisitor<TArgument, TResult> visitor, TArgument argument) [](start = 8, length = 115)
Should this be abstract instead? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -1714,6 +1794,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic | |||
Return OperationKind.None | |||
End Function | |||
|
|||
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) [](start = 8, length = 79)
BoundConditionalGoto is a lower level node, no need to modify it. #Closed
@@ -1806,6 +1910,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic | |||
Return OperationKind.None | |||
End Function | |||
|
|||
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) [](start = 8, length = 79)
BoundUnstructuredExceptionOnErrorSwitch is a lower level node, no need to modify it. #Closed
Done with review pass. #Closed |
@AlekseyTs The PR is ready for another review. Thanks! |
|
||
internal partial class BoundDeclarationPattern | ||
{ | ||
protected override ImmutableArray<IOperation> Children => ImmutableArray.Create<IOperation>(this.VariableAccess); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protected override ImmutableArray Children => ImmutableArray.Create(this.VariableAccess); [](start = 8, length = 113)
I do not understand why is this changed back? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aleksey, this was breaking a test where we do not get an ILocalReferenceExpression for the variable access.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess, it is fine to expose that node until we figure out how declaration pattern is supposed to be represented. #Closed
I deleted it accidentally in one of the previous commits, and I have added it back in the latest commit. |
It looks like it wasn't added back, at least I don't see it. #Closed |
references: new[] { MscorlibRef, SystemRef, compilation0.EmitToImageReference(embedInteropTypes: true) }); | ||
|
||
string expectedOperationTree = @" | ||
IOperation: (OperationKind.None) (Syntax: 'new I(x)') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like we should have a node for x
. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but the generated bound node doesn't seem to have it. I will file an issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filed #19219
@@ -2375,6 +2426,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic | |||
Return OperationKind.None | |||
End Function | |||
|
|||
Protected Overrides ReadOnly Property Children As ImmutableArray(Of IOperation) | |||
Get | |||
Return ImmutableArray.Create(Of IOperation)(Me.OriginalArgument) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return ImmutableArray.Create(Of IOperation)(Me.OriginalArgument) [](start = 16, length = 64)
Is this code path covered by tests? #Closed
Done with review pass. #Closed |
… as it is never exposed in the operation tree
Also tagging @cston for review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@ManishJayaswal @Pilchie for ask mode approval. |
@@ -9,23 +9,52 @@ | |||
|
|||
namespace Microsoft.CodeAnalysis.CSharp | |||
{ | |||
internal partial class BoundStatement : IOperation | |||
internal partial class BoundNode : IOperation, IOperationWithChildren |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there an advantage to having a separate interface for IOperationWithChildren
? Aren't callers forced to check for an implementation of IOperationWithChildren
, then check .Children.IsEmpty
or perhaps even .IsDefault
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cston This is supposed to be a temporary internal interface that will exist until will we finish IOperation API design and implementation. Only the OperationWalker is aware about this interface to enable the analyzer driver to walk the descendants under unimplemented bound nodes. Having a special interface allows us to then just delete the interface and verify/fix up bound nodes still relying on it.
{ | ||
get | ||
{ | ||
var builder = ImmutableArray.CreateBuilder<IOperation>(this.SwitchSections.Length + 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.SwitchSections.Length + 1
?
|
||
partial class BoundPatternSwitchSection | ||
{ | ||
protected override ImmutableArray<IOperation> Children => this.SwitchLabels.As<IOperation>().AddRange(this.Statements).ToImmutableArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AddRange()
returns an ImmutableArray<T>
. No need for .ToImmutableArray()
.
LGTM |
FYI @heejaechang - once this PR is merged, it will cause merge conflicts in the IOperation feature branch due to your OperationFactory work. We can sit together and resolve these conflicts. |
@dotnet-bot retest windows_debug_unit32_prtest please |
@mavasani sure. |
@dotnet-bot retest windows_debug_unit32_prtest please |
@dotnet-bot retest windows_release_vs-integration_prtest please |
… nodes for whom IOperation support is not yet implemented
Customer scenario
Due to large number of unimplemented IOperation APIs for new language and existing features, all the IOperation analyzers (such as FXCop analyzers) do not receive any operation callbacks for code containing these features (tuples, string interpolation, query expressions, object and collection initializers, etc.). This causes lot of false positives in the analyzer diagnostics as it doesn't see parameter or field references within such code. Additionally, the analyzers do not report true issues in expressions within this language features. This is one of the biggest blocker towards dogfooding the FXCop analyzers.
Bugs this fixes:
Fixes #8884
Workarounds, if any
Disable the rule or suppress all the false positives. All our three repos consuming the FXCop analyzers (Roslyn, Project system and Roslyn-analyzers) were forced to disable these rules due to extremely large number of false positives.
Risk
Low. Proposed fix is to introduce a temporary internal interface
IOperationWithChildren
for bound nodes for which we haven't designed the IOperation APIs or they have missing pieces. This will allow the OperationWalker to walk the descendants of such nodes and make analyzer callbacks and drastically reduce the false positives. This change doesn't modify the publically available IOperation tree or the API, so we should be able to remove it once we have fully designed and implemented IOperation (tracked by #19060)Performance impact
Low. We will now walk additional IOperation nodes and make callbacks, but this is a functional requirement.
Is this a regression from a previous update?
No.
Root cause analysis:
IOperation based analyzers are still being developed and tested.
How was the bug found?
Lots of customer reports about false positives.