Skip to content

Fix rewriter when the sync method is in the same extensions class#110

Merged
virzak merged 1 commit intozompinc:masterfrom
0xced:fix-sync-in-same-extension
Apr 29, 2026
Merged

Fix rewriter when the sync method is in the same extensions class#110
virzak merged 1 commit intozompinc:masterfrom
0xced:fix-sync-in-same-extension

Conversation

@0xced
Copy link
Copy Markdown
Contributor

@0xced 0xced commented Dec 16, 2025

Pull request #108 made sure that EF Core async extensions (such as AnyAsync are properly translated to the System.Linq.Queryable.Any extension method).

Unfortunately, this introduced a regression. When async and sync methods are in the same extension class (such as ExecuteDeleteAsync and ExecuteDelete) the generated sync method would be wrong. I.e., generated in System.Linq.Queryable instead of Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.

This pull request tries to address this issue. Unfortunately, I'm again facing discrepancies between the GenerationSandbox.Tests and Generator.Tests projects. This is why this pull request is opened a draft.

When used in the GenerationSandbox.Tests project, the generated code is correct.

// <auto-generated/>
#nullable enable
namespace GenerationSandbox.Tests
{
    public partial class EntityFrameworkQueryableExtensions
    {
        /// <summary>
        /// Test method.
        /// </summary>
        /// <param name="dbContext">The db context.</param>
        /// <returns>The result.</returns>
        public int QueryableExtension(global::Microsoft.EntityFrameworkCore.DbContext dbContext)
        {
            var dbSet = dbContext.Set<object>();
            if (global::System.Linq.Queryable.Any(dbSet))
            {
                return global::Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteDelete(dbSet);
            }

            return 0;
        }
    }
}

When generated in the Generator.Tests project, the generated code is incorrect. Note how the
Any and ExecuteDelete methods are not generated with their fully qualified names.

//HintName: Zomp.SyncMethodGenerator.IntegrationTests.EntityFrameworkQueryableExtensions.QueryableExtensionAsync.g.cs
// <auto-generated/>
#nullable enable
namespace Zomp.SyncMethodGenerator.IntegrationTests
{
    public partial class EntityFrameworkQueryableExtensions
    {
        public int QueryableExtension(global::Microsoft.EntityFrameworkCore.DbContext dbContext)
        {
            var dbSet = dbContext.Set<object>();
            if (dbSet.Any())
            {
                return dbSet.ExecuteDelete();
            }
            
            return 0;
        }
    }
}

I don't understand why the generated code is different. I have identified that var symbol = GetSymbol(node) returns null for InvocationExpressionSyntax InvocationExpression dbSet.AnyAsync(cancellationToken) in Generator.Tests and not null in GenerationSandbox.Tests but I don't understand why.

@0xced 0xced marked this pull request as draft December 16, 2025 16:44
@0xced 0xced force-pushed the fix-sync-in-same-extension branch from 3a210c4 to 48e1da9 Compare January 28, 2026 15:35
@0xced
Copy link
Copy Markdown
Contributor Author

0xced commented Jan 28, 2026

@virzak Would love to have your eyes on this when you get some spare time. No rush, I just wanted to make sure it didn’t slip through the cracks.

@virzak
Copy link
Copy Markdown
Contributor

virzak commented Jan 28, 2026

Will look at it this weekend probably

@virzak
Copy link
Copy Markdown
Contributor

virzak commented Apr 26, 2026

I don't understand why the generated code is different. I have identified that var symbol = GetSymbol(node) returns null for InvocationExpressionSyntax InvocationExpression dbSet.AnyAsync(cancellationToken) in Generator.Tests and not null in GenerationSandbox.Tests but I don't understand why.

After a long time trying to understand what could be missing I decided to compile the original and the generated code in the test project. It immediately provided all the missing types. In your case we needed to add typeof(IListSource).Assembly.Location

Also compilation revealed lots of issues in the test cases which had to be fixed.

Once you rebase, you should be good to continue.

Pull request zompinc#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.
@0xced 0xced force-pushed the fix-sync-in-same-extension branch from 48e1da9 to 4e83b85 Compare April 29, 2026 09:00
@0xced 0xced marked this pull request as ready for review April 29, 2026 09:04
@0xced
Copy link
Copy Markdown
Contributor Author

0xced commented Apr 29, 2026

Awesome, thanks for figuring this out!

I have rebased my branch and everything is working as expected now. 🥳

This pull request is now ready for review.

@virzak virzak merged commit 6128ddb into zompinc:master Apr 29, 2026
3 of 4 checks passed
@virzak
Copy link
Copy Markdown
Contributor

virzak commented Apr 29, 2026

Version 2.0.13 is on nuget. Thanks so much, Cédric!

@0xced 0xced deleted the fix-sync-in-same-extension branch April 30, 2026 07:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants