From aec3890a256a85e420aee396b0fc967319e438ff Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 17 Dec 2024 10:52:02 +0100 Subject: [PATCH] Transform Span-based overloads to Enumerable in funcletizer Fixes #35100 (cherry picked from commit 6c0106b2693c3d3d87c26d8d63d97ad2236c3f4f) --- .github/workflows/TestCosmos.yaml | 2 +- .../ParameterExtractingExpressionVisitor.cs | 55 ++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/.github/workflows/TestCosmos.yaml b/.github/workflows/TestCosmos.yaml index 52b8aeb3cbd..cb9d7103301 100644 --- a/.github/workflows/TestCosmos.yaml +++ b/.github/workflows/TestCosmos.yaml @@ -35,7 +35,7 @@ jobs: shell: cmd - name: Publish Test Results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: test-results diff --git a/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs b/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs index 558104b7d40..410f2dd2839 100644 --- a/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs @@ -31,6 +31,9 @@ public class ParameterExtractingExpressionVisitor : ExpressionVisitor private static readonly bool UseOldBehavior31552 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue31552", out var enabled31552) && enabled31552; + private static readonly bool UseOldBehavior35100 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35100", out var enabled35100) && enabled35100; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -181,9 +184,11 @@ protected override Expression VisitConditional(ConditionalExpression conditional /// protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { + var method = methodCallExpression.Method; + if (!UseOldBehavior31552 - && methodCallExpression.Method.DeclaringType == typeof(EF) - && methodCallExpression.Method.Name == nameof(EF.Constant)) + && method.DeclaringType == typeof(EF) + && method.Name == nameof(EF.Constant)) { // If this is a call to EF.Constant(), then examine its operand. If the operand isn't evaluatable (i.e. contains a reference // to a database table), throw immediately. @@ -197,6 +202,52 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp return Evaluate(operand, generateParameter: false); } + // .NET 10 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans"). + // Unfortunately, the LINQ interpreter does not support ref structs, so we rewrite e.g. MemoryExtensions.Contains to + // Enumerable.Contains here. See https://github.com/dotnet/runtime/issues/109757. + if (method.DeclaringType == typeof(MemoryExtensions) && !UseOldBehavior35100) + { + switch (method.Name) + { + case nameof(MemoryExtensions.Contains) + when methodCallExpression.Arguments is [var arg0, var arg1] && + TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0): + { + return Visit( + Expression.Call( + EnumerableMethods.Contains.MakeGenericMethod(method.GetGenericArguments()[0]), + unwrappedArg0, arg1)); + } + + case nameof(MemoryExtensions.SequenceEqual) + when methodCallExpression.Arguments is [var arg0, var arg1] + && TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0) + && TryUnwrapSpanImplicitCast(arg1, out var unwrappedArg1): + return Visit( + Expression.Call( + EnumerableMethods.SequenceEqual.MakeGenericMethod(method.GetGenericArguments()[0]), + unwrappedArg0, unwrappedArg1)); + } + + static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result) + { + if (expression is MethodCallExpression + { + Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType }, + Arguments: [var unwrapped] + } + && implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition + && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>))) + { + result = unwrapped; + return true; + } + + result = null; + return false; + } + } + return base.VisitMethodCall(methodCallExpression); }