Skip to content

Commit b5def68

Browse files
Deal with case insensitive comparisons
1 parent dd8558c commit b5def68

3 files changed

Lines changed: 34 additions & 16 deletions

File tree

CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -641,9 +641,14 @@ public override async Task<SyntaxList<StatementSyntax>> VisitGoToStatement(VBSyn
641641
public override async Task<SyntaxList<StatementSyntax>> VisitSelectBlock(VBSyntax.SelectBlockSyntax node)
642642
{
643643
var vbExpr = node.SelectStatement.Expression;
644-
var wrapAllCases = CommonConversions.VisualBasicEqualityComparison.OptionCompareTextCaseInsensitive &&
645-
_semanticModel.GetTypeInfo(vbExpr).ConvertedType?.SpecialType == SpecialType.System_String;
644+
var vbEquality = CommonConversions.VisualBasicEqualityComparison;
646645
var (csExpr, stmts, csExprWithSourceMapping) = await GetExpressionWithoutSideEffectsAsync(vbExpr, "switchExpr");
646+
var switchExprTypeInfo = _semanticModel.GetTypeInfo(vbExpr);
647+
var isStringComparison = switchExprTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String;
648+
var caseInsensitiveStringComparison = vbEquality.OptionCompareTextCaseInsensitive &&
649+
isStringComparison;
650+
csExpr = vbEquality.VbCoerceToNonNullString(vbExpr, csExpr, switchExprTypeInfo);
651+
647652
var usedConstantValues = new HashSet<object>();
648653
var sections = new List<SwitchSectionSyntax>();
649654
foreach (var block in node.CaseBlocks) {
@@ -655,22 +660,30 @@ public override async Task<SyntaxList<StatementSyntax>> VisitSelectBlock(VBSynta
655660
var typeConversionKind = CommonConversions.TypeConversionAnalyzer.AnalyzeConversion(s.Value);
656661
var correctTypeExpressionSyntax = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(s.Value, originalExpressionSyntax, typeConversionKind, true, true);
657662
var constantValue = _semanticModel.GetConstantValue(s.Value);
663+
var caseTypeInfo = _semanticModel.GetTypeInfo(s.Value);
658664
var notAlreadyUsed = !constantValue.HasValue || usedConstantValues.Add(constantValue.Value);
659-
var caseSwitchLabelSyntax = !wrapAllCases && correctTypeExpressionSyntax.IsConst && notAlreadyUsed
660-
? (SwitchLabelSyntax)SyntaxFactory.CaseSwitchLabel(correctTypeExpressionSyntax.Expr)
661-
: WrapInCasePatternSwitchLabelSyntax(node, correctTypeExpressionSyntax.Expr);
665+
var csCase = correctTypeExpressionSyntax.Expr;
666+
667+
// Pass both halves in case we can optimize away the check based on the switch expr
668+
if (isStringComparison && !caseInsensitiveStringComparison) {
669+
csCase = vbEquality.VbCoerceToNonNullString(vbExpr, csExpr, switchExprTypeInfo, s.Value, correctTypeExpressionSyntax.Expr, caseTypeInfo).rhs;
670+
}
671+
var caseSwitchLabelSyntax = !caseInsensitiveStringComparison && correctTypeExpressionSyntax.IsConst && notAlreadyUsed
672+
? (SwitchLabelSyntax)SyntaxFactory.CaseSwitchLabel(csCase)
673+
: WrapInCasePatternSwitchLabelSyntax(node, s.Value, csCase, caseInsensitiveStringComparison);
662674
labels.Add(caseSwitchLabelSyntax);
663675
} else if (c is VBSyntax.ElseCaseClauseSyntax) {
664676
labels.Add(SyntaxFactory.DefaultSwitchLabel());
665677
} else if (c is VBSyntax.RelationalCaseClauseSyntax relational) {
666678
var operatorKind = VBasic.VisualBasicExtensions.Kind(relational);
667-
var binaryExp = SyntaxFactory.BinaryExpression(operatorKind.ConvertToken(TokenContext.Local), csExpr, (ExpressionSyntax)await relational.Value.AcceptAsync(_expressionVisitor));
668-
labels.Add(WrapInCasePatternSwitchLabelSyntax(node, binaryExp, treatAsBoolean: true));
679+
var relationalValue = (ExpressionSyntax)await relational.Value.AcceptAsync(_expressionVisitor);
680+
var binaryExp = SyntaxFactory.BinaryExpression(operatorKind.ConvertToken(TokenContext.Local), csExpr, relationalValue);
681+
labels.Add(WrapInCasePatternSwitchLabelSyntax(node, relational.Value, binaryExp, caseInsensitiveStringComparison, treatAsBoolean: true));
669682
} else if (c is VBSyntax.RangeCaseClauseSyntax range) {
670683
var lowerBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, (ExpressionSyntax)await range.LowerBound.AcceptAsync(_expressionVisitor), csExpr);
671684
var upperBoundCheck = SyntaxFactory.BinaryExpression(SyntaxKind.LessThanOrEqualExpression, csExpr, (ExpressionSyntax)await range.UpperBound.AcceptAsync(_expressionVisitor));
672685
var withinBounds = SyntaxFactory.BinaryExpression(SyntaxKind.LogicalAndExpression, lowerBoundCheck, upperBoundCheck);
673-
labels.Add(WrapInCasePatternSwitchLabelSyntax(node, withinBounds, treatAsBoolean: true));
686+
labels.Add(WrapInCasePatternSwitchLabelSyntax(node, range.LowerBound, withinBounds, caseInsensitiveStringComparison, treatAsBoolean: true));
674687
} else throw new NotSupportedException(c.Kind().ToString());
675688
}
676689

@@ -722,7 +735,7 @@ private async Task<bool> IsNeverMutatedAsync(VBSyntax.NameSyntax ns)
722735
return symbol.MatchesKind(SymbolKind.Parameter, SymbolKind.Local) && await CommonConversions.Document.Project.Solution.IsNeverWrittenAsync(symbol, allowedLocation);
723736
}
724737

725-
private CasePatternSwitchLabelSyntax WrapInCasePatternSwitchLabelSyntax(VBSyntax.SelectBlockSyntax node, ExpressionSyntax expression, bool treatAsBoolean = false)
738+
private CasePatternSwitchLabelSyntax WrapInCasePatternSwitchLabelSyntax(VBSyntax.SelectBlockSyntax node, VBSyntax.ExpressionSyntax vbCase, ExpressionSyntax expression, bool caseInsensitiveTextComparison, bool treatAsBoolean = false)
726739
{
727740
var typeInfo = _semanticModel.GetTypeInfo(node.SelectStatement.Expression);
728741

@@ -736,7 +749,13 @@ private CasePatternSwitchLabelSyntax WrapInCasePatternSwitchLabelSyntax(VBSyntax
736749
//CodeAnalysis upgrade to 3.0.0 needed for VarPattern. Correct text comes out, but tree is invalid so the tests this will generate "CS0825: The contextual keyword 'var' may only appear within a local variable declaration or in script code"
737750
patternMatch = SyntaxFactory.DeclarationPattern(
738751
ValidSyntaxFactory.VarType, SyntaxFactory.SingleVariableDesignation(varName));
739-
expression = SyntaxFactory.BinaryExpression(SyntaxKind.EqualsExpression, SyntaxFactory.IdentifierName(varName), expression);
752+
ExpressionSyntax csLeft = SyntaxFactory.IdentifierName(varName), csRight = expression;
753+
if (caseInsensitiveTextComparison) {
754+
(csLeft, csRight) = CommonConversions.VisualBasicEqualityComparison
755+
.AdjustForVbStringComparison(node.SelectStatement.Expression, csLeft, typeInfo, vbCase, expression, _semanticModel.GetTypeInfo(vbCase));
756+
}
757+
expression = SyntaxFactory.BinaryExpression(SyntaxKind.EqualsExpression, csLeft, csRight);
758+
740759
}
741760

742761
var casePatternSwitchLabelSyntax = SyntaxFactory.CasePatternSwitchLabel(patternMatch,

CodeConverter/CSharp/VisualBasicEqualityComparison.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ private static bool IsNonEmptyStringLiteral(VBSyntax.ExpressionSyntax vbExpr)
8585
return vbExpr.SkipParens().IsKind(VBSyntaxKind.StringLiteralExpression) && vbExpr is VBSyntax.LiteralExpressionSyntax literal && !IsEmptyString(literal);
8686
}
8787

88-
private ExpressionSyntax VbCoerceToNonNullString(VBSyntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, TypeInfo typeInfo)
88+
public ExpressionSyntax VbCoerceToNonNullString(VBSyntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, TypeInfo typeInfo)
8989
{
9090
bool isStringType = typeInfo.Type.SpecialType == SpecialType.System_String;
9191

@@ -204,9 +204,9 @@ private static ExpressionSyntax NegateIfNeeded(VBSyntax.BinaryExpressionSyntax n
204204

205205
public (ExpressionSyntax csLeft, ExpressionSyntax csRight) AdjustForVbStringComparison(VBSyntax.ExpressionSyntax vbLeft, ExpressionSyntax csLeft, TypeInfo lhsTypeInfo, VBSyntax.ExpressionSyntax vbRight, ExpressionSyntax csRight, TypeInfo rhsTypeInfo)
206206
{
207+
(csLeft, csRight) = VbCoerceToNonNullString(vbLeft, csLeft, lhsTypeInfo, vbLeft, csRight, rhsTypeInfo);
207208
if (OptionCompareTextCaseInsensitive) {
208209
ExtraUsingDirectives.Add("System.Globalization");
209-
(csLeft, csRight) = VbCoerceToNonNullString(vbLeft, csLeft, lhsTypeInfo, vbLeft, csRight, rhsTypeInfo);
210210
var compareOptions = SyntaxFactory.Argument(GetCompareTextCaseInsensitiveCompareOptions());
211211
var compareString = SyntaxFactory.InvocationExpression(ValidSyntaxFactory.MemberAccess(nameof(CultureInfo), nameof(CultureInfo.CurrentCulture),
212212
nameof(CultureInfo.CompareInfo), nameof(CompareInfo.Compare)),
@@ -216,8 +216,6 @@ private static ExpressionSyntax NegateIfNeeded(VBSyntax.BinaryExpressionSyntax n
216216
csLeft = compareString;
217217
csRight = SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression,
218218
SyntaxFactory.Literal(0));
219-
} else {
220-
(csLeft, csRight) = VbCoerceToNonNullString(vbLeft, csLeft, lhsTypeInfo, vbRight, csRight, rhsTypeInfo);
221219
}
222220

223221
return (csLeft, csRight);

Tests/CSharp/StatementTests/StatementTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,14 +1776,15 @@ Case Else
17761776
Return False
17771777
End Select
17781778
End Function
1779-
End Class", @"
1779+
End Class", @"using System.Globalization;
1780+
17801781
internal partial class Issue579SelectCaseWithCaseInsensitiveTextCompare
17811782
{
17821783
private bool Test(string astr_Temp)
17831784
{
17841785
switch (astr_Temp)
17851786
{
1786-
case var @case when @case == ""Test"":
1787+
case var @case when CultureInfo.CurrentCulture.CompareInfo.Compare(@case ?? """", ""Test"" ?? """", CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth) == 0:
17871788
{
17881789
return true;
17891790
}

0 commit comments

Comments
 (0)