Skip to content

Keep diagnostic locations inside the compilation being analyzed - #192

Open
kairoi-llc wants to merge 1 commit into
pakrym:mainfrom
kairoi-llc:fix/diagnostic-location-outside-compilation
Open

kairoi-llc wants to merge 1 commit into
pakrym:mainfrom
kairoi-llc:fix/diagnostic-location-outside-compilation

Conversation

@kairoi-llc

@kairoi-llc kairoi-llc commented Sep 3, 2026

Copy link
Copy Markdown

Fixes #191.

Locations taken from symbols can belong to a compilation other than the one being analyzed — a registration declared on a [ServiceProviderModule] in a referenced project, or a constructor parameter of an implementation type defined there. Roslyn rejects a diagnostic carrying such a location on the paths that validate locations (notably .NET Hot Reload / EnC), and ContainerGenerator.Execute's catch-all turns the resulting ArgumentException into JAB0001 — so no container is generated and dotnet watch stops applying changes.

This adds a small helper and applies it to every symbol-derived location in ServiceProviderBuilder:

private Location? InCurrentCompilation(Location? location) =>
    location?.SourceTree is { } tree && !_context.Compilation.ContainsSyntaxTree(tree)
        ? null                       // Diagnostic.Create maps null -> Location.None
        : location;

It is deliberately applied to all nine sites rather than only JAB0014: JAB0002, JAB0013 and JAB0019 are built from the same registrationLocation, and JAB0008/JAB0016/JAB0017 from context.RequestLocation, which is derived from a parameter symbol that may also live in another project. They would throw identically on a graph that triggers them; they simply do not fire on a healthy one.

A no-op whenever the location is already in the current compilation, so ordinary builds are unaffected.

Verification

Two-project sample per the issue — a [ServiceProviderModule] in a library with a nullable, no-default constructor parameter whose service is registered, imported by a container in the app project.

  • Before: dotnet watch, first edit → error JAB0001: ... 'JAB0014' has a source location in file '.../Lib/Module.cs', which is not part of the compilation being analyzed, followed by CS1061: 'Container' does not contain a definition for 'GetService'.
  • After: three consecutive edits, no JAB0001, container generated each time.

Also exercised on a larger multi-project Avalonia app (~320 registrations across imported modules in a referenced library, two containers) — a full dotnet build is unchanged, and repeated edits under dotnet watch no longer fail generation.

I did not add a test: Jab.Tests builds its compilations from single-source fixtures, and reproducing this needs a second compilation referenced by the first so a registration's SourceTree is genuinely foreign.

A possible follow-up, not included here

NullableServiceRegistered (JAB0014) is the only Info-severity descriptor, and it fires on any nullable-annotated parameter without a default whose service is registered — which is a common and reasonable shape when the parameter is nullable so tests can pass null explicitly. It is invisible in normal builds, so the location bug went unnoticed until hot reload validated it. Whether that diagnostic should fire so broadly seems worth its own discussion; this PR only stops it from breaking generation.

Locations taken from symbols can belong to a different compilation: a registration declared on a
[ServiceProviderModule] in a referenced project, or a constructor parameter of an implementation
type defined there. Roslyn rejects a diagnostic whose location is outside the compilation being
analyzed, on the paths that validate locations — notably .NET Hot Reload / EnC. ContainerGenerator's
catch-all then turns the ArgumentException into JAB0001, so no container is generated and
`dotnet watch` stops applying changes.

Ordinary builds do not validate diagnostic locations, which is why this is invisible outside hot
reload. It is reached easily because JAB0014 (NullableServiceRegistered) is Info severity and fires
on healthy graphs — any nullable-annotated parameter without a default whose service is registered.
Suppression cannot help: ReportDiagnostic validates the location before severity filtering.

Adds InCurrentCompilation() and applies it to every symbol-derived location in ServiceProviderBuilder,
not only JAB0014 — JAB0002, JAB0013 and JAB0019 are built from the same registrationLocation and
would throw identically on a graph that triggers them.

Fixes pakrym#191
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.

Generator throws JAB0001 under dotnet watch: JAB0014 reported with a Location from a referenced compilation

1 participant