Skip to content

Commit 1a2ea64

Browse files
committed
Transform Span-based overloads to Enumerable in funcletizer
Fixes #35100 (cherry picked from commit 6c0106b)
1 parent fb2f189 commit 1a2ea64

File tree

1 file changed

+48
-0
lines changed

1 file changed

+48
-0
lines changed

Diff for: src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs

+48
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ public class ExpressionTreeFuncletizer : ExpressionVisitor
109109
private static readonly bool UseOldBehavior35111 =
110110
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35111", out var enabled35111) && enabled35111;
111111

112+
private static readonly bool UseOldBehavior35100 =
113+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35100", out var enabled35100) && enabled35100;
114+
112115
private static readonly MethodInfo ReadOnlyCollectionIndexerGetter = typeof(ReadOnlyCollection<Expression>).GetProperties()
113116
.Single(p => p.GetIndexParameters() is { Length: 1 } indexParameters && indexParameters[0].ParameterType == typeof(int)).GetMethod!;
114117

@@ -971,6 +974,51 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall)
971974
}
972975
}
973976

977+
// .NET 10 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans").
978+
// Unfortunately, the LINQ interpreter does not support ref structs, so we rewrite e.g. MemoryExtensions.Contains to
979+
// Enumerable.Contains here. See https://github.com/dotnet/runtime/issues/109757.
980+
if (method.DeclaringType == typeof(MemoryExtensions) && !UseOldBehavior35100)
981+
{
982+
switch (method.Name)
983+
{
984+
case nameof(MemoryExtensions.Contains)
985+
when methodCall.Arguments is [var arg0, var arg1] && TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0):
986+
{
987+
return Visit(
988+
Call(
989+
EnumerableMethods.Contains.MakeGenericMethod(methodCall.Method.GetGenericArguments()[0]),
990+
unwrappedArg0, arg1));
991+
}
992+
993+
case nameof(MemoryExtensions.SequenceEqual)
994+
when methodCall.Arguments is [var arg0, var arg1]
995+
&& TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0)
996+
&& TryUnwrapSpanImplicitCast(arg1, out var unwrappedArg1):
997+
return Visit(
998+
Call(
999+
EnumerableMethods.SequenceEqual.MakeGenericMethod(methodCall.Method.GetGenericArguments()[0]),
1000+
unwrappedArg0, unwrappedArg1));
1001+
}
1002+
1003+
static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
1004+
{
1005+
if (expression is MethodCallExpression
1006+
{
1007+
Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
1008+
Arguments: [var unwrapped]
1009+
}
1010+
&& implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
1011+
&& (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
1012+
{
1013+
result = unwrapped;
1014+
return true;
1015+
}
1016+
1017+
result = null;
1018+
return false;
1019+
}
1020+
}
1021+
9741022
// Regular/arbitrary method handling from here on
9751023

9761024
// First, visit the object and all arguments, saving states as well

0 commit comments

Comments
 (0)