Skip to content

Commit eeab918

Browse files
authored
Transform Span-based overloads to Enumerable in funcletizer (#35720)
Fixes #35100 (cherry picked from commit 6c0106b)
1 parent e52e0fb commit eeab918

File tree

1 file changed

+53
-2
lines changed

1 file changed

+53
-2
lines changed

src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs

+53-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public class ParameterExtractingExpressionVisitor : ExpressionVisitor
3131
private static readonly bool UseOldBehavior31552 =
3232
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue31552", out var enabled31552) && enabled31552;
3333

34+
private static readonly bool UseOldBehavior35100 =
35+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35100", out var enabled35100) && enabled35100;
36+
3437
/// <summary>
3538
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
3639
/// 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
181184
/// </summary>
182185
protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
183186
{
187+
var method = methodCallExpression.Method;
188+
184189
if (!UseOldBehavior31552
185-
&& methodCallExpression.Method.DeclaringType == typeof(EF)
186-
&& methodCallExpression.Method.Name == nameof(EF.Constant))
190+
&& method.DeclaringType == typeof(EF)
191+
&& method.Name == nameof(EF.Constant))
187192
{
188193
// If this is a call to EF.Constant(), then examine its operand. If the operand isn't evaluatable (i.e. contains a reference
189194
// to a database table), throw immediately.
@@ -197,6 +202,52 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
197202
return Evaluate(operand, generateParameter: false);
198203
}
199204

205+
// .NET 10 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans").
206+
// Unfortunately, the LINQ interpreter does not support ref structs, so we rewrite e.g. MemoryExtensions.Contains to
207+
// Enumerable.Contains here. See https://github.com/dotnet/runtime/issues/109757.
208+
if (method.DeclaringType == typeof(MemoryExtensions) && !UseOldBehavior35100)
209+
{
210+
switch (method.Name)
211+
{
212+
case nameof(MemoryExtensions.Contains)
213+
when methodCallExpression.Arguments is [var arg0, var arg1] &&
214+
TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0):
215+
{
216+
return Visit(
217+
Expression.Call(
218+
EnumerableMethods.Contains.MakeGenericMethod(method.GetGenericArguments()[0]),
219+
unwrappedArg0, arg1));
220+
}
221+
222+
case nameof(MemoryExtensions.SequenceEqual)
223+
when methodCallExpression.Arguments is [var arg0, var arg1]
224+
&& TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0)
225+
&& TryUnwrapSpanImplicitCast(arg1, out var unwrappedArg1):
226+
return Visit(
227+
Expression.Call(
228+
EnumerableMethods.SequenceEqual.MakeGenericMethod(method.GetGenericArguments()[0]),
229+
unwrappedArg0, unwrappedArg1));
230+
}
231+
232+
static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
233+
{
234+
if (expression is MethodCallExpression
235+
{
236+
Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
237+
Arguments: [var unwrapped]
238+
}
239+
&& implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
240+
&& (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
241+
{
242+
result = unwrapped;
243+
return true;
244+
}
245+
246+
result = null;
247+
return false;
248+
}
249+
}
250+
200251
return base.VisitMethodCall(methodCallExpression);
201252
}
202253

0 commit comments

Comments
 (0)