Skip to content

Commit c3f6a44

Browse files
Correct conversion for equality of overloaded types - fixes #594
1 parent 1a7781d commit c3f6a44

8 files changed

Lines changed: 86 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1111
### VB -> C#
1212
* Correct logic for conversion "objectWithOverloadedEquals Is Nothing" [#591](https://github.com/icsharpcode/CodeConverter/issues/591)
1313
* Coercing enum to a string now correctly uses its numeric value [#590](https://github.com/icsharpcode/CodeConverter/issues/590)
14+
* Correct conversion for equality of overloaded types [#594](https://github.com/icsharpcode/CodeConverter/issues/594)
1415

1516
### C# -> VB
1617

CodeConverter/CSharp/BuiltInVisualBasicOperatorSubstitutions.cs

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,31 +44,58 @@ public BuiltInVisualBasicOperatorSubstitutions(CommentConvertingVisitorWrapper t
4444
_typeConversionAnalyzer = typeConversionAnalyzer;
4545
}
4646

47-
public async Task<ExpressionSyntax> ConvertNothingComparisonOrNullAsync(VBSyntax.ExpressionSyntax exprNode, bool negateExpression = false)
47+
public async Task<ExpressionSyntax> ConvertReferenceOrNothingComparisonOrNullAsync(VBSyntax.ExpressionSyntax exprNode, bool negateExpression = false)
4848
{
49-
if (!(exprNode is VBSyntax.BinaryExpressionSyntax node) || !node.IsKind(VBasic.SyntaxKind.IsExpression, VBasic.SyntaxKind.EqualsExpression, VBasic.SyntaxKind.IsNotExpression, VBasic.SyntaxKind.NotEqualsExpression)) {
49+
if (!(exprNode is VBSyntax.BinaryExpressionSyntax node) ||
50+
!node.IsKind(VBasic.SyntaxKind.IsExpression, VBasic.SyntaxKind.EqualsExpression, VBasic.SyntaxKind.IsNotExpression, VBasic.SyntaxKind.NotEqualsExpression)) {
5051
return null;
5152
}
53+
54+
var notted =
55+
node.IsKind(VBasic.SyntaxKind.IsNotExpression, VBasic.SyntaxKind.NotEqualsExpression) ||
56+
negateExpression;
57+
var isReferenceComparison = node.IsKind(VBasic.SyntaxKind.IsExpression, VBasic.SyntaxKind.IsNotExpression);
58+
59+
if (ArgComparedToNull(node) is {} vbOtherArg) {
60+
var csOtherArg = await ConvertIsOrIsNotExpressionArgAsync(vbOtherArg);
61+
return notted
62+
? CommonConversions.NotNothingComparison(csOtherArg, isReferenceComparison)
63+
: CommonConversions.NothingComparison(csOtherArg, isReferenceComparison);
64+
}
5265

53-
VBSyntax.ExpressionSyntax vbOtherArg;
54-
if (node.Left.IsKind(VBasic.SyntaxKind.NothingLiteralExpression)) {
55-
vbOtherArg = node.Right;
56-
} else if (node.Right.IsKind(VBasic.SyntaxKind.NothingLiteralExpression)) {
57-
vbOtherArg = node.Left;
58-
} else {
59-
return null;
66+
if (isReferenceComparison) {
67+
68+
var lhs = await ConvertIsOrIsNotExpressionArgAsync(node.Left);
69+
var rhs = await ConvertIsOrIsNotExpressionArgAsync(node.Right);
70+
71+
var equalityCheck = new KnownMethod(nameof(System), nameof(Object), nameof(object.ReferenceEquals))
72+
.Invoke(_visualBasicEqualityComparison.ExtraUsingDirectives,
73+
ConvertTo(node.Left, lhs, SpecialType.System_Object), rhs);
74+
return notted
75+
? SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, equalityCheck)
76+
: equalityCheck;
77+
}
78+
return null;
79+
}
80+
81+
private static VBSyntax.ExpressionSyntax ArgComparedToNull(VBSyntax.BinaryExpressionSyntax node)
82+
{
83+
if (node.Left.IsKind(VBasic.SyntaxKind.NothingLiteralExpression))
84+
{
85+
return node.Right;
6086
}
61-
var csOtherArg = (ExpressionSyntax)await ConvertIsOrIsNotExpressionArgAsync(vbOtherArg);
62-
var typeSymbol = _semanticModel.GetTypeInfo(vbOtherArg).Type;
63-
var isReferenceComparison = typeSymbol?.IsValueType != true || node.IsKind(VBasic.SyntaxKind.IsExpression, VBasic.SyntaxKind.IsNotExpression);
64-
var notted = node.IsKind(VBasic.SyntaxKind.IsNotExpression, VBasic.SyntaxKind.NotEqualsExpression) || negateExpression;
65-
return notted ? CommonConversions.NotNothingComparison(csOtherArg, isReferenceComparison) : CommonConversions.NothingComparison(csOtherArg, isReferenceComparison);
87+
else if (node.Right.IsKind(VBasic.SyntaxKind.NothingLiteralExpression))
88+
{
89+
return node.Left;
90+
}
91+
92+
return null;
6693
}
6794

68-
private async Task<CSharpSyntaxNode> ConvertIsOrIsNotExpressionArgAsync(VBSyntax.ExpressionSyntax binaryExpressionArg)
95+
private async Task<ExpressionSyntax> ConvertIsOrIsNotExpressionArgAsync(VBSyntax.ExpressionSyntax binaryExpressionArg)
6996
{
70-
return await ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(binaryExpressionArg)
71-
?? await binaryExpressionArg.AcceptAsync(_triviaConvertingVisitor);
97+
return (ExpressionSyntax) (await ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(binaryExpressionArg)
98+
?? await binaryExpressionArg.AcceptAsync(_triviaConvertingVisitor));
7299
}
73100

74101
private async Task<ExpressionSyntax> ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(SyntaxNode node)
@@ -104,9 +131,9 @@ private async Task<ExpressionSyntax> ConvertToPowOperatorAsync(VBSyntax.BinaryEx
104131
.Invoke(_visualBasicEqualityComparison.ExtraUsingDirectives, lhs, rhs);
105132
}
106133

107-
private ExpressionSyntax ConvertTo(VBSyntax.ExpressionSyntax node, ExpressionSyntax lhs, SpecialType targetType)
134+
private ExpressionSyntax ConvertTo(VBSyntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, SpecialType targetType)
108135
{
109-
return _typeConversionAnalyzer.AddExplicitConversion(node, lhs, forceTargetType: _semanticModel.Compilation.GetSpecialType(targetType));
136+
return _typeConversionAnalyzer.AddExplicitConversion(vbNode, csNode, forceTargetType: _semanticModel.Compilation.GetSpecialType(targetType));
110137
}
111138

112139
/// <remarks>No need to implement these since this is only called for things that are already decimal and hence will resolve operator in C#</remarks>
@@ -173,7 +200,8 @@ public async Task<ExpressionSyntax> ConvertRewrittenBinaryOperatorOrNullAsync(VB
173200
switch (opKind) {
174201
case BinaryOperatorKind.IsExpression:
175202
case BinaryOperatorKind.IsNotExpression: {
176-
if (await ConvertNothingComparisonOrNullAsync(node) is CSharpSyntaxNode nothingComparison) return (ExpressionSyntax) nothingComparison;
203+
if (await ConvertReferenceOrNothingComparisonOrNullAsync(node) is { } nothingComparison) return nothingComparison;
204+
177205
break;
178206
}
179207

CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ private ExpressionSyntax AsBool(VBSyntax.UnaryExpressionSyntax node, ExpressionS
679679

680680
private async Task<ExpressionSyntax> NegateAndSimplifyOrNullAsync(VBSyntax.UnaryExpressionSyntax node, ExpressionSyntax expr)
681681
{
682-
if (await _operatorConverter.ConvertNothingComparisonOrNullAsync(node.Operand.SkipIntoParens(), true) is { } nothingComparison) {
682+
if (await _operatorConverter.ConvertReferenceOrNothingComparisonOrNullAsync(node.Operand.SkipIntoParens(), true) is { } nothingComparison) {
683683
return nothingComparison;
684684
} else if (expr is BinaryExpressionSyntax bes && bes.OperatorToken.IsKind(SyntaxKind.EqualsToken)) {
685685
return bes.WithOperatorToken(SyntaxFactory.Token(SyntaxKind.ExclamationEqualsToken));

CodeConverter/CSharp/IOperatorConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace ICSharpCode.CodeConverter.CSharp
66
{
77
public interface IOperatorConverter
88
{
9-
Task<Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax> ConvertNothingComparisonOrNullAsync(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax exprNode, bool negateExpression = false);
9+
Task<Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax> ConvertReferenceOrNothingComparisonOrNullAsync(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax exprNode, bool negateExpression = false);
1010
Task<Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax> ConvertRewrittenBinaryOperatorOrNullAsync(Microsoft.CodeAnalysis.VisualBasic.Syntax.BinaryExpressionSyntax node, bool inExpressionLambda = false);
1111
}
1212
}

Tests/CSharp/ExpressionTests/BinaryExpressionTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ await TestConversionVisualBasicToCSharpAsync(@"Class TestClass
4747
End Class", @"
4848
internal partial class TestClass
4949
{
50-
private bool bIs = new object() == new object();
51-
private bool bIsNot = new object() != new object();
50+
private bool bIs = ReferenceEquals(new object(), new object());
51+
private bool bIsNot = !ReferenceEquals(new object(), new object());
5252
private int bLeftShift = 1 << 3;
5353
private int bRightShift = 8 >> 3;
5454
}");

Tests/TestData/MultiFileCharacterization/VBToCSResults/ConvertWholeSolution/VbNetStandardLib/My Project/MyNamespace.Static.2.Designer.cs

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tests/TestData/MultiFileCharacterization/VBToCSResults/ConvertWholeSolution/WindowsAppVb/My Project/MyNamespace.Dynamic.Designer.cs

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tests/TestData/SelfVerifyingTests/VBToCS/ObjectEqualityTests.vb

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ Imports Xunit
55

66

77
Friend Class CTst
8+
Public Shared Property GetOperatorResult As Func(Of CTst, CTst, Boolean)
9+
810
Public Shared Operator <>(ByVal aCls1 As CTst, ByVal aCls2 As CTst) As Boolean
9-
Assert.Empty("<> operator should not be called")
10-
Return True
11+
Return Not GetOperatorResult()(aCls1, aCls2)
1112
End Operator
1213
Public Shared Operator =(ByVal aCls1 As CTst, ByVal aCls2 As CTst) As Boolean
13-
Assert.Empty("= operator should not be called")
14-
Return True
14+
Return GetOperatorResult()(aCls1, aCls2)
1515
End Operator
1616
End Class
1717

@@ -21,6 +21,10 @@ Public Class ObjectEqualityTests
2121
Public Sub ComparingToNothingDoesNotCallOperatorOverload()
2222
Dim Test1 As CTst
2323

24+
CTst.GetOperatorResult = Function(a, b)
25+
Assert.Empty("operator should not be called")
26+
Return False
27+
End Function
2428
If Not Test1 Is Nothing Then
2529
Assert.Empty("First branch should not be taken")
2630
ElseIf Test1 IsNot Nothing Then
@@ -34,6 +38,7 @@ Public Class ObjectEqualityTests
3438
End If
3539
End Sub
3640

41+
3742
<Fact>
3843
Public Sub GenericCanBeAssignedNothing()
3944
Generic(5, False)
@@ -44,4 +49,23 @@ Public Class ObjectEqualityTests
4449
input = Nothing
4550
If isReference Then Assert.True(input Is Nothing) Else Assert.True(input IsNot Nothing)
4651
End Sub
52+
53+
<Fact>
54+
Public Sub ConstrainedGenericCanBeEquatedToNothing()
55+
Dim callCount = 0
56+
CTst.GetOperatorResult = Function(a, b)
57+
callCount += 1
58+
Return Object.ReferenceEquals(a, b)
59+
End Function
60+
GenericEquatable(New CTst)
61+
Assert.Equal(callCount, 4)
62+
End Sub
63+
64+
Private Sub GenericEquatable(Of T As CTst)(input As T)
65+
Assert.False(input = Nothing)
66+
Assert.True(input <> Nothing)
67+
input = Nothing
68+
Assert.True(input = Nothing)
69+
Assert.False(input <> Nothing)
70+
End Sub
4771
End Class

0 commit comments

Comments
 (0)