Skip to content

Commit 625c207

Browse files
Merge pull request #1128 from TymurGubayev/fix/SelectCaseObject/1
convert `Select Case [object]` using `Operators.ConditionalCompareObject...`
2 parents 1dd49d9 + eb6872d commit 625c207

4 files changed

Lines changed: 153 additions & 21 deletions

File tree

CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.CodeAnalysis.Simplification;
1313
using Microsoft.VisualBasic;
1414
using Microsoft.VisualBasic.CompilerServices;
15+
using ComparisonKind = ICSharpCode.CodeConverter.CSharp.VisualBasicEqualityComparison.ComparisonKind;
1516

1617
namespace ICSharpCode.CodeConverter.CSharp;
1718

@@ -916,7 +917,7 @@ private async Task<CSharpSyntaxNode> ConvertBinaryExpressionAsync(VBasic.Syntax.
916917
omitConversion = true; // Already handled within for the appropriate types (rhs can become int in comparison)
917918
break;
918919
case VisualBasicEqualityComparison.RequiredType.Object:
919-
return _visualBasicEqualityComparison.GetFullExpressionForVbObjectComparison(lhs, rhs, node.IsKind(VBasic.SyntaxKind.NotEqualsExpression));
920+
return _visualBasicEqualityComparison.GetFullExpressionForVbObjectComparison(lhs, rhs, ComparisonKind.Equals, node.IsKind(VBasic.SyntaxKind.NotEqualsExpression));
920921
}
921922

922923
var lhsTypeIgnoringNullable = lhsTypeInfo.Type.GetNullableUnderlyingType() ?? lhsTypeInfo.Type;

CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.CodeAnalysis.Text;
88
using ICSharpCode.CodeConverter.Util.FromRoslyn;
99
using ICSharpCode.CodeConverter.VB;
10+
using ComparisonKind = ICSharpCode.CodeConverter.CSharp.VisualBasicEqualityComparison.ComparisonKind;
1011

1112
namespace ICSharpCode.CodeConverter.CSharp;
1213

@@ -744,7 +745,8 @@ public override async Task<SyntaxList<StatementSyntax>> VisitSelectBlock(VBSynta
744745
var csSwitchExpr = await vbExpr.AcceptAsync<ExpressionSyntax>(_expressionVisitor);
745746
csSwitchExpr = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(vbExpr, csSwitchExpr);
746747
var switchExprTypeInfo = _semanticModel.GetTypeInfo(vbExpr);
747-
var isStringComparison = switchExprTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String || switchExprTypeInfo.ConvertedType?.IsArrayOf(SpecialType.System_Char) == true;
748+
var isObjectComparison = switchExprTypeInfo.ConvertedType.SpecialType == SpecialType.System_Object;
749+
var isStringComparison = !isObjectComparison && switchExprTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String || switchExprTypeInfo.ConvertedType?.IsArrayOf(SpecialType.System_Char) == true;
748750
var caseInsensitiveStringComparison = vbEquality.OptionCompareTextCaseInsensitive &&
749751
isStringComparison;
750752
if (isStringComparison) {
@@ -772,32 +774,53 @@ public override async Task<SyntaxList<StatementSyntax>> VisitSelectBlock(VBSynta
772774
var isBooleanCase = caseTypeInfo.Type?.SpecialType == SpecialType.System_Boolean;
773775
bool enumRelated = IsEnumOrNullableEnum(switchExprTypeInfo.ConvertedType) || IsEnumOrNullableEnum(caseTypeInfo.Type);
774776
bool convertingEnum = IsEnumOrNullableEnum(switchExprTypeInfo.ConvertedType) ^ IsEnumOrNullableEnum(caseTypeInfo.Type);
775-
var csExpressionToUse = !isBooleanCase && (convertingEnum || !enumRelated && correctTypeExpressionSyntax.IsConst) ? correctTypeExpressionSyntax.Expr : originalExpressionSyntax;
777+
var csExpressionToUse = !isObjectComparison && !isBooleanCase && (convertingEnum || !enumRelated && correctTypeExpressionSyntax.IsConst)
778+
? correctTypeExpressionSyntax.Expr
779+
: originalExpressionSyntax;
776780

777-
var caseSwitchLabelSyntax = !wrapForStringComparison && correctTypeExpressionSyntax.IsConst && notAlreadyUsed
781+
var caseSwitchLabelSyntax = !isObjectComparison && !wrapForStringComparison && correctTypeExpressionSyntax.IsConst && notAlreadyUsed
778782
? (SwitchLabelSyntax)SyntaxFactory.CaseSwitchLabel(csExpressionToUse)
779783
: WrapInCasePatternSwitchLabelSyntax(node, s.Value, csExpressionToUse, isBooleanCase);
780784
labels.Add(caseSwitchLabelSyntax);
781785
} else if (c is VBSyntax.ElseCaseClauseSyntax) {
782786
labels.Add(SyntaxFactory.DefaultSwitchLabel());
783787
} else if (c is VBSyntax.RelationalCaseClauseSyntax relational) {
784788

785-
var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case"));
786-
ExpressionSyntax csLeft = ValidSyntaxFactory.IdentifierName(varName);
787789
var operatorKind = VBasic.VisualBasicExtensions.Kind(relational);
788790
var csRelationalValue = await relational.Value.AcceptAsync<ExpressionSyntax>(_expressionVisitor);
789-
csRelationalValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(relational.Value, csRelationalValue);
790-
var binaryExp = SyntaxFactory.BinaryExpression(operatorKind.ConvertToken(), csLeft, csRelationalValue);
791-
labels.Add(VarWhen(varName, binaryExp));
791+
CasePatternSwitchLabelSyntax caseSwitchLabelSyntax;
792+
if (isObjectComparison) {
793+
caseSwitchLabelSyntax = WrapInCasePatternSwitchLabelSyntax(node, relational.Value, csRelationalValue, false, operatorKind);
794+
}
795+
else {
796+
var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case"));
797+
ExpressionSyntax csLeft = ValidSyntaxFactory.IdentifierName(varName);
798+
csRelationalValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(relational.Value, csRelationalValue);
799+
var binaryExp = SyntaxFactory.BinaryExpression(operatorKind.ConvertToken(), csLeft, csRelationalValue);
800+
caseSwitchLabelSyntax = VarWhen(varName, binaryExp);
801+
}
802+
labels.Add(caseSwitchLabelSyntax);
792803
} else if (c is VBSyntax.RangeCaseClauseSyntax range) {
793804
var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case"));
794-
ExpressionSyntax csLeft = ValidSyntaxFactory.IdentifierName(varName);
805+
ExpressionSyntax csCaseVar = ValidSyntaxFactory.IdentifierName(varName);
795806
var lowerBound = await range.LowerBound.AcceptAsync<ExpressionSyntax>(_expressionVisitor);
796-
lowerBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.LowerBound, lowerBound);
797-
var lowerBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, lowerBound, csLeft);
807+
ExpressionSyntax lowerBoundCheck;
808+
if (isObjectComparison) {
809+
var caseTypeInfo = _semanticModel.GetTypeInfo(range.LowerBound);
810+
lowerBoundCheck = ComparisonAdjustedForStringComparison(node, range.LowerBound, caseTypeInfo, lowerBound, csCaseVar, switchExprTypeInfo, ComparisonKind.LessThanOrEqual);
811+
} else {
812+
lowerBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.LowerBound, lowerBound);
813+
lowerBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, lowerBound, csCaseVar);
814+
}
798815
var upperBound = await range.UpperBound.AcceptAsync<ExpressionSyntax>(_expressionVisitor);
799-
upperBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.UpperBound, upperBound);
800-
var upperBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, csLeft, upperBound);
816+
ExpressionSyntax upperBoundCheck;
817+
if (isObjectComparison) {
818+
var caseTypeInfo = _semanticModel.GetTypeInfo(range.UpperBound);
819+
upperBoundCheck = ComparisonAdjustedForStringComparison(node, range.UpperBound, switchExprTypeInfo, csCaseVar, upperBound, caseTypeInfo, ComparisonKind.LessThanOrEqual);
820+
} else {
821+
upperBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.UpperBound, upperBound);
822+
upperBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, csCaseVar, upperBound);
823+
}
801824
var withinBounds = SyntaxFactory.BinaryExpression(SyntaxKind.LogicalAndExpression, lowerBoundCheck, upperBoundCheck);
802825
labels.Add(VarWhen(varName, withinBounds));
803826
} else {
@@ -916,7 +939,7 @@ private async Task<bool> IsNeverMutatedAsync(VBSyntax.NameSyntax ns)
916939
return symbol.MatchesKind(SymbolKind.Parameter, SymbolKind.Local) && await CommonConversions.Document.Project.Solution.IsNeverWrittenAsync(symbol, allowedLocation);
917940
}
918941

919-
private CasePatternSwitchLabelSyntax WrapInCasePatternSwitchLabelSyntax(VBSyntax.SelectBlockSyntax node, VBSyntax.ExpressionSyntax vbCase, ExpressionSyntax expression, bool treatAsBoolean = false)
942+
private CasePatternSwitchLabelSyntax WrapInCasePatternSwitchLabelSyntax(VBSyntax.SelectBlockSyntax node, VBSyntax.ExpressionSyntax vbCase, ExpressionSyntax expression, bool treatAsBoolean = false, VBasic.SyntaxKind caseClauseKind = VBasic.SyntaxKind.CaseEqualsClause)
920943
{
921944
var typeInfo = _semanticModel.GetTypeInfo(node.SelectStatement.Expression);
922945

@@ -930,28 +953,48 @@ private CasePatternSwitchLabelSyntax WrapInCasePatternSwitchLabelSyntax(VBSyntax
930953
patternMatch = ValidSyntaxFactory.VarPattern(varName);
931954
ExpressionSyntax csLeft = ValidSyntaxFactory.IdentifierName(varName), csRight = expression;
932955
var caseTypeInfo = _semanticModel.GetTypeInfo(vbCase);
933-
expression = EqualsAdjustedForStringComparison(node, vbCase, typeInfo, csLeft, csRight, caseTypeInfo);
956+
expression = ComparisonAdjustedForStringComparison(node, vbCase, typeInfo, csLeft, csRight, caseTypeInfo, GetComparisonKind(caseClauseKind));
934957
}
935958

936959
var colonToken = SyntaxFactory.Token(SyntaxKind.ColonToken);
937960
return SyntaxFactory.CasePatternSwitchLabel(patternMatch, SyntaxFactory.WhenClause(expression), colonToken);
938961
}
939962

940-
private ExpressionSyntax EqualsAdjustedForStringComparison(VBSyntax.SelectBlockSyntax node, VBSyntax.ExpressionSyntax vbCase, TypeInfo lhsTypeInfo, ExpressionSyntax csLeft, ExpressionSyntax csRight, TypeInfo rhsTypeInfo)
963+
private ComparisonKind GetComparisonKind(VBasic.SyntaxKind caseClauseKind) => caseClauseKind switch {
964+
VBasic.SyntaxKind.CaseLessThanClause => ComparisonKind.LessThan,
965+
VBasic.SyntaxKind.CaseLessThanOrEqualClause => ComparisonKind.LessThanOrEqual,
966+
VBasic.SyntaxKind.CaseEqualsClause => ComparisonKind.Equals,
967+
VBasic.SyntaxKind.CaseNotEqualsClause => ComparisonKind.NotEquals,
968+
VBasic.SyntaxKind.CaseGreaterThanOrEqualClause => ComparisonKind.GreaterThanOrEqual,
969+
VBasic.SyntaxKind.CaseGreaterThanClause => ComparisonKind.GreaterThan,
970+
_ => throw new ArgumentOutOfRangeException(nameof(caseClauseKind), caseClauseKind, null)
971+
};
972+
973+
private ExpressionSyntax ComparisonAdjustedForStringComparison(VBSyntax.SelectBlockSyntax node, VBSyntax.ExpressionSyntax vbCase, TypeInfo lhsTypeInfo, ExpressionSyntax csLeft, ExpressionSyntax csRight, TypeInfo rhsTypeInfo, ComparisonKind comparisonKind)
941974
{
942975
var vbEquality = CommonConversions.VisualBasicEqualityComparison;
943976
switch (_visualBasicEqualityComparison.GetObjectEqualityType(lhsTypeInfo, rhsTypeInfo)) {
944977
case VisualBasicEqualityComparison.RequiredType.Object:
945-
return vbEquality.GetFullExpressionForVbObjectComparison(csLeft, csRight);
978+
return vbEquality.GetFullExpressionForVbObjectComparison(csLeft, csRight, comparisonKind);
946979
case VisualBasicEqualityComparison.RequiredType.StringOnly:
947980
// We know lhs isn't null, because we always coalesce it in the switch expression
948981
(csLeft, csRight) = vbEquality
949982
.AdjustForVbStringComparison(node.SelectStatement.Expression, csLeft, lhsTypeInfo, true, vbCase, csRight, rhsTypeInfo, false);
950983
break;
951984
}
952-
return SyntaxFactory.BinaryExpression(SyntaxKind.EqualsExpression, csLeft, csRight);
985+
return SyntaxFactory.BinaryExpression(GetSyntaxKind(comparisonKind), csLeft, csRight);
953986
}
954987

988+
private CS.SyntaxKind GetSyntaxKind(ComparisonKind comparisonKind) => comparisonKind switch {
989+
ComparisonKind.LessThan => CS.SyntaxKind.LessThanExpression,
990+
ComparisonKind.LessThanOrEqual => CS.SyntaxKind.LessThanOrEqualExpression,
991+
ComparisonKind.Equals => CS.SyntaxKind.EqualsExpression,
992+
ComparisonKind.NotEquals => CS.SyntaxKind.NotEqualsExpression,
993+
ComparisonKind.GreaterThanOrEqual => CS.SyntaxKind.GreaterThanOrEqualExpression,
994+
ComparisonKind.GreaterThan => CS.SyntaxKind.GreaterThanExpression,
995+
_ => throw new ArgumentOutOfRangeException(nameof(comparisonKind), comparisonKind, null)
996+
};
997+
955998
public override async Task<SyntaxList<StatementSyntax>> VisitWithBlock(VBSyntax.WithBlockSyntax node)
956999
{
9571000
var (lhsExpression, prefixDeclarations, _) = await GetExpressionWithoutSideEffectsAsync(node.WithStatement.Expression, "withBlock");

CodeConverter/CSharp/VisualBasicEqualityComparison.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ public enum RequiredType
3333
Object
3434
}
3535

36+
public enum ComparisonKind
37+
{
38+
Unknown,
39+
LessThan,
40+
LessThanOrEqual,
41+
Equals,
42+
NotEquals,
43+
GreaterThanOrEqual,
44+
GreaterThan,
45+
}
46+
3647
public bool OptionCompareTextCaseInsensitive { get; set; }
3748

3849
public LiteralExpressionSyntax OptionCompareTextCaseInsensitiveBoolExpression {
@@ -250,11 +261,11 @@ private static PrefixUnaryExpressionSyntax Negate(InvocationExpressionSyntax pos
250261
return (csLeft, csRight);
251262
}
252263

253-
public ExpressionSyntax GetFullExpressionForVbObjectComparison(ExpressionSyntax lhs, ExpressionSyntax rhs, bool negate = false)
264+
public ExpressionSyntax GetFullExpressionForVbObjectComparison(ExpressionSyntax lhs, ExpressionSyntax rhs, ComparisonKind comparisonKind, bool negate = false)
254265
{
255266
ExtraUsingDirectives.Add("Microsoft.VisualBasic.CompilerServices");
256267
var optionCompareTextCaseInsensitive = SyntaxFactory.Argument(OptionCompareTextCaseInsensitiveBoolExpression);
257-
var compareObject = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(Operators), nameof(Operators.ConditionalCompareObjectEqual)),
268+
var compareObject = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(Operators), GetOperationName(comparisonKind)),
258269
SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[]
259270
{SyntaxFactory.Argument(lhs), SyntaxFactory.Argument(rhs), optionCompareTextCaseInsensitive})));
260271
return negate ? Negate(compareObject) : compareObject;
@@ -266,4 +277,14 @@ private static BinaryExpressionSyntax GetCompareTextCaseInsensitiveCompareOption
266277
SyntaxFactory.BinaryExpression(SyntaxKind.BitwiseOrExpression, ValidSyntaxFactory.MemberAccess(nameof(CompareOptions), nameof(CompareOptions.IgnoreCase)), ValidSyntaxFactory.MemberAccess(nameof(CompareOptions), nameof(CompareOptions.IgnoreKanaType))), ValidSyntaxFactory.MemberAccess(nameof(CompareOptions), nameof(CompareOptions.IgnoreWidth))
267278
);
268279
}
280+
281+
private static string GetOperationName(ComparisonKind comparisonKind) => comparisonKind switch {
282+
ComparisonKind.LessThan => nameof(Operators.ConditionalCompareObjectLess),
283+
ComparisonKind.LessThanOrEqual => nameof(Operators.ConditionalCompareObjectLessEqual),
284+
ComparisonKind.Equals => nameof(Operators.ConditionalCompareObjectEqual),
285+
ComparisonKind.NotEquals => nameof(Operators.ConditionalCompareObjectNotEqual),
286+
ComparisonKind.GreaterThanOrEqual => nameof(Operators.ConditionalCompareObjectGreaterEqual),
287+
ComparisonKind.GreaterThan => nameof(Operators.ConditionalCompareObjectGreater),
288+
_ => throw new ArgumentOutOfRangeException(nameof(comparisonKind), comparisonKind, null)
289+
};
269290
}

Tests/CSharp/ExpressionTests/ExpressionTests.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2296,6 +2296,73 @@ public int OnLoad()
22962296
}");
22972297
}
22982298

2299+
[Fact]
2300+
public async Task SelectCaseObjectCaseIntegerAsync()
2301+
{
2302+
await TestConversionVisualBasicToCSharpAsync(
2303+
@"Public Class SelectObjectCaseIntegerTest
2304+
Sub S()
2305+
Dim o As Object
2306+
Dim j As Integer
2307+
o = 2.0
2308+
Select Case o
2309+
Case 1
2310+
j = 1
2311+
Case 2
2312+
j = 2
2313+
Case 3 To 4
2314+
j = 3
2315+
Case > 4
2316+
j = 4
2317+
Case Else
2318+
j = -1
2319+
End Select
2320+
End Sub
2321+
End Class", @"using Microsoft.VisualBasic.CompilerServices; // Install-Package Microsoft.VisualBasic
2322+
2323+
public partial class SelectObjectCaseIntegerTest
2324+
{
2325+
public void S()
2326+
{
2327+
object o;
2328+
int j;
2329+
o = 2.0d;
2330+
switch (o)
2331+
{
2332+
case var @case when Operators.ConditionalCompareObjectEqual(@case, 1, false):
2333+
{
2334+
j = 1;
2335+
break;
2336+
}
2337+
case var case1 when Operators.ConditionalCompareObjectEqual(case1, 2, false):
2338+
{
2339+
j = 2;
2340+
break;
2341+
}
2342+
case var case2 when Operators.ConditionalCompareObjectLessEqual(3, case2, false) && Operators.ConditionalCompareObjectLessEqual(case2, 4, false):
2343+
{
2344+
j = 3;
2345+
break;
2346+
}
2347+
case var case3 when Operators.ConditionalCompareObjectGreater(case3, 4, false):
2348+
{
2349+
j = 4;
2350+
break;
2351+
}
2352+
2353+
default:
2354+
{
2355+
j = -1;
2356+
break;
2357+
}
2358+
}
2359+
}
2360+
}
2361+
1 target compilation errors:
2362+
CS0825: The contextual keyword 'var' may only appear within a local variable declaration or in script code");
2363+
//BUG: Correct textual output, but requires var pattern syntax construct not available before CodeAnalysis 3
2364+
}
2365+
22992366
[Fact]
23002367
public async Task TupleAsync()
23012368
{

0 commit comments

Comments
 (0)