Skip to content

Commit 42d10b3

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

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
@@ -112,6 +112,9 @@ public class ExpressionTreeFuncletizer : ExpressionVisitor
112112
private static readonly bool UseOldBehavior35656 =
113113
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35656", out var enabled35656) && enabled35656;
114114

115+
private static readonly bool UseOldBehavior35100 =
116+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35100", out var enabled35100) && enabled35100;
117+
115118
private static readonly MethodInfo ReadOnlyCollectionIndexerGetter = typeof(ReadOnlyCollection<Expression>).GetProperties()
116119
.Single(p => p.GetIndexParameters() is { Length: 1 } indexParameters && indexParameters[0].ParameterType == typeof(int)).GetMethod!;
117120

@@ -974,6 +977,51 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall)
974977
}
975978
}
976979

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

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

0 commit comments

Comments
 (0)