diff --git a/CLAUDE.md b/CLAUDE.md index 42f8c01..767a2c2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,17 +43,21 @@ CI targets both .NET 8.0 and .NET 10.0 SDKs. 1. **Compile-time (source generation):** Two incremental generators in `ExpressiveSharp.Generator` (targets netstandard2.0): - `ExpressiveGenerator` — finds `[Expressive]` members, validates them via `ExpressiveInterpreter`, emits expression trees via `ExpressionTreeEmitter`, and builds a runtime registry via `ExpressionRegistryEmitter` - - `PolyfillInterceptorGenerator` — uses C# 13 `[InterceptsLocation]` to rewrite `ExpressionPolyfill.Create()` and `IRewritableQueryable` LINQ call sites from delegate form to expression tree form + - `PolyfillInterceptorGenerator` — uses C# 13 `[InterceptsLocation]` to rewrite `ExpressionPolyfill.Create()` and `IRewritableQueryable` LINQ call sites from delegate form to expression tree form. Supports all standard `Queryable` methods, multi-lambda methods (Join, GroupJoin, GroupBy overloads), non-lambda-first methods (Zip, ExceptBy, etc.), and custom target types via `[PolyfillTarget]` (e.g., EF Core's `EntityFrameworkQueryableExtensions` for async methods) 2. **Runtime:** `ExpressiveResolver` looks up generated expressions by (DeclaringType, MemberName, ParameterTypes). `ExpressiveReplacer` is an `ExpressionVisitor` that substitutes `[Expressive]` member accesses with the generated expression trees. Transformers (in `Transformers/`) post-process trees for provider compatibility. ### Key Source Files - `src/ExpressiveSharp.Generator/ExpressiveGenerator.cs` — main generator entry point -- `src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs` — maps IOperation nodes to `Expression.*` factory calls (the heart of code generation) +- `src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs` — maps IOperation nodes to `Expression.*` factory calls (the heart of code generation). Uses `varPrefix` to ensure unique local variable names across multi-lambda emitters - `src/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.cs` — validates and prepares `[Expressive]` members -- `src/ExpressiveSharp.Generator/PolyfillInterceptorGenerator.cs` — interceptor generation +- `src/ExpressiveSharp.Generator/PolyfillInterceptorGenerator.cs` — interceptor generation. Dedicated emitters for complex methods (Join, GroupJoin, GroupBy multi-lambda), enhanced generic fallback (`EmitGenericSingleLambda`) for single-lambda methods with non-lambda arg forwarding, `[PolyfillTarget]` support for custom target types - `src/ExpressiveSharp/Services/ExpressiveResolver.cs` — runtime expression registry lookup +- `src/ExpressiveSharp/PolyfillTargetAttribute.cs` — specifies a non-`Queryable` target type for interceptor forwarding (e.g., `EntityFrameworkQueryableExtensions`) +- `src/ExpressiveSharp/Extensions/RewritableQueryableLinqExtensions.cs` — delegate-based LINQ stubs on `IRewritableQueryable` (~85 intercepted + ~15 passthrough methods) +- `src/ExpressiveSharp.EntityFrameworkCore/Extensions/RewritableQueryableEfCoreExtensions.cs` — EF Core-specific stubs: chain-continuity (AsNoTracking, TagWith, etc.), Include/ThenInclude, and async lambda methods (AnyAsync, SumAsync, etc.) +- `src/ExpressiveSharp.EntityFrameworkCore/IIncludableRewritableQueryable.cs` — hybrid interface bridging `IIncludableQueryable` and `IRewritableQueryable` for Include/ThenInclude chain continuity ### Project Dependencies @@ -66,7 +70,9 @@ ExpressiveSharp.Generator (source generator, netstandard2.0) ExpressiveSharp.EntityFrameworkCore (net8.0;net10.0) ├── ExpressiveSharp - └── EF Core 8.0.25 / 10.0.0 + ├── EF Core 8.0.25 / 10.0.0 + └── Provides: ExpressiveDbSet, IIncludableRewritableQueryable, + chain-continuity stubs, async lambda stubs with [PolyfillTarget] ExpressiveSharp.EntityFrameworkCore.CodeFixers (Roslyn analyzer, netstandard2.0) └── Microsoft.CodeAnalysis.CSharp.Workspaces 4.12.0 diff --git a/README.md b/README.md index 2cfbbba..d67ad82 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ var results = queryable The source generator intercepts these calls at compile time and rewrites them to use proper expression trees — no runtime overhead. -Available LINQ methods: `Where`, `Select`, `SelectMany`, `OrderBy`, `OrderByDescending`, `ThenBy`, `ThenByDescending`, `GroupBy`. +All standard `Queryable` methods are supported — filtering (`Where`, `Any`, `All`), projection (`Select`, `SelectMany`), ordering (`OrderBy`, `ThenBy`), grouping (`GroupBy`), joins (`Join`, `GroupJoin`, `Zip`), aggregation (`Sum`, `Average`, `Min`, `Max`, `Count`), element access (`First`, `Single`, `Last` and their `OrDefault` variants), set operations (`ExceptBy`, `IntersectBy`, `UnionBy`, `DistinctBy`), and more. Non-lambda operators like `Take`, `Skip`, `Distinct`, and `Reverse` preserve the `IRewritableQueryable` chain. Comparer overloads (`IEqualityComparer`, `IComparer`) are also supported. ### `ExpressionPolyfill.Create` @@ -213,6 +213,17 @@ public class MyDbContext : DbContext ctx.Orders.Where(o => o.Customer?.Name == "Alice"); ``` +`ExpressiveDbSet` preserves chain continuity across EF Core operations — `Include`/`ThenInclude`, `AsNoTracking`, `IgnoreQueryFilters`, `TagWith`, and all async lambda methods (`AnyAsync`, `FirstAsync`, `SumAsync`, etc.) work seamlessly: + +```csharp +var result = await ctx.Orders + .Include(o => o.Customer) + .ThenInclude(c => c.Address) + .AsNoTracking() + .Where(o => o.Customer?.Name == "Alice") + .FirstOrDefaultAsync(o => o.Total > 100); +``` + ## Supported C# Features ### Expression-Level diff --git a/samples/EFCoreSample/Models.cs b/samples/EFCoreSample/Models.cs index ef0a554..883bbec 100644 --- a/samples/EFCoreSample/Models.cs +++ b/samples/EFCoreSample/Models.cs @@ -1,8 +1,6 @@ using System.ComponentModel; using ExpressiveSharp; -// ── Enums ──────────────────────────────────────────────────────────────────── - public enum OrderStatus { [Description("Awaiting processing")] @@ -27,8 +25,6 @@ public static class OrderStatusExtensions }; } -// ── Domain models ──────────────────────────────────────────────────────────── - public class Customer { public int Id { get; set; } @@ -47,59 +43,19 @@ public class Order public int CustomerId { get; set; } public Customer Customer { get; set; } = null!; - /// Price * Quantity — becomes a SQL expression, not a client-side property. [Expressive] public double Total => Price * Quantity; - /// Null-conditional navigation — UseExpressives() strips the null check - /// automatically so EF Core can translate it to a JOIN. - [Expressive] - public string? CustomerEmail => Total >= 0 ? Customer?.Email : ""; - - /// Enum method expansion — each enum value is evaluated at compile time, - /// producing a CASE WHEN chain that translates directly to SQL. [Expressive] public string StatusDescription => Status.GetDescription(); - /// Switch expression with relational patterns — becomes SQL CASE WHEN. [Expressive] - public string GetGrade() => Price switch + public string Grade => Price switch { >= 100 => "Premium", >= 50 => "Standard", _ => "Budget", }; - - /// Block body with local variable + if/else — requires AllowBlockBody opt-in. - /// The FlattenBlockExpressions transformer inlines locals for SQL translation. - [Expressive(AllowBlockBody = true)] - public string GetCategory() - { - var threshold = Quantity * 10; - if (threshold > 100) - return "Bulk"; - else - return "Regular"; - } } -// ── DTO for constructor projection ─────────────────────────────────────────── - -public class OrderSummaryDto -{ - public int Id { get; set; } - public string Description { get; set; } = ""; - public double Total { get; set; } - - public OrderSummaryDto() { } - - /// Expands to MemberInit so EF Core translates this projection to a clean - /// SQL SELECT instead of loading entire Order entities. - [Expressive] - public OrderSummaryDto(int id, string description, double total) - { - Id = id; - Description = description; - Total = total; - } -} +public record OrderSummaryDto(int OrderId, string CustomerName, double Total, string Grade, string Status); diff --git a/samples/EFCoreSample/Program.cs b/samples/EFCoreSample/Program.cs index 902c0cf..11acb11 100644 --- a/samples/EFCoreSample/Program.cs +++ b/samples/EFCoreSample/Program.cs @@ -1,100 +1,63 @@ +// A small order-management tool that queries an SQLite database. +// ExpressiveSharp lets us write natural C# (null-conditional ?., switch expressions, +// computed properties) and have it all translate to SQL instead of evaluating client-side. + using ExpressiveSharp.Extensions; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; -// ── Setup ──────────────────────────────────────────────────────────────────── - var connection = new SqliteConnection("Data Source=:memory:"); connection.Open(); var options = new DbContextOptionsBuilder() .UseSqlite(connection) - .UseExpressives() // ← this one line enables everything below + .UseExpressives() .Options; using var db = new SampleDbContext(options); db.Database.EnsureCreated(); SeedData(db); -// ── 1. Computed properties in SQL ──────────────────────────────────────────── -// Total => Price * Quantity becomes a SQL expression, not a client-side eval. -Section("Computed Properties in SQL"); - -var totalsQuery = db.Orders.AsQueryable().Select(o => new { o.Id, o.Total }); -PrintSql(totalsQuery); -foreach (var r in totalsQuery) Console.WriteLine($" Order #{r.Id}: Total = {r.Total}"); - -// ── 2. Modern syntax in queries ────────────────────────────────────────────── -// ExpressiveDbSet lets you use ?. directly in delegate lambdas. -// The source generator rewrites them to expression trees at compile time. -Section("Modern Syntax in Queries"); - -// Where with null-conditional ?. -var emailFilter = db.Orders.Where(o => o.Customer?.Email != null); -PrintSql(emailFilter); -foreach (var o in emailFilter) Console.WriteLine($" Order #{o.Id}: {o.Customer.Name} <{o.Customer.Email}>"); - -// Switch expression in Select — becomes CASE WHEN in SQL -var gradesQuery = db.Orders.AsQueryable().Select(o => new { o.Id, o.Price, Grade = o.GetGrade() }); -PrintSql(gradesQuery); -foreach (var r in gradesQuery) Console.WriteLine($" Order #{r.Id} (${r.Price}): {r.Grade}"); - -// GroupBy with ?. -var groupQuery = db.Orders.GroupBy(o => o.Customer?.Email); -PrintSql(groupQuery); -foreach (var g in groupQuery) - Console.WriteLine($" Email={g.Key ?? "(null)"}: [{string.Join(", ", g.Select(o => $"#{o.Id}"))}]"); - -// ── 3. Block bodies and enum expansion ─────────────────────────────────────── -// Block-bodied methods and enum method calls are flattened into SQL-translatable -// expressions automatically. -Section("Block Bodies and Enum Expansion"); - -// GetCategory() — block body with local variable + if/else → CASE WHEN -var catsQuery = db.Orders.AsQueryable().Select(o => new { o.Id, o.Quantity, Category = o.GetCategory() }); -PrintSql(catsQuery); -foreach (var r in catsQuery) Console.WriteLine($" Order #{r.Id} (qty={r.Quantity}): {r.Category}"); - -// StatusDescription — enum method expansion → ternary chain in SQL -var statusQuery = db.Orders.AsQueryable().Select(o => new { o.Id, o.Status, Desc = o.StatusDescription }); -PrintSql(statusQuery); -foreach (var r in statusQuery) Console.WriteLine($" Order #{r.Id} ({r.Status}): \"{r.Desc}\""); - -// ── 4. Constructor projection ──────────────────────────────────────────────── -// [Expressive] constructors expand to MemberInit, so EF Core translates the -// projection to a clean SQL SELECT instead of loading entire entities. -Section("Constructor Projection"); - -var dtoQuery = db.Orders.AsQueryable().Select(o => new OrderSummaryDto(o.Id, o.Tag ?? "N/A", o.Total)); -PrintSql(dtoQuery); -foreach (var dto in dtoQuery) Console.WriteLine($" {{ Id={dto.Id}, Desc=\"{dto.Description}\", Total={dto.Total} }}"); - -// ── 5. Combining everything ────────────────────────────────────────────────── -// One query: ?. filter + [Expressive] property + switch expression. -Section("Combining Everything"); - -// ExpressiveDbSet handles the ?. in Where; UseExpressives() expands -// [Expressive] members when they appear elsewhere in the query pipeline. -var combined = db.Orders.Where(o => o.Customer?.Name == "Alice"); -PrintSql(combined); -foreach (var o in combined) Console.WriteLine($" Order #{o.Id}: Total={o.Total}, Grade={o.GetGrade()}"); - -// ── Helpers ────────────────────────────────────────────────────────────────── +Console.WriteLine(); -connection.Close(); +// Find all orders where the customer has an email on file. +// The ?. operator works directly in the lambda — the source generator +// rewrites it to an expression tree that EF Core translates to SQL. +var contactableOrders = db.Orders + .Where(o => o.Customer?.Email != null) + .Select(o => new OrderSummaryDto(o.Id, o.Customer.Name, o.Total, o.Grade, o.StatusDescription)) + .ToList(); -static void Section(string title) -{ - Console.WriteLine(); - Console.WriteLine($"--- {title} ---"); - Console.WriteLine(); -} +Console.WriteLine("Orders with customer email on file:"); +foreach (var dto in contactableOrders) + Console.WriteLine($" #{dto.OrderId} {dto.CustomerName} — ${dto.Total:N2} ({dto.Grade}, {dto.Status})"); -static void PrintSql(IQueryable query) -{ - Console.WriteLine($" SQL: {query.ToQueryString()}"); - Console.WriteLine(); -} +Console.WriteLine(); + +// Premium approved orders — filter on [Expressive] computed properties. +var premiumApproved = db.Orders + .Where(o => o.Total >= 200 && o.Status == OrderStatus.Approved) + .Select(o => new { o.Id, o.Total, o.Grade }) + .ToList(); + +Console.WriteLine("Premium approved orders (total >= $200):"); +foreach (var o in premiumApproved) + Console.WriteLine($" #{o.Id} — ${o.Total:N2} ({o.Grade})"); + +Console.WriteLine(); + +// Revenue by status — the [Expressive] StatusDescription property expands to a +// CASE WHEN chain in SQL via UseExpressives(), no special queryable needed. +var revenueByStatus = db.Orders.AsQueryable() + .GroupBy(o => o.StatusDescription) + .Select(g => new { Status = g.Key, Revenue = g.Sum(o => o.Total) }) + .ToList(); + +Console.WriteLine("Revenue by status:"); +foreach (var row in revenueByStatus) + Console.WriteLine($" {row.Status}: ${row.Revenue:N2}"); + +connection.Close(); static void SeedData(SampleDbContext db) { diff --git a/samples/EFCoreSample/SampleDbContext.cs b/samples/EFCoreSample/SampleDbContext.cs index 99e4fac..e68d8ec 100644 --- a/samples/EFCoreSample/SampleDbContext.cs +++ b/samples/EFCoreSample/SampleDbContext.cs @@ -1,5 +1,6 @@ using ExpressiveSharp.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; public class SampleDbContext : DbContext { @@ -8,6 +9,13 @@ public class SampleDbContext : DbContext public SampleDbContext(DbContextOptions options) : base(options) { } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder + .LogTo(Console.WriteLine, LogLevel.Information) + .EnableSensitiveDataLogging(); + } + protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(e => e.HasKey(c => c.Id)); diff --git a/src/ExpressiveSharp.EntityFrameworkCore/Extensions/RewritableQueryableEfCoreExtensions.cs b/src/ExpressiveSharp.EntityFrameworkCore/Extensions/RewritableQueryableEfCoreExtensions.cs new file mode 100644 index 0000000..cf450a6 --- /dev/null +++ b/src/ExpressiveSharp.EntityFrameworkCore/Extensions/RewritableQueryableEfCoreExtensions.cs @@ -0,0 +1,308 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Linq.Expressions; +using ExpressiveSharp; +using ExpressiveSharp.EntityFrameworkCore; +using ExpressiveSharp.EntityFrameworkCore.Infrastructure; +using ExpressiveSharp.Extensions; + +// ReSharper disable once CheckNamespace — intentionally in Microsoft.EntityFrameworkCore for discoverability +namespace Microsoft.EntityFrameworkCore; + +/// +/// Extension methods on for EF Core operations. +/// Passthrough stubs maintain the chain. +/// Async lambda stubs are intercepted by the source generator via +/// to forward to . +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public static class RewritableQueryableEfCoreExtensions +{ + private const string InterceptedMessage = + "This method must be intercepted by the ExpressiveSharp source generator. " + + "Ensure the generator package is installed and the InterceptorsNamespaces MSBuild property is configured."; + // ── Tracking behavior ──────────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable AsNoTracking( + this IRewritableQueryable source) + where TEntity : class + => EntityFrameworkQueryableExtensions.AsNoTracking(source).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable AsNoTrackingWithIdentityResolution( + this IRewritableQueryable source) + where TEntity : class + => EntityFrameworkQueryableExtensions.AsNoTrackingWithIdentityResolution(source).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable AsTracking( + this IRewritableQueryable source) + where TEntity : class + => EntityFrameworkQueryableExtensions.AsTracking(source).WithExpressionRewrite(); + + // ── Query filters ──────────────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable IgnoreAutoIncludes( + this IRewritableQueryable source) + where TEntity : class + => EntityFrameworkQueryableExtensions.IgnoreAutoIncludes(source).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable IgnoreQueryFilters( + this IRewritableQueryable source) + where TEntity : class + => EntityFrameworkQueryableExtensions.IgnoreQueryFilters(source).WithExpressionRewrite(); + + // ── Query tagging ──────────────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable TagWith( + this IRewritableQueryable source, + string tag) + where TEntity : class + => EntityFrameworkQueryableExtensions.TagWith(source, tag).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable TagWithCallSite( + this IRewritableQueryable source, + [System.Runtime.CompilerServices.CallerFilePath] string filePath = "", + [System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0) + where TEntity : class + => EntityFrameworkQueryableExtensions.TagWithCallSite(source, filePath, lineNumber).WithExpressionRewrite(); + + // ── Include / ThenInclude (runtime, not intercepted) ─────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IIncludableRewritableQueryable Include( + this IRewritableQueryable source, + Expression> navigationPropertyPath) + where TEntity : class + => new IncludableRewritableQueryableWrapper( + EntityFrameworkQueryableExtensions.Include(source, navigationPropertyPath)); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Include( + this IRewritableQueryable source, + string navigationPropertyPath) + where TEntity : class + => EntityFrameworkQueryableExtensions.Include(source, navigationPropertyPath) + .WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IIncludableRewritableQueryable + ThenInclude( + this IIncludableRewritableQueryable source, + Expression> navigationPropertyPath) + where TEntity : class + => new IncludableRewritableQueryableWrapper( + EntityFrameworkQueryableExtensions.ThenInclude(source, navigationPropertyPath)); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IIncludableRewritableQueryable + ThenInclude( + this IIncludableRewritableQueryable> source, + Expression> navigationPropertyPath) + where TEntity : class + => new IncludableRewritableQueryableWrapper( + EntityFrameworkQueryableExtensions.ThenInclude(source, navigationPropertyPath)); + + // ── Async predicate methods (intercepted) ──────────────────────────── + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task AnyAsync( + this IRewritableQueryable source, + Func predicate, + CancellationToken cancellationToken = default) + where TEntity : class + => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task AllAsync( + this IRewritableQueryable source, + Func predicate, + CancellationToken cancellationToken = default) + where TEntity : class + => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task CountAsync( + this IRewritableQueryable source, + Func predicate, + CancellationToken cancellationToken = default) + where TEntity : class + => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task LongCountAsync( + this IRewritableQueryable source, + Func predicate, + CancellationToken cancellationToken = default) + where TEntity : class + => throw new UnreachableException(InterceptedMessage); + + // ── Async element methods (intercepted) ────────────────────────────── + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task FirstAsync( + this IRewritableQueryable source, + Func predicate, + CancellationToken cancellationToken = default) + where TEntity : class + => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task FirstOrDefaultAsync( + this IRewritableQueryable source, + Func predicate, + CancellationToken cancellationToken = default) + where TEntity : class + => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task LastAsync( + this IRewritableQueryable source, + Func predicate, + CancellationToken cancellationToken = default) + where TEntity : class + => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task LastOrDefaultAsync( + this IRewritableQueryable source, + Func predicate, + CancellationToken cancellationToken = default) + where TEntity : class + => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task SingleAsync( + this IRewritableQueryable source, + Func predicate, + CancellationToken cancellationToken = default) + where TEntity : class + => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task SingleOrDefaultAsync( + this IRewritableQueryable source, + Func predicate, + CancellationToken cancellationToken = default) + where TEntity : class + => throw new UnreachableException(InterceptedMessage); + + // ── Async Sum (intercepted) ────────────────────────────────────────── + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task SumAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task SumAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task SumAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task SumAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task SumAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task SumAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task SumAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task SumAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task SumAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task SumAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + // ── Async Average (intercepted) ────────────────────────────────────── + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task AverageAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task AverageAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task AverageAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task AverageAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task AverageAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task AverageAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task AverageAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task AverageAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task AverageAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task AverageAsync(this IRewritableQueryable source, Func selector, CancellationToken cancellationToken = default) where TEntity : class => throw new UnreachableException(InterceptedMessage); + + // ── Async Min / Max (intercepted) ──────────────────────────────────── + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task MinAsync( + this IRewritableQueryable source, + Func selector, + CancellationToken cancellationToken = default) + where TEntity : class + => throw new UnreachableException(InterceptedMessage); + + [PolyfillTarget(typeof(EntityFrameworkQueryableExtensions))] + [EditorBrowsable(EditorBrowsableState.Never)] + public static Task MaxAsync( + this IRewritableQueryable source, + Func selector, + CancellationToken cancellationToken = default) + where TEntity : class + => throw new UnreachableException(InterceptedMessage); +} diff --git a/src/ExpressiveSharp.EntityFrameworkCore/IIncludableRewritableQueryable.cs b/src/ExpressiveSharp.EntityFrameworkCore/IIncludableRewritableQueryable.cs new file mode 100644 index 0000000..a40c690 --- /dev/null +++ b/src/ExpressiveSharp.EntityFrameworkCore/IIncludableRewritableQueryable.cs @@ -0,0 +1,15 @@ +using Microsoft.EntityFrameworkCore.Query; + +namespace ExpressiveSharp.EntityFrameworkCore; + +/// +/// Combines (for ThenInclude chaining) +/// with (for delegate-based LINQ stubs with modern C# syntax). +/// Returned by Include and ThenInclude on sources. +/// +public interface IIncludableRewritableQueryable + : IIncludableQueryable, + IRewritableQueryable + where TEntity : class +{ +} diff --git a/src/ExpressiveSharp.EntityFrameworkCore/Infrastructure/IncludableRewritableQueryableWrapper.cs b/src/ExpressiveSharp.EntityFrameworkCore/Infrastructure/IncludableRewritableQueryableWrapper.cs new file mode 100644 index 0000000..6ae8d4c --- /dev/null +++ b/src/ExpressiveSharp.EntityFrameworkCore/Infrastructure/IncludableRewritableQueryableWrapper.cs @@ -0,0 +1,26 @@ +using System.Collections; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Query; + +namespace ExpressiveSharp.EntityFrameworkCore.Infrastructure; + +/// +/// Wraps an to also implement +/// , preserving chain continuity for delegate-based LINQ stubs. +/// +internal sealed class IncludableRewritableQueryableWrapper + : IIncludableRewritableQueryable + where TEntity : class +{ + private readonly IIncludableQueryable _inner; + + public IncludableRewritableQueryableWrapper(IIncludableQueryable inner) + => _inner = inner; + + Type IQueryable.ElementType => ((IQueryable)_inner).ElementType; + Expression IQueryable.Expression => ((IQueryable)_inner).Expression; + IQueryProvider IQueryable.Provider => ((IQueryable)_inner).Provider; + + IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_inner).GetEnumerator(); +} diff --git a/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs b/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs index bb7401e..bf85715 100644 --- a/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs +++ b/src/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs @@ -51,15 +51,18 @@ internal sealed class ExpressionTreeEmitter private int _lineCount; private readonly Stack<(string VarName, ITypeSymbol? Type)> _conditionalAccessReceiverStack = new(); private readonly Dictionary _typeAliases = new(SymbolEqualityComparer.Default); + private readonly string _varPrefix; public ExpressionTreeEmitter( SemanticModel semanticModel, SourceProductionContext? context = null, - string fieldPrefix = "") + string fieldPrefix = "", + string varPrefix = "") { _semanticModel = semanticModel; _context = context; _fieldCache = new ReflectionFieldCache(fieldPrefix); + _varPrefix = varPrefix; } /// @@ -90,7 +93,7 @@ public EmitResult Emit( var paramVarNames = new List(); foreach (var param in parameters) { - var varName = $"p_{SanitizeIdentifier(param.Name)}"; + var varName = $"{_varPrefix}p_{SanitizeIdentifier(param.Name)}"; paramVarNames.Add(varName); AppendLine($"var {varName} = {Expr}.Parameter(typeof({param.TypeFqn}), \"{param.Name}\");"); @@ -126,6 +129,19 @@ public EmitResult Emit( var bodyVar = EmitOperation(operation); + // If the body's type doesn't match the delegate return type, insert an Expression.Convert. + // This handles cases like int → int? (Nullable) in Join key selectors. + if (operation.Type is not null) + { + var bodyTypeFqn = ResolveTypeFqn(operation.Type); + if (bodyTypeFqn != returnTypeFqn) + { + var convertVar = NextVar(); + AppendLine($"var {convertVar} = {Expr}.Convert({bodyVar}, typeof({returnTypeFqn}));"); + bodyVar = convertVar; + } + } + var paramsArg = paramVarNames.Count > 0 ? string.Join(", ", paramVarNames) : $"global::System.Array.Empty()"; @@ -154,7 +170,7 @@ public EmitResult EmitConstructor( var paramVarNames = new List(); foreach (var param in parameters) { - var varName = $"p_{SanitizeIdentifier(param.Name)}"; + var varName = $"{_varPrefix}p_{SanitizeIdentifier(param.Name)}"; paramVarNames.Add(varName); AppendLine($"var {varName} = {Expr}.Parameter(typeof({param.TypeFqn}), \"{param.Name}\");"); @@ -2852,7 +2868,7 @@ private string EmitUnsupported(IOperation operation) // ── Helpers ────────────────────────────────────────────────────────────── - private string NextVar() => $"expr_{_varCounter++}"; + private string NextVar() => $"{_varPrefix}expr_{_varCounter++}"; private void AppendLine(string line) { diff --git a/src/ExpressiveSharp.Generator/PolyfillInterceptorGenerator.cs b/src/ExpressiveSharp.Generator/PolyfillInterceptorGenerator.cs index a450368..0a62819 100644 --- a/src/ExpressiveSharp.Generator/PolyfillInterceptorGenerator.cs +++ b/src/ExpressiveSharp.Generator/PolyfillInterceptorGenerator.cs @@ -249,14 +249,26 @@ public InterceptsLocationAttribute(int version, string data) { } if (model.GetSymbolInfo(inv).Symbol is not IMethodSymbol method) return null; - // The stub convention: the first non-receiver parameter must be a Func<> delegate, + // The stub convention: at least one non-receiver parameter must be a Func<> delegate, // not an Expression>. This distinguishes user/library IRewritableQueryable stubs // from regular IQueryable extension methods. + // For most methods, Func<> is Parameters[0]. For Join/GroupJoin/Zip/ExceptBy etc., + // it may be at a later position (e.g., Parameters[1]) after an IEnumerable<> arg. if (method.Parameters.IsEmpty) return null; - if (method.Parameters[0].Type is not INamedTypeSymbol firstParamType) return null; - if (firstParamType.ConstructedFrom.Name != "Func" || - firstParamType.ConstructedFrom.ContainingNamespace?.ToDisplayString() != "System") - return null; + int funcParamIndex = -1; + int funcParamCount = 0; + for (int i = 0; i < method.Parameters.Length; i++) + { + if (method.Parameters[i].Type is INamedTypeSymbol pt && + pt.ConstructedFrom.Name == "Func" && + pt.ConstructedFrom.ContainingNamespace?.ToDisplayString() == "System") + { + funcParamCount++; + if (funcParamIndex < 0) + funcParamIndex = i; + } + } + if (funcParamIndex < 0) return null; // Get the interceptable location using the Roslyn 5.0+ API. // This produces the correct [InterceptsLocation(version, data)] attribute text. @@ -285,7 +297,17 @@ public InterceptsLocationAttribute(int version, string data) { } var typeArgs = method.TypeArguments; // Per-call WithExpressionRewrite() → MSBuild global → hardcoded Ignore fallback. - + + // Resolve the target type for the Queryable.* call. Defaults to System.Linq.Queryable + // unless overridden by [PolyfillTarget(typeof(...))] on the stub method. + var targetTypeFqn = "global::System.Linq.Queryable"; + var polyfillAttr = method.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.Name == "PolyfillTargetAttribute"); + if (polyfillAttr?.ConstructorArguments.Length > 0 && + polyfillAttr.ConstructorArguments[0].Value is INamedTypeSymbol targetType) + { + targetTypeFqn = targetType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + } return methodName switch { "Where" @@ -305,20 +327,40 @@ public InterceptsLocationAttribute(int version, string data) { } => EmitSelectMany3(inv, model, spc, interceptAttr, index, elementSymbol, elementFqn, typeArgs[1], typeArgs[2], globalOptions, allStaticFields), "OrderBy" or "OrderByDescending" - when typeArgs.Length == 2 + when typeArgs.Length == 2 && method.Parameters.Length == 1 => EmitOrdering(inv, model, spc, interceptAttr, index, methodName, elementSymbol, elementFqn, typeArgs[1], ordered: false, globalOptions, allStaticFields), "ThenBy" or "ThenByDescending" - when typeArgs.Length == 2 + when typeArgs.Length == 2 && method.Parameters.Length == 1 => EmitOrdering(inv, model, spc, interceptAttr, index, methodName, elementSymbol, elementFqn, typeArgs[1], ordered: true, globalOptions, allStaticFields), "GroupBy" - when typeArgs.Length == 2 + when typeArgs.Length == 2 && method.Parameters.Length == 1 => EmitGroupBy(inv, model, spc, interceptAttr, index, elementSymbol, elementFqn, typeArgs[1], globalOptions, allStaticFields), - // Generic fallback: any user-defined or future single-lambda stub whose name - // matches a Queryable.* method by convention. - _ => EmitGenericSingleLambda(inv, model, spc, interceptAttr, index, methodName, elementSymbol, elementFqn, method, globalOptions, allStaticFields), + "GroupBy" + when typeArgs.Length == 3 + => EmitGroupByMulti(inv, model, spc, interceptAttr, index, elementSymbol, elementFqn, typeArgs, globalOptions, allStaticFields), + + "GroupBy" + when typeArgs.Length == 4 + => EmitGroupByMulti(inv, model, spc, interceptAttr, index, elementSymbol, elementFqn, typeArgs, globalOptions, allStaticFields), + + "Join" or "LeftJoin" or "RightJoin" + when typeArgs.Length == 4 + => EmitJoin(inv, model, spc, interceptAttr, index, methodName, elementSymbol, elementFqn, typeArgs, globalOptions, allStaticFields), + + "GroupJoin" + when typeArgs.Length == 4 + => EmitGroupJoin(inv, model, spc, interceptAttr, index, elementSymbol, elementFqn, typeArgs, globalOptions, allStaticFields), + + // Generic fallback: single-lambda stubs only. Multi-Func stubs (e.g., AggregateBy) + // need dedicated emitters — the fallback would only rewrite the first Func<>, + // leaving the rest as raw delegates which won't match Expression> parameters. + _ when funcParamCount == 1 + => EmitGenericSingleLambda(inv, model, spc, interceptAttr, index, methodName, elementSymbol, elementFqn, method, funcParamIndex, targetTypeFqn, globalOptions, allStaticFields), + + _ => null, // Unsupported multi-Func stub — interceptor not generated, stub throws at runtime }; } @@ -348,7 +390,7 @@ public InterceptsLocationAttribute(int version, string data) { } var bodyNode = lambda.Body is ExpressionSyntax expr ? (SyntaxNode)expr : lambda.Body; if (bodyNode is null) return null; - var emitter = new Emitter.ExpressionTreeEmitter(model, spc, fieldPrefix); + var emitter = new Emitter.ExpressionTreeEmitter(model, spc, fieldPrefix, varPrefix: fieldPrefix); if (typeAliases is not null) { @@ -778,30 +820,255 @@ private static bool IsAnonymousType(ITypeSymbol type) """; } + /// + /// Multi-lambda emitter for GroupBy overloads with element/result selectors (arity 3 or 4). + /// + private static string? EmitGroupByMulti( + InvocationExpressionSyntax inv, SemanticModel model, + SourceProductionContext spc, string interceptAttr, int idx, + INamedTypeSymbol elemSym, string elemFqn, + ImmutableArray typeArgs, + ExpressiveGlobalOptions globalOptions, + List allStaticFields) + { + // Arity 3: GroupBy(keySelector, elementSelector) or + // GroupBy(keySelector, resultSelector) + // Arity 4: GroupBy(keySelector, elementSelector, resultSelector) + if (inv.ArgumentList.Arguments.Count < 2) return null; + if (inv.ArgumentList.Arguments[0].Expression is not LambdaExpressionSyntax lam1) return null; + if (inv.ArgumentList.Arguments[1].Expression is not LambdaExpressionSyntax lam2) return null; + + if (typeArgs.Length == 3) + { + // Two lambdas at [0] and [1] + var keyFqn = typeArgs[1].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var thirdFqn = typeArgs[2].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + // Determine if this is element-selector or result-selector by checking 2nd lambda arity. + var methodSym = (IMethodSymbol)model.GetSymbolInfo(inv).Symbol!; + var secondFuncType = (INamedTypeSymbol)methodSym.Parameters[1].Type; + var isElementSelector = secondFuncType.TypeArguments.Length == 2; // Func + + var delegateFqn1 = $"global::System.Func<{elemFqn}, {keyFqn}>"; + var emitResult1 = EmitLambdaBody(lam1, elemSym, model, spc, globalOptions, delegateFqn1, "__lambda1", fieldPrefix: $"i{idx}a_"); + if (emitResult1 is null) return null; + allStaticFields.AddRange(emitResult1.StaticFields); + + string delegateFqn2; + if (isElementSelector) + delegateFqn2 = $"global::System.Func<{elemFqn}, {thirdFqn}>"; + else + delegateFqn2 = $"global::System.Func<{keyFqn}, global::System.Collections.Generic.IEnumerable<{elemFqn}>, {thirdFqn}>"; + + var emitResult2 = EmitLambdaBody(lam2, elemSym, model, spc, globalOptions, delegateFqn2, "__lambda2", fieldPrefix: $"i{idx}b_"); + if (emitResult2 is null) return null; + allStaticFields.AddRange(emitResult2.StaticFields); + + if (isElementSelector) + { + return $$""" + {{interceptAttr}} + internal static global::ExpressiveSharp.IRewritableQueryable> {{MethodId("GroupBy", idx)}}( + this global::ExpressiveSharp.IRewritableQueryable<{{elemFqn}}> source, + global::System.Func<{{elemFqn}}, {{keyFqn}}> _1, + global::System.Func<{{elemFqn}}, {{thirdFqn}}> _2) + { + {{emitResult1.Body}}{{emitResult2.Body}} return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.GroupBy( + (global::System.Linq.IQueryable<{{elemFqn}}>)source, + __lambda1, + __lambda2)); + } + + """; + } + else + { + return $$""" + {{interceptAttr}} + internal static global::ExpressiveSharp.IRewritableQueryable<{{thirdFqn}}> {{MethodId("GroupBy", idx)}}( + this global::ExpressiveSharp.IRewritableQueryable<{{elemFqn}}> source, + global::System.Func<{{elemFqn}}, {{keyFqn}}> _1, + global::System.Func<{{keyFqn}}, global::System.Collections.Generic.IEnumerable<{{elemFqn}}>, {{thirdFqn}}> _2) + { + {{emitResult1.Body}}{{emitResult2.Body}} return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.GroupBy( + (global::System.Linq.IQueryable<{{elemFqn}}>)source, + __lambda1, + __lambda2)); + } + + """; + } + } + + // Arity 4: GroupBy(keySelector, elementSelector, resultSelector) + if (typeArgs.Length == 4) + { + if (inv.ArgumentList.Arguments.Count < 3) return null; + if (inv.ArgumentList.Arguments[2].Expression is not LambdaExpressionSyntax lam3) return null; + + var keyFqn = typeArgs[1].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var elemSelFqn = typeArgs[2].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var resultFqn = typeArgs[3].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + var delegateFqn1 = $"global::System.Func<{elemFqn}, {keyFqn}>"; + var emitResult1 = EmitLambdaBody(lam1, elemSym, model, spc, globalOptions, delegateFqn1, "__lambda1", fieldPrefix: $"i{idx}a_"); + if (emitResult1 is null) return null; + allStaticFields.AddRange(emitResult1.StaticFields); + + var delegateFqn2 = $"global::System.Func<{elemFqn}, {elemSelFqn}>"; + var emitResult2 = EmitLambdaBody(lam2, elemSym, model, spc, globalOptions, delegateFqn2, "__lambda2", fieldPrefix: $"i{idx}b_"); + if (emitResult2 is null) return null; + allStaticFields.AddRange(emitResult2.StaticFields); + + var delegateFqn3 = $"global::System.Func<{keyFqn}, global::System.Collections.Generic.IEnumerable<{elemSelFqn}>, {resultFqn}>"; + var emitResult3 = EmitLambdaBody(lam3, elemSym, model, spc, globalOptions, delegateFqn3, "__lambda3", fieldPrefix: $"i{idx}c_"); + if (emitResult3 is null) return null; + allStaticFields.AddRange(emitResult3.StaticFields); + + return $$""" + {{interceptAttr}} + internal static global::ExpressiveSharp.IRewritableQueryable<{{resultFqn}}> {{MethodId("GroupBy", idx)}}( + this global::ExpressiveSharp.IRewritableQueryable<{{elemFqn}}> source, + global::System.Func<{{elemFqn}}, {{keyFqn}}> _1, + global::System.Func<{{elemFqn}}, {{elemSelFqn}}> _2, + global::System.Func<{{keyFqn}}, global::System.Collections.Generic.IEnumerable<{{elemSelFqn}}>, {{resultFqn}}> _3) + { + {{emitResult1.Body}}{{emitResult2.Body}}{{emitResult3.Body}} return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.GroupBy( + (global::System.Linq.IQueryable<{{elemFqn}}>)source, + __lambda1, + __lambda2, + __lambda3)); + } + + """; + } + + return null; + } + + /// + /// Emitter for Join (and LeftJoin/RightJoin) with 3 lambdas and 1 forwarded IEnumerable argument. + /// + private static string? EmitJoin( + InvocationExpressionSyntax inv, SemanticModel model, + SourceProductionContext spc, string interceptAttr, int idx, + string methodName, INamedTypeSymbol elemSym, string elemFqn, + ImmutableArray typeArgs, + ExpressiveGlobalOptions globalOptions, + List allStaticFields) + { + // Arguments: [0] inner, [1] outerKeySelector, [2] innerKeySelector, [3] resultSelector + if (inv.ArgumentList.Arguments.Count < 4) return null; + if (inv.ArgumentList.Arguments[1].Expression is not LambdaExpressionSyntax lam1) return null; + if (inv.ArgumentList.Arguments[2].Expression is not LambdaExpressionSyntax lam2) return null; + if (inv.ArgumentList.Arguments[3].Expression is not LambdaExpressionSyntax lam3) return null; + + var innerType = typeArgs[1]; + var keyType = typeArgs[2]; + var resultType = typeArgs[3]; + var innerFqn = innerType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var keyFqn = keyType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var resultFqn = resultType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + // Lambda 1: outerKeySelector — Func + var delegateFqn1 = $"global::System.Func<{elemFqn}, {keyFqn}>"; + var emitResult1 = EmitLambdaBody(lam1, elemSym, model, spc, globalOptions, delegateFqn1, "__lambda1", fieldPrefix: $"i{idx}a_"); + if (emitResult1 is null) return null; + allStaticFields.AddRange(emitResult1.StaticFields); + + // Lambda 2: innerKeySelector — Func + // Note: element type for this lambda is TInner, not T + if (innerType is not INamedTypeSymbol innerSym) return null; + var delegateFqn2 = $"global::System.Func<{innerFqn}, {keyFqn}>"; + var emitResult2 = EmitLambdaBody(lam2, innerSym, model, spc, globalOptions, delegateFqn2, "__lambda2", fieldPrefix: $"i{idx}b_"); + if (emitResult2 is null) return null; + allStaticFields.AddRange(emitResult2.StaticFields); + + // Lambda 3: resultSelector — Func + // Use the stub's parameter type for the result selector delegate FQN. + var resultSelectorParam = model.GetSymbolInfo(inv).Symbol is IMethodSymbol m && m.Parameters.Length > 3 + ? m.Parameters[3].Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + : $"global::System.Func<{elemFqn}, {innerFqn}, {resultFqn}>"; + var emitResult3 = EmitLambdaBody(lam3, elemSym, model, spc, globalOptions, resultSelectorParam, "__lambda3", fieldPrefix: $"i{idx}c_"); + if (emitResult3 is null) return null; + allStaticFields.AddRange(emitResult3.StaticFields); + + return $$""" + {{interceptAttr}} + internal static global::ExpressiveSharp.IRewritableQueryable<{{resultFqn}}> {{MethodId(methodName, idx)}}( + this global::ExpressiveSharp.IRewritableQueryable<{{elemFqn}}> source, + global::System.Collections.Generic.IEnumerable<{{innerFqn}}> inner, + global::System.Func<{{elemFqn}}, {{keyFqn}}> _1, + global::System.Func<{{innerFqn}}, {{keyFqn}}> _2, + {{resultSelectorParam}} _3) + { + {{emitResult1.Body}}{{emitResult2.Body}}{{emitResult3.Body}} return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.{{methodName}}( + (global::System.Linq.IQueryable<{{elemFqn}}>)source, + inner, + __lambda1, + __lambda2, + __lambda3)); + } + + """; + } + + /// + /// Emitter for GroupJoin with 3 lambdas and 1 forwarded IEnumerable argument. + /// Same as Join but resultSelector is Func<T, IEnumerable<TInner>, TResult>. + /// + private static string? EmitGroupJoin( + InvocationExpressionSyntax inv, SemanticModel model, + SourceProductionContext spc, string interceptAttr, int idx, + INamedTypeSymbol elemSym, string elemFqn, + ImmutableArray typeArgs, + ExpressiveGlobalOptions globalOptions, + List allStaticFields) + { + // Delegate to EmitJoin — the method name is "GroupJoin" and the result selector shape + // differs, but the generated code structure is identical. + return EmitJoin(inv, model, spc, interceptAttr, idx, "GroupJoin", elemSym, elemFqn, typeArgs, globalOptions, allStaticFields); + } + /// /// Generic single-lambda emitter for user-defined stubs and future LINQ operators. /// Convention: the interceptor calls Queryable.{methodName} with the same name. /// The return type is taken from the stub's declared return type. + /// Supports methods where the Func<> parameter is not necessarily at position 0 + /// (e.g., ExceptBy, Zip where the first parameter is IEnumerable<T>). + /// Non-lambda parameters are forwarded directly to the Queryable.* call. /// private static string? EmitGenericSingleLambda( InvocationExpressionSyntax inv, SemanticModel model, SourceProductionContext spc, string interceptAttr, int idx, string methodName, INamedTypeSymbol elemSym, string elemFqn, - IMethodSymbol method, + IMethodSymbol method, int funcParamIndex, string targetTypeFqn, ExpressiveGlobalOptions globalOptions, List allStaticFields) { - if (inv.ArgumentList.Arguments[0].Expression is not LambdaExpressionSyntax lam) return null; + if (inv.ArgumentList.Arguments[funcParamIndex].Expression is not LambdaExpressionSyntax lam) return null; - // Build the full Func<…> type string from the method's first parameter. - var funcTypeArgs = ((INamedTypeSymbol)method.Parameters[0].Type).TypeArguments; + // Build the full Func<…> type string from the Func<> parameter. + var funcTypeArgs = ((INamedTypeSymbol)method.Parameters[funcParamIndex].Type).TypeArguments; var hasAnyAnon = funcTypeArgs.Any(t => IsAnonymousType(t)); - // Return type comes from the stub's declared return type (must be IRewritableQueryable). - if (method.ReturnType is not INamedTypeSymbol returnType || returnType.TypeArguments.IsEmpty) - return null; - var returnElemType = returnType.TypeArguments[0]; - hasAnyAnon = hasAnyAnon || IsAnonymousType(returnElemType); + // Determine if the stub returns IRewritableQueryable (queryable) or a scalar type. + var isRewritableReturn = method.ReturnType is INamedTypeSymbol rqType + && rqType.ConstructedFrom.ToDisplayString() == IRewritableQueryableOpenTypeName; + + ITypeSymbol? returnElemType = null; + if (isRewritableReturn) + { + returnElemType = ((INamedTypeSymbol)method.ReturnType).TypeArguments[0]; + hasAnyAnon = hasAnyAnon || IsAnonymousType(returnElemType); + } + + // For scalar returns, the FQN of the return type itself. + var scalarReturnFqn = method.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); // ThenBy/ThenByDescending require IOrderedQueryable. var isOrdered = methodName is "ThenBy" or "ThenByDescending"; @@ -809,27 +1076,71 @@ private static bool IsAnonymousType(ITypeSymbol type) ? $"global::System.Linq.IOrderedQueryable<{elemFqn}>" : $"global::System.Linq.IQueryable<{elemFqn}>"; + // Build parameter list and Queryable argument list, forwarding non-lambda args. + // interceptorParams: the parameter declarations for the generated method + // queryableArgs: the arguments passed to Queryable.Method(source, ...) + var interceptorParams = new List(); + var queryableArgs = new List(); + for (int i = 0; i < method.Parameters.Length; i++) + { + var paramType = method.Parameters[i].Type; + var paramTypeFqn = paramType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + if (i == funcParamIndex) + { + // This is the Func<> parameter — will be rewritten to an expression tree. + // Use placeholder name "_" in the interceptor (it's unused at runtime). + interceptorParams.Add($"{paramTypeFqn} _"); + queryableArgs.Add("__lambda"); + } + else + { + // Non-lambda parameter — forward directly. + var paramName = method.Parameters[i].Name; + interceptorParams.Add($"{paramTypeFqn} {paramName}"); + queryableArgs.Add(paramName); + } + } + var interceptorParamList = string.Join(",\n ", interceptorParams); + var queryableArgList = string.Join(",\n ", queryableArgs); + if (!hasAnyAnon) { var funcFqn = "global::System.Func<" + string.Join(", ", funcTypeArgs.Select(t => t.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))) + ">"; - var returnElemFqn = returnElemType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var delegateFqn = funcFqn; var emitResult = EmitLambdaBody(lam, elemSym, model, spc, globalOptions, delegateFqn, fieldPrefix: $"i{idx}_"); if (emitResult is null) return null; allStaticFields.AddRange(emitResult.StaticFields); + if (isRewritableReturn) + { + var returnElemFqn = returnElemType!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + return $$""" + {{interceptAttr}} + internal static global::ExpressiveSharp.IRewritableQueryable<{{returnElemFqn}}> {{MethodId(methodName, idx)}}( + this global::ExpressiveSharp.IRewritableQueryable<{{elemFqn}}> source, + {{interceptorParamList}}) + { + {{emitResult.Body}} return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + {{targetTypeFqn}}.{{methodName}}( + ({{castFqn}})source, + {{queryableArgList}})); + } + + """; + } + + // Scalar return — call target type method directly without wrapping. return $$""" {{interceptAttr}} - internal static global::ExpressiveSharp.IRewritableQueryable<{{returnElemFqn}}> {{MethodId(methodName, idx)}}( + internal static {{scalarReturnFqn}} {{MethodId(methodName, idx)}}( this global::ExpressiveSharp.IRewritableQueryable<{{elemFqn}}> source, - {{funcFqn}} _) + {{interceptorParamList}}) { - {{emitResult.Body}} return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( - global::System.Linq.Queryable.{{methodName}}( + {{emitResult.Body}} return {{targetTypeFqn}}.{{methodName}}( ({{castFqn}})source, - __lambda)); + {{queryableArgList}}); } """; @@ -867,17 +1178,56 @@ private static bool IsAnonymousType(ITypeSymbol type) if (anonEmitResult is null) return null; allStaticFields.AddRange(anonEmitResult.StaticFields); + // Build anonymous-branch parameter and argument lists with forwarding. + var anonInterceptorParams = new List(); + var anonQueryableArgs = new List(); + for (int i = 0; i < method.Parameters.Length; i++) + { + if (i == funcParamIndex) + { + anonInterceptorParams.Add($"{funcFqnGeneric} _"); + anonQueryableArgs.Add("__lambda"); + } + else + { + var paramType = method.Parameters[i].Type; + var paramTypeFqn = paramType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var paramName = method.Parameters[i].Name; + anonInterceptorParams.Add($"{paramTypeFqn} {paramName}"); + anonQueryableArgs.Add(paramName); + } + } + var anonInterceptorParamList = string.Join(",\n ", anonInterceptorParams); + var anonQueryableArgList = string.Join(",\n ", anonQueryableArgs); + + if (isRewritableReturn) + { + return $$""" + {{interceptAttr}} + internal static global::ExpressiveSharp.IRewritableQueryable<{{returnParamName}}> {{MethodId(methodName, idx)}}{{typeParams}}( + this global::ExpressiveSharp.IRewritableQueryable source, + {{anonInterceptorParamList}}) + { + {{anonEmitResult.Body}} return (global::ExpressiveSharp.IRewritableQueryable<{{returnParamName}}>)(object) + global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + {{targetTypeFqn}}.{{methodName}}( + ({{castFqn}})(object)source, + {{anonQueryableArgList}})); + } + + """; + } + + // Scalar return with anonymous types — return directly without wrapping. return $$""" {{interceptAttr}} - internal static global::ExpressiveSharp.IRewritableQueryable<{{returnParamName}}> {{MethodId(methodName, idx)}}{{typeParams}}( + internal static {{returnParamName}} {{MethodId(methodName, idx)}}{{typeParams}}( this global::ExpressiveSharp.IRewritableQueryable source, - {{funcFqnGeneric}} _) + {{anonInterceptorParamList}}) { - {{anonEmitResult.Body}} return (global::ExpressiveSharp.IRewritableQueryable<{{returnParamName}}>)(object) - global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( - global::System.Linq.Queryable.{{methodName}}( - ({{castFqn}})(object)source, - __lambda)); + {{anonEmitResult.Body}} return {{targetTypeFqn}}.{{methodName}}( + ({{castFqn}})(object)source, + {{anonQueryableArgList}}); } """; diff --git a/src/ExpressiveSharp/ExpressionPolyfill.cs b/src/ExpressiveSharp/ExpressionPolyfill.cs index 4fbd8e2..9925a20 100644 --- a/src/ExpressiveSharp/ExpressionPolyfill.cs +++ b/src/ExpressiveSharp/ExpressionPolyfill.cs @@ -26,7 +26,7 @@ public static Expression Create( TDelegate lambda) where TDelegate : Delegate => throw new UnreachableException( "Must be intercepted by the ExpressiveSharp source generator. " + - "Ensure the generator package is installed and InterceptorsPreviewNamespaces is configured."); + "Ensure the generator package is installed and the InterceptorsNamespaces MSBuild property is configured."); /// /// Converts a lambda that uses modern C# syntax (e.g., ?.) into a valid @@ -43,5 +43,5 @@ public static Expression Create( params IExpressionTreeTransformer[] transformers) where TDelegate : Delegate => throw new UnreachableException( "Must be intercepted by the ExpressiveSharp source generator. " + - "Ensure the generator package is installed and InterceptorsPreviewNamespaces is configured."); + "Ensure the generator package is installed and the InterceptorsNamespaces MSBuild property is configured."); } diff --git a/src/ExpressiveSharp/Extensions/ExpressionRewriteLinqExtensions.cs b/src/ExpressiveSharp/Extensions/ExpressionRewriteLinqExtensions.cs deleted file mode 100644 index 5a0de65..0000000 --- a/src/ExpressiveSharp/Extensions/ExpressionRewriteLinqExtensions.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; - -namespace ExpressiveSharp.Extensions -{ - /// - /// Delegate-based LINQ overloads on that shadow the - /// extension methods. These stubs are intercepted at compile time by - /// the ExpressiveSharp source generator, which rewrites the inline lambda body - /// into an expression tree and routes the call to the corresponding - /// operator. Do not call these methods directly. - /// - /// - /// These methods are hidden from IntelliSense. The C# compiler resolves them in preference to the - /// equivalents because is a more specific - /// type than , which causes the delegate-accepting overload to win, - /// allowing modern C# syntax (null-conditional operators, pattern matching, etc.) to compile. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public static class RewritableQueryableLinqExtensions - { - private const string InterceptedMessage = - "This method must be intercepted by the ExpressiveSharp source generator. " + - "Ensure the generator package is installed and InterceptorsPreviewNamespaces is configured."; - - [EditorBrowsable(EditorBrowsableState.Never)] - public static IRewritableQueryable Where( - this IRewritableQueryable source, - Func predicate) - => throw new UnreachableException(InterceptedMessage); - - [EditorBrowsable(EditorBrowsableState.Never)] - public static IRewritableQueryable Select( - this IRewritableQueryable source, - Func selector) - => throw new UnreachableException(InterceptedMessage); - - [EditorBrowsable(EditorBrowsableState.Never)] - public static IRewritableQueryable SelectMany( - this IRewritableQueryable source, - Func> selector) - => throw new UnreachableException(InterceptedMessage); - - [EditorBrowsable(EditorBrowsableState.Never)] - public static IRewritableQueryable SelectMany( - this IRewritableQueryable source, - Func> collectionSelector, - Func resultSelector) - => throw new UnreachableException(InterceptedMessage); - - [EditorBrowsable(EditorBrowsableState.Never)] - public static IRewritableQueryable OrderBy( - this IRewritableQueryable source, - Func keySelector) - => throw new UnreachableException(InterceptedMessage); - - [EditorBrowsable(EditorBrowsableState.Never)] - public static IRewritableQueryable OrderByDescending( - this IRewritableQueryable source, - Func keySelector) - => throw new UnreachableException(InterceptedMessage); - - [EditorBrowsable(EditorBrowsableState.Never)] - public static IRewritableQueryable ThenBy( - this IRewritableQueryable source, - Func keySelector) - => throw new UnreachableException(InterceptedMessage); - - [EditorBrowsable(EditorBrowsableState.Never)] - public static IRewritableQueryable ThenByDescending( - this IRewritableQueryable source, - Func keySelector) - => throw new UnreachableException(InterceptedMessage); - - [EditorBrowsable(EditorBrowsableState.Never)] - public static IRewritableQueryable> GroupBy( - this IRewritableQueryable source, - Func keySelector) - => throw new UnreachableException(InterceptedMessage); - } -} diff --git a/src/ExpressiveSharp/Extensions/RewritableQueryableLinqExtensions.cs b/src/ExpressiveSharp/Extensions/RewritableQueryableLinqExtensions.cs new file mode 100644 index 0000000..180f4c0 --- /dev/null +++ b/src/ExpressiveSharp/Extensions/RewritableQueryableLinqExtensions.cs @@ -0,0 +1,601 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; + +namespace ExpressiveSharp.Extensions +{ + /// + /// Delegate-based LINQ overloads on that shadow the + /// extension methods. These stubs are intercepted at compile time by + /// the ExpressiveSharp source generator, which rewrites the inline lambda body + /// into an expression tree and routes the call to the corresponding + /// operator. Do not call these methods directly. + /// + /// + /// These methods are hidden from IntelliSense. The C# compiler resolves them in preference to the + /// equivalents because is a more specific + /// type than , which causes the delegate-accepting overload to win, + /// allowing modern C# syntax (null-conditional operators, pattern matching, etc.) to compile. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class RewritableQueryableLinqExtensions + { + private const string InterceptedMessage = + "This method must be intercepted by the ExpressiveSharp source generator. " + + "Ensure the generator package is installed and the InterceptorsNamespaces MSBuild property is configured."; + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Where( + this IRewritableQueryable source, + Func predicate) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Select( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable SelectMany( + this IRewritableQueryable source, + Func> selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable SelectMany( + this IRewritableQueryable source, + Func> collectionSelector, + Func resultSelector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable OrderBy( + this IRewritableQueryable source, + Func keySelector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable OrderByDescending( + this IRewritableQueryable source, + Func keySelector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable ThenBy( + this IRewritableQueryable source, + Func keySelector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable ThenByDescending( + this IRewritableQueryable source, + Func keySelector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable> GroupBy( + this IRewritableQueryable source, + Func keySelector) + => throw new UnreachableException(InterceptedMessage); + + // ── Partitioning ───────────────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable TakeWhile( + this IRewritableQueryable source, + Func predicate) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable SkipWhile( + this IRewritableQueryable source, + Func predicate) + => throw new UnreachableException(InterceptedMessage); + + // ── Set operations with key selector ───────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable DistinctBy( + this IRewritableQueryable source, + Func keySelector) + => throw new UnreachableException(InterceptedMessage); + +#if NET9_0_OR_GREATER + // ── .NET 9+ methods ────────────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable> CountBy( + this IRewritableQueryable source, + Func keySelector) + => throw new UnreachableException(InterceptedMessage); +#endif + + // ── Predicate methods (scalar-returning) ───────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool Any( + this IRewritableQueryable source, + Func predicate) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool All( + this IRewritableQueryable source, + Func predicate) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static int Count( + this IRewritableQueryable source, + Func predicate) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static long LongCount( + this IRewritableQueryable source, + Func predicate) + => throw new UnreachableException(InterceptedMessage); + + // ── Element methods (scalar-returning) ─────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static T First( + this IRewritableQueryable source, + Func predicate) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static T? FirstOrDefault( + this IRewritableQueryable source, + Func predicate) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static T Last( + this IRewritableQueryable source, + Func predicate) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static T? LastOrDefault( + this IRewritableQueryable source, + Func predicate) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static T Single( + this IRewritableQueryable source, + Func predicate) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static T? SingleOrDefault( + this IRewritableQueryable source, + Func predicate) + => throw new UnreachableException(InterceptedMessage); + + // ── Aggregation: Sum ───────────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static int Sum( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static int? Sum( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static long Sum( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static long? Sum( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static float Sum( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static float? Sum( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static double Sum( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static double? Sum( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static decimal Sum( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static decimal? Sum( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + // ── Aggregation: Average ───────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static double Average( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static double? Average( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static double Average( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static double? Average( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static float Average( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static float? Average( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static double Average( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static double? Average( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static decimal Average( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static decimal? Average( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + // ── Aggregation: Min / Max ─────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static TResult Min( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static TResult Max( + this IRewritableQueryable source, + Func selector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static T? MinBy( + this IRewritableQueryable source, + Func keySelector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static T? MaxBy( + this IRewritableQueryable source, + Func keySelector) + => throw new UnreachableException(InterceptedMessage); + + // ── Passthrough chain-continuity stubs (runtime, not intercepted) ──── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Take( + this IRewritableQueryable source, + int count) + => Queryable.Take(source, count).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Skip( + this IRewritableQueryable source, + int count) + => Queryable.Skip(source, count).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Distinct( + this IRewritableQueryable source) + => Queryable.Distinct(source).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Reverse( + this IRewritableQueryable source) + => Queryable.Reverse(source).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Concat( + this IRewritableQueryable source, + IEnumerable other) + => Queryable.Concat(source, other).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Union( + this IRewritableQueryable source, + IEnumerable other) + => Queryable.Union(source, other).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Intersect( + this IRewritableQueryable source, + IEnumerable other) + => Queryable.Intersect(source, other).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Except( + this IRewritableQueryable source, + IEnumerable other) + => Queryable.Except(source, other).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable DefaultIfEmpty( + this IRewritableQueryable source) + => Queryable.DefaultIfEmpty(source).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable DefaultIfEmpty( + this IRewritableQueryable source, + T defaultValue) + => Queryable.DefaultIfEmpty(source, defaultValue).WithExpressionRewrite(); + +#if NET9_0_OR_GREATER + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable<(int Index, T Item)> Index( + this IRewritableQueryable source) + => Queryable.Index(source).WithExpressionRewrite(); +#endif + + // ── Non-lambda-first intercepted methods ───────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Zip( + this IRewritableQueryable source, + IEnumerable second, + Func resultSelector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable<(T First, TSecond Second)> Zip( + this IRewritableQueryable source, + IEnumerable second) + => Queryable.Zip(source, second).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable ExceptBy( + this IRewritableQueryable source, + IEnumerable second, + Func keySelector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable IntersectBy( + this IRewritableQueryable source, + IEnumerable second, + Func keySelector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable UnionBy( + this IRewritableQueryable source, + IEnumerable second, + Func keySelector) + => throw new UnreachableException(InterceptedMessage); + + // ── Multi-lambda methods ───────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable> GroupBy( + this IRewritableQueryable source, + Func keySelector, + Func elementSelector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable GroupBy( + this IRewritableQueryable source, + Func keySelector, + Func, TResult> resultSelector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable GroupBy( + this IRewritableQueryable source, + Func keySelector, + Func elementSelector, + Func, TResult> resultSelector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Join( + this IRewritableQueryable source, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable GroupJoin( + this IRewritableQueryable source, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func, TResult> resultSelector) + => throw new UnreachableException(InterceptedMessage); + +#if NET10_0_OR_GREATER + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable LeftJoin( + this IRewritableQueryable source, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable RightJoin( + this IRewritableQueryable source, + IEnumerable inner, + Func outerKeySelector, + Func innerKeySelector, + Func resultSelector) + => throw new UnreachableException(InterceptedMessage); +#endif + +#if NET9_0_OR_GREATER + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable> AggregateBy( + this IRewritableQueryable source, + Func keySelector, + TAccumulate seed, + Func func) + => throw new UnreachableException(InterceptedMessage); +#endif + + // ── IEqualityComparer / IComparer overloads (intercepted) ──────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable OrderBy( + this IRewritableQueryable source, + Func keySelector, + IComparer? comparer) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable OrderByDescending( + this IRewritableQueryable source, + Func keySelector, + IComparer? comparer) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable ThenBy( + this IRewritableQueryable source, + Func keySelector, + IComparer? comparer) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable ThenByDescending( + this IRewritableQueryable source, + Func keySelector, + IComparer? comparer) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable> GroupBy( + this IRewritableQueryable source, + Func keySelector, + IEqualityComparer? comparer) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable DistinctBy( + this IRewritableQueryable source, + Func keySelector, + IEqualityComparer? comparer) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable ExceptBy( + this IRewritableQueryable source, + IEnumerable second, + Func keySelector, + IEqualityComparer? comparer) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable IntersectBy( + this IRewritableQueryable source, + IEnumerable second, + Func keySelector, + IEqualityComparer? comparer) + => throw new UnreachableException(InterceptedMessage); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable UnionBy( + this IRewritableQueryable source, + IEnumerable second, + Func keySelector, + IEqualityComparer? comparer) + => throw new UnreachableException(InterceptedMessage); + + // ── IEqualityComparer passthrough (no lambda, chain continuity) ────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Distinct( + this IRewritableQueryable source, + IEqualityComparer? comparer) + => Queryable.Distinct(source, comparer).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Union( + this IRewritableQueryable source, + IEnumerable other, + IEqualityComparer? comparer) + => Queryable.Union(source, other, comparer).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Intersect( + this IRewritableQueryable source, + IEnumerable other, + IEqualityComparer? comparer) + => Queryable.Intersect(source, other, comparer).WithExpressionRewrite(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IRewritableQueryable Except( + this IRewritableQueryable source, + IEnumerable other, + IEqualityComparer? comparer) + => Queryable.Except(source, other, comparer).WithExpressionRewrite(); + } +} diff --git a/src/ExpressiveSharp/PolyfillTargetAttribute.cs b/src/ExpressiveSharp/PolyfillTargetAttribute.cs new file mode 100644 index 0000000..5570827 --- /dev/null +++ b/src/ExpressiveSharp/PolyfillTargetAttribute.cs @@ -0,0 +1,21 @@ +namespace ExpressiveSharp; + +/// +/// Specifies the static type whose extension method the polyfill interceptor should forward to, +/// instead of the default . +/// +/// +/// Apply this attribute to delegate-based stubs when the +/// matching Expression<Func<…>> overload lives in a type other than +/// (e.g., EF Core's EntityFrameworkQueryableExtensions). +/// +[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] +public sealed class PolyfillTargetAttribute : Attribute +{ + /// + /// The static type that declares the target extension method. + /// + public Type TargetType { get; } + + public PolyfillTargetAttribute(Type targetType) => TargetType = targetType; +} diff --git a/src/ExpressiveSharp/RewritableQueryableWrapper.cs b/src/ExpressiveSharp/RewritableQueryableWrapper.cs index fb7dbf8..6cc49ab 100644 --- a/src/ExpressiveSharp/RewritableQueryableWrapper.cs +++ b/src/ExpressiveSharp/RewritableQueryableWrapper.cs @@ -15,7 +15,7 @@ namespace ExpressiveSharp /// so that the source-generated ThenBy/ThenByDescending interceptors can cast the wrapper /// to without a runtime exception. /// - internal sealed class RewritableQueryableWrapper : IRewritableQueryable, IOrderedQueryable + internal sealed class RewritableQueryableWrapper : IRewritableQueryable, IOrderedQueryable, IAsyncEnumerable { private readonly IQueryable _source; @@ -29,5 +29,15 @@ public RewritableQueryableWrapper(IQueryable source) public IQueryProvider Provider => _source.Provider; public IEnumerator GetEnumerator() => _source.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_source).GetEnumerator(); + + IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken) + { + if (_source is IAsyncEnumerable asyncEnumerable) + return asyncEnumerable.GetAsyncEnumerator(cancellationToken); + + throw new InvalidOperationException( + $"The source IQueryable<{typeof(T).Name}> does not implement IAsyncEnumerable<{typeof(T).Name}>. " + + "Async operations require an async-capable provider such as Entity Framework Core."); + } } } diff --git a/tests/ExpressiveSharp.EntityFrameworkCore.Tests/IncludeThenIncludeTests.cs b/tests/ExpressiveSharp.EntityFrameworkCore.Tests/IncludeThenIncludeTests.cs new file mode 100644 index 0000000..2fd9646 --- /dev/null +++ b/tests/ExpressiveSharp.EntityFrameworkCore.Tests/IncludeThenIncludeTests.cs @@ -0,0 +1,86 @@ +using ExpressiveSharp.EntityFrameworkCore; +using ExpressiveSharp.EntityFrameworkCore.Tests.Models; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; + +namespace ExpressiveSharp.EntityFrameworkCore.Tests; + +[TestClass] +public class IncludeThenIncludeTests +{ + private SqliteConnection _connection = null!; + + [TestInitialize] + public void Setup() + { + _connection = new SqliteConnection("Data Source=:memory:"); + _connection.Open(); + } + + [TestCleanup] + public void Cleanup() + { + _connection.Dispose(); + } + + private TestDbContext CreateContext() + { + var options = new DbContextOptionsBuilder() + .UseSqlite(_connection) + .UseExpressives() + .Options; + var ctx = new TestDbContext(options); + ctx.Database.EnsureCreated(); + return ctx; + } + + [TestMethod] + public void Include_PreservesRewritableQueryable() + { + using var ctx = CreateContext(); + var result = ctx.Orders.Include(o => o.Customer); + + Assert.IsInstanceOfType>(result); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void StringInclude_PreservesRewritableQueryable() + { + using var ctx = CreateContext(); + var result = ctx.Orders.Include("Customer"); + + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void MultipleIncludes_PreservesChain() + { + using var ctx = CreateContext(); + + // Second Include should resolve to our overload (IRewritableQueryable is more specific) + var result = ctx.Orders + .Include(o => o.Customer) + .Include(o => o.Customer); + + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public async Task Include_ThenWhere_ExecutesCorrectly() + { + using var ctx = CreateContext(); + ctx.Customers.Add(new Customer { Id = 1, Name = "Alice" }); + ctx.Orders.Add(new Order { Id = 1, Price = 100, Quantity = 1, CustomerId = 1 }); + await ctx.SaveChangesAsync(); + + var results = await ctx.Orders + .Include(o => o.Customer) + .Where(o => o.Customer!.Name == "Alice") + .ToListAsync(); + + Assert.AreEqual(1, results.Count); + Assert.IsNotNull(results[0].Customer); + Assert.AreEqual("Alice", results[0].Customer!.Name); + } +} diff --git a/tests/ExpressiveSharp.EntityFrameworkCore.Tests/RewritableQueryableAsyncTests.cs b/tests/ExpressiveSharp.EntityFrameworkCore.Tests/RewritableQueryableAsyncTests.cs new file mode 100644 index 0000000..1b6bc77 --- /dev/null +++ b/tests/ExpressiveSharp.EntityFrameworkCore.Tests/RewritableQueryableAsyncTests.cs @@ -0,0 +1,234 @@ +using ExpressiveSharp.EntityFrameworkCore.Tests.Models; +using ExpressiveSharp.Extensions; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; + +namespace ExpressiveSharp.EntityFrameworkCore.Tests; + +/// +/// Integration tests that exercise delegate-based stubs +/// against a real EF Core SQLite provider — async terminal methods, multi-lambda operators, +/// chain continuity, and null-conditional syntax. +/// +[TestClass] +public class RewritableQueryableAsyncTests +{ + private SqliteConnection _connection = null!; + + [TestInitialize] + public async Task Setup() + { + _connection = new SqliteConnection("Data Source=:memory:"); + _connection.Open(); + + using var ctx = CreateContext(); + ctx.Customers.AddRange( + new Customer { Id = 1, Name = "Alice" }, + new Customer { Id = 2, Name = "Bob" }, + new Customer { Id = 3, Name = null }); + ctx.Orders.AddRange( + new Order { Id = 1, Price = 120, Quantity = 2, CustomerId = 1, Status = OrderStatus.Approved }, + new Order { Id = 2, Price = 75, Quantity = 20, CustomerId = 2, Status = OrderStatus.Pending }, + new Order { Id = 3, Price = 10, Quantity = 3, CustomerId = null, Status = OrderStatus.Pending }, + new Order { Id = 4, Price = 50, Quantity = 5, CustomerId = 3, Status = OrderStatus.Approved }); + await ctx.SaveChangesAsync(); + } + + [TestCleanup] + public void Cleanup() => _connection.Dispose(); + + private TestDbContext CreateContext() + { + var options = new DbContextOptionsBuilder() + .UseSqlite(_connection) + .UseExpressives() + .Options; + var ctx = new TestDbContext(options); + ctx.Database.EnsureCreated(); + return ctx; + } + + // ── 1. Async terminal methods with predicates ──────────────────────── + + [TestMethod] + public async Task AnyAsync_WithPredicate_ExecutesAgainstEfCore() + { + using var ctx = CreateContext(); + var result = await ctx.Orders.AnyAsync(o => o.Price > 100); + Assert.IsTrue(result); + } + + [TestMethod] + public async Task AllAsync_WithPredicate_ExecutesAgainstEfCore() + { + using var ctx = CreateContext(); + var result = await ctx.Orders.AllAsync(o => o.Price > 0); + Assert.IsTrue(result); + } + + [TestMethod] + public async Task CountAsync_WithPredicate_ExecutesAgainstEfCore() + { + using var ctx = CreateContext(); + var result = await ctx.Orders.CountAsync(o => o.Status == OrderStatus.Pending); + Assert.AreEqual(2, result); + } + + [TestMethod] + public async Task FirstAsync_WithPredicate_ExecutesAgainstEfCore() + { + using var ctx = CreateContext(); + var result = await ctx.Orders.FirstAsync(o => o.Price > 100); + Assert.AreEqual(1, result.Id); + } + + [TestMethod] + public async Task FirstOrDefaultAsync_WithPredicate_ReturnsNullWhenNoMatch() + { + using var ctx = CreateContext(); + var result = await ctx.Orders.FirstOrDefaultAsync(o => o.Price > 9999); + Assert.IsNull(result); + } + + [TestMethod] + public async Task SingleAsync_WithPredicate_ExecutesAgainstEfCore() + { + using var ctx = CreateContext(); + var result = await ctx.Orders.SingleAsync(o => o.Id == 2); + Assert.AreEqual(75, result.Price); + } + + // ── 2. Async aggregation with selectors ────────────────────────────── + + [TestMethod] + public async Task SumAsync_WithSelector_ExecutesAgainstEfCore() + { + using var ctx = CreateContext(); + var result = await ctx.Orders.SumAsync(o => o.Quantity); + Assert.AreEqual(30, result); // 2 + 20 + 3 + 5 + } + + [TestMethod] + public async Task MinAsync_WithSelector_ExecutesAgainstEfCore() + { + using var ctx = CreateContext(); + var result = await ctx.Orders.MinAsync(o => o.Price); + Assert.AreEqual(10, result); + } + + [TestMethod] + public async Task MaxAsync_WithSelector_ExecutesAgainstEfCore() + { + using var ctx = CreateContext(); + var result = await ctx.Orders.MaxAsync(o => o.Price); + Assert.AreEqual(120, result); + } + + // ── 3. Chain continuity + async execution ──────────────────────────── + + [TestMethod] + public async Task AsNoTracking_Where_ToListAsync_ExecutesCorrectly() + { + using var ctx = CreateContext(); + var results = await ctx.Orders + .AsNoTracking() + .Where(o => o.Price > 50) + .ToListAsync(); + + Assert.AreEqual(2, results.Count); // 120 and 75 + } + + [TestMethod] + public async Task Include_Where_ToListAsync_LoadsNavigation() + { + using var ctx = CreateContext(); + var results = await ctx.Orders + .Include(o => o.Customer) + .Where(o => o.CustomerId != null) + .ToListAsync(); + + Assert.AreEqual(3, results.Count); + Assert.IsTrue(results.All(o => o.Customer != null)); + } + + [TestMethod] + public async Task TagWith_Where_FirstAsync_ExecutesCorrectly() + { + using var ctx = CreateContext(); + var result = await ctx.Orders + .TagWith("integration test") + .FirstAsync(o => o.Id == 1); + + Assert.AreEqual(120, result.Price); + } + + // ── 4. Multi-lambda methods ────────────────────────────────────────── + + [TestMethod] + public async Task GroupBy_WithElementSelector_ExecutesAgainstEfCore() + { + using var ctx = CreateContext(); + var results = await ctx.Orders + .GroupBy(o => o.Status, o => o.Price) + .Select(g => new { Status = g.Key, Total = g.Sum() }) + .ToListAsync(); + + Assert.AreEqual(2, results.Count); // Approved and Pending + } + + [TestMethod] + public async Task Join_ExecutesAgainstEfCore() + { + using var ctx = CreateContext(); + var results = await ctx.Orders + .Join(ctx.Customers, + o => o.CustomerId, + c => c.Id, + (o, c) => c.Name) + .ToListAsync(); + + Assert.AreEqual(3, results.Count); + CollectionAssert.Contains(results, "Alice"); + CollectionAssert.Contains(results, "Bob"); + } + + // ── 5. Passthrough chain-continuity ───────────────────────────────── + + [TestMethod] + public async Task Take_Skip_Where_ToListAsync_ExecutesCorrectly() + { + using var ctx = CreateContext(); + var results = await ctx.Orders + .OrderBy(o => o.Id) + .Take(3) + .Skip(1) + .ToListAsync(); + + Assert.AreEqual(2, results.Count); + } + + // ── 7. Null-conditional in async context ───────────────────────────── + + [TestMethod] + public async Task NullConditional_InWhere_ToListAsync_ExecutesCorrectly() + { + using var ctx = CreateContext(); + var results = await ctx.Orders + .Where(o => o.Customer?.Name == "Alice") + .ToListAsync(); + + Assert.AreEqual(1, results.Count); + Assert.AreEqual(1, results[0].Id); + } + + [TestMethod] + public async Task NullConditional_InFirstAsync_Predicate_ExecutesCorrectly() + { + using var ctx = CreateContext(); + var result = await ctx.Orders + .FirstOrDefaultAsync(o => o.Customer?.Name == "Bob"); + + Assert.IsNotNull(result); + Assert.AreEqual(2, result!.Id); + } +} diff --git a/tests/ExpressiveSharp.EntityFrameworkCore.Tests/RewritableQueryableEfCoreExtensionsTests.cs b/tests/ExpressiveSharp.EntityFrameworkCore.Tests/RewritableQueryableEfCoreExtensionsTests.cs new file mode 100644 index 0000000..2721665 --- /dev/null +++ b/tests/ExpressiveSharp.EntityFrameworkCore.Tests/RewritableQueryableEfCoreExtensionsTests.cs @@ -0,0 +1,97 @@ +using ExpressiveSharp.EntityFrameworkCore.Tests.Models; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; + +namespace ExpressiveSharp.EntityFrameworkCore.Tests; + +[TestClass] +public class RewritableQueryableEfCoreExtensionsTests +{ + private SqliteConnection _connection = null!; + + [TestInitialize] + public void Setup() + { + _connection = new SqliteConnection("Data Source=:memory:"); + _connection.Open(); + } + + [TestCleanup] + public void Cleanup() + { + _connection.Dispose(); + } + + private TestDbContext CreateContext() + { + var options = new DbContextOptionsBuilder() + .UseSqlite(_connection) + .UseExpressives() + .Options; + var ctx = new TestDbContext(options); + ctx.Database.EnsureCreated(); + return ctx; + } + + [TestMethod] + public void AsNoTracking_PreservesRewritableQueryable() + { + using var ctx = CreateContext(); + var source = ctx.Orders; + Assert.IsInstanceOfType>(source); + + var result = source.AsNoTracking(); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void AsNoTrackingWithIdentityResolution_PreservesRewritableQueryable() + { + using var ctx = CreateContext(); + var result = ctx.Orders.AsNoTrackingWithIdentityResolution(); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void AsTracking_PreservesRewritableQueryable() + { + using var ctx = CreateContext(); + var result = ctx.Orders.AsTracking(); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void IgnoreAutoIncludes_PreservesRewritableQueryable() + { + using var ctx = CreateContext(); + var result = ctx.Orders.IgnoreAutoIncludes(); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void IgnoreQueryFilters_PreservesRewritableQueryable() + { + using var ctx = CreateContext(); + var result = ctx.Orders.IgnoreQueryFilters(); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void TagWith_PreservesRewritableQueryable() + { + using var ctx = CreateContext(); + var result = ctx.Orders.TagWith("test tag"); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void ChainedEfCoreModifiers_PreservesRewritableQueryable() + { + using var ctx = CreateContext(); + var result = ctx.Orders + .AsNoTracking() + .IgnoreAutoIncludes() + .TagWith("chained test"); + Assert.IsInstanceOfType>(result); + } +} diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.NestedTupleLiteral.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.NestedTupleLiteral.verified.txt index 813d3fa..968b5b2 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.NestedTupleLiteral.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.NestedTupleLiteral.verified.txt @@ -17,7 +17,8 @@ namespace ExpressiveSharp.Generated var p_c = global::System.Linq.Expressions.Expression.Parameter(typeof(bool), "c"); var expr_0 = global::System.Linq.Expressions.Expression.New(typeof((string, bool)).GetConstructor(new global::System.Type[] { typeof(string), typeof(bool) }), p_b, p_c); // (b, c) var expr_1 = global::System.Linq.Expressions.Expression.New(typeof((int, (string b, bool c))).GetConstructor(new global::System.Type[] { typeof(int), typeof((string b, bool c)) }), p_a, expr_0); - return global::System.Linq.Expressions.Expression.Lambda>>>(expr_1, p__this, p_a, p_b, p_c); + var expr_2 = global::System.Linq.Expressions.Expression.Convert(expr_1, typeof(global::System.ValueTuple>)); + return global::System.Linq.Expressions.Expression.Lambda>>>(expr_2, p__this, p_a, p_b, p_c); } } } diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralInMethod.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralInMethod.verified.txt index beae52f..ceb15fd 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralInMethod.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralInMethod.verified.txt @@ -15,7 +15,8 @@ namespace ExpressiveSharp.Generated var expr_0 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Id")); // Id var expr_1 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Name")); // Name var expr_2 = global::System.Linq.Expressions.Expression.New(typeof((int, string)).GetConstructor(new global::System.Type[] { typeof(int), typeof(string) }), expr_0, expr_1); - return global::System.Linq.Expressions.Expression.Lambda>>(expr_2, p__this); + var expr_3 = global::System.Linq.Expressions.Expression.Convert(expr_2, typeof(global::System.ValueTuple)); + return global::System.Linq.Expressions.Expression.Lambda>>(expr_3, p__this); } } } diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralWith8Elements.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralWith8Elements.verified.txt index 661d933..c28e992 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralWith8Elements.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralWith8Elements.verified.txt @@ -22,7 +22,8 @@ namespace ExpressiveSharp.Generated var p_h = global::System.Linq.Expressions.Expression.Parameter(typeof(int), "h"); var expr_0 = global::System.Linq.Expressions.Expression.New(typeof(global::System.ValueTuple).GetConstructor(new global::System.Type[] { typeof(int) }), p_h); // (a, b, c, d, e, f, g, h) var expr_1 = global::System.Linq.Expressions.Expression.New(typeof((int, int, int, int, int, int, int, int)).GetConstructor(new global::System.Type[] { typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(global::System.ValueTuple) }), p_a, p_b, p_c, p_d, p_e, p_f, p_g, expr_0); - return global::System.Linq.Expressions.Expression.Lambda>>>(expr_1, p__this, p_a, p_b, p_c, p_d, p_e, p_f, p_g, p_h); + var expr_2 = global::System.Linq.Expressions.Expression.Convert(expr_1, typeof(global::System.ValueTuple>)); + return global::System.Linq.Expressions.Expression.Lambda>>>(expr_2, p__this, p_a, p_b, p_c, p_d, p_e, p_f, p_g, p_h); } } } diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralWithNamedElements.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralWithNamedElements.verified.txt index 6a042d4..7606a0b 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralWithNamedElements.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralWithNamedElements.verified.txt @@ -15,7 +15,8 @@ namespace ExpressiveSharp.Generated var expr_0 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Id")); // Id var expr_1 = global::System.Linq.Expressions.Expression.Property(p__this, typeof(global::Foo.C).GetProperty("Name")); // Name var expr_2 = global::System.Linq.Expressions.Expression.New(typeof((int, string)).GetConstructor(new global::System.Type[] { typeof(int), typeof(string) }), expr_0, expr_1); - return global::System.Linq.Expressions.Expression.Lambda>>(expr_2, p__this); + var expr_3 = global::System.Linq.Expressions.Expression.Convert(expr_2, typeof(global::System.ValueTuple)); + return global::System.Linq.Expressions.Expression.Lambda>>(expr_3, p__this); } } } diff --git a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralWithParameters.verified.txt b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralWithParameters.verified.txt index 71c0adc..7dfcef8 100644 --- a/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralWithParameters.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/ExpressiveGenerator/TupleTests.TupleLiteralWithParameters.verified.txt @@ -15,7 +15,8 @@ namespace ExpressiveSharp.Generated var p_id = global::System.Linq.Expressions.Expression.Parameter(typeof(int), "id"); var p_name = global::System.Linq.Expressions.Expression.Parameter(typeof(string), "name"); var expr_0 = global::System.Linq.Expressions.Expression.New(typeof((int, string)).GetConstructor(new global::System.Type[] { typeof(int), typeof(string) }), p_id, p_name); // (id, name) - return global::System.Linq.Expressions.Expression.Lambda>>(expr_0, p__this, p_id, p_name); + var expr_1 = global::System.Linq.Expressions.Expression.Convert(expr_0, typeof(global::System.ValueTuple)); + return global::System.Linq.Expressions.Expression.Lambda>>(expr_1, p__this, p_id, p_name); } } } diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ComparerOverloadTests.DistinctBy_WithComparer_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ComparerOverloadTests.DistinctBy_WithComparer_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..110c902 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ComparerOverloadTests.DistinctBy_WithComparer_GeneratesInterceptor.verified.txt @@ -0,0 +1,34 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_DistinctBy_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _, + global::System.Collections.Generic.IEqualityComparer comparer) + { + // Source: o => o.Tag + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.DistinctBy( + (global::System.Linq.IQueryable)source, + __lambda, + comparer)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ComparerOverloadTests.GroupBy_WithComparer_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ComparerOverloadTests.GroupBy_WithComparer_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..e06d986 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ComparerOverloadTests.GroupBy_WithComparer_GeneratesInterceptor.verified.txt @@ -0,0 +1,34 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable> __Polyfill_GroupBy_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _, + global::System.Collections.Generic.IEqualityComparer comparer) + { + // Source: o => o.Tag + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.GroupBy( + (global::System.Linq.IQueryable)source, + __lambda, + comparer)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ComparerOverloadTests.OrderBy_WithComparer_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ComparerOverloadTests.OrderBy_WithComparer_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..9501105 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ComparerOverloadTests.OrderBy_WithComparer_GeneratesInterceptor.verified.txt @@ -0,0 +1,34 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_OrderBy_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _, + global::System.Collections.Generic.IComparer comparer) + { + // Source: o => o.Tag + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.OrderBy( + (global::System.Linq.IQueryable)source, + __lambda, + comparer)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ComparerOverloadTests.cs b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ComparerOverloadTests.cs new file mode 100644 index 0000000..c863687 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ComparerOverloadTests.cs @@ -0,0 +1,93 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using VerifyMSTest; +using ExpressiveSharp.Generator.Tests.Infrastructure; + +namespace ExpressiveSharp.Generator.Tests.PolyfillInterceptorGenerator; + +[TestClass] +public class ComparerOverloadTests : GeneratorTestBase +{ + [TestMethod] + public Task OrderBy_WithComparer_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Tag { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + query.WithExpressionRewrite() + .OrderBy(o => o.Tag, System.StringComparer.OrdinalIgnoreCase) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task GroupBy_WithComparer_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Tag { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + query.WithExpressionRewrite() + .GroupBy(o => o.Tag, System.StringComparer.OrdinalIgnoreCase) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task DistinctBy_WithComparer_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Tag { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + query.WithExpressionRewrite() + .DistinctBy(o => o.Tag, System.StringComparer.OrdinalIgnoreCase) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } +} diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GlobalOptionsTests.Polyfill_GlobalNullConditionalIgnore_UsesIgnoreMode.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GlobalOptionsTests.Polyfill_GlobalNullConditionalIgnore_UsesIgnoreMode.verified.txt index 96782bb..d18378f 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GlobalOptionsTests.Polyfill_GlobalNullConditionalIgnore_UsesIgnoreMode.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GlobalOptionsTests.Polyfill_GlobalNullConditionalIgnore_UsesIgnoreMode.verified.txt @@ -10,15 +10,15 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => o.Tag?.Length - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_0 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag - var expr_1 = global::System.Linq.Expressions.Expression.Property(expr_0, typeof(string).GetProperty("Length")); // .Length - var expr_2 = global::System.Linq.Expressions.Expression.Convert(expr_1, typeof(int?)); - var expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(string)); - var expr_5 = global::System.Linq.Expressions.Expression.NotEqual(expr_0, expr_4); - var expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(int?)); - var expr_3 = global::System.Linq.Expressions.Expression.Condition(expr_5, expr_2, expr_6, typeof(int?)); - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_3, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var i0_expr_1 = global::System.Linq.Expressions.Expression.Property(i0_expr_0, typeof(string).GetProperty("Length")); // .Length + var i0_expr_2 = global::System.Linq.Expressions.Expression.Convert(i0_expr_1, typeof(int?)); + var i0_expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(string)); + var i0_expr_5 = global::System.Linq.Expressions.Expression.NotEqual(i0_expr_0, i0_expr_4); + var i0_expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(int?)); + var i0_expr_3 = global::System.Linq.Expressions.Expression.Condition(i0_expr_5, i0_expr_2, i0_expr_6, typeof(int?)); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_3, i0_p_o); return __lambda; } } diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GlobalOptionsTests.Where_GlobalNullConditionalRewrite_AppliesWithoutPerCallOverride.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GlobalOptionsTests.Where_GlobalNullConditionalRewrite_AppliesWithoutPerCallOverride.verified.txt index e83f4d8..69e8437 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GlobalOptionsTests.Where_GlobalNullConditionalRewrite_AppliesWithoutPerCallOverride.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GlobalOptionsTests.Where_GlobalNullConditionalRewrite_AppliesWithoutPerCallOverride.verified.txt @@ -11,16 +11,16 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => o.Customer?.Name == "Alice" - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_1 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Customer")); // o.Customer - var expr_2 = global::System.Linq.Expressions.Expression.Property(expr_1, typeof(global::TestNs.Customer).GetProperty("Name")); // .Name - var expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::TestNs.Customer)); - var expr_5 = global::System.Linq.Expressions.Expression.NotEqual(expr_1, expr_4); - var expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(string)); - var expr_3 = global::System.Linq.Expressions.Expression.Condition(expr_5, expr_2, expr_6, typeof(string)); - var expr_7 = global::System.Linq.Expressions.Expression.Constant("Alice", typeof(string)); // "Alice" - var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, expr_3, expr_7); - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_1 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Customer")); // o.Customer + var i0_expr_2 = global::System.Linq.Expressions.Expression.Property(i0_expr_1, typeof(global::TestNs.Customer).GetProperty("Name")); // .Name + var i0_expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::TestNs.Customer)); + var i0_expr_5 = global::System.Linq.Expressions.Expression.NotEqual(i0_expr_1, i0_expr_4); + var i0_expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(string)); + var i0_expr_3 = global::System.Linq.Expressions.Expression.Condition(i0_expr_5, i0_expr_2, i0_expr_6, typeof(string)); + var i0_expr_7 = global::System.Linq.Expressions.Expression.Constant("Alice", typeof(string)); // "Alice" + var i0_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, i0_expr_3, i0_expr_7); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( global::System.Linq.Queryable.Where( (global::System.Linq.IQueryable)source, diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GlobalOptionsTests.Where_PerCallOverridesGlobal.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GlobalOptionsTests.Where_PerCallOverridesGlobal.verified.txt index e83f4d8..69e8437 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GlobalOptionsTests.Where_PerCallOverridesGlobal.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GlobalOptionsTests.Where_PerCallOverridesGlobal.verified.txt @@ -11,16 +11,16 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => o.Customer?.Name == "Alice" - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_1 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Customer")); // o.Customer - var expr_2 = global::System.Linq.Expressions.Expression.Property(expr_1, typeof(global::TestNs.Customer).GetProperty("Name")); // .Name - var expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::TestNs.Customer)); - var expr_5 = global::System.Linq.Expressions.Expression.NotEqual(expr_1, expr_4); - var expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(string)); - var expr_3 = global::System.Linq.Expressions.Expression.Condition(expr_5, expr_2, expr_6, typeof(string)); - var expr_7 = global::System.Linq.Expressions.Expression.Constant("Alice", typeof(string)); // "Alice" - var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, expr_3, expr_7); - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_1 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Customer")); // o.Customer + var i0_expr_2 = global::System.Linq.Expressions.Expression.Property(i0_expr_1, typeof(global::TestNs.Customer).GetProperty("Name")); // .Name + var i0_expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::TestNs.Customer)); + var i0_expr_5 = global::System.Linq.Expressions.Expression.NotEqual(i0_expr_1, i0_expr_4); + var i0_expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(string)); + var i0_expr_3 = global::System.Linq.Expressions.Expression.Condition(i0_expr_5, i0_expr_2, i0_expr_6, typeof(string)); + var i0_expr_7 = global::System.Linq.Expressions.Expression.Constant("Alice", typeof(string)); // "Alice" + var i0_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, i0_expr_3, i0_expr_7); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( global::System.Linq.Queryable.Where( (global::System.Linq.IQueryable)source, diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.GroupBy_GeneratesGroupingReturnType.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.GroupBy_GeneratesGroupingReturnType.verified.txt index d296606..13d91d4 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.GroupBy_GeneratesGroupingReturnType.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.GroupBy_GeneratesGroupingReturnType.verified.txt @@ -11,9 +11,9 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => o.Tag - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_0 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( global::System.Linq.Queryable.GroupBy( (global::System.Linq.IQueryable)source, diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.GroupBy_WithElementAndResultSelector_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.GroupBy_WithElementAndResultSelector_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..64055cd --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.GroupBy_WithElementAndResultSelector_GeneratesInterceptor.verified.txt @@ -0,0 +1,44 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_GroupBy_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _1, + global::System.Func _2, + global::System.Func, string> _3) + { + // Source: o => o.Tag + var i0a_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0a_expr_0 = global::System.Linq.Expressions.Expression.Property(i0a_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var __lambda1 = global::System.Linq.Expressions.Expression.Lambda>(i0a_expr_0, i0a_p_o); + // Source: o => o.Name + var i0b_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0b_expr_0 = global::System.Linq.Expressions.Expression.Property(i0b_p_o, typeof(global::TestNs.Order).GetProperty("Name")); // o.Name + var __lambda2 = global::System.Linq.Expressions.Expression.Lambda>(i0b_expr_0, i0b_p_o); + // Source: (key, names) => key + var i0c_p_key = global::System.Linq.Expressions.Expression.Parameter(typeof(string), "key"); + var i0c_p_names = global::System.Linq.Expressions.Expression.Parameter(typeof(global::System.Collections.Generic.IEnumerable), "names"); + var __lambda3 = global::System.Linq.Expressions.Expression.Lambda, string>>(i0c_p_key, i0c_p_key, i0c_p_names); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.GroupBy( + (global::System.Linq.IQueryable)source, + __lambda1, + __lambda2, + __lambda3)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.GroupBy_WithElementSelector_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.GroupBy_WithElementSelector_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..b76e14e --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.GroupBy_WithElementSelector_GeneratesInterceptor.verified.txt @@ -0,0 +1,38 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable> __Polyfill_GroupBy_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _1, + global::System.Func _2) + { + // Source: o => o.Tag + var i0a_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0a_expr_0 = global::System.Linq.Expressions.Expression.Property(i0a_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var __lambda1 = global::System.Linq.Expressions.Expression.Lambda>(i0a_expr_0, i0a_p_o); + // Source: o => o.Name + var i0b_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0b_expr_0 = global::System.Linq.Expressions.Expression.Property(i0b_p_o, typeof(global::TestNs.Order).GetProperty("Name")); // o.Name + var __lambda2 = global::System.Linq.Expressions.Expression.Lambda>(i0b_expr_0, i0b_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.GroupBy( + (global::System.Linq.IQueryable)source, + __lambda1, + __lambda2)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.GroupBy_WithResultSelector_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.GroupBy_WithResultSelector_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..470d1f6 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.GroupBy_WithResultSelector_GeneratesInterceptor.verified.txt @@ -0,0 +1,38 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_GroupBy_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _1, + global::System.Func, string> _2) + { + // Source: o => o.Tag + var i0a_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0a_expr_0 = global::System.Linq.Expressions.Expression.Property(i0a_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var __lambda1 = global::System.Linq.Expressions.Expression.Lambda>(i0a_expr_0, i0a_p_o); + // Source: (key, orders) => key + var i0b_p_key = global::System.Linq.Expressions.Expression.Parameter(typeof(string), "key"); + var i0b_p_orders = global::System.Linq.Expressions.Expression.Parameter(typeof(global::System.Collections.Generic.IEnumerable), "orders"); + var __lambda2 = global::System.Linq.Expressions.Expression.Lambda, string>>(i0b_p_key, i0b_p_key, i0b_p_orders); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.GroupBy( + (global::System.Linq.IQueryable)source, + __lambda1, + __lambda2)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.cs b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.cs index 46441a4..f4eea56 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.cs +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/GroupByTests.cs @@ -32,4 +32,88 @@ public void Run(System.Linq.IQueryable query) return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); } + + [TestMethod] + public Task GroupBy_WithElementSelector_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Tag { get; set; } public string Name { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + query.WithExpressionRewrite() + .GroupBy(o => o.Tag, o => o.Name) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task GroupBy_WithResultSelector_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Tag { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + query.WithExpressionRewrite() + .GroupBy(o => o.Tag, (key, orders) => key) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task GroupBy_WithElementAndResultSelector_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Tag { get; set; } public string Name { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + query.WithExpressionRewrite() + .GroupBy(o => o.Tag, o => o.Name, (key, names) => key) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } } diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/JoinTests.GroupJoin_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/JoinTests.GroupJoin_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..d219633 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/JoinTests.GroupJoin_GeneratesInterceptor.verified.txt @@ -0,0 +1,47 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_GroupJoin_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Collections.Generic.IEnumerable inner, + global::System.Func _1, + global::System.Func _2, + global::System.Func, int> _3) + { + // Source: o => o.CustomerId + var i0a_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0a_expr_0 = global::System.Linq.Expressions.Expression.Property(i0a_p_o, typeof(global::TestNs.Order).GetProperty("CustomerId")); // o.CustomerId + var __lambda1 = global::System.Linq.Expressions.Expression.Lambda>(i0a_expr_0, i0a_p_o); + // Source: c => c.Id + var i0b_p_c = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Customer), "c"); + var i0b_expr_0 = global::System.Linq.Expressions.Expression.Property(i0b_p_c, typeof(global::TestNs.Customer).GetProperty("Id")); // c.Id + var __lambda2 = global::System.Linq.Expressions.Expression.Lambda>(i0b_expr_0, i0b_p_c); + // Source: (o, cs) => o.CustomerId + var i0c_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0c_p_cs = global::System.Linq.Expressions.Expression.Parameter(typeof(global::System.Collections.Generic.IEnumerable), "cs"); + var i0c_expr_0 = global::System.Linq.Expressions.Expression.Property(i0c_p_o, typeof(global::TestNs.Order).GetProperty("CustomerId")); // o.CustomerId + var __lambda3 = global::System.Linq.Expressions.Expression.Lambda, int>>(i0c_expr_0, i0c_p_o, i0c_p_cs); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.GroupJoin( + (global::System.Linq.IQueryable)source, + inner, + __lambda1, + __lambda2, + __lambda3)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/JoinTests.Join_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/JoinTests.Join_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..5b0c55b --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/JoinTests.Join_GeneratesInterceptor.verified.txt @@ -0,0 +1,51 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_Join_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Collections.Generic.IEnumerable inner, + global::System.Func _1, + global::System.Func _2, + global::System.Func _3) + { + // Source: o => o.CustomerId + var i0a_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0a_expr_0 = global::System.Linq.Expressions.Expression.Property(i0a_p_o, typeof(global::TestNs.Order).GetProperty("CustomerId")); // o.CustomerId + var __lambda1 = global::System.Linq.Expressions.Expression.Lambda>(i0a_expr_0, i0a_p_o); + // Source: c => c.Id + var i0b_p_c = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Customer), "c"); + var i0b_expr_0 = global::System.Linq.Expressions.Expression.Property(i0b_p_c, typeof(global::TestNs.Customer).GetProperty("Id")); // c.Id + var __lambda2 = global::System.Linq.Expressions.Expression.Lambda>(i0b_expr_0, i0b_p_c); + // Source: (o, c) => o.Product + " - " + c.Name + var i0c_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0c_p_c = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Customer), "c"); + var i0c_expr_2 = global::System.Linq.Expressions.Expression.Property(i0c_p_o, typeof(global::TestNs.Order).GetProperty("Product")); // o.Product + var i0c_expr_3 = global::System.Linq.Expressions.Expression.Constant(" - ", typeof(string)); // " - " + var i0c_expr_1 = global::System.Linq.Expressions.Expression.Call(typeof(string).GetMethod("Concat", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(string), typeof(string) }, null), i0c_expr_2, i0c_expr_3); + var i0c_expr_4 = global::System.Linq.Expressions.Expression.Property(i0c_p_c, typeof(global::TestNs.Customer).GetProperty("Name")); // c.Name + var i0c_expr_0 = global::System.Linq.Expressions.Expression.Call(typeof(string).GetMethod("Concat", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(string), typeof(string) }, null), i0c_expr_1, i0c_expr_4); + var __lambda3 = global::System.Linq.Expressions.Expression.Lambda>(i0c_expr_0, i0c_p_o, i0c_p_c); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.Join( + (global::System.Linq.IQueryable)source, + inner, + __lambda1, + __lambda2, + __lambda3)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/JoinTests.cs b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/JoinTests.cs new file mode 100644 index 0000000..039c11d --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/JoinTests.cs @@ -0,0 +1,73 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using VerifyMSTest; +using ExpressiveSharp.Generator.Tests.Infrastructure; + +namespace ExpressiveSharp.Generator.Tests.PolyfillInterceptorGenerator; + +[TestClass] +public class JoinTests : GeneratorTestBase +{ + [TestMethod] + public Task Join_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public int CustomerId { get; set; } public string Product { get; set; } } + class Customer { public int Id { get; set; } public string Name { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable orders, System.Collections.Generic.IEnumerable customers) + { + orders.WithExpressionRewrite() + .Join(customers, + o => o.CustomerId, + c => c.Id, + (o, c) => o.Product + " - " + c.Name) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task GroupJoin_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public int CustomerId { get; set; } } + class Customer { public int Id { get; set; } public string Name { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable orders, System.Collections.Generic.IEnumerable customers) + { + orders.WithExpressionRewrite() + .GroupJoin(customers, + o => o.CustomerId, + c => c.Id, + (o, cs) => o.CustomerId) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } +} diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/NullConditionalTests.NullConditionalRewrite_ExpandsInWhereBody.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/NullConditionalTests.NullConditionalRewrite_ExpandsInWhereBody.verified.txt index e83f4d8..69e8437 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/NullConditionalTests.NullConditionalRewrite_ExpandsInWhereBody.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/NullConditionalTests.NullConditionalRewrite_ExpandsInWhereBody.verified.txt @@ -11,16 +11,16 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => o.Customer?.Name == "Alice" - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_1 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Customer")); // o.Customer - var expr_2 = global::System.Linq.Expressions.Expression.Property(expr_1, typeof(global::TestNs.Customer).GetProperty("Name")); // .Name - var expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::TestNs.Customer)); - var expr_5 = global::System.Linq.Expressions.Expression.NotEqual(expr_1, expr_4); - var expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(string)); - var expr_3 = global::System.Linq.Expressions.Expression.Condition(expr_5, expr_2, expr_6, typeof(string)); - var expr_7 = global::System.Linq.Expressions.Expression.Constant("Alice", typeof(string)); // "Alice" - var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, expr_3, expr_7); - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_1 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Customer")); // o.Customer + var i0_expr_2 = global::System.Linq.Expressions.Expression.Property(i0_expr_1, typeof(global::TestNs.Customer).GetProperty("Name")); // .Name + var i0_expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(global::TestNs.Customer)); + var i0_expr_5 = global::System.Linq.Expressions.Expression.NotEqual(i0_expr_1, i0_expr_4); + var i0_expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(string)); + var i0_expr_3 = global::System.Linq.Expressions.Expression.Condition(i0_expr_5, i0_expr_2, i0_expr_6, typeof(string)); + var i0_expr_7 = global::System.Linq.Expressions.Expression.Constant("Alice", typeof(string)); // "Alice" + var i0_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, i0_expr_3, i0_expr_7); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( global::System.Linq.Queryable.Where( (global::System.Linq.IQueryable)source, diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/OrderByTests.OrderBy_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/OrderByTests.OrderBy_GeneratesInterceptor.verified.txt index 2645549..53e1c8c 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/OrderByTests.OrderBy_GeneratesInterceptor.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/OrderByTests.OrderBy_GeneratesInterceptor.verified.txt @@ -11,9 +11,9 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => o.Priority - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_0 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Priority")); // o.Priority - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Priority")); // o.Priority + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( global::System.Linq.Queryable.OrderBy( (global::System.Linq.IQueryable)source, diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/OrderByTests.ThenBy_CastsToIOrderedQueryable.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/OrderByTests.ThenBy_CastsToIOrderedQueryable.verified.txt index da08838..d0d8e08 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/OrderByTests.ThenBy_CastsToIOrderedQueryable.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/OrderByTests.ThenBy_CastsToIOrderedQueryable.verified.txt @@ -11,9 +11,9 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => o.Name - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_0 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Name")); // o.Name - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Name")); // o.Name + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( global::System.Linq.Queryable.ThenBy( (global::System.Linq.IOrderedQueryable)source, @@ -25,9 +25,9 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => o.Priority - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_0 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Priority")); // o.Priority - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i1_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i1_expr_0 = global::System.Linq.Expressions.Expression.Property(i1_p_o, typeof(global::TestNs.Order).GetProperty("Priority")); // o.Priority + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i1_expr_0, i1_p_o); return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( global::System.Linq.Queryable.OrderBy( (global::System.Linq.IQueryable)source, diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTargetTests.PolyfillTarget_RoutesToSpecifiedType.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTargetTests.PolyfillTarget_RoutesToSpecifiedType.verified.txt new file mode 100644 index 0000000..2a905fb --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTargetTests.PolyfillTarget_RoutesToSpecifiedType.verified.txt @@ -0,0 +1,33 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::System.Threading.Tasks.Task __Polyfill_AnyAsync_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _, + global::System.Threading.CancellationToken cancellationToken) + { + // Source: o => o.Active + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Active")); // o.Active + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::TestNs.MockEfExtensions.AnyAsync( + (global::System.Linq.IQueryable)source, + __lambda, + cancellationToken); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTargetTests.PolyfillTarget_WithoutAttribute_DefaultsToQueryable.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTargetTests.PolyfillTarget_WithoutAttribute_DefaultsToQueryable.verified.txt new file mode 100644 index 0000000..8b36f04 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTargetTests.PolyfillTarget_WithoutAttribute_DefaultsToQueryable.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static bool __Polyfill_Any_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Active + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Active")); // o.Active + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Any( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTargetTests.cs b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTargetTests.cs new file mode 100644 index 0000000..d72e4c9 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTargetTests.cs @@ -0,0 +1,85 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using VerifyMSTest; +using ExpressiveSharp.Generator.Tests.Infrastructure; + +namespace ExpressiveSharp.Generator.Tests.PolyfillInterceptorGenerator; + +[TestClass] +public class PolyfillTargetTests : GeneratorTestBase +{ + [TestMethod] + public Task PolyfillTarget_RoutesToSpecifiedType() + { + var source = + """ + using System; + using System.Linq; + using System.Linq.Expressions; + using System.Threading; + using System.Threading.Tasks; + using ExpressiveSharp; + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public bool Active { get; set; } } + + // Mock target type simulating EntityFrameworkQueryableExtensions + static class MockEfExtensions + { + public static Task AnyAsync(IQueryable source, Expression> predicate, CancellationToken ct = default) + => Task.FromResult(false); + } + + static class Stubs + { + [PolyfillTarget(typeof(MockEfExtensions))] + public static Task AnyAsync( + this IRewritableQueryable source, + Func predicate, + CancellationToken cancellationToken = default) + => throw new System.Diagnostics.UnreachableException(); + } + + class TestClass + { + public void Run(IQueryable query) + { + var result = query.WithExpressionRewrite().AnyAsync(o => o.Active); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task PolyfillTarget_WithoutAttribute_DefaultsToQueryable() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public bool Active { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Any(o => o.Active); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } +} diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTests.Polyfill_InferredTypeArgument_GeneratesExpression.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTests.Polyfill_InferredTypeArgument_GeneratesExpression.verified.txt index 6da54bf..67b5c70 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTests.Polyfill_InferredTypeArgument_GeneratesExpression.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTests.Polyfill_InferredTypeArgument_GeneratesExpression.verified.txt @@ -10,11 +10,11 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: (Order o) => o.Tag == "urgent" - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_1 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag - var expr_2 = global::System.Linq.Expressions.Expression.Constant("urgent", typeof(string)); // "urgent" - var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, expr_1, expr_2); - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_1 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var i0_expr_2 = global::System.Linq.Expressions.Expression.Constant("urgent", typeof(string)); // "urgent" + var i0_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, i0_expr_1, i0_expr_2); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); return __lambda; } } diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTests.Polyfill_NullConditional_RewritesOperator.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTests.Polyfill_NullConditional_RewritesOperator.verified.txt index 96782bb..d18378f 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTests.Polyfill_NullConditional_RewritesOperator.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTests.Polyfill_NullConditional_RewritesOperator.verified.txt @@ -10,15 +10,15 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => o.Tag?.Length - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_0 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag - var expr_1 = global::System.Linq.Expressions.Expression.Property(expr_0, typeof(string).GetProperty("Length")); // .Length - var expr_2 = global::System.Linq.Expressions.Expression.Convert(expr_1, typeof(int?)); - var expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(string)); - var expr_5 = global::System.Linq.Expressions.Expression.NotEqual(expr_0, expr_4); - var expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(int?)); - var expr_3 = global::System.Linq.Expressions.Expression.Condition(expr_5, expr_2, expr_6, typeof(int?)); - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_3, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var i0_expr_1 = global::System.Linq.Expressions.Expression.Property(i0_expr_0, typeof(string).GetProperty("Length")); // .Length + var i0_expr_2 = global::System.Linq.Expressions.Expression.Convert(i0_expr_1, typeof(int?)); + var i0_expr_4 = global::System.Linq.Expressions.Expression.Constant(null, typeof(string)); + var i0_expr_5 = global::System.Linq.Expressions.Expression.NotEqual(i0_expr_0, i0_expr_4); + var i0_expr_6 = global::System.Linq.Expressions.Expression.Default(typeof(int?)); + var i0_expr_3 = global::System.Linq.Expressions.Expression.Condition(i0_expr_5, i0_expr_2, i0_expr_6, typeof(int?)); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_3, i0_p_o); return __lambda; } } diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTests.Polyfill_SimpleLambda_GeneratesExpression.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTests.Polyfill_SimpleLambda_GeneratesExpression.verified.txt index a4794af..0d1f5ee 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTests.Polyfill_SimpleLambda_GeneratesExpression.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/PolyfillTests.Polyfill_SimpleLambda_GeneratesExpression.verified.txt @@ -10,11 +10,11 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => o.Tag == "urgent" - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_1 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag - var expr_2 = global::System.Linq.Expressions.Expression.Constant("urgent", typeof(string)); // "urgent" - var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, expr_1, expr_2); - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_1 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var i0_expr_2 = global::System.Linq.Expressions.Expression.Constant("urgent", typeof(string)); // "urgent" + var i0_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, i0_expr_1, i0_expr_2); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); return __lambda; } } diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.All_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.All_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..957faa6 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.All_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static bool __Polyfill_All_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Active + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Active")); // o.Active + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.All( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Any_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Any_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..1419e1f --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Any_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,33 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static bool __Polyfill_Any_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Tag == "urgent" + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_1 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var i0_expr_2 = global::System.Linq.Expressions.Expression.Constant("urgent", typeof(string)); // "urgent" + var i0_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, i0_expr_1, i0_expr_2); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Any( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_DecimalSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_DecimalSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..f612119 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_DecimalSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static decimal __Polyfill_Average_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Average( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_DoubleSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_DoubleSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..813bfc2 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_DoubleSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static double __Polyfill_Average_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Average( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_FloatSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_FloatSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..6bc8eac --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_FloatSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static float __Polyfill_Average_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Average( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_IntSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_IntSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..ddd726a --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_IntSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static double __Polyfill_Average_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Average( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_LongSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_LongSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..a03c26b --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_LongSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static double __Polyfill_Average_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Average( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableDecimalSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableDecimalSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..4c4ce10 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableDecimalSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static decimal? __Polyfill_Average_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Average( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableDoubleSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableDoubleSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..1f6eb9c --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableDoubleSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static double? __Polyfill_Average_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Average( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableFloatSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableFloatSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..e3dcd8b --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableFloatSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static float? __Polyfill_Average_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Average( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableIntSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableIntSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..2e24f5b --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableIntSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static double? __Polyfill_Average_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Average( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableLongSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableLongSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..c06ce53 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Average_NullableLongSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static double? __Polyfill_Average_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Average( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Count_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Count_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..b536f1d --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Count_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,33 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static int __Polyfill_Count_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount > 100 + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_1 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var i0_expr_2 = global::System.Linq.Expressions.Expression.Constant(100, typeof(int)); // 100 + var i0_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, i0_expr_1, i0_expr_2); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Count( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.FirstOrDefault_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.FirstOrDefault_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..582ce65 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.FirstOrDefault_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::TestNs.Order __Polyfill_FirstOrDefault_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Active + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Active")); // o.Active + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.FirstOrDefault( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.First_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.First_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..3fc3544 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.First_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::TestNs.Order __Polyfill_First_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Active + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Active")); // o.Active + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.First( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.LastOrDefault_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.LastOrDefault_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..3223386 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.LastOrDefault_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::TestNs.Order __Polyfill_LastOrDefault_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Active + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Active")); // o.Active + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.LastOrDefault( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Last_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Last_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..ebc19ec --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Last_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::TestNs.Order __Polyfill_Last_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Active + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Active")); // o.Active + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Last( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.LongCount_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.LongCount_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..668ff41 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.LongCount_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,33 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static long __Polyfill_LongCount_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount > 100 + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_1 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var i0_expr_2 = global::System.Linq.Expressions.Expression.Constant(100, typeof(int)); // 100 + var i0_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.GreaterThan, i0_expr_1, i0_expr_2); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.LongCount( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.MaxBy_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.MaxBy_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..92279bf --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.MaxBy_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::TestNs.Order __Polyfill_MaxBy_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.MaxBy( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Max_GenericSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Max_GenericSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..d8cb2a5 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Max_GenericSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static string __Polyfill_Max_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Name + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Name")); // o.Name + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Max( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.MinBy_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.MinBy_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..7b74bd4 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.MinBy_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::TestNs.Order __Polyfill_MinBy_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.MinBy( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Min_GenericSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Min_GenericSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..ea4205d --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Min_GenericSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static string __Polyfill_Min_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Name + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Name")); // o.Name + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Min( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.SingleOrDefault_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.SingleOrDefault_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..7e11dd2 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.SingleOrDefault_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::TestNs.Order __Polyfill_SingleOrDefault_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Active + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Active")); // o.Active + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.SingleOrDefault( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Single_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Single_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..da8f40e --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Single_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::TestNs.Order __Polyfill_Single_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Active + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Active")); // o.Active + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Single( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_DecimalSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_DecimalSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..7c9ea6f --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_DecimalSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static decimal __Polyfill_Sum_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Sum( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_DoubleSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_DoubleSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..2fd4fdf --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_DoubleSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static double __Polyfill_Sum_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Sum( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_FloatSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_FloatSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..4fe65d4 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_FloatSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static float __Polyfill_Sum_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Sum( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_IntSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_IntSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..c01cb4e --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_IntSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static int __Polyfill_Sum_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Sum( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_LongSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_LongSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..57782fb --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_LongSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static long __Polyfill_Sum_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Sum( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableDecimalSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableDecimalSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..85d1316 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableDecimalSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static decimal? __Polyfill_Sum_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Sum( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableDoubleSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableDoubleSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..ae11483 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableDoubleSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static double? __Polyfill_Sum_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Sum( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableFloatSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableFloatSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..44fe4ee --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableFloatSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static float? __Polyfill_Sum_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Sum( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableIntSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableIntSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..f47eb7f --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableIntSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static int? __Polyfill_Sum_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Sum( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableLongSelector_GeneratesScalarInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableLongSelector_GeneratesScalarInterceptor.verified.txt new file mode 100644 index 0000000..9571317 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.Sum_NullableLongSelector_GeneratesScalarInterceptor.verified.txt @@ -0,0 +1,31 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static long? __Polyfill_Sum_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::System.Linq.Queryable.Sum( + (global::System.Linq.IQueryable)source, + __lambda); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.cs b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.cs new file mode 100644 index 0000000..a32c5ec --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ScalarMethodTests.cs @@ -0,0 +1,905 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using VerifyMSTest; +using ExpressiveSharp.Generator.Tests.Infrastructure; + +namespace ExpressiveSharp.Generator.Tests.PolyfillInterceptorGenerator; + +[TestClass] +public class ScalarMethodTests : GeneratorTestBase +{ + // ── Predicate methods (bool-returning) ─────────────────────────────── + + [TestMethod] + public Task Any_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Tag { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Any(o => o.Tag == "urgent"); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task All_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public bool Active { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().All(o => o.Active); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + // ── Counting methods ───────────────────────────────────────────────── + + [TestMethod] + public Task Count_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public int Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Count(o => o.Amount > 100); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task LongCount_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public int Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().LongCount(o => o.Amount > 100); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + // ── Element methods ────────────────────────────────────────────────── + + [TestMethod] + public Task First_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public bool Active { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().First(o => o.Active); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task FirstOrDefault_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public bool Active { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().FirstOrDefault(o => o.Active); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Last_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public bool Active { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Last(o => o.Active); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task LastOrDefault_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public bool Active { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().LastOrDefault(o => o.Active); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Single_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public bool Active { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Single(o => o.Active); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task SingleOrDefault_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public bool Active { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().SingleOrDefault(o => o.Active); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + // ── Min / Max ──────────────────────────────────────────────────────── + + [TestMethod] + public Task Min_GenericSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Name { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Min(o => o.Name); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Max_GenericSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Name { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Max(o => o.Name); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task MinBy_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public int Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().MinBy(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task MaxBy_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public int Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().MaxBy(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + // ── Sum ────────────────────────────────────────────────────────────── + + [TestMethod] + public Task Sum_IntSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public int Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Sum(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Sum_NullableIntSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public int? Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Sum(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Sum_LongSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public long Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Sum(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Sum_NullableLongSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public long? Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Sum(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Sum_FloatSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public float Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Sum(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Sum_NullableFloatSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public float? Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Sum(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Sum_DoubleSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public double Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Sum(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Sum_NullableDoubleSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public double? Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Sum(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Sum_DecimalSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public decimal Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Sum(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Sum_NullableDecimalSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public decimal? Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Sum(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + // ── Average ────────────────────────────────────────────────────────── + + [TestMethod] + public Task Average_IntSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public int Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Average(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Average_NullableIntSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public int? Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Average(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Average_LongSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public long Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Average(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Average_NullableLongSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public long? Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Average(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Average_FloatSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public float Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Average(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Average_NullableFloatSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public float? Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Average(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Average_DoubleSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public double Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Average(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Average_NullableDoubleSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public double? Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Average(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Average_DecimalSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public decimal Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Average(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task Average_NullableDecimalSelector_GeneratesScalarInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public decimal? Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + var result = query.WithExpressionRewrite().Average(o => o.Amount); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } +} diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectManyTests.SelectMany_CollectionSelector_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectManyTests.SelectMany_CollectionSelector_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..8b21f36 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectManyTests.SelectMany_CollectionSelector_GeneratesInterceptor.verified.txt @@ -0,0 +1,33 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_SelectMany_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func> _) + { + // Source: o => o.Tags + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tags")); // o.Tags + var i0_expr_1 = global::System.Linq.Expressions.Expression.Convert(i0_expr_0, typeof(global::System.Collections.Generic.IEnumerable)); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>>(i0_expr_1, i0_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.SelectMany( + (global::System.Linq.IQueryable)source, + __lambda)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectManyTests.SelectMany_WithResultSelector_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectManyTests.SelectMany_WithResultSelector_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..5791743 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectManyTests.SelectMany_WithResultSelector_GeneratesInterceptor.verified.txt @@ -0,0 +1,43 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_SelectMany_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func> _1, + global::System.Func _2) + { + // Source: o => o.Tags + var i0a_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0a_expr_0 = global::System.Linq.Expressions.Expression.Property(i0a_p_o, typeof(global::TestNs.Order).GetProperty("Tags")); // o.Tags + var i0a_expr_1 = global::System.Linq.Expressions.Expression.Convert(i0a_expr_0, typeof(global::System.Collections.Generic.IEnumerable)); + var __lambda1 = global::System.Linq.Expressions.Expression.Lambda>>(i0a_expr_1, i0a_p_o); + // Source: (o, t) => o.Name + ": " + t + var i0b_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0b_p_t = global::System.Linq.Expressions.Expression.Parameter(typeof(string), "t"); + var i0b_expr_2 = global::System.Linq.Expressions.Expression.Property(i0b_p_o, typeof(global::TestNs.Order).GetProperty("Name")); // o.Name + var i0b_expr_3 = global::System.Linq.Expressions.Expression.Constant(": ", typeof(string)); // ": " + var i0b_expr_1 = global::System.Linq.Expressions.Expression.Call(typeof(string).GetMethod("Concat", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(string), typeof(string) }, null), i0b_expr_2, i0b_expr_3); + var i0b_expr_0 = global::System.Linq.Expressions.Expression.Call(typeof(string).GetMethod("Concat", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static, null, new global::System.Type[] { typeof(string), typeof(string) }, null), i0b_expr_1, i0b_p_t); + var __lambda2 = global::System.Linq.Expressions.Expression.Lambda>(i0b_expr_0, i0b_p_o, i0b_p_t); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.SelectMany( + (global::System.Linq.IQueryable)source, + __lambda1, + __lambda2)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectManyTests.cs b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectManyTests.cs new file mode 100644 index 0000000..9d6875e --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectManyTests.cs @@ -0,0 +1,65 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using VerifyMSTest; +using ExpressiveSharp.Generator.Tests.Infrastructure; + +namespace ExpressiveSharp.Generator.Tests.PolyfillInterceptorGenerator; + +[TestClass] +public class SelectManyTests : GeneratorTestBase +{ + [TestMethod] + public Task SelectMany_CollectionSelector_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public System.Collections.Generic.List Tags { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + query.WithExpressionRewrite() + .SelectMany(o => o.Tags) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task SelectMany_WithResultSelector_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public System.Collections.Generic.List Tags { get; set; } public string Name { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + query.WithExpressionRewrite() + .SelectMany(o => o.Tags, (o, t) => o.Name + ": " + t) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } +} diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectTests.Select_AnonymousType_GeneratesGenericInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectTests.Select_AnonymousType_GeneratesGenericInterceptor.verified.txt index eaa56e2..aa81531 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectTests.Select_AnonymousType_GeneratesGenericInterceptor.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectTests.Select_AnonymousType_GeneratesGenericInterceptor.verified.txt @@ -11,12 +11,12 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => new { o.Id, o.Name } - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_1 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Id")); // o.Id - var expr_2 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Name")); // o.Name - var expr_3 = typeof(TResult).GetConstructors()[0]; - var expr_0 = global::System.Linq.Expressions.Expression.New(expr_3, new global::System.Linq.Expressions.Expression[] { expr_1, expr_2 }, new global::System.Reflection.MemberInfo[] { typeof(TResult).GetProperty("Id"), typeof(TResult).GetProperty("Name") }); - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_1 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Id")); // o.Id + var i0_expr_2 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Name")); // o.Name + var i0_expr_3 = typeof(TResult).GetConstructors()[0]; + var i0_expr_0 = global::System.Linq.Expressions.Expression.New(i0_expr_3, new global::System.Linq.Expressions.Expression[] { i0_expr_1, i0_expr_2 }, new global::System.Reflection.MemberInfo[] { typeof(TResult).GetProperty("Id"), typeof(TResult).GetProperty("Name") }); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); return (global::ExpressiveSharp.IRewritableQueryable)(object) global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( global::System.Linq.Queryable.Select( diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectTests.Select_AnonymousType_WithComputedExpression_GeneratesCorrectly.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectTests.Select_AnonymousType_WithComputedExpression_GeneratesCorrectly.verified.txt index aeb0268..e9847e6 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectTests.Select_AnonymousType_WithComputedExpression_GeneratesCorrectly.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectTests.Select_AnonymousType_WithComputedExpression_GeneratesCorrectly.verified.txt @@ -11,15 +11,15 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => new { o.Id, Total = o.Price * o.Qty } - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_1 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Id")); // o.Id - var expr_3 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Price")); // o.Price - var expr_5 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Qty")); // o.Qty - var expr_4 = global::System.Linq.Expressions.Expression.Convert(expr_5, typeof(decimal)); - var expr_2 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, expr_3, expr_4); - var expr_6 = typeof(TResult).GetConstructors()[0]; - var expr_0 = global::System.Linq.Expressions.Expression.New(expr_6, new global::System.Linq.Expressions.Expression[] { expr_1, expr_2 }, new global::System.Reflection.MemberInfo[] { typeof(TResult).GetProperty("Id"), typeof(TResult).GetProperty("Total") }); - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_1 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Id")); // o.Id + var i0_expr_3 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Price")); // o.Price + var i0_expr_5 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Qty")); // o.Qty + var i0_expr_4 = global::System.Linq.Expressions.Expression.Convert(i0_expr_5, typeof(decimal)); + var i0_expr_2 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Multiply, i0_expr_3, i0_expr_4); + var i0_expr_6 = typeof(TResult).GetConstructors()[0]; + var i0_expr_0 = global::System.Linq.Expressions.Expression.New(i0_expr_6, new global::System.Linq.Expressions.Expression[] { i0_expr_1, i0_expr_2 }, new global::System.Reflection.MemberInfo[] { typeof(TResult).GetProperty("Id"), typeof(TResult).GetProperty("Total") }); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); return (global::ExpressiveSharp.IRewritableQueryable)(object) global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( global::System.Linq.Queryable.Select( diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectTests.Select_NamedType_GeneratesConcreteInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectTests.Select_NamedType_GeneratesConcreteInterceptor.verified.txt index 8306315..e89c47c 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectTests.Select_NamedType_GeneratesConcreteInterceptor.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SelectTests.Select_NamedType_GeneratesConcreteInterceptor.verified.txt @@ -11,9 +11,9 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => o.Name - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_0 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Name")); // o.Name - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Name")); // o.Name + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( global::System.Linq.Queryable.Select( (global::System.Linq.IQueryable)source, diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SetOperationTests.ExceptBy_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SetOperationTests.ExceptBy_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..6a53693 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SetOperationTests.ExceptBy_GeneratesInterceptor.verified.txt @@ -0,0 +1,34 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_ExceptBy_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Collections.Generic.IEnumerable second, + global::System.Func _) + { + // Source: o => o.Tag + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.ExceptBy( + (global::System.Linq.IQueryable)source, + second, + __lambda)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SetOperationTests.IntersectBy_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SetOperationTests.IntersectBy_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..e9fda13 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SetOperationTests.IntersectBy_GeneratesInterceptor.verified.txt @@ -0,0 +1,34 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_IntersectBy_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Collections.Generic.IEnumerable second, + global::System.Func _) + { + // Source: o => o.Tag + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.IntersectBy( + (global::System.Linq.IQueryable)source, + second, + __lambda)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SetOperationTests.UnionBy_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SetOperationTests.UnionBy_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..e26d11a --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SetOperationTests.UnionBy_GeneratesInterceptor.verified.txt @@ -0,0 +1,34 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_UnionBy_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Collections.Generic.IEnumerable second, + global::System.Func _) + { + // Source: o => o.Tag + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.UnionBy( + (global::System.Linq.IQueryable)source, + second, + __lambda)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SetOperationTests.cs b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SetOperationTests.cs new file mode 100644 index 0000000..0a69b5e --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SetOperationTests.cs @@ -0,0 +1,93 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using VerifyMSTest; +using ExpressiveSharp.Generator.Tests.Infrastructure; + +namespace ExpressiveSharp.Generator.Tests.PolyfillInterceptorGenerator; + +[TestClass] +public class SetOperationTests : GeneratorTestBase +{ + [TestMethod] + public Task ExceptBy_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Tag { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query, System.Collections.Generic.IEnumerable excluded) + { + query.WithExpressionRewrite() + .ExceptBy(excluded, o => o.Tag) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task IntersectBy_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Tag { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query, System.Collections.Generic.IEnumerable included) + { + query.WithExpressionRewrite() + .IntersectBy(included, o => o.Tag) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task UnionBy_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Tag { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query, System.Collections.Generic.IEnumerable extra) + { + query.WithExpressionRewrite() + .UnionBy(extra, o => o.Tag) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } +} diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.CountBy_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.CountBy_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..250834a --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.CountBy_GeneratesInterceptor.verified.txt @@ -0,0 +1,32 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable> __Polyfill_CountBy_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Tag + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.CountBy( + (global::System.Linq.IQueryable)source, + __lambda)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.DistinctBy_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.DistinctBy_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..90840d5 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.DistinctBy_GeneratesInterceptor.verified.txt @@ -0,0 +1,32 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_DistinctBy_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Tag + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.DistinctBy( + (global::System.Linq.IQueryable)source, + __lambda)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.OrderByDescending_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.OrderByDescending_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..6af712c --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.OrderByDescending_GeneratesInterceptor.verified.txt @@ -0,0 +1,32 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_OrderByDescending_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.OrderByDescending( + (global::System.Linq.IQueryable)source, + __lambda)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.SkipWhile_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.SkipWhile_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..70615e8 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.SkipWhile_GeneratesInterceptor.verified.txt @@ -0,0 +1,34 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_SkipWhile_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount < 50 + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_1 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var i0_expr_2 = global::System.Linq.Expressions.Expression.Constant(50, typeof(int)); // 50 + var i0_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.LessThan, i0_expr_1, i0_expr_2); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.SkipWhile( + (global::System.Linq.IQueryable)source, + __lambda)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.TakeWhile_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.TakeWhile_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..c1365ac --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.TakeWhile_GeneratesInterceptor.verified.txt @@ -0,0 +1,32 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_TakeWhile_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Active + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Active")); // o.Active + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.TakeWhile( + (global::System.Linq.IQueryable)source, + __lambda)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.ThenByDescending_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.ThenByDescending_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..4a45ce3 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.ThenByDescending_GeneratesInterceptor.verified.txt @@ -0,0 +1,46 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_ThenByDescending_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Tag + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.ThenByDescending( + (global::System.Linq.IOrderedQueryable)source, + __lambda)); + } + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_OrderBy_1( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Func _) + { + // Source: o => o.Amount + var i1_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i1_expr_0 = global::System.Linq.Expressions.Expression.Property(i1_p_o, typeof(global::TestNs.Order).GetProperty("Amount")); // o.Amount + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i1_expr_0, i1_p_o); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.OrderBy( + (global::System.Linq.IQueryable)source, + __lambda)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.cs b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.cs new file mode 100644 index 0000000..e00cbc7 --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/SingleLambdaQueryableTests.cs @@ -0,0 +1,170 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using VerifyMSTest; +using ExpressiveSharp.Generator.Tests.Infrastructure; + +namespace ExpressiveSharp.Generator.Tests.PolyfillInterceptorGenerator; + +[TestClass] +public class SingleLambdaQueryableTests : GeneratorTestBase +{ + [TestMethod] + public Task TakeWhile_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public bool Active { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + query.WithExpressionRewrite().TakeWhile(o => o.Active).ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task SkipWhile_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public int Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + query.WithExpressionRewrite().SkipWhile(o => o.Amount < 50).ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task DistinctBy_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Tag { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + query.WithExpressionRewrite().DistinctBy(o => o.Tag).ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task OrderByDescending_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public int Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + query.WithExpressionRewrite().OrderByDescending(o => o.Amount).ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + + [TestMethod] + public Task ThenByDescending_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public int Amount { get; set; } public string Tag { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + query.WithExpressionRewrite() + .OrderBy(o => o.Amount) + .ThenByDescending(o => o.Tag) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } + +#if NET9_0_OR_GREATER + [TestMethod] + public Task CountBy_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Tag { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable query) + { + query.WithExpressionRewrite().CountBy(o => o.Tag).ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } +#endif +} diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/WhereTests.ChainedWhereAndSelect_GeneratesTwoInterceptors.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/WhereTests.ChainedWhereAndSelect_GeneratesTwoInterceptors.verified.txt index 9170094..512e3f0 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/WhereTests.ChainedWhereAndSelect_GeneratesTwoInterceptors.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/WhereTests.ChainedWhereAndSelect_GeneratesTwoInterceptors.verified.txt @@ -11,9 +11,9 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => o.Name - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_0 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Name")); // o.Name - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Name")); // o.Name + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( global::System.Linq.Queryable.Select( (global::System.Linq.IQueryable)source, @@ -25,11 +25,11 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => o.Tag == "urgent" - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_1 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag - var expr_2 = global::System.Linq.Expressions.Expression.Constant("urgent", typeof(string)); // "urgent" - var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, expr_1, expr_2); - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i1_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i1_expr_1 = global::System.Linq.Expressions.Expression.Property(i1_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var i1_expr_2 = global::System.Linq.Expressions.Expression.Constant("urgent", typeof(string)); // "urgent" + var i1_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, i1_expr_1, i1_expr_2); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i1_expr_0, i1_p_o); return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( global::System.Linq.Queryable.Where( (global::System.Linq.IQueryable)source, diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/WhereTests.Where_SimpleCondition_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/WhereTests.Where_SimpleCondition_GeneratesInterceptor.verified.txt index 673c091..7d313ba 100644 --- a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/WhereTests.Where_SimpleCondition_GeneratesInterceptor.verified.txt +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/WhereTests.Where_SimpleCondition_GeneratesInterceptor.verified.txt @@ -11,11 +11,11 @@ namespace ExpressiveSharp.Generated.Interceptors global::System.Func _) { // Source: o => o.Tag == "urgent" - var p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); - var expr_1 = global::System.Linq.Expressions.Expression.Property(p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag - var expr_2 = global::System.Linq.Expressions.Expression.Constant("urgent", typeof(string)); // "urgent" - var expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, expr_1, expr_2); - var __lambda = global::System.Linq.Expressions.Expression.Lambda>(expr_0, p_o); + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_expr_1 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Tag")); // o.Tag + var i0_expr_2 = global::System.Linq.Expressions.Expression.Constant("urgent", typeof(string)); // "urgent" + var i0_expr_0 = global::System.Linq.Expressions.Expression.MakeBinary(global::System.Linq.Expressions.ExpressionType.Equal, i0_expr_1, i0_expr_2); + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o); return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( global::System.Linq.Queryable.Where( (global::System.Linq.IQueryable)source, diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ZipTests.Zip_WithResultSelector_GeneratesInterceptor.verified.txt b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ZipTests.Zip_WithResultSelector_GeneratesInterceptor.verified.txt new file mode 100644 index 0000000..5dcf54c --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ZipTests.Zip_WithResultSelector_GeneratesInterceptor.verified.txt @@ -0,0 +1,35 @@ +// +#nullable disable + +namespace ExpressiveSharp.Generated.Interceptors +{ + internal static class PolyfillInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(/* scrubbed */)] + internal static global::ExpressiveSharp.IRewritableQueryable __Polyfill_Zip_0( + this global::ExpressiveSharp.IRewritableQueryable source, + global::System.Collections.Generic.IEnumerable second, + global::System.Func _) + { + // Source: (o, p) => o.Name + var i0_p_o = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Order), "o"); + var i0_p_p = global::System.Linq.Expressions.Expression.Parameter(typeof(global::TestNs.Price), "p"); + var i0_expr_0 = global::System.Linq.Expressions.Expression.Property(i0_p_o, typeof(global::TestNs.Order).GetProperty("Name")); // o.Name + var __lambda = global::System.Linq.Expressions.Expression.Lambda>(i0_expr_0, i0_p_o, i0_p_p); + return global::ExpressiveSharp.Extensions.ExpressionRewriteExtensions.WithExpressionRewrite( + global::System.Linq.Queryable.Zip( + (global::System.Linq.IQueryable)source, + second, + __lambda)); + } + } +} + +namespace System.Runtime.CompilerServices +{ + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(int version, string data) { } + } +} \ No newline at end of file diff --git a/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ZipTests.cs b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ZipTests.cs new file mode 100644 index 0000000..b0fb93c --- /dev/null +++ b/tests/ExpressiveSharp.Generator.Tests/PolyfillInterceptorGenerator/ZipTests.cs @@ -0,0 +1,38 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using VerifyMSTest; +using ExpressiveSharp.Generator.Tests.Infrastructure; + +namespace ExpressiveSharp.Generator.Tests.PolyfillInterceptorGenerator; + +[TestClass] +public class ZipTests : GeneratorTestBase +{ + [TestMethod] + public Task Zip_WithResultSelector_GeneratesInterceptor() + { + var source = + """ + using ExpressiveSharp.Extensions; + + namespace TestNs + { + class Order { public string Name { get; set; } } + class Price { public decimal Amount { get; set; } } + class TestClass + { + public void Run(System.Linq.IQueryable orders, System.Collections.Generic.IEnumerable prices) + { + orders.WithExpressionRewrite() + .Zip(prices, (o, p) => o.Name) + .ToList(); + } + } + } + """; + var result = RunPolyfillInterceptorGenerator(CreateCompilation(source)); + + Assert.AreEqual(1, result.GeneratedTrees.Length); + + return Verifier.Verify(result.GeneratedTrees[0].GetText().ToString()); + } +} diff --git a/tests/ExpressiveSharp.IntegrationTests.ExpressionCompile/Tests/Common/MultiLambdaInterceptorTests.cs b/tests/ExpressiveSharp.IntegrationTests.ExpressionCompile/Tests/Common/MultiLambdaInterceptorTests.cs new file mode 100644 index 0000000..6a98c1e --- /dev/null +++ b/tests/ExpressiveSharp.IntegrationTests.ExpressionCompile/Tests/Common/MultiLambdaInterceptorTests.cs @@ -0,0 +1,107 @@ +using ExpressiveSharp.Extensions; +using ExpressiveSharp.IntegrationTests.Scenarios.Store.Models; + +namespace ExpressiveSharp.IntegrationTests.ExpressionCompile.Tests.Common; + +[TestClass] +public class MultiLambdaInterceptorTests +{ + private static readonly List _orders = new() + { + new Order + { + Id = 1, Tag = "A", Price = 100, Quantity = 2, + Items = new List + { + new() { Id = 1, OrderId = 1, ProductName = "Widget", UnitPrice = 50, Quantity = 2 }, + new() { Id = 2, OrderId = 1, ProductName = "Gadget", UnitPrice = 75, Quantity = 1 }, + } + }, + new Order + { + Id = 2, Tag = "B", Price = 50, Quantity = 1, + Items = new List + { + new() { Id = 3, OrderId = 2, ProductName = "Doohickey", UnitPrice = 25, Quantity = 3 }, + } + }, + }; + + /// + /// SelectMany with result selector uses two lambdas that share the parameter name 'o'. + /// This tests that the generator emits unique variable names for each lambda body. + /// + [TestMethod] + public void SelectMany_WithResultSelector_SharedParamName_CompilesAndRuns() + { + var source = _orders.AsQueryable(); + + // Both lambdas use 'o' — this triggers the duplicate variable name bug + // if the generator doesn't prefix local variables per lambda. + var results = source.WithExpressionRewrite() + .SelectMany(o => o.Items, (o, item) => o.Tag + ": " + item.ProductName) + .ToList(); + + Assert.AreEqual(3, results.Count); + CollectionAssert.Contains(results, "A: Widget"); + CollectionAssert.Contains(results, "A: Gadget"); + CollectionAssert.Contains(results, "B: Doohickey"); + } + + /// + /// Join with three lambdas where outer and inner key selectors share parameter patterns. + /// Tests unique variable naming across all three lambda bodies. + /// Uses non-nullable int keys (Id) to avoid nullable type mismatch issues. + /// + [TestMethod] + public void Join_ThreeLambdas_CompilesAndRuns() + { + var orders = _orders.AsQueryable(); + var lineItems = new List + { + new() { Id = 1, OrderId = 1, ProductName = "Widget", UnitPrice = 50, Quantity = 2 }, + new() { Id = 2, OrderId = 2, ProductName = "Gadget", UnitPrice = 25, Quantity = 3 }, + }; + + var results = orders.WithExpressionRewrite() + .Join(lineItems, + o => o.Id, + li => li.OrderId, + (o, li) => o.Tag + ": " + li.ProductName) + .ToList(); + + Assert.AreEqual(2, results.Count); + CollectionAssert.Contains(results, "A: Widget"); + CollectionAssert.Contains(results, "B: Gadget"); + } + + /// + /// Join where the outer key is nullable (int?) but the inner key is non-nullable (int). + /// The emitter must insert an implicit conversion so the expression tree compiles. + /// + [TestMethod] + public void Join_NullableOuterKey_NonNullableInnerKey_CompilesAndRuns() + { + var orders = _orders.AsQueryable(); + var customers = new List + { + new() { Id = 1, Name = "Alice" }, + new() { Id = 2, Name = "Bob" }, + }; + + // Order.CustomerId is int? — Customer.Id is int — TKey resolves to int? + _orders[0].CustomerId = 1; + _orders[1].CustomerId = 2; + + var results = orders.WithExpressionRewrite() + .Join(customers, + o => o.CustomerId, + c => c.Id, + (o, c) => c.Name) + .ToList(); + + Assert.AreEqual(2, results.Count); + CollectionAssert.Contains(results, "Alice"); + CollectionAssert.Contains(results, "Bob"); + } +} diff --git a/tests/ExpressiveSharp.Tests/Extensions/RewritableQueryableLinqExtensionsTests.cs b/tests/ExpressiveSharp.Tests/Extensions/RewritableQueryableLinqExtensionsTests.cs new file mode 100644 index 0000000..94f5170 --- /dev/null +++ b/tests/ExpressiveSharp.Tests/Extensions/RewritableQueryableLinqExtensionsTests.cs @@ -0,0 +1,108 @@ +using ExpressiveSharp.Extensions; + +namespace ExpressiveSharp.Tests.Extensions; + +[TestClass] +public class RewritableQueryableLinqExtensionsTests +{ + private IRewritableQueryable CreateRewritableSource() + => new[] { 1, 2, 3 }.AsQueryable().WithExpressionRewrite(); + + [TestMethod] + public void Take_PreservesRewritableQueryable() + { + var source = CreateRewritableSource(); + var result = source.Take(2); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void Skip_PreservesRewritableQueryable() + { + var source = CreateRewritableSource(); + var result = source.Skip(1); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void Distinct_PreservesRewritableQueryable() + { + var source = CreateRewritableSource(); + var result = source.Distinct(); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void Reverse_PreservesRewritableQueryable() + { + var source = CreateRewritableSource(); + var result = source.Reverse(); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void Concat_PreservesRewritableQueryable() + { + var source = CreateRewritableSource(); + var result = source.Concat(new[] { 4, 5 }); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void Union_PreservesRewritableQueryable() + { + var source = CreateRewritableSource(); + var result = source.Union(new[] { 3, 4 }); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void Intersect_PreservesRewritableQueryable() + { + var source = CreateRewritableSource(); + var result = source.Intersect(new[] { 2, 3 }); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void Except_PreservesRewritableQueryable() + { + var source = CreateRewritableSource(); + var result = source.Except(new[] { 1 }); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void DefaultIfEmpty_PreservesRewritableQueryable() + { + var source = new string[0].AsQueryable().WithExpressionRewrite(); + var result = source.DefaultIfEmpty(); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void DefaultIfEmpty_WithValue_PreservesRewritableQueryable() + { + var source = new int[0].AsQueryable().WithExpressionRewrite(); + var result = source.DefaultIfEmpty(42); + Assert.IsInstanceOfType>(result); + } + + [TestMethod] + public void ChainedPassthrough_PreservesRewritableQueryable() + { + var source = CreateRewritableSource(); + var result = source.Take(10).Skip(1).Distinct(); + Assert.IsInstanceOfType>(result); + } + +#if NET9_0_OR_GREATER + [TestMethod] + public void Index_PreservesRewritableQueryable() + { + var source = CreateRewritableSource(); + var result = source.Index(); + Assert.IsInstanceOfType>(result); + } +#endif +}