Skip to content

Commit 76b1e25

Browse files
Merge pull request #1257 from GrahamTheCoder/claude/fix-issue-780-select-case-range-cast
Fix #780: Cast enum values to underlying type in Select Case range comparisons
2 parents cdbab2a + 45a048c commit 76b1e25

File tree

2 files changed

+62
-2
lines changed

2 files changed

+62
-2
lines changed

CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -861,7 +861,8 @@ public override async Task<SyntaxList<StatementSyntax>> VisitSelectBlock(VBSynta
861861
lowerBoundCheck = ComparisonAdjustedForStringComparison(node, range.LowerBound, caseTypeInfo, lowerBound, csCaseVar, switchExprTypeInfo, ComparisonKind.LessThanOrEqual);
862862
} else {
863863
lowerBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.LowerBound, lowerBound);
864-
lowerBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, lowerBound, csCaseVar);
864+
var (lowerBoundForComparison, csCaseVarForLower) = CastBothToUnderlyingTypeIfEnum(switchExprTypeInfo.ConvertedType, lowerBound, csCaseVar);
865+
lowerBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, lowerBoundForComparison, csCaseVarForLower);
865866
}
866867
var upperBound = await range.UpperBound.AcceptAsync<ExpressionSyntax>(_expressionVisitor);
867868
ExpressionSyntax upperBoundCheck;
@@ -870,7 +871,8 @@ public override async Task<SyntaxList<StatementSyntax>> VisitSelectBlock(VBSynta
870871
upperBoundCheck = ComparisonAdjustedForStringComparison(node, range.UpperBound, switchExprTypeInfo, csCaseVar, upperBound, caseTypeInfo, ComparisonKind.LessThanOrEqual);
871872
} else {
872873
upperBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.UpperBound, upperBound);
873-
upperBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, csCaseVar, upperBound);
874+
var (csCaseVarForUpper, upperBoundForComparison) = CastBothToUnderlyingTypeIfEnum(switchExprTypeInfo.ConvertedType, csCaseVar, upperBound);
875+
upperBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, csCaseVarForUpper, upperBoundForComparison);
874876
}
875877
var withinBounds = SyntaxFactory.BinaryExpression(SyntaxKind.LogicalAndExpression, lowerBoundCheck, upperBoundCheck);
876878
labels.Add(VarWhen(varName, withinBounds));
@@ -930,6 +932,21 @@ bool IsExitStatement(StatementSyntax node)
930932
private static bool IsEnumOrNullableEnum(ITypeSymbol convertedType) =>
931933
convertedType?.IsEnumType() == true || convertedType?.GetNullableUnderlyingType()?.IsEnumType() == true;
932934

935+
/// <summary>
936+
/// When the switch expression is an enum, C# does not support &lt;= comparisons directly on enum values.
937+
/// Cast both sides to the enum's underlying integer type so the comparison compiles.
938+
/// </summary>
939+
private (ExpressionSyntax Left, ExpressionSyntax Right) CastBothToUnderlyingTypeIfEnum(ITypeSymbol switchExprType, ExpressionSyntax left, ExpressionSyntax right)
940+
{
941+
var enumType = switchExprType?.IsEnumType() == true ? switchExprType as INamedTypeSymbol
942+
: switchExprType?.GetNullableUnderlyingType() as INamedTypeSymbol;
943+
if (enumType?.EnumUnderlyingType is not { } underlyingType) {
944+
return (left, right);
945+
}
946+
var typeSyntax = CommonConversions.GetTypeSyntax(underlyingType);
947+
return (ValidSyntaxFactory.CastExpression(typeSyntax, left), ValidSyntaxFactory.CastExpression(typeSyntax, right));
948+
}
949+
933950
private static CasePatternSwitchLabelSyntax VarWhen(SyntaxToken varName, ExpressionSyntax binaryExp)
934951
{
935952
var patternMatch = ValidSyntaxFactory.VarPattern(varName);

Tests/CSharp/StatementTests/MethodStatementTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,49 @@ public static string TimeAgo(int daysAgo)
11111111
CS0825: The contextual keyword 'var' may only appear within a local variable declaration or in script code");
11121112
}
11131113

1114+
[Fact]
1115+
public async Task SelectCaseWithEnumRangeAsync()
1116+
{
1117+
await TestConversionVisualBasicToCSharpAsync(@"Public Class TestClass
1118+
Enum UserLevel
1119+
City_Staff
1120+
Admin
1121+
Fixity_ROOT
1122+
End Enum
1123+
1124+
Shared Function IsPrivileged(level As UserLevel) As Boolean
1125+
Select Case level
1126+
Case UserLevel.City_Staff To UserLevel.Fixity_ROOT
1127+
Return True
1128+
End Select
1129+
Return False
1130+
End Function
1131+
End Class", @"
1132+
public partial class TestClass
1133+
{
1134+
public enum UserLevel
1135+
{
1136+
City_Staff,
1137+
Admin,
1138+
Fixity_ROOT
1139+
}
1140+
1141+
public static bool IsPrivileged(UserLevel level)
1142+
{
1143+
switch (level)
1144+
{
1145+
case var @case when (int)UserLevel.City_Staff <= (int)@case && (int)@case <= (int)UserLevel.Fixity_ROOT:
1146+
{
1147+
return true;
1148+
}
1149+
}
1150+
return false;
1151+
}
1152+
}
1153+
1 target compilation errors:
1154+
CS0825: The contextual keyword 'var' may only appear within a local variable declaration or in script code");
1155+
}
1156+
11141157
[Fact]
11151158
public async Task SelectCaseWithStringAsync()
11161159
{

0 commit comments

Comments
 (0)