diff --git a/AGENTS.md b/AGENTS.md index 4e812f47..f6f2376a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -165,6 +165,7 @@ referenced for every project via `Directory.Packages.props`). suffix, `CancellationToken` and `IProgress` (unless `PreserveCancellationToken` / `PreserveProgress`), maps `Task`/`ValueTask` to `void`/`T` and `Memory` to `Span`, and rewrites `FooAsync(...)` calls to `Foo(...)`. Modifiers, including `override`/`virtual`, are kept. + Memory collection elements and iterator locals remain `Memory` / `ReadOnlyMemory`. - **Attribute individual methods, never the whole type.** A type-level attribute also generates the members that must not exist (see below), so you would need more `[SkipSyncVersion]` than `[CreateSyncVersion]`. @@ -179,6 +180,9 @@ referenced for every project via `Directory.Packages.props`). specified stream.") — it is emitted onto both copies. - Use `#if SYNC_ONLY` / `#if !SYNC_ONLY` only for a localised I/O idiom that genuinely differs; if it would cover more than a small part of the method, keep two hand-written methods instead. +- Do not define `SYNC_ONLY` as a compilation symbol. `ZSMGEN006` warns when dropping a + `CancellationToken` makes a loop endless; preserve the token only when the existing sync + signature has it, or retain a terminating sync implementation. - To see what was generated, build with `-p:EmitCompilerGeneratedFiles=true` and look in `obj///generated/Zomp.SyncMethodGenerator/`. A sync stack frame may therefore name a method that has no file in the repo — debug it via the async source. diff --git a/docs/SYNC_METHOD_GENERATION.md b/docs/SYNC_METHOD_GENERATION.md index 9bf9ca1b..3550c689 100644 --- a/docs/SYNC_METHOD_GENERATION.md +++ b/docs/SYNC_METHOD_GENERATION.md @@ -20,6 +20,30 @@ the verification workflow, and the record of what is blocked and why. ## Status +### Upstream compatibility + +The current reference is **2.0.56**, the latest upstream release as checked on October 1, 2026. +`GlobalPackageReference` implicitly keeps the generator private to the build; consumers do not need +a runtime reference. Continue using method-level attributes and the existing preservation options. + +Recent releases relevant to migrations: + +- [2.0.56](https://github.com/zompinc/sync-method-generator/releases/tag/v2.0.56) preserves + `Memory` / `ReadOnlyMemory` locals in generated iterators, rather than turning them into + spans that cannot survive a `yield` boundary. Do not assume every memory local becomes a span + when comparing generated iterator bodies. It also adds `ZSMGEN006` for cancellation-dependent + loops that become endless after synchronization. +- [2.0.53](https://github.com/zompinc/sync-method-generator/releases/tag/v2.0.53) fixes async LINQ + translations and static-form queryable extension calls, and preserves the line break after an + opening parenthesis. Inspect the selected synchronous overload when migrating query code. +- [2.0.41](https://github.com/zompinc/sync-method-generator/releases/tag/v2.0.41) carries source-file + `using` directives into generated files, including those needed by XML documentation `cref`s; + [2.0.42](https://github.com/zompinc/sync-method-generator/releases/tag/v2.0.42) preserves rewritten + argument-list spacing. `SYNC_ONLY` code is still copied verbatim, so keep its names fully qualified. + +An all-six-TFM Release rebuild with generated-source emission and targeted net10.0 tests for +streams, providers, archive detection, ZIP, and GZip passed with 2.0.56 during this review. + | Batch | Area | Status | | --- | --- | --- | | 0 | `Archives/IArchiveEntryExtensions` (5 methods) | done — `c303856c` | @@ -43,7 +67,8 @@ Proven on batch 1. Per file pair: 1. **Map the signature by hand** for each async method: drop the `Async` suffix; `Task`/`ValueTask`→ `void`, `Task`/`ValueTask`→`T`; drop `CancellationToken` and `IProgress` (unless `PreserveCancellationToken` / `PreserveProgress`); `Memory`→`Span`, - `ReadOnlyMemory`→`ReadOnlySpan`; modifiers (`public`, `override`, `virtual`, `static`, + `ReadOnlyMemory`→`ReadOnlySpan` (memory collection elements and iterator locals stay memory); + modifiers (`public`, `override`, `virtual`, `static`, `sealed`) copied verbatim. 2. **Attribute only when that mapped signature already exists by hand.** Generating a member that did not exist before is a behaviour change, not a deduplication — a generated `Read(Span)` @@ -314,6 +339,11 @@ Constraints: works in statements, parameter lists and argument lists; cannot nes (`ZSMGEN003`); contents are copied **verbatim**, so fully qualify names. `ZSMGEN004` tells you to use it when the async method awaits several operations at once. +`ZSMGEN006` warns when removing a `CancellationToken` makes a loop condition always true with no +other exit. With this repository's `TreatWarningsAsErrors`, it fails the build. Preserve the token +only if the existing sync signature includes it; otherwise provide a terminating sync implementation +in `SYNC_ONLY` or leave the pair hand-written. + **Budget: at most two `SYNC_ONLY` sites per method, and never more than ~15% of its lines.** Past that, two honest files beat one half-preprocessor file. @@ -442,7 +472,7 @@ one is ever active. Collisions that *are* possible (two async overloads differin `TreatWarningsAsErrors` is on. Analyzers skip `.g.cs`, but **compiler CS warnings do not**: `CS0162` unreachable (a `return`/`break` that vanishes from the sync copy), `CS0219`/`CS0168` unused local (a result only checked on the async side), `CS8602`/`CS8604` nullable (the generator emits -`#nullable enable` unconditionally — `OmitNullableDirective = true` is the escape hatch), +`#nullable enable` on C# 8 and above — `OmitNullableDirective = true` is the escape hatch), `CS0108`/`CS0114` hiding. Fix in the async source, or `#pragma warning disable`/`restore` there — it is copied into the generated file. `CS0111`/`CS0534` are the *desired* safety net for "did I delete the right thing".