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

Expose if a Binary/Unary operator was 'Lifted' at the IExpression level. #14779

Merged

Conversation

CyrusNajmabadi
Copy link
Member

Fixes #14767

@CyrusNajmabadi
Copy link
Member Author

Tagging @dotnet/roslyn-compiler @JohnHamby @ManishJayaswal

BinaryOperationKind binaryOperationKind,
IOperation left, IOperation right,
ITypeSymbol resultType,
SyntaxNode syntax, bool isLifted)
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 28, 2016

Choose a reason for hiding this comment

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

, bool isLifted [](start = 29, length = 15)

Consider placing this parameter after the binaryOperationKind. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

i preferred this approach because it made using named-arguments later much easier... But if you prefer it next to the kind, i can oblige :)

@@ -636,11 +636,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
' Use the operator methods. Figure out the precise rules first.
Return Nothing
Else
' Operators are not used here. So this is all simple, non-lifted stepping.
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 28, 2016

Choose a reason for hiding this comment

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

' Operators are not used here. So this is all simple, non-lifted stepping. [](start = 28, length = 75)

Is this true? For example:

        Dim x As Integer?
        Dim y As Integer? = 16
        For x = 12 To y
            System.Console.WriteLine(x)
            If x = 14 Then
                x = Nothing
            End If
        Next
``` #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

The case where there are user defined operators is in the higher 'if' branch (which is currently not implemented for ioperation). That was my reading of the code at least.

Copy link
Contributor

@AlekseyTs AlekseyTs Oct 28, 2016

Choose a reason for hiding this comment

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

The example doesn't use user-defined operators. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

oh. good point... will take a looksee.

@@ -742,6 +760,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End Get
End Property

Private ReadOnly Property IUnaryOperatorExpression_IsLifted As Boolean Implements IBinaryOperatorExpression.IsLifted
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 28, 2016

Choose a reason for hiding this comment

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

IUnaryOperatorExpression_IsLifted As Boolean Implements IBinaryOperatorExpression.IsLifted [](start = 34, length = 90)

Name mismatch. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

D'oh

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

@@ -864,6 +888,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End Get
End Property

Private ReadOnly Property IUnaryOperatorExpression_IsLifted As Boolean Implements IBinaryOperatorExpression.IsLifted
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 28, 2016

Choose a reason for hiding this comment

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

IUnaryOperatorExpression_IsLifted As Boolean Implements IBinaryOperatorExpression.IsLifted [](start = 33, length = 91)

Name mismatch #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Oct 28, 2016

It looks like there are no tests for C#. It looks like not all touched code paths in VB are covered by new tests. Some interesting scenarios are also not covered: user-defined operators, etc. #Closed

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

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

More testing is needed.

@CyrusNajmabadi
Copy link
Member Author

Will add more tests!

@CyrusNajmabadi
Copy link
Member Author

Ok. A bunch more tests added. And i went and walked through lowering (which was insane BTW). I think the logic ends up being fairly straightforward though once you see how the binary expressions finally lower.

@msJohnHamby
Copy link
Contributor

Is lifting a language concept or an implementation concept?

@CyrusNajmabadi
Copy link
Member Author

Is lifting a language concept

It's a language concept. In C#, for example, it's in sections: "6.4.2 Lifted Conversion Operators" and "7.3.7 Lifted Operators".

The languages basically will promote existing operators on value types so that they will work on Nullable versions of those value types. That lifting occurs for both built-in opreators (like + on integers) as well as user defined operators.

This is, for example, what allows you to do the following:

int? a = 1;
int? b = 1;
var c = a + b;

Nullable<int> has no + operator. But it gets one for free from the built-in + operator for int.

@CyrusNajmabadi
Copy link
Member Author

BTW< i like having the baseline-generating system for IOperation tests. It should be very helpful for banging out tons of tests when we get around to enabling this system again.

Note: for places where IOperation synthesizes operations behind the scenes, has there been any thought about having the languages change their default bound representation to just be that form, and then have the current forms be the 'lowered' stage of the operations? I'm a little bit worried about having the languages pre-lower, and then having IOperation attempt to stitch in nodes that it thinks shoudl be there. It would be nice if the normal bound trees were just what IOperation exposed, without needing to translate.

@CyrusNajmabadi
Copy link
Member Author

@AlekseyTs Can you take another look?


namespace Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.Diagnostics
{
public class IOperationTests : CSharpTestBase
Copy link
Contributor

Choose a reason for hiding this comment

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

IOperationTests [](start = 17, length = 15)

I thought we already had IOperation tests that verified tree shape for C# compiler. @JohnHamby, is this the case? Could you point to the location? I think we would prefer to keep tests in one place.

Copy link
Contributor

Choose a reason for hiding this comment

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

There is an IOperationTests.vb file with some tests of the proper form. I think that all C# tests of similar form are in PRs that have yet to be merged. For example, PR 11450 has some examples.

Local_1: System.Int32? y
IVariableDeclarationStatement (1 variables) (OperationKind.VariableDeclarationStatement)
IVariableDeclaration: System.Int32? y (OperationKind.VariableDeclaration)
Initializer: IUnaryOperatorExpression (UnaryOperationKind.IntegerMinus, IsLifted) (OperationKind.UnaryOperatorExpression, Type: System.Int32?)
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 31, 2016

Choose a reason for hiding this comment

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

IsLifted [](start = 80, length = 8)

All tests here are for a "lifted" case. Do we have tests for non-lifted cases? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

I only created non-lifted tests for VB. i'll add some for C# as well!

Copy link
Member Author

Choose a reason for hiding this comment

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

Tests added!

@@ -208,7 +208,8 @@ private void VisitInstanceExpression(IOperation instance)

internal override void VisitNoneOperation(IOperation operation)
{
Assert.True(false, "Encountered an IOperation with `Kind == OperationKind.None` while walking the operation tree.");
Copy link
Contributor

Choose a reason for hiding this comment

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

Assert.True(false, "Encountered an IOperation with Kind == OperationKind.None while walking the operation tree."); [](start = 12, length = 116)

It looks like this is not supposed to happen. Bug?

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct. We have some cases where bound nodes don't get converted to proper operations. I wanted to at least still allow baselines to get generated, rather than having tests that coudln't actually verify anything. We can trivially change this back in the future once we're reasonably sure that we've completed converting all bound nodes to operations.

@@ -790,7 +807,8 @@ public override void VisitLiteralExpression(ILiteralExpression operation)
{
LogString(nameof(ILiteralExpression));

if (operation.ConstantValue.HasValue && operation.ConstantValue.Value.ToString() == operation.Text)
if (operation.ConstantValue.HasValue &&
operation.ConstantValue.Value?.ToString() == operation.Text)
Copy link
Contributor

Choose a reason for hiding this comment

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

operation.ConstantValue.Value?.ToString() == operation.Text [](start = 16, length = 59)

Do we ever want to get in to the if when operation.Text is 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.

Note: this was an issue where the user used the 'null' literal. Null literal is a constant with a constant value of 'null'. so "Value.ToString()" was crashing.

@JohnHamby to comment on what we want to do here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Dunno if this is correct when the value is null and the text is "null". Maybe null requires a special case here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I feel like the appropriate thing to do is just log the constant value. It's not clear to me why we're looking at the operation text at all.

' This will be a lifted comparison if either the control variable or
' limit value is nullable itself.
Dim isLifted = controlVariable.Type.SpecialType = SpecialType.System_Nullable_T OrElse
limitValue.Type.SpecialType = SpecialType.System_Nullable_T
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 31, 2016

Choose a reason for hiding this comment

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

This code looks incorrect, I believe constructed types are never special and always return None from SpecialType property. #Closed

Copy link
Contributor

Choose a reason for hiding this comment

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

There are more cases like this below.

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. Thanks!

@@ -218,6 +218,773 @@ IExpressionStatement (OperationKind.ExpressionStatement)
End Sub

<Fact>
Public Sub VerifyLiftedUnaryOperators1()
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 31, 2016

Choose a reason for hiding this comment

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

VerifyLiftedUnaryOperators1 [](start = 19, length = 27)

Is there similar test for non-lifted scenario? #Closed

End Sub

<Fact>
Public Sub VerifyLiftedUserDefinedUnaryOperators1()
Copy link
Contributor

@AlekseyTs AlekseyTs Oct 31, 2016

Choose a reason for hiding this comment

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

VerifyLiftedUserDefinedUnaryOperators1 [](start = 19, length = 38)

Is there similar test for non-lifted scenario? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Will check!

End Sub

<Fact>
Public Sub VerifyLiftedUserDefinedBinaryOperators1()
Copy link
Contributor

Choose a reason for hiding this comment

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

VerifyLiftedUserDefinedBinaryOperators1 [](start = 19, length = 39)

Are there tests for BoundUserDefinedShortCircuitingOperator?

Copy link
Member Author

Choose a reason for hiding this comment

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

There are now :)


IForLoopStatement (LoopKind.For) (OperationKind.LoopStatement)
Condition: IConditionalChoiceExpression (OperationKind.ConditionalChoiceExpression, Type: System.Boolean, IsInvalid)
Condition: IBinaryOperatorExpression (BinaryOperationKind.Invalid) (OperationKind.BinaryOperatorExpression, Type: System.Boolean, IsInvalid)
Copy link
Contributor

Choose a reason for hiding this comment

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

BinaryOperationKind.Invalid [](start = 46, length = 27)

What does this mean here and below?

@AlekseyTs
Copy link
Contributor

@CyrusNajmabadi Tests for for statements are not readable and, given the issues found in implementation, you didn't verify the output yourself. I will not be verifying them and will not sign-off on them in this form. Please change the tests so they are targeted to the changes that you are making.

@AlekseyTs AlekseyTs closed this Oct 31, 2016
@AlekseyTs AlekseyTs reopened this Oct 31, 2016
Public Sub VerifyLiftedUnaryOperators1()
Dim source = <![CDATA[
Class C
Sub Foo(x as Integer?)
Copy link
Member

Choose a reason for hiding this comment

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

Please rename to F, here and in other tests.

Copy link
Member

Choose a reason for hiding this comment

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

Would FU work? 👼

Copy link
Member

Choose a reason for hiding this comment

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

Please rename here and in other tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

Wait, was this serious? Why would this be renamed?

}

[Fact]
public void VerifyLiftedBinaryOperators1()
Copy link
Member

Choose a reason for hiding this comment

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

The BinaryOperator tests are in _IUnaryOperatorExpression.cs. Should the file be renamed? Similar comment for 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.

yes. i will move.

@CyrusNajmabadi
Copy link
Member Author

@cston Ready for rereview.

/// operator that is defined to work on a value type, 'lifted' operators are
/// created to work on the <see cref="System.Nullable{T}"/> versions of those
/// value types.
/// </summary>
Copy link
Member

Choose a reason for hiding this comment

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

Should the comment use a term other than operator here?

Copy link
Member Author

Choose a reason for hiding this comment

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

#fixed

@@ -1016,6 +1022,13 @@ internal abstract partial class BaseCompoundAssignmentExpression : AssignmentExp
/// </summary>
public BinaryOperationKind BinaryOperationKind { get; }
/// <summary>
/// <code>true</code> if this is a 'Lifted' compound assignment. When there is an
Copy link
Member

Choose a reason for hiding this comment

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

'lifted' (lowercase L) here and in other comments.

Copy link
Member Author

Choose a reason for hiding this comment

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

#fixed


Dim expectedOperationTree = <![CDATA[
IForLoopStatement (LoopKind.For) (OperationKind.LoopStatement) (Syntax: 'For x = 12 ... Next')
Condition: IBinaryOperatorExpression (BinaryOperationKind.IntegerLessThanOrEqual) (OperationKind.BinaryOperatorExpression, Type: System.Boolean) (Syntax: 'y')
Copy link
Member

@cston cston Jul 28, 2017

Choose a reason for hiding this comment

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

This is not a lifted comparison. Is that expected? (It looks like we may have calculated an Int32 value from the limit value before the loop.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Investigating.

Copy link
Member Author

Choose a reason for hiding this comment

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

The compiler is somewhat interesting here... the limit value is actually viewed to have a 'narrowing nullable' conversion from "int?" to "int" here.

I've viewed the IL generated here and that matches what's going on:

IL_0004: call instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
	IL_0009: ldloca.s y
	IL_000b: call instance !0 valuetype [mscorlib]System.Nullable`1<int32>::get_Value()
	IL_0010: stloc.2
	IL_0011: ldc.i4.s 12
	IL_0013: stloc.0
	IL_0014: br.s IL_001a
	// loop start (head: IL_001a)
		IL_0016: ldloc.0
		IL_0017: ldc.i4.1
		IL_0018: add.ovf
		IL_0019: stloc.0

		IL_001a: ldloc.0
		IL_001b: ldloc.2
		IL_001c: ble.s IL_0016
	// end loop

In other words, afaict, the way this works is that instead of comparing against the boxed nullable value as we go through the loop, the boxed nullable is unboxed to get its value (which will throw if it is null), and then we compare against that as we go through. So this seems by design right now.

@CyrusNajmabadi
Copy link
Member Author

@cston I think this is good to go in. Are you satisfied with the explanation for the "non lifted nullable comparison in the for loop" case?

Thanks!

@cston
Copy link
Member

cston commented Aug 1, 2017

LGTM. Please squash commits when merging.

@CyrusNajmabadi
Copy link
Member Author

Thanks!

@CyrusNajmabadi CyrusNajmabadi merged commit e63e0d4 into dotnet:features/ioperation Aug 1, 2017
@CyrusNajmabadi CyrusNajmabadi deleted the isLiftedOperations branch August 1, 2017 21:36
333fred added a commit to 333fred/roslyn that referenced this pull request Aug 4, 2017
…sion-expression-rewrite

* dotnet/features/ioperation: (229 commits)
  Adding Ioperation tests for WhileUntilLoopStatement (dotnet#21047)
  marked assert that needs to be re-enabled
  addressing more PR feedbacks
  PR feedback
  Remove InvocationReasons enum boxing
  PR feedbacks
  Expose if a Binary/Unary operator was 'Lifted' at the IExpression level. (dotnet#14779)
  addressing PR feedback
  added comments
  Update VS Integration machines to 15.3 Preview 6 (dotnet#21240)
  fixed typo
  Fixed dotnet#18763 Compiler crash on bad code in the IDE (dotnet#20903)
  Fix typo in ERR_RefReturnParameter2 (dotnet#21235)
  Fix unbound recursion with const var field in script (dotnet#21223)
  Typo fix (dotnet#20513)
  PR feedbacks and added some more tests
  When producing character literals, surrogate characters should be escaped. (dotnet#20720)
  Fix build correctness issues
  Fix possible null reference warnings
  Adding ioperation tests for ForEachStatement (dotnet#21048)
  ...
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.

9 participants