Fix what five trial applications ran into, and lead the README with the problem - #38
Merged
Merged
Conversation
Both tests assert that nothing has logged an exit between the task being handed back and being awaited, and manufactured that window with `await Task.Delay(20)`. The timer and its continuation are independent of the test thread, so any stall longer than 20ms lets the whole chain finish before the assertion reads the log — the failure is the log arriving complete rather than partial. A 60ms stall reproduces it 3/3. The work now awaits a gate the test releases, so the assertion is about a suspension rather than a stopwatch. It survives the same 60ms stall, and it is a stronger claim than before: the old shape would also have passed on a lucky margin, with the work at 19ms and the assertion at 5ms. The runtime gate defaults to Task.CompletedTask so a test with no interest in it cannot deadlock, and the generator one hangs off the Recorder the harness already owns, which lives in the generated assembly and so cannot leak between compilations. InterceptorGenerationTests had the identical shape and had simply not lost the coin flip yet; fixing only the test that failed would have left it live. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMV4J1eVscS5EBMUhhWc2x
A registered service type carries whatever nullable annotation its declaration used, so `class GetBookHandler : IHandler<GetBook, Book?>` emits `typeof(...Book?)`. Roslyn requires generated code to open a nullable context explicitly however the consuming project is configured, and the registrations file was the one generated file that never did — the module, attribute and interceptor writers all already call EnableNullable. The result was CS8669 on a find-by-id handler, which is about as ordinary a shape as exists: a warning the consumer cannot fix from their own source without dropping the annotation from their own domain signatures, and a hard build failure under TreatWarningsAsErrors. It reproduced on both the attribute and the convention path, because the convention path emits through the same writer — which is also why one call fixes both files. Nullability is not stripped from the emitted typeof. It is inert there, since `typeof(Book?)` and `typeof(Book)` are one runtime type, and removing it would mean touching type modelling that decoration and interception rely on: ConstructorArgumentWriter reads nullability to choose GetService over GetRequiredService, and the interceptor wrappers need the annotations to keep implementing the interfaces they wrap. Probing a decorator declared against `IStore<Document>` over a registration of `IStore<Document?>` confirmed the two still match, so the annotation is cosmetic rather than a silent miss. The nine snapshots re-approve with two lines each and no other change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMV4J1eVscS5EBMUhhWc2x
It was the only one of the three testing attributes without an AttributeUsage — MockAttribute is pinned to parameters and TestExportAttribute to methods — so writing it on a test method compiled, was never read, and then failed inside ActivatorUtilities with "Multiple constructors accepting all given argument types have been found in type 'System.String'", which names neither the parameter nor the real mistake. It is now CS0592 at the attribute. The remarks also say what the values are, since the guide's table listing this against "String parameters" describes the one case it cannot do: they are the parameter type's constructor arguments, combined with what the container supplies. A parameter that should simply be a value wants [InlineData]. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMV4J1eVscS5EBMUhhWc2x
It opened by naming the implementation — "a C# source generator package that uses attributes to create dependency injection registration modules" — which says what the thing is before the reader knows why they want one. The single most persuasive artefact in the document, a generated registration that is plainly ordinary C#, sat at the very bottom below four hundred lines of reference. Now: the hook, a link to the documentation site, the attribute and the code it generates side by side, and a table answering the question every reader of a .NET DI library arrives with, which is why not Scrutor. The reference material that the site covers in depth is condensed to a lookup table that links out, so the README is a pitch and an index rather than a second copy of the docs. Two corrections to code that did not compile. The quick start now carries the `using DependencyModules.Runtime.Attributes;` its services need and the `using YourRootNamespace;` that top-level statements need to name the generated ApplicationModule — the latter being a real papercut, since the module takes the project's root namespace while top-level statements sit in the global one. integ-tests/ConsoleTestProject has always had that using; the README omitted it and sent every reader into a CS0246 that names a type they never wrote. Every snippet in this file was compiled and run before committing, which is how both omissions were found. Sample links are absolute, since this file also ships as the NuGet package readme where relative paths do not resolve. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMV4J1eVscS5EBMUhhWc2x
Three fixes, all found by building the examples rather than reading them. [InjectValues] was introduced as being for "a string, an id, a record combining both", and the comparison table said "the parameter is data, not a service". A bare string is the one thing it cannot do: the values are the parameter type's constructor arguments, so asking for a string tries to construct System.String from a string and fails with "A suitable constructor for type 'System.String' could not be located". The prose below it was already right; only the framing promised something else. Data rows are what a parameter that simply is a value wants, and [InlineData] composing with [ModuleTest] is now shown, since nothing said so. The testing bootstrap and the top-level statements example both referenced a module without importing its namespace. A module generates its attribute in its own namespace and an assembly attribute has no namespace context, so the first fails on a type the reader never wrote; the generated ApplicationModule takes the project's RootNamespace while top-level statements sit in the global one, so the second fails on the module itself. Both now carry the using, and say why it is load-bearing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMV4J1eVscS5EBMUhhWc2x
…shot The API snapshot moves because AttributeUsage is part of the public surface, which is the test doing its job on a deliberate change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMV4J1eVscS5EBMUhhWc2x
A module generates its attribute in the module's own namespace, and an assembly-level attribute has no namespace context to inherit — a `using` written inside a namespace declaration cannot reach it, because assembly attributes precede every namespace in the file. So `[assembly: ApplicationModule]` without the import fails with CS0246 naming `ApplicationModuleAttribute`: a type the developer never wrote, generated into a namespace the error does not mention, by a generator whose output they have probably never opened. Every part of that message points away from the fix, which is one line. Alone among these diagnostics it is read from syntax rather than the semantic model, and has to be: the attribute is written by the generator that is running, so it does not exist in the compilation being examined and nothing about it resolves. Every assembly-level module attribute looks unresolved from here, which is why the question is "is there a module by this name, and could this file see it" rather than "did it bind". That makes the false positives the part worth testing, and they are what most of the tests cover. It stays quiet for an attribute matching no module in the compilation, a module in the global namespace with nothing to import, a usage already written qualified, and a namespace supplied by a `global using` in any file — the last being why the check reads every compilation unit rather than only the one the attribute sits in. A `using` alias is deliberately not accepted, since it imports one name rather than a namespace and does not bring the attribute into scope under the name written. Registered from SourceGenerator rather than the base class, so a framework generator loaded alongside this one does not report the same usage twice. Confirmed against the trial project the papercut was found in: it reports on the right line, names both fixes, and disappears when either is applied. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMV4J1eVscS5EBMUhhWc2x
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Built five applications against the released package — a console tool, a three-project minimal API, a worker, an xUnit suite and a Native AOT binary — and fixed everything that got in the way.
Bugs
A nullable type argument in a service type broke the build.
class GetBookHandler : IHandler<GetBook, Book?>emitstypeof(…Book?), and the registrations file was the one generated file that never opened a nullable context.CS8669on a find-by-id handler: a warning the consumer cannot silence from their own source, and a hard failure underTreatWarningsAsErrors. One call fixes both the attribute and the convention path, since both emit through the same writer. Regression test fails 2/2 without it.Two async interception tests raced a wall clock. Both assert nothing has logged an exit between the task being handed back and awaited, and manufactured that window with
await Task.Delay(20). Any stall longer than 20ms fails them — the log arrives complete rather than partial. A 60ms stall reproduces it 3/3; the gated version survives the same stall.InterceptorGenerationTestshad the identical shape and had simply not lost the coin flip yet.[InjectValues]did nothing where it was written. The only one of the three testing attributes without anAttributeUsage, so a method-level usage compiled, was never read, and failed insideActivatorUtilitiesnamingSystem.String. NowCS0592. Breaking, deliberately.Documentation
The README opened by naming the implementation, and the most persuasive thing in it — a generated registration that is plainly ordinary C# — sat at the bottom under four hundred lines of reference. It now opens with the hook, a link to the docs site, the attribute beside the code it generates, and a table answering why-not-Scrutor. The reference links out instead of duplicating the site, and
integ-tests/is pointed at as the sample gallery it already is.Three documented examples did not compile, all found by building them: a missing attributes
using, and two places naming a module without importing its namespace.integ-tests/ConsoleTestProjecthas always carried thatusing; the docs omitted it.Verification
scripts/verify-packages.shpasses for net8.0 and net10.0TreatWarningsAsErrors, including Native AOT with zero IL warningsNot done
I had proposed emitting the auto-generated
ApplicationModuleinto the global namespace so the quickstart needs nousing. Investigation reversed that: theRootNamespaceplacement is deliberate and covered byConfigurationTests.RootNamespace_NamesTheAutoGeneratedApplicationModule, and aglobal usingalias works but is not something to introduce days before 1.0. Documented instead.🤖 Generated with Claude Code
https://claude.ai/code/session_01CMV4J1eVscS5EBMUhhWc2x