This is an automated email from the ASF dual-hosted git repository.

gurustron pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 479eeecc3c3 IGNITE-27068 .NET: LINQ: Support C# 14 span methods (#7933)
479eeecc3c3 is described below

commit 479eeecc3c3f88a789e162fbcb382579f8b57530
Author: gurustron <[email protected]>
AuthorDate: Mon Apr 6 15:52:43 2026 +0300

    IGNITE-27068 .NET: LINQ: Support C# 14 span methods (#7933)
    
    Co-authored-by: Pavel Tupitsyn <[email protected]>
    Co-authored-by: sstronchinskiy <[email protected]>
---
 .../Linq/LinqSqlGenerationTests.cs                 |  8 +++
 .../Internal/Linq/IgniteQueryParser.cs             | 75 ++++++++++++++++++++--
 2 files changed, 79 insertions(+), 4 deletions(-)

diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.cs
index bd07e82a0ff..9c3254429d2 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.cs
@@ -158,6 +158,14 @@ public partial class LinqSqlGenerationTests
                 .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();
+        });
+
     [Test]
     public void TestFirst() =>
         AssertSql("select _T0.KEY, _T0.VAL from PUBLIC.TBL1 as _T0 limit 1", q 
=> q.First());
diff --git 
a/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryParser.cs 
b/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryParser.cs
index 3ddcfa636cc..f3b183c8715 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryParser.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryParser.cs
@@ -17,6 +17,12 @@
 
 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.Parsing.ExpressionVisitors.Transformation;
@@ -44,6 +50,7 @@ internal static class IgniteQueryParser
     private static QueryParser CreateParser()
     {
         var transformerRegistry = 
ExpressionTransformerRegistry.CreateDefault();
+        transformerRegistry.Register(new 
MemoryExtensionsContainsExpressionTransformer());
 
         var proc = CreateCompoundProcessor(transformerRegistry);
 
@@ -78,16 +85,76 @@ internal static class IgniteQueryParser
         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)
+            ])!;
+
+        [SuppressMessage("Performance", "CA1819:Properties should not return 
arrays", Justification = "Interface impl")]
+        public ExpressionType[] SupportedExpressionTypes => 
[ExpressionType.Call];
+
+        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;
+        }
     }
 }

Reply via email to