-
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
Fix IEventAssignmentExpression #20960
Conversation
{ | ||
IEventSymbol @event = boundEventAssignmentOperator.Event; | ||
Lazy<IOperation> instance = new Lazy<IOperation>(() => Create(boundEventAssignmentOperator.Event.IsStatic ? null : boundEventAssignmentOperator.ReceiverOpt)); | ||
ISymbol member = boundEventAssignmentOperator.Event; |
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.
member
is not used.
Lazy<IOperation> instance = new Lazy<IOperation>(() => Create(boundEventAssignmentOperator.Event.IsStatic ? null : boundEventAssignmentOperator.ReceiverOpt)); | ||
ISymbol member = boundEventAssignmentOperator.Event; | ||
// BoundEventAssignmentOperator doesn't hold on to all the data from the provided BoundEventAccess during binding. | ||
// Based on the implementation of those two bound node types, the following data can be retieved w/o changing BoundEventAssignmentOperator: |
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.
retrieved
?
Return New EventAssignmentExpression( | ||
[event], instance, Create(statement.Handler), adds:=False, syntax:=statement.Syntax, type:=Nothing, constantValue:=Nothing) | ||
eventReference, Create(statement.Handler), adds:=False, syntax:=statement.Syntax, type:=Nothing, constantValue:=Nothing) | ||
End Function |
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.
These two methods look identical. Can they be replaced by a single GetAddOrRemoveHandlerStatementExpression
?
|
||
Sub Add(receiver As TestClass) | ||
AddHandler TestEvent, AddressOf M'BIND:"AddHandler TestEvent, AddressOf M" | ||
End Sub |
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.
receiver
is not used in these tests. #Resolved
I think we should do this change in this PR. Is it non-trivial to change the bound node? |
// 1. The type of BoundEventAccess is the type of the event symbol. | ||
// 2. the constant value of BoundEventAccess is always null. | ||
// However, we can't reliably get the syntax node for the BoundEventAccess w/o changing BoundEventAssignmentOperator, so the syntax of entire | ||
// BoundEventAssignmentOperator is used instead. |
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.
Can we cast the syntax to AssignmentOperatorSyntax and use the Left/Target field of this syntax node? This way the correct syntax will be used, and we can optimize the implementation as per your comments later. Also, please file a bug for this TODO.
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.
If the syntax from left can be guaranteed to be correct, then I guess we can do w/o changing bound node after all.
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.
A closer look suggests that the syntax can only be AssignmentOperatorSyntax
for a BoundEventAssignmentOperator
. There's no need to change bound node then.
Lazy<IEventReferenceExpression> eventReference = new Lazy<IEventReferenceExpression>(() => | ||
{ | ||
IEventSymbol @event = boundEventAssignmentOperator.Event; | ||
Lazy<IOperation> instance = new Lazy<IOperation>(() => Create(boundEventAssignmentOperator.Event.IsStatic ? null : boundEventAssignmentOperator.ReceiverOpt)); |
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.
Do we need the conditional expression here, i.e. is there a case where event is static but ReceiverOpt is non-null?
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.
Good point, will fix.
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.
Actually, it seems the conditional is necessary, since the receiver will not be null for static event reference:
http://source.roslyn.io/#Microsoft.CodeAnalysis.CSharp/Binder/Binder_Expressions.cs,5809
@mavasani I think there's a few options:
|
856028a
to
4a418b8
Compare
Dim instance = If([event] Is Nothing OrElse [event].IsStatic, Nothing, If(eventAccess IsNot Nothing, Create(eventAccess.ReceiverOpt), Nothing)) | ||
|
||
Dim eventReference = If(eventAccess Is Nothing, Nothing, CreateBoundEventAccessOperation(eventAccess)) | ||
Dim adds = TypeOf statement Is BoundAddHandlerStatement |
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.
Dim adds = statement.Kind = BoundKind.AddHandlerStatement
{ | ||
if (eventAssignment.EventInstance == null && eventAssignment.HasErrors(operationContext.Compilation, operationContext.CancellationToken)) | ||
if (eventAssignment.EventReference?.Instance == null && eventAssignment.HasErrors(operationContext.Compilation, operationContext.CancellationToken)) | ||
{ | ||
// report inside after checking for null to make sure it does't crash. |
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.
Typo: doesn't
{ | ||
if (eventAssignment.EventInstance == null && eventAssignment.HasErrors(operationContext.Compilation, operationContext.CancellationToken)) | ||
if (eventAssignment.EventReference?.Instance == null && eventAssignment.HasErrors(operationContext.Compilation, operationContext.CancellationToken)) |
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.
When are both EventReference?.Event == null
and EventReference?.Instance == null
? Is that only when EventReference == null
? If so, consider checking that instead.
04d9246
to
0e0e4ef
Compare
@@ -1247,24 +1247,24 @@ public void Next() | |||
string expectedOperationTree = @" | |||
IForLoopStatement (LoopKind.For) (OperationKind.LoopStatement) (Syntax: 'for (d.Init ... }') | |||
Condition: IUnaryOperatorExpression (UnaryOperationKind.DynamicTrue) (OperationKind.UnaryOperatorExpression, Type: System.Boolean) (Syntax: 'd.Done') | |||
Operand: IOperation: (OperationKind.None) (Syntax: 'd.Done') |
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.
@jinujoseph This seems to be caused by two PRs merged around the same time w/o running tests on up-to-date code?
Ping @mavasani @dotnet/roslyn-compiler |
// 2. the constant value of BoundEventAccess is always null. | ||
// 3. the syntax of the boundEventAssignmentOperator is always AssignmentExpressionSyntax, so the syntax for the event reference would be the LHS of the assignment. | ||
IEventSymbol @event = boundEventAssignmentOperator.Event; | ||
Lazy<IOperation> instance = new Lazy<IOperation>(() => Create(boundEventAssignmentOperator.Event.IsStatic ? null : boundEventAssignmentOperator.ReceiverOpt)); |
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.
We should include the receiver in the static
case as well, so the consumer can walk the entire expression even in an error scenario.
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.
Good point. I have made the change for both C# and VB (and I noticed in test that adding handler to static event through an instance is not an error in VB.
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.
As I just mentioned offline, making this change causes us to return a None
operation as the instance derived from a BoundTypeExpression
as BoundEventAssignmentOperator.ReceiverOpt
. If we want to expose as much information as possible in erroneous case, we will need to accurately check for error when creating the EventReferenceOperation
plus modify the interface definition to make it clear that instance could be non null in this case. We probably discuss this during our design meeting.
Meanwhile, I 'm gonna revert this change (but keep those tests) to make the behavior consistent with other related operations, e.g.:
https://github.com/dotnet/roslyn/blob/features/ioperation/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs#L306
Is this OK?
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, we should have consistent behavior for related operations. It sounds reasonable to revert the change for now while we discuss the overall approach.
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.
By the way, there's already an issue tracking the BoundTypeExpression
issue:
#8909
} | ||
|
||
[Fact] | ||
public void AddEventHandler_AssignToStaticEventOnInstance() |
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.
For completeness, please test a reference to a instance event without a receiver, and test the same two cases in VB.
LGTM |
IEventSymbol @event = boundEventAssignmentOperator.Event; | ||
Lazy<IOperation> eventInstance = new Lazy<IOperation>(() => Create(boundEventAssignmentOperator.Event.IsStatic ? null : boundEventAssignmentOperator.ReceiverOpt)); | ||
SyntaxNode syntax = boundEventAssignmentOperator.Syntax; | ||
Lazy<IEventReferenceExpression> eventReference = new Lazy<IEventReferenceExpression>(() => |
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.
Can you refactor this into a separate method CreateBoundEventReferenceExpressionOperation
?
Can I get another review from @dotnet/roslyn-compiler? |
Looking now. |
|
||
namespace Microsoft.CodeAnalysis.CSharp.UnitTests | ||
{ | ||
public partial class IOperationTests : SemanticModelTestBase |
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 should be a trait to tag IOperation tests, if there isn't one already.
Something like [CompilerTrait(CompilerFeature.Tuples)]
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.
@dotnet/analyzer-ioperation Do we have this already? I don't see it in existing tests. I can create one in a separate PR if not.
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.
No we don't ...there is a issue tracking this #20652
We can handle that separately
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
If you don't have a trait for IOperations tests. Please add one in future PR. Thanks
7fa7b67
to
0f17f9e
Compare
@dotnet/roslyn-infrastructure Linux builds are failing with following error, any ideas? I see the
|
@brettfo Is this something we just need to take another merge for? |
Fix #8346
Replaced the event symbol in
IEventAssignmentExpression
withIEventReferenceExpression
Note that for C#, the bound node doesn't contain the syntax for the event reference, even though it was provided during binding. Because of this, the syntax of entire event assignment expression is used for the syntax event reference operation, just so I can get the interface change done first. To address this issue, we probably need to at least change
BoundEventAssignmentOperator
.@dotnet/analyzer-ioperation @cston @jcouv