From 4b852d5282a739f7e7cc10ab735054b12c80f6d0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Apr 2026 21:53:58 +0000 Subject: [PATCH 1/2] Fix #803: Use C# 9 relational patterns for VB `Case Is constant` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When converting VB `Select Case` with `Case Is < value`, `Case Is > value`, etc. (RelationalCaseClauseSyntax), emit clean C# 9 relational patterns (`case < 1000:`) instead of the verbose `case var @case when @case < 1000:` pattern, for non-object, non-string comparisons where the case value is a constant expression. Also handle RelationalPatternSyntax in the C#→VB converter so that C# 9 relational patterns round-trip correctly back to VB `Case Is value`. https://claude.ai/code/session_01AkwUvu3XuCdj3D4axoX4UX --- .../MethodBodyExecutableStatementVisitor.cs | 15 +++++ CodeConverter/VB/NodesVisitor.cs | 15 +++++ .../StatementTests/MethodStatementTests.cs | 55 ++++++++++++++++--- 3 files changed, 76 insertions(+), 9 deletions(-) diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index bc48a7dcc..c9cde385c 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -843,6 +843,13 @@ public override async Task> VisitSelectBlock(VBSynta if (isObjectComparison) { caseSwitchLabelSyntax = WrapInCasePatternSwitchLabelSyntax(node, relational.Value, csRelationalValue, false, operatorKind); } + else if (!isStringComparison && _semanticModel.GetConstantValue(relational.Value).HasValue) { + csRelationalValue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(relational.Value, csRelationalValue); + var operatorToken = SyntaxFactory.Token(GetRelationalPatternTokenKind(operatorKind)); + caseSwitchLabelSyntax = SyntaxFactory.CasePatternSwitchLabel( + SyntaxFactory.RelationalPattern(operatorToken, csRelationalValue), + SyntaxFactory.Token(SyntaxKind.ColonToken)); + } else { var varName = CommonConversions.CsEscapedIdentifier(GetUniqueVariableNameInScope(node, "case")); ExpressionSyntax csLeft = ValidSyntaxFactory.IdentifierName(varName); @@ -1021,6 +1028,14 @@ private CasePatternSwitchLabelSyntax WrapInCasePatternSwitchLabelSyntax(VBSyntax _ => throw new ArgumentOutOfRangeException(nameof(caseClauseKind), caseClauseKind, null) }; + private static CS.SyntaxKind GetRelationalPatternTokenKind(VBasic.SyntaxKind caseClauseKind) => caseClauseKind switch { + VBasic.SyntaxKind.CaseLessThanClause => CS.SyntaxKind.LessThanToken, + VBasic.SyntaxKind.CaseLessThanOrEqualClause => CS.SyntaxKind.LessThanEqualsToken, + VBasic.SyntaxKind.CaseGreaterThanOrEqualClause => CS.SyntaxKind.GreaterThanEqualsToken, + VBasic.SyntaxKind.CaseGreaterThanClause => CS.SyntaxKind.GreaterThanToken, + _ => throw new ArgumentOutOfRangeException(nameof(caseClauseKind), caseClauseKind, null) + }; + private ExpressionSyntax ComparisonAdjustedForStringComparison(VBSyntax.SelectBlockSyntax node, VBSyntax.ExpressionSyntax vbCase, TypeInfo lhsTypeInfo, ExpressionSyntax csLeft, ExpressionSyntax csRight, TypeInfo rhsTypeInfo, ComparisonKind comparisonKind) { var vbEquality = CommonConversions.VisualBasicEqualityComparison; diff --git a/CodeConverter/VB/NodesVisitor.cs b/CodeConverter/VB/NodesVisitor.cs index 48911590e..2287f66f2 100644 --- a/CodeConverter/VB/NodesVisitor.cs +++ b/CodeConverter/VB/NodesVisitor.cs @@ -1643,6 +1643,12 @@ public override VisualBasicSyntaxNode VisitThrowExpression(CSSyntax.ThrowExpress public override VisualBasicSyntaxNode VisitCasePatternSwitchLabel(CSSyntax.CasePatternSwitchLabelSyntax node) { + if (node.Pattern is CSSyntax.RelationalPatternSyntax relational) { + var value = (ExpressionSyntax)relational.Expression.Accept(TriviaConvertingVisitor); + var (caseClauseKind, tokenKind) = GetRelationalCaseClauseKinds(relational.OperatorToken.Kind()); + return SyntaxFactory.RelationalCaseClause(caseClauseKind, + SyntaxFactory.Token(tokenKind), value); + } var condition = node.WhenClause.Condition.SkipIntoParens(); switch (condition) { case CSSyntax.BinaryExpressionSyntax bes when node.Pattern.ToString().StartsWith("var", StringComparison.InvariantCulture): //VarPatternSyntax (not available in current library version) @@ -1654,6 +1660,15 @@ public override VisualBasicSyntaxNode VisitCasePatternSwitchLabel(CSSyntax.CaseP throw new NotSupportedException(condition.GetType() + " in switch case"); } } + + private static (SyntaxKind CaseClauseKind, SyntaxKind TokenKind) GetRelationalCaseClauseKinds(CS.SyntaxKind csTokenKind) => csTokenKind switch { + CS.SyntaxKind.LessThanToken => (SyntaxKind.CaseLessThanClause, SyntaxKind.LessThanToken), + CS.SyntaxKind.LessThanEqualsToken => (SyntaxKind.CaseLessThanOrEqualClause, SyntaxKind.LessThanEqualsToken), + CS.SyntaxKind.GreaterThanToken => (SyntaxKind.CaseGreaterThanClause, SyntaxKind.GreaterThanToken), + CS.SyntaxKind.GreaterThanEqualsToken => (SyntaxKind.CaseGreaterThanOrEqualClause, SyntaxKind.GreaterThanEqualsToken), + _ => throw new NotSupportedException($"Relational operator token {csTokenKind} not supported in VB case clause") + }; + private INamedTypeSymbol GetStructOrClassSymbol(CS.CSharpSyntaxNode node) { return (INamedTypeSymbol)_semanticModel.GetDeclaredSymbol(node.Ancestors().First(x => x is CSSyntax.ClassDeclarationSyntax || x is CSSyntax.StructDeclarationSyntax)); } diff --git a/Tests/CSharp/StatementTests/MethodStatementTests.cs b/Tests/CSharp/StatementTests/MethodStatementTests.cs index bc20f2181..ddfb659ad 100644 --- a/Tests/CSharp/StatementTests/MethodStatementTests.cs +++ b/Tests/CSharp/StatementTests/MethodStatementTests.cs @@ -1089,13 +1089,13 @@ public static string TimeAgo(int daysAgo) { case var @case when 0 <= @case && @case <= 3: case 4: - case var case1 when case1 >= 5: - case var case2 when case2 < 6: - case var case3 when case3 <= 7: + case >= 5: + case < 6: + case <= 7: { return ""this week""; } - case var case4 when case4 > 0: + case > 0: { return daysAgo / 7 + "" weeks ago""; } @@ -1249,7 +1249,7 @@ public void DoesNotThrow() var rand = new Random(); switch (rand.Next(8)) { - case var @case when @case < 4: + case < 4: { break; } @@ -1257,7 +1257,7 @@ public void DoesNotThrow() { break; } - case var case1 when case1 > 4: + case > 4: { break; } @@ -1268,9 +1268,46 @@ public void DoesNotThrow() } } } -} -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 Issue803SelectCaseIsRelationalUsesC9PatternAsync() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Class TestClass + Function Rollo(Breite As Integer) As Integer + Select Case Breite + Case Is < 1000 + Return 12 + Case Is < 1200 + Return 15 + Case Else + Return 28 + End Select + End Function +End Class", @" +public partial class TestClass +{ + public int Rollo(int Breite) + { + switch (Breite) + { + case < 1000: + { + return 12; + } + case < 1200: + { + return 15; + } + + default: + { + return 28; + } + } + } +}"); } [Fact] From dc08ad9ac817b2e330d7f39e53b3a34665c07e71 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Apr 2026 22:29:21 +0000 Subject: [PATCH 2/2] Fix #803 compile error: use CS.CSharpExtensions.Kind() to avoid VB extension method resolution https://claude.ai/code/session_01AkwUvu3XuCdj3D4axoX4UX --- CodeConverter/VB/NodesVisitor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeConverter/VB/NodesVisitor.cs b/CodeConverter/VB/NodesVisitor.cs index 2287f66f2..979c58d2c 100644 --- a/CodeConverter/VB/NodesVisitor.cs +++ b/CodeConverter/VB/NodesVisitor.cs @@ -1645,7 +1645,7 @@ public override VisualBasicSyntaxNode VisitCasePatternSwitchLabel(CSSyntax.CaseP { if (node.Pattern is CSSyntax.RelationalPatternSyntax relational) { var value = (ExpressionSyntax)relational.Expression.Accept(TriviaConvertingVisitor); - var (caseClauseKind, tokenKind) = GetRelationalCaseClauseKinds(relational.OperatorToken.Kind()); + var (caseClauseKind, tokenKind) = GetRelationalCaseClauseKinds(CS.CSharpExtensions.Kind(relational.OperatorToken)); return SyntaxFactory.RelationalCaseClause(caseClauseKind, SyntaxFactory.Token(tokenKind), value); }