Copilot commented on code in PR #7933:
URL: https://github.com/apache/ignite-3/pull/7933#discussion_r3035599059


##########
modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryParser.cs:
##########
@@ -78,16 +86,79 @@ private static CompoundExpressionTreeProcessor 
CreateCompoundProcessor(
         return new CompoundExpressionTreeProcessor(
             new IExpressionTreeProcessor[]
             {
-                new PartialEvaluatingExpressionTreeProcessor(new 
NullEvaluatableExpressionFilter()),
+                new PartialEvaluatingExpressionTreeProcessor(new 
IgniteEvaluatableExpressionFilter()),
                 new TransformingExpressionTreeProcessor(transformationProvider)
             });
     }
 
     /// <summary>
-    /// Empty implementation of IEvaluatableExpressionFilter.
+    /// Implementation of IEvaluatableExpressionFilter.
     /// </summary>
-    private sealed class NullEvaluatableExpressionFilter : 
EvaluatableExpressionFilterBase
+    private sealed class IgniteEvaluatableExpressionFilter : 
EvaluatableExpressionFilterBase
     {
-        // No-op.
+        // Ignores implicit ReadOnlySpan conversion to support C# 14 first 
class span Contains.
+        public override bool IsEvaluatableMethodCall(MethodCallExpression node)
+        {
+            ArgumentNullException.ThrowIfNull(node);
+            if 
(MemoryExtensionsContainsExpressionTransformer.IsSpanImplicitConversion(node))
+            {
+                return false;
+            }
+
+            return base.IsEvaluatableMethodCall(node);
+        }
+    }
+
+    /// <summary>
+    /// Implementation of IExpressionTransformer handling C# 14 first class 
span Contains.
+    /// </summary>
+    private sealed class MemoryExtensionsContainsExpressionTransformer : 
IExpressionTransformer<MethodCallExpression>
+    {
+        private static readonly MethodInfo SourceMethodInfo = 
typeof(MemoryExtensions)
+            .GetMethod(nameof(MemoryExtensions.Contains), [
+                
typeof(ReadOnlySpan<>).MakeGenericType(Type.MakeGenericMethodParameter(0)),
+                Type.MakeGenericMethodParameter(0)
+            ])!;
+
+        private static readonly MethodInfo TargetMethodInfo = 
typeof(Enumerable)
+            .GetMethod(nameof(Enumerable.Contains), [
+                
typeof(IEnumerable<>).MakeGenericType(Type.MakeGenericMethodParameter(0)),
+                Type.MakeGenericMethodParameter(0)
+            ])!;

Review Comment:
   `TargetMethodInfo` is obtained via reflection 
(`typeof(Enumerable).GetMethod(...)`) and then invoked via `MakeGenericMethod`. 
Since `Apache.Ignite` is marked trimmable, this kind of reflection can break in 
trimmed apps if the linker removes the `Enumerable.Contains` overload. Prefer a 
direct method reference (e.g., obtain `MethodInfo` from a closed generic 
delegate and call `GetGenericMethodDefinition()`), or add the necessary 
trimming annotations/dependencies to ensure the method is preserved.
   ```suggestion
           private static readonly MethodInfo TargetMethodInfo =
               ((Func<IEnumerable<int>, int, 
bool>)Enumerable.Contains).Method.GetGenericMethodDefinition();
   ```



##########
modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryParser.cs:
##########
@@ -78,16 +86,79 @@ private static CompoundExpressionTreeProcessor 
CreateCompoundProcessor(
         return new CompoundExpressionTreeProcessor(
             new IExpressionTreeProcessor[]
             {
-                new PartialEvaluatingExpressionTreeProcessor(new 
NullEvaluatableExpressionFilter()),
+                new PartialEvaluatingExpressionTreeProcessor(new 
IgniteEvaluatableExpressionFilter()),
                 new TransformingExpressionTreeProcessor(transformationProvider)
             });
     }
 
     /// <summary>
-    /// Empty implementation of IEvaluatableExpressionFilter.
+    /// Implementation of IEvaluatableExpressionFilter.
     /// </summary>
-    private sealed class NullEvaluatableExpressionFilter : 
EvaluatableExpressionFilterBase
+    private sealed class IgniteEvaluatableExpressionFilter : 
EvaluatableExpressionFilterBase
     {
-        // No-op.
+        // Ignores implicit ReadOnlySpan conversion to support C# 14 first 
class span Contains.
+        public override bool IsEvaluatableMethodCall(MethodCallExpression node)
+        {
+            ArgumentNullException.ThrowIfNull(node);
+            if 
(MemoryExtensionsContainsExpressionTransformer.IsSpanImplicitConversion(node))
+            {
+                return false;
+            }
+
+            return base.IsEvaluatableMethodCall(node);
+        }
+    }
+
+    /// <summary>
+    /// Implementation of IExpressionTransformer handling C# 14 first class 
span Contains.
+    /// </summary>
+    private sealed class MemoryExtensionsContainsExpressionTransformer : 
IExpressionTransformer<MethodCallExpression>
+    {
+        private static readonly MethodInfo SourceMethodInfo = 
typeof(MemoryExtensions)
+            .GetMethod(nameof(MemoryExtensions.Contains), [
+                
typeof(ReadOnlySpan<>).MakeGenericType(Type.MakeGenericMethodParameter(0)),
+                Type.MakeGenericMethodParameter(0)
+            ])!;
+
+        private static readonly MethodInfo TargetMethodInfo = 
typeof(Enumerable)
+            .GetMethod(nameof(Enumerable.Contains), [
+                
typeof(IEnumerable<>).MakeGenericType(Type.MakeGenericMethodParameter(0)),
+                Type.MakeGenericMethodParameter(0)
+            ])!;
+
+        public static bool 
IsSpanImplicitConversion([NotNullWhen(true)]MethodCallExpression? node) =>
+            node?.Method is { IsSpecialName: true, Name: "op_Implicit" }
+            && node.Method.DeclaringType is { IsGenericType: true }
+            && node.Method.DeclaringType.GetGenericTypeDefinition() == 
typeof(ReadOnlySpan<>);
+
+        public Expression Transform(MethodCallExpression expression)
+        {
+            if (expression.Method.IsConstructedGenericMethod && 
expression.Method.GetGenericMethodDefinition() == SourceMethodInfo)
+            {
+                var genericType = expression.Method.GetGenericArguments()[0];
+
+                var enumerableParameter = expression.Arguments[0] as 
MethodCallExpression;
+                if (!IsSpanImplicitConversion(enumerableParameter))
+                {
+                    throw new NotSupportedException("Was not able to process 
parameter for MemoryExtensions.Contains, " +
+                                                    "expected implicit 
conversion, got: " + expression.Arguments[0]);
+                }
+
+                var argument = enumerableParameter.Arguments[0]!;
+
+                var targetProp = expression.Arguments[1];
+
+                var target = TargetMethodInfo.MakeGenericMethod(genericType);
+                return Expression.Call(target, argument, targetProp);
+            }
+
+            return expression;
+        }
+
+#pragma warning disable CA1819
+#pragma warning disable SA1201
+        public ExpressionType[] SupportedExpressionTypes => 
[ExpressionType.Call];
+#pragma warning restore SA1201
+#pragma warning restore CA1819

Review Comment:
   The `SupportedExpressionTypes` property returns a new array each access and 
adds pragma suppressions (CA1819/SA1201) inline. Consider switching to a 
`static readonly ExpressionType[]` field and/or reordering members to avoid 
StyleCop suppressions and repeated allocations.



##########
modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.cs:
##########
@@ -158,6 +158,14 @@ public void TestSelectOrderByOffsetLimit() =>
                 .Take(3)
                 .ToList());
 
+    [Test]
+    public void TestContains() =>
+        AssertSql("select (_T0.KEY IN (?, ?)) from PUBLIC.TBL1 as _T0", q =>
+        {
+            var keys = new long[] { 4, 2 };
+            return q.Select(x => keys.Contains(x.Key)).ToList();
+        });

Review Comment:
   This test uses `keys.Contains(x.Key)` under the repo-wide `LangVersion=12`, 
where the call is expected to bind to `Enumerable.Contains` (not 
`MemoryExtensions.Contains`). As a result, it likely doesn't exercise the new 
`MemoryExtensionsContainsExpressionTransformer` / span implicit-conversion 
filter. Consider adding a test that explicitly builds (or forces) a 
`MemoryExtensions.Contains(ReadOnlySpan<T>, T)` call in the expression tree so 
the new code path is covered.



##########
modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryParser.cs:
##########
@@ -17,8 +17,15 @@
 
 namespace Apache.Ignite.Internal.Linq;
 
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
 using System.Threading;
 using Dml;
+using Remotion.Linq.Clauses.Expressions;

Review Comment:
   `using Remotion.Linq.Clauses.Expressions;` appears to be unused in this 
file. With `TreatWarningsAsErrors=true`, this will fail the build with CS8019; 
please remove the unnecessary using directive.
   ```suggestion
   
   ```



##########
modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryParser.cs:
##########
@@ -78,16 +86,79 @@ private static CompoundExpressionTreeProcessor 
CreateCompoundProcessor(
         return new CompoundExpressionTreeProcessor(
             new IExpressionTreeProcessor[]
             {
-                new PartialEvaluatingExpressionTreeProcessor(new 
NullEvaluatableExpressionFilter()),
+                new PartialEvaluatingExpressionTreeProcessor(new 
IgniteEvaluatableExpressionFilter()),
                 new TransformingExpressionTreeProcessor(transformationProvider)
             });
     }
 
     /// <summary>
-    /// Empty implementation of IEvaluatableExpressionFilter.
+    /// Implementation of IEvaluatableExpressionFilter.
     /// </summary>
-    private sealed class NullEvaluatableExpressionFilter : 
EvaluatableExpressionFilterBase
+    private sealed class IgniteEvaluatableExpressionFilter : 
EvaluatableExpressionFilterBase
     {
-        // No-op.
+        // Ignores implicit ReadOnlySpan conversion to support C# 14 first 
class span Contains.
+        public override bool IsEvaluatableMethodCall(MethodCallExpression node)
+        {
+            ArgumentNullException.ThrowIfNull(node);
+            if 
(MemoryExtensionsContainsExpressionTransformer.IsSpanImplicitConversion(node))
+            {
+                return false;
+            }
+
+            return base.IsEvaluatableMethodCall(node);
+        }
+    }
+
+    /// <summary>
+    /// Implementation of IExpressionTransformer handling C# 14 first class 
span Contains.
+    /// </summary>
+    private sealed class MemoryExtensionsContainsExpressionTransformer : 
IExpressionTransformer<MethodCallExpression>
+    {
+        private static readonly MethodInfo SourceMethodInfo = 
typeof(MemoryExtensions)
+            .GetMethod(nameof(MemoryExtensions.Contains), [
+                
typeof(ReadOnlySpan<>).MakeGenericType(Type.MakeGenericMethodParameter(0)),
+                Type.MakeGenericMethodParameter(0)
+            ])!;
+
+        private static readonly MethodInfo TargetMethodInfo = 
typeof(Enumerable)
+            .GetMethod(nameof(Enumerable.Contains), [
+                
typeof(IEnumerable<>).MakeGenericType(Type.MakeGenericMethodParameter(0)),
+                Type.MakeGenericMethodParameter(0)
+            ])!;
+
+        public static bool 
IsSpanImplicitConversion([NotNullWhen(true)]MethodCallExpression? node) =>
+            node?.Method is { IsSpecialName: true, Name: "op_Implicit" }
+            && node.Method.DeclaringType is { IsGenericType: true }
+            && node.Method.DeclaringType.GetGenericTypeDefinition() == 
typeof(ReadOnlySpan<>);
+
+        public Expression Transform(MethodCallExpression expression)
+        {
+            if (expression.Method.IsConstructedGenericMethod && 
expression.Method.GetGenericMethodDefinition() == SourceMethodInfo)
+            {
+                var genericType = expression.Method.GetGenericArguments()[0];
+
+                var enumerableParameter = expression.Arguments[0] as 
MethodCallExpression;
+                if (!IsSpanImplicitConversion(enumerableParameter))
+                {
+                    throw new NotSupportedException("Was not able to process 
parameter for MemoryExtensions.Contains, " +
+                                                    "expected implicit 
conversion, got: " + expression.Arguments[0]);
+                }
+
+                var argument = enumerableParameter.Arguments[0]!;
+
+                var targetProp = expression.Arguments[1];
+
+                var target = TargetMethodInfo.MakeGenericMethod(genericType);
+                return Expression.Call(target, argument, targetProp);

Review Comment:
   `targetProp` is a misleading name here: the expression represents the value 
being searched for (the second argument to `Contains`), not a property. 
Renaming it to something like `value`/`item` would make the transformation 
logic easier to follow.
   ```suggestion
                   var value = expression.Arguments[1];
   
                   var target = TargetMethodInfo.MakeGenericMethod(genericType);
                   return Expression.Call(target, argument, value);
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to