Skip to content
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

Merged
merged 15 commits into from
Aug 8, 2017

Conversation

genlu
Copy link
Member

@genlu genlu commented Jul 18, 2017

Fix #8346

Replaced the event symbol in IEventAssignmentExpression with IEventReferenceExpression

    /// <summary>
    /// Represents a binding of an event.
    /// </summary>
    /// <remarks>
    /// This interface is reserved for implementation by its associated APIs. We reserve the right to
    /// change it in the future.
    /// </remarks>
    public interface IEventAssignmentExpression : IOperation
    {
        /// <summary>
        /// Reference to the event being bound.
        /// </summary>
        IEventReferenceExpression EventReference { get; }
 
        /// <summary>
        /// Handler supplied for the event.
        /// </summary>
        IOperation HandlerValue { get; }
 
        /// <summary>
        /// True for adding a binding, false for removing one.
        /// </summary>
        bool Adds { get; }
    }

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

{
IEventSymbol @event = boundEventAssignmentOperator.Event;
Lazy<IOperation> instance = new Lazy<IOperation>(() => Create(boundEventAssignmentOperator.Event.IsStatic ? null : boundEventAssignmentOperator.ReceiverOpt));
ISymbol member = boundEventAssignmentOperator.Event;
Copy link
Member

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:
Copy link
Member

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
Copy link
Member

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
Copy link
Member

@cston cston Jul 19, 2017

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

@mavasani
Copy link
Contributor

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

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.
Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Member Author

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));
Copy link
Contributor

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, will fix.

Copy link
Member Author

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

@genlu
Copy link
Member Author

genlu commented Jul 19, 2017

@mavasani I think there's a few options:

  1. The simplest way to fix the syntax of newly added EventReference is to add a field in BoundEventAssignmentOperator just for the syntax of BoundEventAccess.
  2. similar to the one above, we can capture the entire BoundEventAccess, but keep binding/lowering process unchanged.
  3. This is the approach I prefer, we can "un-lower" the BoundEventAssignmentOperator so it contains BoundEventAccess when exposed to us, and modify rewriter to do the lowering logic currently resides in binder. But I'm not sure if this would affect other things, like perf, etc.

@genlu genlu force-pushed the FixEventAssignment branch from 856028a to 4a418b8 Compare July 21, 2017 19:06
@genlu
Copy link
Member Author

genlu commented Jul 24, 2017

ping... @mavasani @cston @jcouv

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
Copy link
Member

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.
Copy link
Member

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))
Copy link
Member

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.

@genlu genlu force-pushed the FixEventAssignment branch from 04d9246 to 0e0e4ef Compare July 28, 2017 19:55
@@ -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')
Copy link
Member Author

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?

@genlu
Copy link
Member Author

genlu commented Jul 28, 2017

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));
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member Author

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?

Copy link
Member

@cston cston Aug 1, 2017

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.

Copy link
Member Author

@genlu genlu Aug 1, 2017

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()
Copy link
Member

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.

@cston
Copy link
Member

cston commented Aug 1, 2017

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>(() =>
Copy link
Contributor

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?

@genlu
Copy link
Member Author

genlu commented Aug 2, 2017

Can I get another review from @dotnet/roslyn-compiler?

@jcouv
Copy link
Member

jcouv commented Aug 2, 2017

Looking now.


namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
public partial class IOperationTests : SemanticModelTestBase
Copy link
Member

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

Copy link
Member Author

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.

Copy link
Contributor

@jinujoseph jinujoseph Aug 2, 2017

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

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
If you don't have a trait for IOperations tests. Please add one in future PR. Thanks

@genlu genlu force-pushed the FixEventAssignment branch from 7fa7b67 to 0f17f9e Compare August 3, 2017 01:10
@genlu
Copy link
Member Author

genlu commented Aug 3, 2017

@dotnet/roslyn-infrastructure Linux builds are failing with following error, any ideas?
https://ci.dot.net/job/dotnet_roslyn/job/features_ioperation/job/ubuntu_14_debug_prtest/385/

I see the --nocache was removed from groovy script a couple days ago, why is it still being add here?

9:25:16 + ./cibuild.sh --nocache --debug
09:25:16 Runs our integration suite on Linux
09:25:16 usage: cibuild.sh [options]
09:25:16 
09:25:16 Options
09:25:16   --debug               Build Debug (default)
09:25:16   --release             Build Release
09:25:16   --cleanrun            Clean the project before building
09:25:16   --skiptest            Do not run tests
09:25:16   --skipcommitprinting  Do not print commit information
09:25:17 Build step 'Execute shell' marked build as failure

@jasonmalinowski
Copy link
Member

@brettfo Is this something we just need to take another merge for?

@genlu genlu merged commit 77260eb into dotnet:features/ioperation Aug 8, 2017
@genlu genlu deleted the FixEventAssignment branch August 8, 2017 23:45
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.

8 participants