Skip to content

F# codegen: scoped-DI frame emit + 2.2.6 (#397)#398

Merged
jeremydmiller merged 1 commit into
mainfrom
feat-fsharp-scoped-di
May 29, 2026
Merged

F# codegen: scoped-DI frame emit + 2.2.6 (#397)#398
jeremydmiller merged 1 commit into
mainfrom
feat-fsharp-scoped-di

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Closes #397. Ships as 2.2.6.

Problem

A generated method that resolves a scoped service via service location uses ScopedContainerCreation + GetServiceFromScopedContainerFrame (JasperFx.CodeGeneration.Services), neither of which emitted F#. So any handler injecting a scoped dependency — EF Core DbContext, Marten IDocumentSession, most real handlers — threw NotSupportedException on the F# render.

Fix

  • ScopedContainerCreationuse serviceScope = ServiceProviderServiceExtensions.CreateAsyncScope(serviceScopeFactory) inside a task { } body (the CE's use awaits DisposeAsync on the AsyncServiceScope), or use serviceScope = serviceScopeFactory.CreateScope() for a synchronous body; then postprocessors, then Next. (F# use disposes at end of scope, like using var.)
  • GetServiceFromScopedContainerFramelet x = ServiceProviderServiceExtensions.GetRequiredService<T>(scoped) / …GetRequiredKeyedService<T>(scoped, key). The optional Header (a C#-style /* */ comment fragment) is not emitted on the F# path — those delimiters are invalid F#.

Test

New GeneratedScopedConsumer fixture resolves an AddScoped<IScopedThing>(_ => …) lambda-factory dependency (forcing service location) and awaits it twice, so the compile gate proves the scoped-DI F# compiles:

member this.Handle() : System.Threading.Tasks.Task =
    task {
        use serviceScope = ServiceProviderServiceExtensions.CreateAsyncScope(_serviceScopeFactory)
        let scopedThing = ServiceProviderServiceExtensions.GetRequiredService<IScopedThing>(serviceScope.ServiceProvider)
        do! scopedThing.DoAsync()
        do! scopedThing.DoAsync()
    }

Verification

  • CodegenTests F# generation tests 30/30; CodegenTests.FSharp compile gate green.

Bumps JasperFxVersion 2.2.5 → 2.2.6 (RuntimeCompiler untouched).

🤖 Generated with Claude Code

A generated method that resolves a scoped service via service location uses two
JasperFx.CodeGeneration.Services frames with no F# emit, so any handler injecting
a scoped dependency (EF Core DbContext, Marten IDocumentSession, …) threw on the
F# render.

- ScopedContainerCreation: `use serviceScope = ServiceProviderServiceExtensions
  .CreateAsyncScope(serviceScopeFactory)` inside a task { } body (the CE's `use`
  awaits DisposeAsync), or `use serviceScope = serviceScopeFactory.CreateScope()`
  for a synchronous body; then postprocessors, then Next.
- GetServiceFromScopedContainerFrame: `let x = ServiceProviderServiceExtensions
  .GetRequiredService<T>(scoped)` (or GetRequiredKeyedService). The optional C#
  /* */ Header comment is not emitted on the F# path (invalid F# comment syntax).
- Fixture: GeneratedScopedConsumer resolves an AddScoped<T>(_ => …) lambda-factory
  dependency (forcing service location) and awaits it; the compile gate proves the
  scoped-DI F# compiles. Mirrors Bug_244's scoped-session scenario.
- Bump JasperFxVersion 2.2.5 -> 2.2.6.

Closes #397.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit 6efec19 into main May 29, 2026
1 check passed
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.

F# codegen: scoped-DI frames (ScopedContainerCreation, GetServiceFromScopedContainerFrame) need F# emit

1 participant