Restore semantic model functionality in tuple comparisons#25297
Restore semantic model functionality in tuple comparisons#25297jcouv merged 7 commits intodotnet:features/tuple-equalityfrom
Conversation
|
@AlekseyTs @dotnet/roslyn-compiler for review. Thanks |
|
I will aim to look at this tomorrow. #Resolved |
|
@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"/> |
There was a problem hiding this comment.
[](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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
var lastY = tupleY.Arguments[1].Expression; [](start = 11, length = 44)
Consider testing the "x" side as well. #Closed
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()); |
There was a problem hiding this comment.
null [](start = 30, length = 4)
It would be good to test TypeInfo on "null", unless other tests cover this. #Closed
|
Tests should probably also cover GetConversion API #Closed |
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) |
|
And some tests for GetSymbolInfo would be good In reply to: 371603340 [](ancestors = 371603340) |
|
Done with review pass (iteration 4) #Closed |
|
Not primarily related to this PR, but should we produce any warnings for name mismatch between elements of compared tuples? #Closed |
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); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
model.GetDeclaredSymbol(tuple1) [](start = 58, length = 31)
Interesting, does this API work this way on tuple literals in other context? #Closed
There was a problem hiding this comment.
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)
|
Thanks! |
| if (leftNull && rightNull) | ||
| { | ||
| return new TupleBinaryOperatorInfo.NullNull(kind); | ||
| } |
There was a problem hiding this comment.
Are we planning on using leftNull and rightNull later in the method? If not, consider inlining so we only check right.IsLiteralNull() if necessary.
Two parts of this change:
ConvertedLeftandConvertedRightto 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).TupleBinaryOperatorKindto representnull == nullelement-wise comparison while keeping suchnulls typeless.