Skip to content
Merged
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)


### VB -> C#
* Improve post-conversion experience for designer files - [#569](https://github.com/icsharpcode/CodeConverter/issues/569)
* Optimize away some redundant casts and conversions with strings/chars - [#388](https://github.com/icsharpcode/CodeConverter/issues/388)
* Improve performance of single file conversion
* Improve performance of single file conversion - [#546](https://github.com/icsharpcode/CodeConverter/issues/546)
* Add AsEnumerable where needed in linq "in" clause - [#544](https://github.com/icsharpcode/CodeConverter/issues/544)
* Remove redundant empty string coalesce in string comparison - [#500](https://github.com/icsharpcode/CodeConverter/issues/500)
* Convert VB comparison operators - [#396](https://github.com/icsharpcode/CodeConverter/issues/396)

### C# -> VB

Expand Down
453 changes: 453 additions & 0 deletions CodeConverter/CSharp/BuiltInVisualBasicOperatorSubsitutions.cs

Large diffs are not rendered by default.

73 changes: 8 additions & 65 deletions CodeConverter/CSharp/ExpressionNodeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ internal class ExpressionNodeVisitor : VBasic.VisualBasicSyntaxVisitor<Task<CSha
public CommentConvertingVisitorWrapper TriviaConvertingExpressionVisitor { get; }
private readonly SemanticModel _semanticModel;
private readonly HashSet<string> _extraUsingDirectives;
private readonly IOperatorConverter _operatorConverter;
private readonly bool _optionCompareText = false;
private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison;
private readonly Stack<ExpressionSyntax> _withBlockLhs = new Stack<ExpressionSyntax>();
Expand All @@ -56,7 +57,7 @@ public ExpressionNodeVisitor(SemanticModel semanticModel,
_csCompilation = csCompilation;
_typeContext = typeContext;
_extraUsingDirectives = extraUsingDirectives;

_operatorConverter = VbOperatorConversion.Create(TriviaConvertingExpressionVisitor, semanticModel, visualBasicEqualityComparison);
// If this isn't needed, the assembly with Conversions may not be referenced, so this must be done lazily
_convertMethodsLookupByReturnType =
new Lazy<IDictionary<ITypeSymbol, string>>(() => CreateConvertMethodsLookupByReturnType(semanticModel));
Expand Down Expand Up @@ -674,7 +675,7 @@ private ExpressionSyntax AsBool(VBSyntax.UnaryExpressionSyntax node, ExpressionS

private async Task<ExpressionSyntax> NegateAndSimplifyOrNullAsync(VBSyntax.UnaryExpressionSyntax node, ExpressionSyntax expr)
{
if (await ConvertNothingComparisonOrNullAsync(node.Operand, true) is ExpressionSyntax nothingComparison) {
if (await _operatorConverter.ConvertNothingComparisonOrNullAsync(node.Operand, true) is ExpressionSyntax nothingComparison) {
return nothingComparison;
} else if (expr is BinaryExpressionSyntax bes && bes.OperatorToken.IsKind(SyntaxKind.EqualsToken)) {
return bes.WithOperatorToken(SyntaxFactory.Token(SyntaxKind.ExclamationEqualsToken));
Expand All @@ -695,7 +696,9 @@ private CSharpSyntaxNode ConvertAddressOf(VBSyntax.UnaryExpressionSyntax node, E

public override async Task<CSharpSyntaxNode> VisitBinaryExpression(VBasic.Syntax.BinaryExpressionSyntax node)
{
if (await ConvertNothingComparisonOrNullAsync(node) is CSharpSyntaxNode nothingComparison) return nothingComparison;
if (await _operatorConverter.ConvertRewrittenBinaryOperatorOrNullAsync(node) is ExpressionSyntax operatorNode) {
return operatorNode;
}

var lhsTypeInfo = _semanticModel.GetTypeInfo(node.Left);
var rhsTypeInfo = _semanticModel.GetTypeInfo(node.Right);
Expand Down Expand Up @@ -731,78 +734,18 @@ public override async Task<CSharpSyntaxNode> VisitBinaryExpression(VBasic.Syntax

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


if (node.IsKind(VBasic.SyntaxKind.ExponentiateExpression,
VBasic.SyntaxKind.ExponentiateAssignmentStatement)) {
return SyntaxFactory.InvocationExpression(
ValidSyntaxFactory.MemberAccess(nameof(Math), nameof(Math.Pow)),
ExpressionSyntaxExtensions.CreateArgList(lhs, rhs));
}

if (node.IsKind(VBasic.SyntaxKind.LikeExpression)) {
var compareText = ValidSyntaxFactory.MemberAccess("CompareMethod", _optionCompareText ? "Text" : "Binary");
var likeString = ValidSyntaxFactory.MemberAccess("LikeOperator", "LikeString");
_extraUsingDirectives.Add("Microsoft.VisualBasic");
_extraUsingDirectives.Add("Microsoft.VisualBasic.CompilerServices");
return SyntaxFactory.InvocationExpression(
likeString,
ExpressionSyntaxExtensions.CreateArgList(lhs, rhs, compareText)
);
}

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

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

private async Task<ExpressionSyntax> ConvertNothingComparisonOrNullAsync(VBSyntax.ExpressionSyntax exprNode, bool negateExpression = false)
{
if (!(exprNode is VBSyntax.BinaryExpressionSyntax node) || !node.IsKind(VBasic.SyntaxKind.IsExpression, VBasic.SyntaxKind.EqualsExpression, VBasic.SyntaxKind.IsNotExpression, VBasic.SyntaxKind.NotEqualsExpression)) {
return null;
}
ExpressionSyntax otherArgument;
if (node.Left.IsKind(VBasic.SyntaxKind.NothingLiteralExpression)) {
otherArgument = (ExpressionSyntax)await ConvertIsOrIsNotExpressionArgAsync(node.Right);
} else if (node.Right.IsKind(VBasic.SyntaxKind.NothingLiteralExpression)) {
otherArgument = (ExpressionSyntax)await ConvertIsOrIsNotExpressionArgAsync(node.Left);
} else {
return null;
}

var isReference = node.IsKind(VBasic.SyntaxKind.IsExpression, VBasic.SyntaxKind.IsNotExpression);
var notted = node.IsKind(VBasic.SyntaxKind.IsNotExpression, VBasic.SyntaxKind.NotEqualsExpression) || negateExpression;
return notted ? CommonConversions.NotNothingComparison(otherArgument, isReference) : CommonConversions.NothingComparison(otherArgument, isReference);
}

private async Task<CSharpSyntaxNode> ConvertIsOrIsNotExpressionArgAsync(VBSyntax.ExpressionSyntax binaryExpressionArg)
{
return await ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(binaryExpressionArg)
?? await binaryExpressionArg.AcceptAsync(TriviaConvertingExpressionVisitor);
}

private async Task<ExpressionSyntax> ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(SyntaxNode node)
{
var operation = _semanticModel.GetOperation(node);
switch (operation)
{
case IConversionOperation co:
return await ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(co.Operand.Syntax);
case IPropertyReferenceOperation pro when pro.Property.IsMyGroupCollectionProperty():
var associatedField = pro.Property.GetAssociatedField();
var propertyReferenceOperation = ((IPropertyReferenceOperation) pro.Instance);
var qualification = (ExpressionSyntax) await propertyReferenceOperation.Syntax.AcceptAsync(TriviaConvertingExpressionVisitor);
return SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, qualification, SyntaxFactory.IdentifierName(associatedField.Name));
default:
return null;
}
}

private async Task<CSharpSyntaxNode> WithRemovedRedundantConversionOrNullAsync(VBSyntax.InvocationExpressionSyntax conversionNode, ISymbol invocationSymbol)
{
if (invocationSymbol.ContainingType.Name != nameof(Conversions) ||
Expand Down Expand Up @@ -1521,7 +1464,7 @@ private static CSharpSyntaxNode ReplaceRightmostIdentifierText(CSharpSyntaxNode
/// </summary>
private static bool ProbablyNotAMethodCall(VBasic.Syntax.InvocationExpressionSyntax node, ISymbol symbol, ITypeSymbol symbolReturnType)
{
return !node.IsParentKind(VBasic.SyntaxKind.CallStatement) && !(symbol is IMethodSymbol) && symbolReturnType.IsErrorType() && node.Expression is VBasic.Syntax.IdentifierNameSyntax && node.ArgumentList?.Arguments.Any() == true;
return !node.IsParentKind(VBasic.SyntaxKind.CallStatement) && !(symbol is IMethodSymbol) && symbolReturnType.IsErrorType() && node.Expression is VBasic.Syntax.IdentifierNameSyntax && node.ArgumentList?.Arguments.Count() == 1;
}

private async Task<ArgumentListSyntax> ConvertArgumentListOrEmptyAsync(SyntaxNode node, VBSyntax.ArgumentListSyntax argumentList)
Expand Down
12 changes: 12 additions & 0 deletions CodeConverter/CSharp/IOperatorConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace ICSharpCode.CodeConverter.CSharp
{
public interface IOperatorConverter
{
Task<Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax> ConvertNothingComparisonOrNullAsync(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax exprNode, bool negateExpression = false);
Task<Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax> ConvertRewrittenBinaryOperatorOrNullAsync(Microsoft.CodeAnalysis.VisualBasic.Syntax.BinaryExpressionSyntax node, bool inExpressionLambda = false);
}
}
36 changes: 36 additions & 0 deletions CodeConverter/CSharp/KnownMethod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Collections.Generic;
using ICSharpCode.CodeConverter.Util;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace ICSharpCode.CodeConverter.CSharp
{
internal struct KnownMethod
{
public string Import;
public string TypeName;
public string MethodName;

public KnownMethod(string import, string typeName, string methodName)
{
this.Import = import;
this.TypeName = typeName;
this.MethodName = methodName;
}

public override bool Equals(object obj) =>
obj is KnownMethod other && Import == other.Import && TypeName == other.TypeName && MethodName == other.MethodName;

public override int GetHashCode() =>
(Import, TypeName, MethodName).GetHashCode();

public static implicit operator KnownMethod((string import, string typeName, string methodName) value) =>
new KnownMethod(value.import, value.typeName, value.methodName);

public ExpressionSyntax Invoke(HashSet<string> extraUsingDirectives, params ExpressionSyntax[] args)
{
extraUsingDirectives.Add(Import);
return SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(TypeName, MethodName), ExpressionSyntaxExtensions.CreateArgList(args));
}
}
}
9 changes: 5 additions & 4 deletions CodeConverter/CSharp/VisualBasicEqualityComparison.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ namespace ICSharpCode.CodeConverter.CSharp
internal class VisualBasicEqualityComparison
{
private readonly SemanticModel _semanticModel;
private readonly HashSet<string> _extraUsingDirectives;

public VisualBasicEqualityComparison(SemanticModel semanticModel, HashSet<string> extraUsingDirectives)
{
_extraUsingDirectives = extraUsingDirectives;
ExtraUsingDirectives = extraUsingDirectives;
_semanticModel = semanticModel;
}

Expand All @@ -43,6 +42,8 @@ public enum RequiredType

public bool OptionCompareTextCaseInsensitive { get; set; }

public HashSet<string> ExtraUsingDirectives { get; }

public RequiredType GetObjectEqualityType(VBSyntax.BinaryExpressionSyntax node, TypeInfo leftType, TypeInfo rightType)
{
var typeInfos = new[] {leftType, rightType};
Expand Down Expand Up @@ -191,7 +192,7 @@ private static ExpressionSyntax NegateIfNeeded(VBSyntax.BinaryExpressionSyntax n
public (ExpressionSyntax csLeft, ExpressionSyntax csRight) AdjustForVbStringComparison(VBSyntax.ExpressionSyntax vbLeft, ExpressionSyntax csLeft, TypeInfo lhsTypeInfo, VBSyntax.ExpressionSyntax vbRight, ExpressionSyntax csRight, TypeInfo rhsTypeInfo)
{
if (OptionCompareTextCaseInsensitive) {
_extraUsingDirectives.Add("System.Globalization");
ExtraUsingDirectives.Add("System.Globalization");
var compareOptions = SyntaxFactory.Argument(GetCompareTextCaseInsensitiveCompareOptions());
var compareString = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(CultureInfo), nameof(CultureInfo.CurrentCulture),
nameof(CultureInfo.CompareInfo), nameof(CompareInfo.Compare)),
Expand All @@ -209,7 +210,7 @@ private static ExpressionSyntax NegateIfNeeded(VBSyntax.BinaryExpressionSyntax n

public ExpressionSyntax GetFullExpressionForVbObjectComparison(VBSyntax.BinaryExpressionSyntax node, ExpressionSyntax lhs, ExpressionSyntax rhs)
{
_extraUsingDirectives.Add("Microsoft.VisualBasic.CompilerServices");
ExtraUsingDirectives.Add("Microsoft.VisualBasic.CompilerServices");
var optionCompareTextCaseInsensitive = SyntaxFactory.Argument(SyntaxFactory.LiteralExpression(OptionCompareTextCaseInsensitive ? SyntaxKind.TrueKeyword : SyntaxKind.FalseLiteralExpression));
var compareObject = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(Operators), nameof(Operators.ConditionalCompareObjectEqual)),
SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[]
Expand Down
2 changes: 1 addition & 1 deletion CodeConverter/Shared/DefaultReferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace ICSharpCode.CodeConverter.Shared
/// <summary>
/// This file requires net standard 2.0 or above. Therefore it should be linked into projects referencing the converter to get a wider range of references.
/// </summary>
public class DefaultReferences
public static class DefaultReferences
{
private static readonly Assembly[] DefaultAssemblies = new []{
typeof(object),
Expand Down
15 changes: 5 additions & 10 deletions Tests/CSharp/ExpressionTests/ExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1020,22 +1020,17 @@ internal partial class TestClass
{
private void TestMethod()
{
object test(object a) => a * 2;
object test(object a) => Operators.MultiplyObject(a, 2);
object test2(object a, object b)
{
if (Conversions.ToBoolean(b > (object)0))
return a / b;
if (Conversions.ToBoolean(Operators.ConditionalCompareObjectGreater(b, 0, false)))
return Operators.DivideObject(a, b);
return 0;
};
object test3(object a, object b) => a % b;
object test3(object a, object b) => Operators.ModObject(a, b);
test(3);
}
}
4 target compilation errors:
CS0019: Operator '*' cannot be applied to operands of type 'object' and 'object'
CS0019: Operator '>' cannot be applied to operands of type 'object' and 'object'
CS0019: Operator '/' cannot be applied to operands of type 'object' and 'object'
CS0019: Operator '%' cannot be applied to operands of type 'object' and 'object'");
}");
}

[Fact]
Expand Down
26 changes: 24 additions & 2 deletions Tests/CSharp/ExpressionTests/StringExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public void Foo()
throw new Exception();
}

if (s1 == default)
if (s1 == null)
{
//
}
Expand Down Expand Up @@ -199,7 +199,7 @@ public void Foo()
throw new Exception();
}

if (s1 == default)
if (s1 == null)
{
//
}
Expand Down Expand Up @@ -341,6 +341,28 @@ public void Foo()
string x = """";
bool y = x == ""something"";
}
}");
}

[Fact]
public async Task Issue396ComparisonOperatorForStringsAsync()
{
await TestConversionVisualBasicToCSharpAsync(
@"Public Class Issue396ComparisonOperatorForStringsAsync
Private str = 1.ToString()
Private b = str > """"
End Class",
@"using Microsoft.VisualBasic.CompilerServices; // Install-Package Microsoft.VisualBasic

public partial class Issue396ComparisonOperatorForStringsAsync
{
public Issue396ComparisonOperatorForStringsAsync()
{
b = Operators.ConditionalCompareObjectGreater(str, """", false);
}

private object str = 1.ToString();
private object b;
}");
}
}
Expand Down
9 changes: 4 additions & 5 deletions Tests/CSharp/MissingSemanticModelInfo/ExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ internal partial class TestClass

private void TestMethod()
{
var a = DefaultDate[1, 2, 3].Blawer(1, 2, 3);
var a = DefaultDate.Blawer(1, 2, 3);
}
}
2 source compilation errors:
Expand All @@ -338,14 +338,13 @@ internal partial class TestClass
private void TestMethod()
{
if (MyEvent is object)
MyEvent[this, EventArgs.Empty];
MyEvent(this, EventArgs.Empty);
}
}
1 source compilation errors:
BC30451: 'MyEvent' is not declared. It may be inaccessible due to its protection level.
2 target compilation errors:
CS0103: The name 'MyEvent' does not exist in the current context
CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement");
1 target compilation errors:
CS0103: The name 'MyEvent' does not exist in the current context");
}

[Fact]
Expand Down
Loading