diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index bc48a7dc..b526d07e 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -861,7 +861,8 @@ public override async Task> VisitSelectBlock(VBSynta lowerBoundCheck = ComparisonAdjustedForStringComparison(node, range.LowerBound, caseTypeInfo, lowerBound, csCaseVar, switchExprTypeInfo, ComparisonKind.LessThanOrEqual); } else { lowerBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.LowerBound, lowerBound); - lowerBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, lowerBound, csCaseVar); + var (lowerBoundForComparison, csCaseVarForLower) = CastBothToUnderlyingTypeIfEnum(switchExprTypeInfo.ConvertedType, lowerBound, csCaseVar); + lowerBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, lowerBoundForComparison, csCaseVarForLower); } var upperBound = await range.UpperBound.AcceptAsync(_expressionVisitor); ExpressionSyntax upperBoundCheck; @@ -870,7 +871,8 @@ public override async Task> VisitSelectBlock(VBSynta upperBoundCheck = ComparisonAdjustedForStringComparison(node, range.UpperBound, switchExprTypeInfo, csCaseVar, upperBound, caseTypeInfo, ComparisonKind.LessThanOrEqual); } else { upperBound = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(range.UpperBound, upperBound); - upperBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, csCaseVar, upperBound); + var (csCaseVarForUpper, upperBoundForComparison) = CastBothToUnderlyingTypeIfEnum(switchExprTypeInfo.ConvertedType, csCaseVar, upperBound); + upperBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, csCaseVarForUpper, upperBoundForComparison); } var withinBounds = SyntaxFactory.BinaryExpression(SyntaxKind.LogicalAndExpression, lowerBoundCheck, upperBoundCheck); labels.Add(VarWhen(varName, withinBounds)); @@ -930,6 +932,21 @@ bool IsExitStatement(StatementSyntax node) private static bool IsEnumOrNullableEnum(ITypeSymbol convertedType) => convertedType?.IsEnumType() == true || convertedType?.GetNullableUnderlyingType()?.IsEnumType() == true; + /// + /// When the switch expression is an enum, C# does not support <= comparisons directly on enum values. + /// Cast both sides to the enum's underlying integer type so the comparison compiles. + /// + private (ExpressionSyntax Left, ExpressionSyntax Right) CastBothToUnderlyingTypeIfEnum(ITypeSymbol switchExprType, ExpressionSyntax left, ExpressionSyntax right) + { + var enumType = switchExprType?.IsEnumType() == true ? switchExprType as INamedTypeSymbol + : switchExprType?.GetNullableUnderlyingType() as INamedTypeSymbol; + if (enumType?.EnumUnderlyingType is not { } underlyingType) { + return (left, right); + } + var typeSyntax = CommonConversions.GetTypeSyntax(underlyingType); + return (ValidSyntaxFactory.CastExpression(typeSyntax, left), ValidSyntaxFactory.CastExpression(typeSyntax, right)); + } + private static CasePatternSwitchLabelSyntax VarWhen(SyntaxToken varName, ExpressionSyntax binaryExp) { var patternMatch = ValidSyntaxFactory.VarPattern(varName); diff --git a/Tests/CSharp/StatementTests/MethodStatementTests.cs b/Tests/CSharp/StatementTests/MethodStatementTests.cs index bc20f218..85a19e05 100644 --- a/Tests/CSharp/StatementTests/MethodStatementTests.cs +++ b/Tests/CSharp/StatementTests/MethodStatementTests.cs @@ -1111,6 +1111,50 @@ public static string TimeAgo(int daysAgo) CS0825: The contextual keyword 'var' may only appear within a local variable declaration or in script code"); } + [Fact] + public async Task SelectCaseWithEnumRangeAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Class TestClass + Enum UserLevel + City_Staff + Admin + Fixity_ROOT + End Enum + + Shared Function IsPrivileged(level As UserLevel) As Boolean + Select Case level + Case UserLevel.City_Staff To UserLevel.Fixity_ROOT + Return True + End Select + Return False + End Function +End Class", @" +public partial class TestClass +{ + public enum UserLevel + { + City_Staff, + Admin, + Fixity_ROOT + } + + public static bool IsPrivileged(UserLevel level) + { + switch (level) + { + case var @case when (int)UserLevel.City_Staff <= (int)@case && (int)@case <= (int)UserLevel.Fixity_ROOT: + { + return true; + } + } + + return false; + } +} +1 target compilation errors: +CS0825: The contextual keyword 'var' may only appear within a local variable declaration or in script code"); + } + [Fact] public async Task SelectCaseWithStringAsync() {