From 4e83b859aa99a64f8d6bcd5ba318955b1718cbb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Fri, 28 Nov 2025 22:28:29 +0100 Subject: [PATCH] Fix rewriter when the sync method is in the same extensions class Pull request #108 fixed EF Core async to sync generation. EF Core async methods are found in the `EntityFrameworkQueryableExtensions` class, so a mapping from EntityFrameworkQueryableExtensions to System.Linq.Queryable was added. Unfortunately, this was not enough. The `EntityFrameworkQueryableExtensions` class also contains extension methods that have both an async and a sync method. For example: `ExecuteDeleteAsync` and `ExecuteDelete`. This commit fixes the translation for those methods where both the async and sync methods are in the same extension class. --- .../AsyncToSyncRewriter.cs | 13 ++++++++++++- .../EntityFrameworkQueryableExtensions.cs | 15 ++++++++++----- tests/Generator.Tests/ExtensionMethodTests.cs | 11 ++++++++--- ...tensions.QueryableExtensionAsync.g.verified.cs | 10 ++++++++-- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/Zomp.SyncMethodGenerator/AsyncToSyncRewriter.cs b/src/Zomp.SyncMethodGenerator/AsyncToSyncRewriter.cs index 119e81c..9273393 100644 --- a/src/Zomp.SyncMethodGenerator/AsyncToSyncRewriter.cs +++ b/src/Zomp.SyncMethodGenerator/AsyncToSyncRewriter.cs @@ -1844,7 +1844,18 @@ private InvocationExpressionSyntax UnwrapExtension(InvocationExpressionSyntax ie var newName = reducedFrom.Name; newName = changeMemoryToSpan ? GetNewName(reducedFrom) : RemoveAsync(newName); - var fullyQualifiedName = $"{MakeType(reducedFrom.ContainingType)}.{newName}"; + var newNameExistsInContainingType = semanticModel.Compilation.References + .Select(semanticModel.Compilation.GetAssemblyOrModuleSymbol) + .Append(semanticModel.Compilation.Assembly) + .OfType() + .Select(assemblySymbol => assemblySymbol.GetTypeByMetadataName(reducedFrom.ContainingType.ToString())) + .OfType() + .SelectMany(symbol => symbol.GetMembers(newName)) + .Any(); + + var fullyQualifiedName = newNameExistsInContainingType + ? $"{reducedFrom.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.{newName}" + : $"{MakeType(reducedFrom.ContainingType)}.{newName}"; var es = (ies.Expression switch { diff --git a/tests/GenerationSandbox.Tests/EntityFrameworkQueryableExtensions.cs b/tests/GenerationSandbox.Tests/EntityFrameworkQueryableExtensions.cs index 6e94c2e..6999b62 100644 --- a/tests/GenerationSandbox.Tests/EntityFrameworkQueryableExtensions.cs +++ b/tests/GenerationSandbox.Tests/EntityFrameworkQueryableExtensions.cs @@ -1,8 +1,7 @@ -using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace Zomp.SyncMethodGenerator.IntegrationTests; +namespace GenerationSandbox.Tests; using Microsoft.EntityFrameworkCore; @@ -14,12 +13,18 @@ public partial class EntityFrameworkQueryableExtensions /// /// Test method. /// - /// The source. + /// The db context. /// The cancellation token. /// The result. [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async Task QueryableExtensionAsync(IQueryable source, CancellationToken cancellationToken) + public async Task QueryableExtensionAsync(DbContext dbContext, CancellationToken cancellationToken) { - return await source.AnyAsync(cancellationToken); + var dbSet = dbContext.Set(); + if (await dbSet.AnyAsync(cancellationToken)) + { + return await dbSet.ExecuteDeleteAsync(cancellationToken); + } + + return 0; } } diff --git a/tests/Generator.Tests/ExtensionMethodTests.cs b/tests/Generator.Tests/ExtensionMethodTests.cs index ca4669b..14d95ec 100644 --- a/tests/Generator.Tests/ExtensionMethodTests.cs +++ b/tests/Generator.Tests/ExtensionMethodTests.cs @@ -122,7 +122,6 @@ public async IAsyncEnumerable WhereLessThan(T threshold) [Fact] public Task EntityFrameworkQueryableExtensions() => """ -using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -133,9 +132,15 @@ namespace Zomp.SyncMethodGenerator.IntegrationTests public partial class EntityFrameworkQueryableExtensions { [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async Task QueryableExtensionAsync(IQueryable source, CancellationToken cancellationToken) + public async Task QueryableExtensionAsync(DbContext dbContext, CancellationToken cancellationToken) { - return await source.AnyAsync(cancellationToken); + var dbSet = dbContext.Set(); + if (await dbSet.AnyAsync(cancellationToken)) + { + return await dbSet.ExecuteDeleteAsync(cancellationToken); + } + + return 0; } } } diff --git a/tests/Generator.Tests/Snapshots/ExtensionMethodTests.EntityFrameworkQueryableExtensions#Zomp.SyncMethodGenerator.IntegrationTests.EntityFrameworkQueryableExtensions.QueryableExtensionAsync.g.verified.cs b/tests/Generator.Tests/Snapshots/ExtensionMethodTests.EntityFrameworkQueryableExtensions#Zomp.SyncMethodGenerator.IntegrationTests.EntityFrameworkQueryableExtensions.QueryableExtensionAsync.g.verified.cs index b79f037..0e1bb7c 100644 --- a/tests/Generator.Tests/Snapshots/ExtensionMethodTests.EntityFrameworkQueryableExtensions#Zomp.SyncMethodGenerator.IntegrationTests.EntityFrameworkQueryableExtensions.QueryableExtensionAsync.g.verified.cs +++ b/tests/Generator.Tests/Snapshots/ExtensionMethodTests.EntityFrameworkQueryableExtensions#Zomp.SyncMethodGenerator.IntegrationTests.EntityFrameworkQueryableExtensions.QueryableExtensionAsync.g.verified.cs @@ -5,9 +5,15 @@ namespace Zomp.SyncMethodGenerator.IntegrationTests { public partial class EntityFrameworkQueryableExtensions { - public bool QueryableExtension(global::System.Linq.IQueryable source) + public int QueryableExtension(global::Microsoft.EntityFrameworkCore.DbContext dbContext) { - return global::System.Linq.Queryable.Any(source); + var dbSet = dbContext.Set(); + if (global::System.Linq.Queryable.Any(dbSet)) + { + return global::Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteDelete(dbSet); + } + + return 0; } } }