1+ using ICSharpCode.CodeConverter.Util.FromRoslyn;
2+
3+ namespace ICSharpCode.CodeConverter.CSharp;
4+
5+ internal class BinaryExpressionConverter
6+ {
7+ private readonly SemanticModel _semanticModel;
8+ private readonly IOperatorConverter _operatorConverter;
9+ private readonly VisualBasicEqualityComparison _visualBasicEqualityComparison;
10+ private readonly VisualBasicNullableExpressionsConverter _visualBasicNullableTypesConverter;
11+ public CommonConversions CommonConversions { get; }
12+
13+ public CommentConvertingVisitorWrapper TriviaConvertingExpressionVisitor { get; }
14+
15+ public BinaryExpressionConverter(SemanticModel semanticModel, IOperatorConverter operatorConverter, VisualBasicEqualityComparison visualBasicEqualityComparison,
16+ VisualBasicNullableExpressionsConverter visualBasicNullableTypesConverter, CommonConversions commonConversions)
17+ {
18+ CommonConversions = commonConversions;
19+ _semanticModel = semanticModel;
20+ _operatorConverter = operatorConverter;
21+ _visualBasicEqualityComparison = visualBasicEqualityComparison;
22+ _visualBasicNullableTypesConverter = visualBasicNullableTypesConverter;
23+ TriviaConvertingExpressionVisitor = commonConversions.TriviaConvertingExpressionVisitor;
24+ }
25+
26+ public async Task<CSharpSyntaxNode> ConvertBinaryExpressionAsync(VBSyntax.BinaryExpressionSyntax entryNode)
27+ {
28+ // Walk down the syntax tree for deeply nested binary expressions to avoid stack overflow
29+ // e.g. 3 + 4 + 5 + ...
30+ // Test "DeeplyNestedBinaryExpressionShouldNotStackOverflowAsync()" skipped because it's too slow
31+
32+ CSSyntax.ExpressionSyntax csLhs = null;
33+ int levelsToConvert = 0;
34+ VBSyntax.BinaryExpressionSyntax currentNode = entryNode;
35+
36+ // Walk down the nested levels to count them
37+ for (var nextNode = entryNode; nextNode != null; currentNode = nextNode, nextNode = currentNode.Left as VBSyntax.BinaryExpressionSyntax, levelsToConvert++) {
38+ // 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.
39+ if (await RewriteBinaryOperatorOrNullAsync(nextNode) is { } operatorNode) {
40+ csLhs = operatorNode;
41+ break;
42+ }
43+ }
44+
45+ // Walk back up the same levels converting as we go.
46+ for (; levelsToConvert > 0; currentNode = currentNode!.Parent as VBSyntax.BinaryExpressionSyntax, levelsToConvert--) {
47+ csLhs = (CSSyntax.ExpressionSyntax)await ConvertBinaryExpressionAsync(currentNode, csLhs);
48+ }
49+
50+ return csLhs;
51+ }
52+
53+ private async Task<CSharpSyntaxNode> ConvertBinaryExpressionAsync(VBasic.Syntax.BinaryExpressionSyntax node, CSSyntax.ExpressionSyntax lhs, CSSyntax.ExpressionSyntax rhs = null)
54+ {
55+ lhs ??= await node.Left.AcceptAsync<CSSyntax.ExpressionSyntax>(TriviaConvertingExpressionVisitor);
56+ rhs ??= await node.Right.AcceptAsync<CSSyntax.ExpressionSyntax>(TriviaConvertingExpressionVisitor);
57+
58+ var lhsTypeInfo = _semanticModel.GetTypeInfo(node.Left);
59+ var rhsTypeInfo = _semanticModel.GetTypeInfo(node.Right);
60+
61+ ITypeSymbol forceLhsTargetType = null;
62+ bool omitRightConversion = false;
63+ bool omitConversion = false;
64+ if (lhsTypeInfo.Type != null && rhsTypeInfo.Type != null)
65+ {
66+ if (node.IsKind(VBasic.SyntaxKind.ConcatenateExpression) &&
67+ !lhsTypeInfo.Type.IsEnumType() && !rhsTypeInfo.Type.IsEnumType() &&
68+ !lhsTypeInfo.Type.IsDateType() && !rhsTypeInfo.Type.IsDateType())
69+ {
70+ omitRightConversion = true;
71+ omitConversion = lhsTypeInfo.Type.SpecialType == SpecialType.System_String ||
72+ rhsTypeInfo.Type.SpecialType == SpecialType.System_String;
73+ if (lhsTypeInfo.ConvertedType.SpecialType != SpecialType.System_String) {
74+ forceLhsTargetType = CommonConversions.KnownTypes.String;
75+ }
76+ }
77+ }
78+
79+ var objectEqualityType = _visualBasicEqualityComparison.GetObjectEqualityType(node, lhsTypeInfo, rhsTypeInfo);
80+
81+ switch (objectEqualityType) {
82+ case VisualBasicEqualityComparison.RequiredType.StringOnly:
83+ if (lhsTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String &&
84+ rhsTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String &&
85+ _visualBasicEqualityComparison.TryConvertToNullOrEmptyCheck(node, lhs, rhs, lhsTypeInfo, rhsTypeInfo, out CSharpSyntaxNode visitBinaryExpression)) {
86+ return visitBinaryExpression;
87+ }
88+ if (lhsTypeInfo.Type?.SpecialType == SpecialType.System_Char && rhsTypeInfo.Type?.SpecialType == SpecialType.System_Char) {
89+ // Do nothing, char comparison
90+ } else if ((lhsTypeInfo.Type?.SpecialType == SpecialType.System_Char && rhsTypeInfo.Type?.SpecialType == SpecialType.System_String && _visualBasicEqualityComparison.IsNothingOrEmpty(node.Right)) ||
91+ (rhsTypeInfo.Type?.SpecialType == SpecialType.System_Char && lhsTypeInfo.Type?.SpecialType == SpecialType.System_String && _visualBasicEqualityComparison.IsNothingOrEmpty(node.Left))) {
92+ if (lhsTypeInfo.Type?.SpecialType == SpecialType.System_Char) {
93+ rhs = CS.SyntaxFactory.MemberAccessExpression(CS.SyntaxKind.SimpleMemberAccessExpression, CS.SyntaxFactory.PredefinedType(CS.SyntaxFactory.Token(CS.SyntaxKind.CharKeyword)), CS.SyntaxFactory.IdentifierName("MinValue"));
94+ omitConversion = true;
95+ } else {
96+ lhs = CS.SyntaxFactory.MemberAccessExpression(CS.SyntaxKind.SimpleMemberAccessExpression, CS.SyntaxFactory.PredefinedType(CS.SyntaxFactory.Token(CS.SyntaxKind.CharKeyword)), CS.SyntaxFactory.IdentifierName("MinValue"));
97+ omitConversion = true;
98+ }
99+ } else {
100+ (lhs, rhs) = _visualBasicEqualityComparison.AdjustForVbStringComparison(node.Left, lhs, lhsTypeInfo, false, node.Right, rhs, rhsTypeInfo, false);
101+ omitConversion = true; // Already handled within for the appropriate types (rhs can become int in comparison)
102+ }
103+ break;
104+ case VisualBasicEqualityComparison.RequiredType.Object:
105+ return _visualBasicEqualityComparison.GetFullExpressionForVbObjectComparison(lhs, rhs, VisualBasicEqualityComparison.ComparisonKind.Equals, node.IsKind(VBasic.SyntaxKind.NotEqualsExpression));
106+ }
107+
108+ var lhsTypeIgnoringNullable = lhsTypeInfo.Type.GetNullableUnderlyingType() ?? lhsTypeInfo.Type;
109+ var rhsTypeIgnoringNullable = rhsTypeInfo.Type.GetNullableUnderlyingType() ?? rhsTypeInfo.Type;
110+ omitConversion |= lhsTypeIgnoringNullable != null && rhsTypeIgnoringNullable != null &&
111+ lhsTypeIgnoringNullable.IsEnumType() && SymbolEqualityComparer.Default.Equals(lhsTypeIgnoringNullable, rhsTypeIgnoringNullable)
112+ && !node.IsKind(VBasic.SyntaxKind.AddExpression, VBasic.SyntaxKind.SubtractExpression, VBasic.SyntaxKind.MultiplyExpression, VBasic.SyntaxKind.DivideExpression, VBasic.SyntaxKind.IntegerDivideExpression, VBasic.SyntaxKind.ModuloExpression)
113+ && forceLhsTargetType == null;
114+ lhs = omitConversion ? lhs : CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Left, lhs, forceTargetType: forceLhsTargetType);
115+ rhs = omitConversion || omitRightConversion ? rhs : CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, rhs);
116+
117+ var kind = VBasic.VisualBasicExtensions.Kind(node).ConvertToken();
118+ var op = CS.SyntaxFactory.Token(CSharpUtil.GetExpressionOperatorTokenKind(kind));
119+
120+ var csBinExp = CS.SyntaxFactory.BinaryExpression(kind, lhs, op, rhs);
121+ var exp = _visualBasicNullableTypesConverter.WithBinaryExpressionLogicForNullableTypes(node, lhsTypeInfo, rhsTypeInfo, csBinExp, lhs, rhs);
122+ return node.Parent.IsKind(VBasic.SyntaxKind.SimpleArgument) ? exp : exp.AddParens();
123+ }
124+
125+ private async Task<CSSyntax.ExpressionSyntax> RewriteBinaryOperatorOrNullAsync(VBSyntax.BinaryExpressionSyntax node) =>
126+ await _operatorConverter.ConvertRewrittenBinaryOperatorOrNullAsync(node, TriviaConvertingExpressionVisitor.IsWithinQuery);
127+ }
0 commit comments