Skip to content

Restore semantic model functionality in tuple comparisons#25297

Merged
jcouv merged 7 commits intodotnet:features/tuple-equalityfrom
jcouv:tuple-semantic
Mar 9, 2018
Merged

Restore semantic model functionality in tuple comparisons#25297
jcouv merged 7 commits intodotnet:features/tuple-equalityfrom
jcouv:tuple-semantic

Conversation

@jcouv
Copy link
Member

@jcouv jcouv commented Mar 7, 2018

Two parts of this change:

  • adding ConvertedLeft and ConvertedRight to the bound node for tuple comparison. Those are only used by the semantic model, but skipped by other visitors. One subtlety: They cannot be made by applying a conversion on the top-level tuple, but rather we try to give each element an converted type if we can (even if the containing tuple remains typeless).
  • adding a third kind of TupleBinaryOperatorKind to represent null == null element-wise comparison while keeping such nulls typeless.

@jcouv jcouv added this to the 15.7 milestone Mar 7, 2018
@jcouv jcouv self-assigned this Mar 7, 2018
@jcouv jcouv requested a review from a team as a code owner March 7, 2018 05:15
@jcouv jcouv added PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. Feature - Tuples Tuples and removed PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. labels Mar 7, 2018
@jcouv
Copy link
Member Author

jcouv commented Mar 7, 2018

@AlekseyTs @dotnet/roslyn-compiler for review. Thanks
I'm working on a follow-up PR which adds support for default in tuple equality. #Resolved

@gafter gafter self-requested a review March 7, 2018 20:38
@gafter
Copy link
Member

gafter commented Mar 8, 2018

I will aim to look at this tomorrow. #Resolved

@jcouv
Copy link
Member Author

jcouv commented Mar 8, 2018

@AlekseyTs Would you have time to look at two tuple equality PRs? I'm currently looking at your CFG PR. Thanks #Resolved


<Field Name="Left" Type="BoundExpression"/>
<Field Name="Right" Type="BoundExpression"/>
<Field Name="ConvertedLeft" Type="BoundExpression" SkipInVisitor="true"/>
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 8, 2018

Choose a reason for hiding this comment

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

[](start = 4, length = 73)

It would be good to add a comment saying that these properties are meant for SemanticModel use only #Closed

#endif
}

internal enum KindEnum
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 8, 2018

Choose a reason for hiding this comment

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

KindEnum [](start = 22, length = 8)

TupleBinaryOperatorInfoKind? Also, consider moving this declaration to the beginning of the file #Closed

{
internal readonly BinaryOperatorKind Kind;

internal NullNull(BinaryOperatorKind kind, TypeSymbol leftConvertedTypeOpt, TypeSymbol rightConvertedTypeOpt)
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 8, 2018

Choose a reason for hiding this comment

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

TypeSymbol leftConvertedTypeOpt, TypeSymbol rightConvertedTypeOpt [](start = 55, length = 65)

Are these always null? If so, consider removing these parameters and simply passing nulls to the base #Closed

return new BoundTupleBinaryOperator(node, left, right, convertedLeft, convertedRight, kind, operators, resultType);
}

BoundExpression ApplyConvertedTypes(BoundExpression expr, TupleBinaryOperatorInfo @operator, bool isRight, DiagnosticBag diagnostics)
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 8, 2018

Choose a reason for hiding this comment

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

BoundExpression ApplyConvertedTypes(BoundExpression expr, TupleBinaryOperatorInfo @operator, bool isRight, DiagnosticBag diagnostics) [](start = 8, length = 133)

private? #Closed

ImmutableArray<BoundExpression> arguments = tuple.Arguments;
int length = arguments.Length;

Debug.Assert(length == multiple.Operators.Length);
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 8, 2018

Choose a reason for hiding this comment

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

Debug.Assert(length == multiple.Operators.Length); [](start = 16, length = 50)

What about error cases when the multiple.Operators is empty? How do we handle cases like that? #Closed

Copy link
Member Author

@jcouv jcouv Mar 8, 2018

Choose a reason for hiding this comment

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

Yes. That is handled in the next PR (with test hitting it).
Let me know if I should move the fix into this PR instead. #Closed

Copy link
Contributor

Choose a reason for hiding this comment

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

Let me know if I should move the fix into this PR instead.

I think that would be the right thing to do, especially if this wouldn't be the only follow-up change in this PR.


In reply to: 173259632 [](ancestors = 173259632)

var builder = ArrayBuilder<BoundExpression>.GetInstance(length);
for (int i = 0; i < length; i++)
{
builder.Add(ApplyConvertedTypes(arguments[i], multiple.Operators[i], isRight, diagnostics));
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 8, 2018

Choose a reason for hiding this comment

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

builder.Add(ApplyConvertedTypes(arguments[i], multiple.Operators[i], isRight, diagnostics)); [](start = 20, length = 92)

It feels like, instead of applying element-wise conversions explicitly, we should simply apply conversion to the entire literal when the convertedType is not null. Otherwise, we are actually changing the element types but leaving tuple's tuple's type unchanged, then we are trying to convert that hacked literal that contains possibly inconsistent information. That might or might not give us result that we want. Why take chances? I think that explicitly applying element-wise conversions makes sense only for cases when we don't have convertedType. #Closed


// PROTOTYPE(tuple-equality)
return;
var lastY = tupleY.Arguments[1].Expression;
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 8, 2018

Choose a reason for hiding this comment

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

var lastY = tupleY.Arguments[1].Expression; [](start = 11, length = 44)

Consider testing the "x" side as well. #Closed

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Mar 8, 2018

    return t == (null, 1) && t == (""hello"", 1);

Unless other tests cover this, consider testing semantic model information on "t", especially when there are come conversions on its side too. #Closed


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs:589 in 851ae16. [](commit_id = 851ae16, deletion_comment = False)

//Assert.Null(tupleType1.Type);
//Assert.Equal("(System.String, System.String)", tupleType1.ConvertedType.ToTestDisplayString());
var tuple1 = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().ElementAt(0);
Assert.Equal("(s, null)", tuple1.ToString());
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 8, 2018

Choose a reason for hiding this comment

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

null [](start = 30, length = 4)

It would be good to test TypeInfo on "null", unless other tests cover this. #Closed

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Mar 8, 2018

    var t1 = (1, 2L);

Consider testing TypeInfo on t1 #Closed


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs:697 in 851ae16. [](commit_id = 851ae16, deletion_comment = False)

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Mar 8, 2018

    System.Console.Write(t1 == (1L, t2));

Consider testing SymbolInfo on t1 and t2 #Closed


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs:725 in 851ae16. [](commit_id = 851ae16, deletion_comment = False)

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Mar 8, 2018

    System.Console.Write((1, t) == (1, (null, null)));

It would be good to test TypeInfo on a literal, including when a conversion is involved. #Closed


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs:753 in 851ae16. [](commit_id = 851ae16, deletion_comment = False)

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Mar 8, 2018

    System.Console.Write((t, (null, null)) == ((null, null), t));

It would be good to test TypeInfo on constituent parts, the ts, nulls, (null, null) #Closed


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs:891 in 851ae16. [](commit_id = 851ae16, deletion_comment = False)

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Mar 8, 2018

Tests should probably also cover GetConversion API #Closed

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Mar 8, 2018

    System.Console.Write((null, null, null) == (null, () => { }, Main));

It would be good to test behavior on lambda and the method group. Also, on something within the lambda, references to locals, lambda parameters, etc. should still be recognized. #Closed


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs:1018 in 851ae16. [](commit_id = 851ae16, deletion_comment = False)

@AlekseyTs
Copy link
Contributor

And some tests for GetSymbolInfo would be good


In reply to: 371603340 [](ancestors = 371603340)

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Mar 8, 2018

Done with review pass (iteration 4) #Closed

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Mar 8, 2018

Not primarily related to this PR, but should we produce any warnings for name mismatch between elements of compared tuples? #Closed

@jcouv
Copy link
Member Author

jcouv commented Mar 8, 2018

should we produce any warnings for name mismatch between elements of compared tuples?

I don't think so, but I've added a work item to confirm.

Also, I have a PROTOTYPE marker to do tests with tuple names. #Resolved


var newTuple = tuple.Update(argumentNamesOpt: default, inferredNamesOpt: default, convertedArguments, tuple.Type);

return convertedType is null ? newTuple : GenerateConversionForAssignment(convertedType, newTuple, diagnostics);
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 9, 2018

Choose a reason for hiding this comment

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

convertedType is null [](start = 23, length = 21)

It looks like this condition is always true. #Closed

return convertedType is null ? newTuple : GenerateConversionForAssignment(convertedType, newTuple, diagnostics);
}

if (convertedType is null)
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 9, 2018

Choose a reason for hiding this comment

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

Consider merging this with the previous if to avoid checking this multiple times. #Closed

var symbol1 = model.GetTypeInfo(tuple1);
Assert.Null(symbol1.Type);
Assert.Equal("(System.String, System.Int64)", symbol1.ConvertedType.ToTestDisplayString());
Assert.Equal("(System.String, System.Int64)", model.GetDeclaredSymbol(tuple1).ToTestDisplayString());
Copy link
Contributor

@AlekseyTs AlekseyTs Mar 9, 2018

Choose a reason for hiding this comment

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

model.GetDeclaredSymbol(tuple1) [](start = 58, length = 31)

Interesting, does this API work this way on tuple literals in other context? #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 think so, and that is what I expected. A tuple literal is where a tuple and its elements are declared. When in the IDE, tuple.Alice highlights the element.
I'll add a test to confirm.


In reply to: 173336165 [](ancestors = 173336165)

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.

LGTM (iteration 5)

@jcouv
Copy link
Member Author

jcouv commented Mar 9, 2018

Thanks!
Tagging @cston for a second review.

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.

LGTM (iteration 6)

if (leftNull && rightNull)
{
return new TupleBinaryOperatorInfo.NullNull(kind);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we planning on using leftNull and rightNull later in the method? If not, consider inlining so we only check right.IsLiteralNull() if necessary.

@jcouv jcouv merged commit 83f6b06 into dotnet:features/tuple-equality Mar 9, 2018
@jcouv jcouv deleted the tuple-semantic branch March 9, 2018 02:16
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.

4 participants