Skip to content

Commit beb085a

Browse files
Merge pull request #576 from icsharpcode/issue/396/object-binary-exp
Issue/396/object binary exp
2 parents c63b0b2 + e5a77a1 commit beb085a

File tree

14 files changed

+609
-102
lines changed

14 files changed

+609
-102
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
88

99

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

1618
### C# -> VB
1719

CodeConverter/CSharp/BuiltInVisualBasicOperatorSubsitutions.cs

Lines changed: 453 additions & 0 deletions
Large diffs are not rendered by default.

CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 8 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ internal class ExpressionNodeVisitor : VBasic.VisualBasicSyntaxVisitor<Task<CSha
3232
public CommentConvertingVisitorWrapper TriviaConvertingExpressionVisitor { get; }
3333
private readonly SemanticModel _semanticModel;
3434
private readonly HashSet<string> _extraUsingDirectives;
35+
private readonly IOperatorConverter _operatorConverter;
3536
private readonly bool _optionCompareText = false;
3637
private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison;
3738
private readonly Stack<ExpressionSyntax> _withBlockLhs = new Stack<ExpressionSyntax>();
@@ -56,7 +57,7 @@ public ExpressionNodeVisitor(SemanticModel semanticModel,
5657
_csCompilation = csCompilation;
5758
_typeContext = typeContext;
5859
_extraUsingDirectives = extraUsingDirectives;
59-
60+
_operatorConverter = VbOperatorConversion.Create(TriviaConvertingExpressionVisitor, semanticModel, visualBasicEqualityComparison);
6061
// If this isn't needed, the assembly with Conversions may not be referenced, so this must be done lazily
6162
_convertMethodsLookupByReturnType =
6263
new Lazy<IDictionary<ITypeSymbol, string>>(() => CreateConvertMethodsLookupByReturnType(semanticModel));
@@ -674,7 +675,7 @@ private ExpressionSyntax AsBool(VBSyntax.UnaryExpressionSyntax node, ExpressionS
674675

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

696697
public override async Task<CSharpSyntaxNode> VisitBinaryExpression(VBasic.Syntax.BinaryExpressionSyntax node)
697698
{
698-
if (await ConvertNothingComparisonOrNullAsync(node) is CSharpSyntaxNode nothingComparison) return nothingComparison;
699+
if (await _operatorConverter.ConvertRewrittenBinaryOperatorOrNullAsync(node) is ExpressionSyntax operatorNode) {
700+
return operatorNode;
701+
}
699702

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

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

738741

739-
if (node.IsKind(VBasic.SyntaxKind.ExponentiateExpression,
740-
VBasic.SyntaxKind.ExponentiateAssignmentStatement)) {
741-
return SyntaxFactory.InvocationExpression(
742-
ValidSyntaxFactory.MemberAccess(nameof(Math), nameof(Math.Pow)),
743-
ExpressionSyntaxExtensions.CreateArgList(lhs, rhs));
744-
}
745-
746-
if (node.IsKind(VBasic.SyntaxKind.LikeExpression)) {
747-
var compareText = ValidSyntaxFactory.MemberAccess("CompareMethod", _optionCompareText ? "Text" : "Binary");
748-
var likeString = ValidSyntaxFactory.MemberAccess("LikeOperator", "LikeString");
749-
_extraUsingDirectives.Add("Microsoft.VisualBasic");
750-
_extraUsingDirectives.Add("Microsoft.VisualBasic.CompilerServices");
751-
return SyntaxFactory.InvocationExpression(
752-
likeString,
753-
ExpressionSyntaxExtensions.CreateArgList(lhs, rhs, compareText)
754-
);
755-
}
756-
757742
var kind = VBasic.VisualBasicExtensions.Kind(node).ConvertToken(TokenContext.Local);
758743
var op = SyntaxFactory.Token(CSharpUtil.GetExpressionOperatorTokenKind(kind));
759744

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

764-
private async Task<ExpressionSyntax> ConvertNothingComparisonOrNullAsync(VBSyntax.ExpressionSyntax exprNode, bool negateExpression = false)
765-
{
766-
if (!(exprNode is VBSyntax.BinaryExpressionSyntax node) || !node.IsKind(VBasic.SyntaxKind.IsExpression, VBasic.SyntaxKind.EqualsExpression, VBasic.SyntaxKind.IsNotExpression, VBasic.SyntaxKind.NotEqualsExpression)) {
767-
return null;
768-
}
769-
ExpressionSyntax otherArgument;
770-
if (node.Left.IsKind(VBasic.SyntaxKind.NothingLiteralExpression)) {
771-
otherArgument = (ExpressionSyntax)await ConvertIsOrIsNotExpressionArgAsync(node.Right);
772-
} else if (node.Right.IsKind(VBasic.SyntaxKind.NothingLiteralExpression)) {
773-
otherArgument = (ExpressionSyntax)await ConvertIsOrIsNotExpressionArgAsync(node.Left);
774-
} else {
775-
return null;
776-
}
777-
778-
var isReference = node.IsKind(VBasic.SyntaxKind.IsExpression, VBasic.SyntaxKind.IsNotExpression);
779-
var notted = node.IsKind(VBasic.SyntaxKind.IsNotExpression, VBasic.SyntaxKind.NotEqualsExpression) || negateExpression;
780-
return notted ? CommonConversions.NotNothingComparison(otherArgument, isReference) : CommonConversions.NothingComparison(otherArgument, isReference);
781-
}
782-
783-
private async Task<CSharpSyntaxNode> ConvertIsOrIsNotExpressionArgAsync(VBSyntax.ExpressionSyntax binaryExpressionArg)
784-
{
785-
return await ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(binaryExpressionArg)
786-
?? await binaryExpressionArg.AcceptAsync(TriviaConvertingExpressionVisitor);
787-
}
788-
789-
private async Task<ExpressionSyntax> ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(SyntaxNode node)
790-
{
791-
var operation = _semanticModel.GetOperation(node);
792-
switch (operation)
793-
{
794-
case IConversionOperation co:
795-
return await ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(co.Operand.Syntax);
796-
case IPropertyReferenceOperation pro when pro.Property.IsMyGroupCollectionProperty():
797-
var associatedField = pro.Property.GetAssociatedField();
798-
var propertyReferenceOperation = ((IPropertyReferenceOperation) pro.Instance);
799-
var qualification = (ExpressionSyntax) await propertyReferenceOperation.Syntax.AcceptAsync(TriviaConvertingExpressionVisitor);
800-
return SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, qualification, SyntaxFactory.IdentifierName(associatedField.Name));
801-
default:
802-
return null;
803-
}
804-
}
805-
806749
private async Task<CSharpSyntaxNode> WithRemovedRedundantConversionOrNullAsync(VBSyntax.InvocationExpressionSyntax conversionNode, ISymbol invocationSymbol)
807750
{
808751
if (invocationSymbol.ContainingType.Name != nameof(Conversions) ||
@@ -1521,7 +1464,7 @@ private static CSharpSyntaxNode ReplaceRightmostIdentifierText(CSharpSyntaxNode
15211464
/// </summary>
15221465
private static bool ProbablyNotAMethodCall(VBasic.Syntax.InvocationExpressionSyntax node, ISymbol symbol, ITypeSymbol symbolReturnType)
15231466
{
1524-
return !node.IsParentKind(VBasic.SyntaxKind.CallStatement) && !(symbol is IMethodSymbol) && symbolReturnType.IsErrorType() && node.Expression is VBasic.Syntax.IdentifierNameSyntax && node.ArgumentList?.Arguments.Any() == true;
1467+
return !node.IsParentKind(VBasic.SyntaxKind.CallStatement) && !(symbol is IMethodSymbol) && symbolReturnType.IsErrorType() && node.Expression is VBasic.Syntax.IdentifierNameSyntax && node.ArgumentList?.Arguments.Count() == 1;
15251468
}
15261469

15271470
private async Task<ArgumentListSyntax> ConvertArgumentListOrEmptyAsync(SyntaxNode node, VBSyntax.ArgumentListSyntax argumentList)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+

2+
using System.Threading.Tasks;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
5+
namespace ICSharpCode.CodeConverter.CSharp
6+
{
7+
public interface IOperatorConverter
8+
{
9+
Task<Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax> ConvertNothingComparisonOrNullAsync(Microsoft.CodeAnalysis.VisualBasic.Syntax.ExpressionSyntax exprNode, bool negateExpression = false);
10+
Task<Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax> ConvertRewrittenBinaryOperatorOrNullAsync(Microsoft.CodeAnalysis.VisualBasic.Syntax.BinaryExpressionSyntax node, bool inExpressionLambda = false);
11+
}
12+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Collections.Generic;
2+
using ICSharpCode.CodeConverter.Util;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
5+
6+
namespace ICSharpCode.CodeConverter.CSharp
7+
{
8+
internal struct KnownMethod
9+
{
10+
public string Import;
11+
public string TypeName;
12+
public string MethodName;
13+
14+
public KnownMethod(string import, string typeName, string methodName)
15+
{
16+
this.Import = import;
17+
this.TypeName = typeName;
18+
this.MethodName = methodName;
19+
}
20+
21+
public override bool Equals(object obj) =>
22+
obj is KnownMethod other && Import == other.Import && TypeName == other.TypeName && MethodName == other.MethodName;
23+
24+
public override int GetHashCode() =>
25+
(Import, TypeName, MethodName).GetHashCode();
26+
27+
public static implicit operator KnownMethod((string import, string typeName, string methodName) value) =>
28+
new KnownMethod(value.import, value.typeName, value.methodName);
29+
30+
public ExpressionSyntax Invoke(HashSet<string> extraUsingDirectives, params ExpressionSyntax[] args)
31+
{
32+
extraUsingDirectives.Add(Import);
33+
return SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(TypeName, MethodName), ExpressionSyntaxExtensions.CreateArgList(args));
34+
}
35+
}
36+
}

CodeConverter/CSharp/VisualBasicEqualityComparison.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,10 @@ namespace ICSharpCode.CodeConverter.CSharp
2626
internal class VisualBasicEqualityComparison
2727
{
2828
private readonly SemanticModel _semanticModel;
29-
private readonly HashSet<string> _extraUsingDirectives;
3029

3130
public VisualBasicEqualityComparison(SemanticModel semanticModel, HashSet<string> extraUsingDirectives)
3231
{
33-
_extraUsingDirectives = extraUsingDirectives;
32+
ExtraUsingDirectives = extraUsingDirectives;
3433
_semanticModel = semanticModel;
3534
}
3635

@@ -43,6 +42,8 @@ public enum RequiredType
4342

4443
public bool OptionCompareTextCaseInsensitive { get; set; }
4544

45+
public HashSet<string> ExtraUsingDirectives { get; }
46+
4647
public RequiredType GetObjectEqualityType(VBSyntax.BinaryExpressionSyntax node, TypeInfo leftType, TypeInfo rightType)
4748
{
4849
var typeInfos = new[] {leftType, rightType};
@@ -191,7 +192,7 @@ private static ExpressionSyntax NegateIfNeeded(VBSyntax.BinaryExpressionSyntax n
191192
public (ExpressionSyntax csLeft, ExpressionSyntax csRight) AdjustForVbStringComparison(VBSyntax.ExpressionSyntax vbLeft, ExpressionSyntax csLeft, TypeInfo lhsTypeInfo, VBSyntax.ExpressionSyntax vbRight, ExpressionSyntax csRight, TypeInfo rhsTypeInfo)
192193
{
193194
if (OptionCompareTextCaseInsensitive) {
194-
_extraUsingDirectives.Add("System.Globalization");
195+
ExtraUsingDirectives.Add("System.Globalization");
195196
var compareOptions = SyntaxFactory.Argument(GetCompareTextCaseInsensitiveCompareOptions());
196197
var compareString = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(CultureInfo), nameof(CultureInfo.CurrentCulture),
197198
nameof(CultureInfo.CompareInfo), nameof(CompareInfo.Compare)),
@@ -209,7 +210,7 @@ private static ExpressionSyntax NegateIfNeeded(VBSyntax.BinaryExpressionSyntax n
209210

210211
public ExpressionSyntax GetFullExpressionForVbObjectComparison(VBSyntax.BinaryExpressionSyntax node, ExpressionSyntax lhs, ExpressionSyntax rhs)
211212
{
212-
_extraUsingDirectives.Add("Microsoft.VisualBasic.CompilerServices");
213+
ExtraUsingDirectives.Add("Microsoft.VisualBasic.CompilerServices");
213214
var optionCompareTextCaseInsensitive = SyntaxFactory.Argument(SyntaxFactory.LiteralExpression(OptionCompareTextCaseInsensitive ? SyntaxKind.TrueKeyword : SyntaxKind.FalseLiteralExpression));
214215
var compareObject = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(Operators), nameof(Operators.ConditionalCompareObjectEqual)),
215216
SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[]

CodeConverter/Shared/DefaultReferences.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace ICSharpCode.CodeConverter.Shared
1212
/// <summary>
1313
/// 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.
1414
/// </summary>
15-
public class DefaultReferences
15+
public static class DefaultReferences
1616
{
1717
private static readonly Assembly[] DefaultAssemblies = new []{
1818
typeof(object),

Tests/CSharp/ExpressionTests/ExpressionTests.cs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,22 +1020,17 @@ internal partial class TestClass
10201020
{
10211021
private void TestMethod()
10221022
{
1023-
object test(object a) => a * 2;
1023+
object test(object a) => Operators.MultiplyObject(a, 2);
10241024
object test2(object a, object b)
10251025
{
1026-
if (Conversions.ToBoolean(b > (object)0))
1027-
return a / b;
1026+
if (Conversions.ToBoolean(Operators.ConditionalCompareObjectGreater(b, 0, false)))
1027+
return Operators.DivideObject(a, b);
10281028
return 0;
10291029
};
1030-
object test3(object a, object b) => a % b;
1030+
object test3(object a, object b) => Operators.ModObject(a, b);
10311031
test(3);
10321032
}
1033-
}
1034-
4 target compilation errors:
1035-
CS0019: Operator '*' cannot be applied to operands of type 'object' and 'object'
1036-
CS0019: Operator '>' cannot be applied to operands of type 'object' and 'object'
1037-
CS0019: Operator '/' cannot be applied to operands of type 'object' and 'object'
1038-
CS0019: Operator '%' cannot be applied to operands of type 'object' and 'object'");
1033+
}");
10391034
}
10401035

10411036
[Fact]

Tests/CSharp/ExpressionTests/StringExpressionTests.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public void Foo()
136136
throw new Exception();
137137
}
138138
139-
if (s1 == default)
139+
if (s1 == null)
140140
{
141141
//
142142
}
@@ -199,7 +199,7 @@ public void Foo()
199199
throw new Exception();
200200
}
201201
202-
if (s1 == default)
202+
if (s1 == null)
203203
{
204204
//
205205
}
@@ -341,6 +341,28 @@ public void Foo()
341341
string x = """";
342342
bool y = x == ""something"";
343343
}
344+
}");
345+
}
346+
347+
[Fact]
348+
public async Task Issue396ComparisonOperatorForStringsAsync()
349+
{
350+
await TestConversionVisualBasicToCSharpAsync(
351+
@"Public Class Issue396ComparisonOperatorForStringsAsync
352+
Private str = 1.ToString()
353+
Private b = str > """"
354+
End Class",
355+
@"using Microsoft.VisualBasic.CompilerServices; // Install-Package Microsoft.VisualBasic
356+
357+
public partial class Issue396ComparisonOperatorForStringsAsync
358+
{
359+
public Issue396ComparisonOperatorForStringsAsync()
360+
{
361+
b = Operators.ConditionalCompareObjectGreater(str, """", false);
362+
}
363+
364+
private object str = 1.ToString();
365+
private object b;
344366
}");
345367
}
346368
}

Tests/CSharp/MissingSemanticModelInfo/ExpressionTests.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ internal partial class TestClass
311311
312312
private void TestMethod()
313313
{
314-
var a = DefaultDate[1, 2, 3].Blawer(1, 2, 3);
314+
var a = DefaultDate.Blawer(1, 2, 3);
315315
}
316316
}
317317
2 source compilation errors:
@@ -338,14 +338,13 @@ internal partial class TestClass
338338
private void TestMethod()
339339
{
340340
if (MyEvent is object)
341-
MyEvent[this, EventArgs.Empty];
341+
MyEvent(this, EventArgs.Empty);
342342
}
343343
}
344344
1 source compilation errors:
345345
BC30451: 'MyEvent' is not declared. It may be inaccessible due to its protection level.
346-
2 target compilation errors:
347-
CS0103: The name 'MyEvent' does not exist in the current context
348-
CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement");
346+
1 target compilation errors:
347+
CS0103: The name 'MyEvent' does not exist in the current context");
349348
}
350349

351350
[Fact]

0 commit comments

Comments
 (0)