Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions CodeConverter/CSharp/BinaryExpressionConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,25 @@ private async Task<CSharpSyntaxNode> ConvertBinaryExpressionAsync(VBasic.Syntax.
case VisualBasicEqualityComparison.RequiredType.StringOnly:
if (lhsTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String &&
rhsTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String &&
_visualBasicEqualityComparison.TryConvertToNullOrEmptyCheck(node, lhs, rhs, out CSharpSyntaxNode visitBinaryExpression)) {
_visualBasicEqualityComparison.TryConvertToNullOrEmptyCheck(node, lhs, rhs, lhsTypeInfo, rhsTypeInfo, out CSharpSyntaxNode visitBinaryExpression)) {
return visitBinaryExpression;
}
(lhs, rhs) = _visualBasicEqualityComparison.AdjustForVbStringComparison(node.Left, lhs, lhsTypeInfo, false, node.Right, rhs, rhsTypeInfo, false);
omitConversion = true; // Already handled within for the appropriate types (rhs can become int in comparison)
if (lhsTypeInfo.Type?.SpecialType == SpecialType.System_Char && rhsTypeInfo.Type?.SpecialType == SpecialType.System_Char) {
// Do nothing, char comparison
} else if (!_visualBasicEqualityComparison.OptionCompareTextCaseInsensitive &&
((lhsTypeInfo.Type?.SpecialType == SpecialType.System_Char && rhsTypeInfo.Type?.SpecialType == SpecialType.System_String && _visualBasicEqualityComparison.IsNothingOrEmpty(node.Right)) ||
(rhsTypeInfo.Type?.SpecialType == SpecialType.System_Char && lhsTypeInfo.Type?.SpecialType == SpecialType.System_String && _visualBasicEqualityComparison.IsNothingOrEmpty(node.Left)))) {
if (lhsTypeInfo.Type?.SpecialType == SpecialType.System_Char) {
rhs = CS.SyntaxFactory.MemberAccessExpression(CS.SyntaxKind.SimpleMemberAccessExpression, CS.SyntaxFactory.PredefinedType(CS.SyntaxFactory.Token(CS.SyntaxKind.CharKeyword)), CS.SyntaxFactory.IdentifierName("MinValue"));
omitConversion = true;
} else {
lhs = CS.SyntaxFactory.MemberAccessExpression(CS.SyntaxKind.SimpleMemberAccessExpression, CS.SyntaxFactory.PredefinedType(CS.SyntaxFactory.Token(CS.SyntaxKind.CharKeyword)), CS.SyntaxFactory.IdentifierName("MinValue"));
omitConversion = true;
}
} else {
(lhs, rhs) = _visualBasicEqualityComparison.AdjustForVbStringComparison(node.Left, lhs, lhsTypeInfo, false, node.Right, rhs, rhsTypeInfo, false);
omitConversion = true; // Already handled within for the appropriate types (rhs can become int in comparison)
}
break;
case VisualBasicEqualityComparison.RequiredType.Object:
return _visualBasicEqualityComparison.GetFullExpressionForVbObjectComparison(lhs, rhs, VisualBasicEqualityComparison.ComparisonKind.Equals, node.IsKind(VBasic.SyntaxKind.NotEqualsExpression));
Expand Down
127 changes: 127 additions & 0 deletions CodeConverter/CSharp/BinaryExpressionConverter.cs.orig
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using ICSharpCode.CodeConverter.Util.FromRoslyn;

namespace ICSharpCode.CodeConverter.CSharp;

internal class BinaryExpressionConverter
{
private readonly SemanticModel _semanticModel;
private readonly IOperatorConverter _operatorConverter;
private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison;
private readonly VisualBasicNullableExpressionsConverter _visualBasicNullableTypesConverter;
public CommonConversions CommonConversions { get; }

public CommentConvertingVisitorWrapper TriviaConvertingExpressionVisitor { get; }

public BinaryExpressionConverter(SemanticModel semanticModel, IOperatorConverter operatorConverter, VisualBasicEqualityComparison visualBasicEqualityComparison,
VisualBasicNullableExpressionsConverter visualBasicNullableTypesConverter, CommonConversions commonConversions)
{
CommonConversions = commonConversions;
_semanticModel = semanticModel;
_operatorConverter = operatorConverter;
_visualBasicEqualityComparison = visualBasicEqualityComparison;
_visualBasicNullableTypesConverter = visualBasicNullableTypesConverter;
TriviaConvertingExpressionVisitor = commonConversions.TriviaConvertingExpressionVisitor;
}

public async Task<CSharpSyntaxNode> ConvertBinaryExpressionAsync(VBSyntax.BinaryExpressionSyntax entryNode)
{
// Walk down the syntax tree for deeply nested binary expressions to avoid stack overflow
// e.g. 3 + 4 + 5 + ...
// Test "DeeplyNestedBinaryExpressionShouldNotStackOverflowAsync()" skipped because it's too slow

CSSyntax.ExpressionSyntax csLhs = null;
int levelsToConvert = 0;
VBSyntax.BinaryExpressionSyntax currentNode = entryNode;

// Walk down the nested levels to count them
for (var nextNode = entryNode; nextNode != null; currentNode = nextNode, nextNode = currentNode.Left as VBSyntax.BinaryExpressionSyntax, levelsToConvert++) {
// Don't go beyond a rewritten operator because that code has many paths that can call VisitBinaryExpression. Passing csLhs through all of that would harm the code quality more than it's worth to help that edge case.
if (await RewriteBinaryOperatorOrNullAsync(nextNode) is { } operatorNode) {
csLhs = operatorNode;
break;
}
}

// Walk back up the same levels converting as we go.
for (; levelsToConvert > 0; currentNode = currentNode!.Parent as VBSyntax.BinaryExpressionSyntax, levelsToConvert--) {
csLhs = (CSSyntax.ExpressionSyntax)await ConvertBinaryExpressionAsync(currentNode, csLhs);
}

return csLhs;
}

private async Task<CSharpSyntaxNode> ConvertBinaryExpressionAsync(VBasic.Syntax.BinaryExpressionSyntax node, CSSyntax.ExpressionSyntax lhs, CSSyntax.ExpressionSyntax rhs = null)
{
lhs ??= await node.Left.AcceptAsync<CSSyntax.ExpressionSyntax>(TriviaConvertingExpressionVisitor);
rhs ??= await node.Right.AcceptAsync<CSSyntax.ExpressionSyntax>(TriviaConvertingExpressionVisitor);

var lhsTypeInfo = _semanticModel.GetTypeInfo(node.Left);
var rhsTypeInfo = _semanticModel.GetTypeInfo(node.Right);

ITypeSymbol forceLhsTargetType = null;
bool omitRightConversion = false;
bool omitConversion = false;
if (lhsTypeInfo.Type != null && rhsTypeInfo.Type != null)
{
if (node.IsKind(VBasic.SyntaxKind.ConcatenateExpression) &&
!lhsTypeInfo.Type.IsEnumType() && !rhsTypeInfo.Type.IsEnumType() &&
!lhsTypeInfo.Type.IsDateType() && !rhsTypeInfo.Type.IsDateType())
{
omitRightConversion = true;
omitConversion = lhsTypeInfo.Type.SpecialType == SpecialType.System_String ||
rhsTypeInfo.Type.SpecialType == SpecialType.System_String;
if (lhsTypeInfo.ConvertedType.SpecialType != SpecialType.System_String) {
forceLhsTargetType = CommonConversions.KnownTypes.String;
}
}
}

var objectEqualityType = _visualBasicEqualityComparison.GetObjectEqualityType(node, lhsTypeInfo, rhsTypeInfo);

switch (objectEqualityType) {
case VisualBasicEqualityComparison.RequiredType.StringOnly:
if (lhsTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String &&
rhsTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String &&
_visualBasicEqualityComparison.TryConvertToNullOrEmptyCheck(node, lhs, rhs, lhsTypeInfo, rhsTypeInfo, out CSharpSyntaxNode visitBinaryExpression)) {
return visitBinaryExpression;
}
if (lhsTypeInfo.Type?.SpecialType == SpecialType.System_Char && rhsTypeInfo.Type?.SpecialType == SpecialType.System_Char) {
// Do nothing, char comparison
} else if ((lhsTypeInfo.Type?.SpecialType == SpecialType.System_Char && rhsTypeInfo.Type?.SpecialType == SpecialType.System_String && _visualBasicEqualityComparison.IsNothingOrEmpty(node.Right)) ||
(rhsTypeInfo.Type?.SpecialType == SpecialType.System_Char && lhsTypeInfo.Type?.SpecialType == SpecialType.System_String && _visualBasicEqualityComparison.IsNothingOrEmpty(node.Left))) {
if (lhsTypeInfo.Type?.SpecialType == SpecialType.System_Char) {
rhs = CS.SyntaxFactory.MemberAccessExpression(CS.SyntaxKind.SimpleMemberAccessExpression, CS.SyntaxFactory.PredefinedType(CS.SyntaxFactory.Token(CS.SyntaxKind.CharKeyword)), CS.SyntaxFactory.IdentifierName("MinValue"));
omitConversion = true;
} else {
lhs = CS.SyntaxFactory.MemberAccessExpression(CS.SyntaxKind.SimpleMemberAccessExpression, CS.SyntaxFactory.PredefinedType(CS.SyntaxFactory.Token(CS.SyntaxKind.CharKeyword)), CS.SyntaxFactory.IdentifierName("MinValue"));
omitConversion = true;
}
} else {
(lhs, rhs) = _visualBasicEqualityComparison.AdjustForVbStringComparison(node.Left, lhs, lhsTypeInfo, false, node.Right, rhs, rhsTypeInfo, false);
omitConversion = true; // Already handled within for the appropriate types (rhs can become int in comparison)
}
break;
case VisualBasicEqualityComparison.RequiredType.Object:
return _visualBasicEqualityComparison.GetFullExpressionForVbObjectComparison(lhs, rhs, VisualBasicEqualityComparison.ComparisonKind.Equals, node.IsKind(VBasic.SyntaxKind.NotEqualsExpression));
}

var lhsTypeIgnoringNullable = lhsTypeInfo.Type.GetNullableUnderlyingType() ?? lhsTypeInfo.Type;
var rhsTypeIgnoringNullable = rhsTypeInfo.Type.GetNullableUnderlyingType() ?? rhsTypeInfo.Type;
omitConversion |= lhsTypeIgnoringNullable != null && rhsTypeIgnoringNullable != null &&
lhsTypeIgnoringNullable.IsEnumType() && SymbolEqualityComparer.Default.Equals(lhsTypeIgnoringNullable, rhsTypeIgnoringNullable)
&& !node.IsKind(VBasic.SyntaxKind.AddExpression, VBasic.SyntaxKind.SubtractExpression, VBasic.SyntaxKind.MultiplyExpression, VBasic.SyntaxKind.DivideExpression, VBasic.SyntaxKind.IntegerDivideExpression, VBasic.SyntaxKind.ModuloExpression)
&& forceLhsTargetType == null;
lhs = omitConversion ? lhs : CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Left, lhs, forceTargetType: forceLhsTargetType);
rhs = omitConversion || omitRightConversion ? rhs : CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, rhs);

var kind = VBasic.VisualBasicExtensions.Kind(node).ConvertToken();
var op = CS.SyntaxFactory.Token(CSharpUtil.GetExpressionOperatorTokenKind(kind));

var csBinExp = CS.SyntaxFactory.BinaryExpression(kind, lhs, op, rhs);
var exp = _visualBasicNullableTypesConverter.WithBinaryExpressionLogicForNullableTypes(node, lhsTypeInfo, rhsTypeInfo, csBinExp, lhs, rhs);
return node.Parent.IsKind(VBasic.SyntaxKind.SimpleArgument) ? exp : exp.AddParens();
}

private async Task<CSSyntax.ExpressionSyntax> RewriteBinaryOperatorOrNullAsync(VBSyntax.BinaryExpressionSyntax node) =>
await _operatorConverter.ConvertRewrittenBinaryOperatorOrNullAsync(node, TriviaConvertingExpressionVisitor.IsWithinQuery);
}
14 changes: 14 additions & 0 deletions CodeConverter/CSharp/BinaryExpressionConverter.cs.rej
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--- BinaryExpressionConverter.cs
+++ BinaryExpressionConverter.cs
@@ -87,8 +87,9 @@
}
if (lhsTypeInfo.Type?.SpecialType == SpecialType.System_Char && rhsTypeInfo.Type?.SpecialType == SpecialType.System_Char) {
// Do nothing, char comparison
- } else if ((lhsTypeInfo.Type?.SpecialType == SpecialType.System_Char && rhsTypeInfo.Type?.SpecialType == SpecialType.System_String && _visualBasicEqualityComparison.IsNothingOrEmpty(node.Right)) ||
- (rhsTypeInfo.Type?.SpecialType == SpecialType.System_Char && lhsTypeInfo.Type?.SpecialType == SpecialType.System_String && _visualBasicEqualityComparison.IsNothingOrEmpty(node.Left))) {
+ } else if (!_visualBasicEqualityComparison.OptionCompareTextCaseInsensitive &&
+ ((lhsTypeInfo.Type?.SpecialType == SpecialType.System_Char && rhsTypeInfo.Type?.SpecialType == SpecialType.System_String && _visualBasicEqualityComparison.IsNothingOrEmpty(node.Right)) ||
+ (rhsTypeInfo.Type?.SpecialType == SpecialType.System_Char && lhsTypeInfo.Type?.SpecialType == SpecialType.System_String && _visualBasicEqualityComparison.IsNothingOrEmpty(node.Left)))) {
if (lhsTypeInfo.Type?.SpecialType == SpecialType.System_Char) {
rhs = CS.SyntaxFactory.MemberAccessExpression(CS.SyntaxKind.SimpleMemberAccessExpression, CS.SyntaxFactory.PredefinedType(CS.SyntaxFactory.Token(CS.SyntaxKind.CharKeyword)), CS.SyntaxFactory.IdentifierName("MinValue"));
omitConversion = true;
11 changes: 9 additions & 2 deletions CodeConverter/CSharp/VisualBasicEqualityComparison.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public RequiredType GetObjectEqualityType(params TypeInfo[] typeInfos)

if (typeInfos.All(
t => t.Type == null || t.Type.SpecialType == SpecialType.System_String ||
t.Type.SpecialType == SpecialType.System_Char ||
t.Type.IsArrayOf(SpecialType.System_Char) ) ) {
return RequiredType.StringOnly;
}
Expand Down Expand Up @@ -177,7 +178,7 @@ private static ObjectCreationExpressionSyntax NewStringFromArg(ExpressionSyntax
}

public bool TryConvertToNullOrEmptyCheck(VBSyntax.BinaryExpressionSyntax node, ExpressionSyntax lhs,
ExpressionSyntax rhs, out CSharpSyntaxNode? visitBinaryExpression)
ExpressionSyntax rhs, TypeInfo lhsTypeInfo, TypeInfo rhsTypeInfo, out CSharpSyntaxNode? visitBinaryExpression)
{
if (OptionCompareTextCaseInsensitive)
{
Expand All @@ -191,6 +192,12 @@ public bool TryConvertToNullOrEmptyCheck(VBSyntax.BinaryExpressionSyntax node, E
if (lhsEmpty || rhsEmpty)
{
var arg = lhsEmpty ? rhs : lhs;
var argType = lhsEmpty ? rhsTypeInfo : lhsTypeInfo;
if (argType.Type?.SpecialType != SpecialType.System_String && argType.Type?.SpecialType != SpecialType.System_Object) {
visitBinaryExpression = null;
return false;
}

var nullOrEmpty = SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.StringKeyword)),
Expand All @@ -207,7 +214,7 @@ public bool TryConvertToNullOrEmptyCheck(VBSyntax.BinaryExpressionSyntax node, E
return false;
}

private bool IsNothingOrEmpty(VBSyntax.ExpressionSyntax expressionSyntax)
public bool IsNothingOrEmpty(VBSyntax.ExpressionSyntax expressionSyntax)
{
expressionSyntax = expressionSyntax.SkipIntoParens();

Expand Down
6 changes: 6 additions & 0 deletions TestDotNet/TestComparison.vbproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
</Project>
23 changes: 23 additions & 0 deletions TestDotNet/TestComparisonCS.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using Microsoft.VisualBasic.CompilerServices;
using System.Globalization;

class Program
{
static void Main()
{
char testChar = default;
Console.WriteLine(CultureInfo.CurrentCulture.CompareInfo.Compare(Conversions.ToString(testChar), "", CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth) == 0);
Console.WriteLine(CultureInfo.CurrentCulture.CompareInfo.Compare(Conversions.ToString(testChar), "a", CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth) == 0);
Console.WriteLine(CultureInfo.CurrentCulture.CompareInfo.Compare("a", Conversions.ToString(testChar), CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth) == 0);
Console.WriteLine(CultureInfo.CurrentCulture.CompareInfo.Compare("ab", Conversions.ToString(testChar), CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth) == 0);

char c = 'a';
Console.WriteLine(CultureInfo.CurrentCulture.CompareInfo.Compare(Conversions.ToString(c), "a", CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth) == 0);
Console.WriteLine(CultureInfo.CurrentCulture.CompareInfo.Compare(Conversions.ToString(c), "ab", CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth) == 0);

string s = "";
Console.WriteLine(CultureInfo.CurrentCulture.CompareInfo.Compare(Conversions.ToString(c), s, CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth) == 0);
Console.WriteLine(CultureInfo.CurrentCulture.CompareInfo.Compare(Conversions.ToString(testChar), s, CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth) == 0);
}
}
6 changes: 6 additions & 0 deletions TestDotNet/TestComparisonCS.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
</Project>
20 changes: 20 additions & 0 deletions TestDotNet/TestComparisonVB.vb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Option Compare Text
Imports System

Module Program
Sub Main()
Dim testChar As Char = Nothing
Console.WriteLine(testChar = "")
Console.WriteLine(testChar = "a")
Console.WriteLine("a" = testChar)
Console.WriteLine("ab" = testChar)

Dim c As Char = "a"c
Console.WriteLine(c = "a")
Console.WriteLine(c = "ab")

Dim s As String = ""
Console.WriteLine(c = s)
Console.WriteLine(testChar = s)
End Sub
End Module
6 changes: 6 additions & 0 deletions TestDotNet/TestComparisonVB.vbproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
</Project>
21 changes: 21 additions & 0 deletions Tests/CSharp/ExpressionTests/StringExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,27 @@ public void Foo()
{
string x = Conversions.ToString(DateTime.Parse(""2022-01-01"")) + "" 15:00"";
}
}");
}

[Fact]
public async Task CharEqualityEmptyStringAsync()
{
await TestConversionVisualBasicToCSharpAsync(@"Class TestClass
Private Sub TestMethod()
Dim testChar As Char = Nothing
Dim testResult = testChar = """"
Dim testResult2 = """" = testChar
End Sub
End Class", @"
internal partial class TestClass
{
private void TestMethod()
{
char testChar = default;
bool testResult = testChar == char.MinValue;
bool testResult2 = char.MinValue == testChar;
}
}");
}
}
Loading
Loading