Skip to content

Commit

Permalink
LanguageNormalizingExpressionVisitor for VB.NET string comparisons
Browse files Browse the repository at this point in the history
Fixes #19592
  • Loading branch information
roji committed Jan 23, 2020
1 parent 09103e0 commit 8852083
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 0 deletions.
102 changes: 102 additions & 0 deletions src/EFCore/Query/Internal/LanguageNormalizingExpressionVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Linq.Expressions;
using System.Reflection;

namespace Microsoft.EntityFrameworkCore.Query.Internal
{
/// <summary>
/// Normalizes certain language-specific aspects of the expression trees produced by languages other
/// than C#, e.g. Visual Basic.
/// </summary>
public class LanguageNormalizingExpressionVisitor : ExpressionVisitor
{
private static readonly MethodInfo _stringCompareMethod
= typeof(string).GetRuntimeMethod(
nameof(string.Compare),
new[] { typeof(string), typeof(string), typeof(StringComparison) });

private static readonly MethodInfo _stringEqualsMethod
= typeof(string).GetRuntimeMethod(
nameof(string.Equals),
new[] { typeof(string), typeof(string), typeof(StringComparison) });

protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
{
var visited = (MethodCallExpression)base.VisitMethodCall(methodCallExpression);

// In VB.NET, comparison operators between strings (equality, greater-than, less-than) yield
// calls to a VB-specific CompareString method. Normalize that to string.Compare.
if (visited.Method.Name == "CompareString"
&& visited.Method.DeclaringType?.Name == "Operators"
&& visited.Method.DeclaringType?.Namespace == "Microsoft.VisualBasic.CompilerServices"
&& visited.Object == null
&& visited.Arguments.Count == 3
&& visited.Arguments[2] is ConstantExpression textCompareConstantExpression)
{
return Expression.Call(
_stringCompareMethod,
visited.Arguments[0],
visited.Arguments[1],
(bool)textCompareConstantExpression.Value
? Expression.Constant(StringComparison.OrdinalIgnoreCase)
: Expression.Constant(StringComparison.Ordinal));
}

return visited;
}

protected override Expression VisitBinary(BinaryExpression binaryExpression)
{
var visitedLeft = Visit(binaryExpression.Left);
var visitedRight = Visit(binaryExpression.Right);

// In VB.NET, str1 = str2 yields CompareString(str1, str2, false) == 0.
// Rewrite this is as a regular equality node.
if (binaryExpression.NodeType == ExpressionType.Equal
|| binaryExpression.NodeType == ExpressionType.NotEqual)
{
var (compareStringExpression, otherExpression) =
IsStringCompare(visitedLeft)
? ((MethodCallExpression)visitedLeft, visitedRight)
: IsStringCompare(visitedRight)
? ((MethodCallExpression)visitedRight, visitedLeft)
: (null, null);

if (compareStringExpression != null
&& (compareStringExpression.Arguments[2] as ConstantExpression)?.Value is StringComparison stringComparison
&& otherExpression is ConstantExpression otherConstantExpression
&& (int)otherConstantExpression.Value == 0)
{
switch (stringComparison)
{
case StringComparison.Ordinal:
return Expression.MakeBinary(
binaryExpression.NodeType,
compareStringExpression.Arguments[0],
compareStringExpression.Arguments[1]);

case StringComparison.OrdinalIgnoreCase:
var stringEqualsExpression = Expression.Call(
_stringEqualsMethod,
compareStringExpression.Arguments[0],
compareStringExpression.Arguments[1],
Expression.Constant(StringComparison.OrdinalIgnoreCase)
);
return binaryExpression.NodeType == ExpressionType.Equal
? (Expression)stringEqualsExpression
: Expression.Not(stringEqualsExpression);
}
}
}

return binaryExpression.Update(visitedLeft, binaryExpression.Conversion, visitedRight);

static bool IsStringCompare(Expression expression)
=> expression is MethodCallExpression methodCallExpression
&& methodCallExpression.Method == _stringCompareMethod;
}
}
}
1 change: 1 addition & 0 deletions src/EFCore/Query/QueryTranslationPreprocessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public virtual Expression Process([NotNull] Expression query)
query = new EnumerableToQueryableMethodConvertingExpressionVisitor().Visit(query);
query = new QueryMetadataExtractingExpressionVisitor(_queryCompilationContext).Visit(query);
query = new InvocationExpressionRemovingExpressionVisitor().Visit(query);
query = new LanguageNormalizingExpressionVisitor().Visit(query);
query = new AllAnyToContainsRewritingExpressionVisitor().Visit(query);
query = new GroupJoinFlatteningExpressionVisitor().Visit(query);
query = new NullCheckRemovingExpressionVisitor().Visit(query);
Expand Down

0 comments on commit 8852083

Please sign in to comment.