From bbf9845a02e458698b2b2e8a21b06a317d8c097a Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 17:32:39 -0400 Subject: [PATCH 01/26] Bump library TFMs to net10.0/net11.0; mark AOT-compatible Drops the net8.0 TFM (EOL 2026-11-10, same week as .NET 11 GA). Consumers on .NET 8 continue to resolve to the netstandard2.0 target. Adds true on the modern TFMs so the Roslyn AOT analyzer runs at library build time. --- src/Directory.Build.props | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index da49490..d82b3d1 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,7 +3,7 @@ - netstandard2.0;net462;net8.0 + netstandard2.0;net462;net10.0;net11.0 Joseph Musser,Brian Buvinghausen Buvinghausen Solutions Copyright © $([System.DateTime]::UtcNow.ToString("yyyy")) Brian Buvinghausen @@ -20,6 +20,10 @@ ..\..\ + + true + + From 9930e8155e701daae64b7da9fb4953361d7ab68a Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 17:36:47 -0400 Subject: [PATCH 02/26] Drop redundant Microsoft.SourceLink.GitHub package reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since .NET SDK 8.0+, the Roslyn compiler emits source link metadata implicitly when ContinuousIntegrationBuild=true (already set on line 19). The explicit Microsoft.SourceLink.GitHub package is now a no-op. EmbedUntrackedSources and PublishRepositoryUrl remain — they still govern PDB content and pack-time URL embedding. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/Directory.Build.props | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index d82b3d1..8c82c6f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -25,7 +25,6 @@ - From b958267407a2e165c7953f1abe4118da10ab6487 Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 17:38:26 -0400 Subject: [PATCH 03/26] Add net11.0 to the test TFM matrix Existing test suite must pass on .NET 11 with default (compiler-generated) state machines before we layer Runtime Async on top in the next commit. --- test/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 7cf4525..e5e4e9c 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -4,7 +4,7 @@ - net10.0;net9.0;net8.0;net472 + net11.0;net10.0;net9.0;net8.0;net472 Exe false true From 1c7786a58756890cee05f31a7dbb831ba5656ba9 Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 17:40:08 -0400 Subject: [PATCH 04/26] Enable Runtime Async for net11.0 test target Compiles the test suite with runtime-async=on on net11.0 only, so every async test method exercises our awaiters via the .NET 11 runtime-managed async lowering path. Other TFMs keep the default compiler-generated state machines. --- test/TaskTupleAwaiter.Tests/TaskTupleAwaiter.Tests.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/TaskTupleAwaiter.Tests/TaskTupleAwaiter.Tests.csproj b/test/TaskTupleAwaiter.Tests/TaskTupleAwaiter.Tests.csproj index aa071fc..4449eb3 100644 --- a/test/TaskTupleAwaiter.Tests/TaskTupleAwaiter.Tests.csproj +++ b/test/TaskTupleAwaiter.Tests/TaskTupleAwaiter.Tests.csproj @@ -3,6 +3,9 @@ + + runtime-async=on + From c2b0418646a45acc71b659de20320c02f0115e6d Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 17:42:02 -0400 Subject: [PATCH 05/26] Add empty AOT smoke-test project PublishAot=true turns on the Roslyn AOT analyzer at build time; the project references the TaskTupleAwaiter library so any AOT-incompatible codegen would surface as an analyzer warning (= build error under TreatWarningsAsErrors=true). Program.cs lands in the next commit. --- .../TaskTupleAwaiter.AotSmokeTest.csproj | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj diff --git a/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj b/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj new file mode 100644 index 0000000..e93a585 --- /dev/null +++ b/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj @@ -0,0 +1,31 @@ + + + + Exe + net10.0;net11.0 + true + false + false + false + TaskTupleAwaiter.AotSmokeTest + + + + + + + + + + + + + + + From e59caaf18dd161812404a7244e97d92e4da2689a Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 17:51:27 -0400 Subject: [PATCH 06/26] Exercise all generator codepaths in the AOT smoke-test Program.cs awaits arity-1, arity-2 (bool and ConfigureAwaitOptions), arity-16, and the non-generic Task tuple. Each result is written to stdout to defeat linker trimming. Local build with PublishAot=true runs the Roslyn AOT analyzer over the library's full surface; no warnings emitted, confirming the source generator's output is AOT-clean. End-to-end NativeAOT publish on net10.0 + net11.0 deferred to CI (windows-latest with the standard VS Build Tools image). --- test/TaskTupleAwaiter.AotSmokeTest/Program.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/TaskTupleAwaiter.AotSmokeTest/Program.cs diff --git a/test/TaskTupleAwaiter.AotSmokeTest/Program.cs b/test/TaskTupleAwaiter.AotSmokeTest/Program.cs new file mode 100644 index 0000000..944546f --- /dev/null +++ b/test/TaskTupleAwaiter.AotSmokeTest/Program.cs @@ -0,0 +1,46 @@ +using System.Threading.Tasks; + +// --- typed arity-1: ValueTuple> --- +// The arity-1 GetAwaiter delegates directly to the inner task's TaskAwaiter. +{ + var tuple = ValueTuple.Create(Task.FromResult(1)); + var a = await tuple; + Console.WriteLine($"arity-1 typed: {a}"); +} + +// --- typed arity-2 with ConfigureAwait(bool) --- +{ + var (b1, b2) = await (Task.FromResult(2), Task.FromResult("two")).ConfigureAwait(false); + Console.WriteLine($"arity-2 typed CA(false): {b1}, {b2}"); +} + +// --- typed arity-2 with ConfigureAwaitOptions --- +{ + var (c1, c2) = await (Task.FromResult(3), Task.FromResult("three")) + .ConfigureAwait(ConfigureAwaitOptions.None); + Console.WriteLine($"arity-2 typed CA(options): {c1}, {c2}"); +} + +// --- typed arity-16 (upper boundary of the generator's struct emission loop) --- +{ + var (d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16) = await ( + Task.FromResult(1), Task.FromResult(2), Task.FromResult(3), Task.FromResult(4), + Task.FromResult(5), Task.FromResult(6), Task.FromResult(7), Task.FromResult(8), + Task.FromResult(9), Task.FromResult(10), Task.FromResult(11), Task.FromResult(12), + Task.FromResult(13), Task.FromResult(14), Task.FromResult(15), Task.FromResult(16)); + Console.WriteLine($"arity-16 typed: {d1}..{d16} (sum check: {d1 + d2 + d3 + d4 + d5 + d6 + d7 + d8 + d9 + d10 + d11 + d12 + d13 + d14 + d15 + d16})"); +} + +// --- non-generic Task tuple arity-2 --- +{ + await (Task.CompletedTask, Task.CompletedTask); + Console.WriteLine("non-generic arity-2: ok"); +} + +// --- non-generic Task tuple arity-2 with ConfigureAwaitOptions --- +{ + await (Task.CompletedTask, Task.CompletedTask).ConfigureAwait(ConfigureAwaitOptions.None); + Console.WriteLine("non-generic arity-2 CA(options): ok"); +} + +Console.WriteLine("AOT smoke-test completed."); From 5c3fd47fd95f14800e02f0ba507aa420a7b3983e Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 17:53:43 -0400 Subject: [PATCH 07/26] CI: install .NET 11 SDK and run AOT smoke-test publish setup-dotnet now installs the .NET 11 SDK alongside 8/9/10 (the older SDKs remain because the test project still targets net8.0 and net9.0). Two new steps publish the AOT smoke-test on net10.0 and net11.0; either failing fails the PR. --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 840862c..08779c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ jobs: 8.0.* 9.0.* 10.0.* + 11.0.* env: DOTNET_NOLOGO: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1 @@ -25,3 +26,7 @@ jobs: run: dotnet build --no-restore - name: Test run: dotnet test --no-build -v n /p:CollectCoverage=true /p:CoverletOutput='../../' /p:CoverletOutputFormat=opencover /p:Threshold=91 /p:SkipAutoProps=true /p:UseSourceLink=true + - name: AOT smoke-test (net10.0) + run: dotnet publish test/TaskTupleAwaiter.AotSmokeTest -c Release -f net10.0 + - name: AOT smoke-test (net11.0) + run: dotnet publish test/TaskTupleAwaiter.AotSmokeTest -c Release -f net11.0 From 3ae6d97239c5be4141f502404e4b438c1985c65c Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 17:55:00 -0400 Subject: [PATCH 08/26] Release workflow: pack with the .NET 11 SDK dotnet pack on a multi-TFM project produces one package containing all targets; only the highest SDK needs to be installed on the runner. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ad06177..ccdfa2a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v5 with: - dotnet-version: 10.0.* + dotnet-version: 11.0.* env: DOTNET_NOLOGO: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1 From 10c2cc39c5ddf86ab92c4a271c323eb331ff93c2 Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 17:55:46 -0400 Subject: [PATCH 09/26] README: document .NET 10 / 11 / AOT / Runtime Async support Updates the compatibility table to list .NET 10 and .NET 11 as the modern targets, adds Features bullets for NativeAOT compatibility and Runtime Async verification, and notes the netstandard2.0 fallback for .NET 8 / .NET 9 consumers. --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8fdb3bd..5ab5e89 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,9 @@ var (user, orders) = await (GetUserAsync(id), GetOrdersAsync(id)); - **`ConfigureAwait` support** — works with `ConfigureAwait(false)` and .NET 8+ `ConfigureAwaitOptions` - **Non-generic `Task` support** — await tuples of `Task` (not just `Task`) when you don't need return values - **Zero dependencies** — a single file, no external packages (except `System.ValueTuple` on .NET Framework 4.6.2) -- **Broad compatibility** — targets .NET Standard 2.0, .NET Framework 4.6.2, and .NET 8+ +- **Broad compatibility** — targets .NET Standard 2.0, .NET Framework 4.6.2, .NET 10, and .NET 11 +- **NativeAOT ready** — on .NET 10 and .NET 11 the package is marked `true`; a CI smoke-test publishes a downstream NativeAOT binary on every commit +- **.NET 11 Runtime Async compatible** — verified by running the full test suite with `runtime-async=on` on the `net11.0` target ## Installation @@ -61,7 +63,7 @@ var (policy, preferences) = await ( ).ConfigureAwait(false); ``` -### ConfigureAwaitOptions (.NET 8+) +### ConfigureAwaitOptions (.NET 8+, also works under .NET 11 Runtime Async) ```csharp var (user, settings) = await ( @@ -98,7 +100,9 @@ TaskTupleAwaiter provides extension methods on `ValueTuple, ..., Task Consumers on .NET 8 or .NET 9 continue to work via the `netstandard2.0` target; the package still installs and behaves identically — just without the modern-TFM-only AOT analyzer guarantees. ## Credits From 08febcd1b6448d5aa597b0b785b190e006155298 Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 17:57:18 -0400 Subject: [PATCH 10/26] CLAUDE.md: correct generator-only layout and add net10/11+AOT context Removes references to the deleted hand-authored TaskTupleExtensions.cs, documents the new AotSmokeTest project, and lists the AOT + Runtime Async TFM coverage so future sessions inherit the correct mental model. --- CLAUDE.md | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 82078d1..752ada0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,26 +2,30 @@ ## Project Overview -TaskTupleAwaiter provides extension methods that allow you to `await` a `ValueTuple` of `Task` (or non-generic `Task`) instances and destructure the results in a single line. The `source_generator` branch replaces the hand-authored `TaskTupleExtensions.cs` (up to arity 16) with a Roslyn incremental source generator. +TaskTupleAwaiter provides extension methods that allow you to `await` a `ValueTuple` of `Task` (or non-generic `Task`) instances and destructure the results in a single line. The implementation is emitted by a Roslyn incremental source generator (`src/TaskTupleAwaiter.Generator`) into the consumer's compilation under `namespace System.Threading.Tasks`, producing typed and non-generic overloads for arities 1–16. The library package itself ships no extension-method source — only the generator and the per-TFM build targets. ## Repository Layout ``` TaskTupleAwaiter/ ├── src/ -│ ├── TaskTupleAwaiter/ # Main library (netstandard2.0) -│ │ └── TaskTupleExtensions.cs # Hand-authored fallback / reference implementation +│ ├── TaskTupleAwaiter/ # Main library shell (netstandard2.0, net462, net10.0, net11.0) +│ │ # No .cs sources — generator-only package. │ └── TaskTupleAwaiter.Generator/ # Roslyn incremental source generator (netstandard2.0) │ └── TaskTupleExtensionsGenerator.cs ├── test/ -│ └── TaskTupleAwaiter.Tests/ # xUnit test project -│ ├── TaskTupleAwaiterTests.cs -│ ├── BehaviorComparisonTests.cs -│ ├── Adapters/ -│ │ └── AwaiterAdapter.cs -│ ├── DummyException.cs -│ ├── On.cs -│ └── SpySynchronizationContext.cs +│ ├── TaskTupleAwaiter.Tests/ # xUnit v3 test project +│ │ ├── TaskTupleAwaiterTests.cs +│ │ ├── BehaviorComparisonTests.cs +│ │ ├── Adapters/ +│ │ │ └── AwaiterAdapter.cs +│ │ ├── DummyException.cs +│ │ ├── On.cs +│ │ └── SpySynchronizationContext.cs +│ └── TaskTupleAwaiter.AotSmokeTest/ # NativeAOT downstream-consumer smoke-test (net10.0, net11.0) +│ ├── TaskTupleAwaiter.AotSmokeTest.csproj +│ └── Program.cs +├── docs/superpowers/ # Specs and implementation plans ├── README.md ├── LICENSE.txt └── CLAUDE.md # This file @@ -32,7 +36,10 @@ TaskTupleAwaiter/ | Concern | Choice | |---|---| | Language | C# 14.0 | -| Library / Generator target | .NET Standard 2.0 | +| Library TFMs | netstandard2.0, net462, net10.0, net11.0 | +| Generator target | netstandard2.0 (Roslyn analyzer requirement) | +| AOT-compatible TFMs | net10.0, net11.0 (`true`) | +| Runtime Async verified on | net11.0 (`runtime-async=on` in the test project) | | Generator framework | Roslyn `IIncrementalGenerator` | | Test framework | xUnit v3 | | Assertion library | Shouldly | From 4bf7aa7b6ce019e9f01d611e1dee9ecdac37152b Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 18:01:50 -0400 Subject: [PATCH 11/26] Post-review hygiene: explicit SelfContained + --no-restore on AOT publish Final whole-branch review flagged two Important items: 1. SelfContained=true was in the plan's smoke-test csproj but got dropped when I dispatched Task 4. PublishAot=true implies it, but stating it explicitly closes the spec/impl paper-trail gap. 2. The CI Test step uses --no-build because Build ran with --no-restore. The two new AOT publish steps re-restored unnecessarily; --no-restore makes them consistent with the rest of the workflow. --- .github/workflows/ci.yml | 4 ++-- .../TaskTupleAwaiter.AotSmokeTest.csproj | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08779c8..29d47cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,6 @@ jobs: - name: Test run: dotnet test --no-build -v n /p:CollectCoverage=true /p:CoverletOutput='../../' /p:CoverletOutputFormat=opencover /p:Threshold=91 /p:SkipAutoProps=true /p:UseSourceLink=true - name: AOT smoke-test (net10.0) - run: dotnet publish test/TaskTupleAwaiter.AotSmokeTest -c Release -f net10.0 + run: dotnet publish test/TaskTupleAwaiter.AotSmokeTest -c Release -f net10.0 --no-restore - name: AOT smoke-test (net11.0) - run: dotnet publish test/TaskTupleAwaiter.AotSmokeTest -c Release -f net11.0 + run: dotnet publish test/TaskTupleAwaiter.AotSmokeTest -c Release -f net11.0 --no-restore diff --git a/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj b/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj index e93a585..5607743 100644 --- a/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj +++ b/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj @@ -4,6 +4,7 @@ Exe net10.0;net11.0 true + true false false false From d5d35228db63e0e478d48f9df199a74e1c5d0b3a Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 20:08:21 -0400 Subject: [PATCH 12/26] Adapt editorconfig to .NET 11 latest-Recommended ruleset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .NET 11 SDK elevates several IDE rules into latest-Recommended that were previously silent. Rather than rewrite working code to match the new defaults, declare the codebase's chosen style as canonical in .editorconfig. Global rules added (apply everywhere): - dotnet_style_require_accessibility_modifiers = omit_if_default:error (matches the existing 'Remove default accessibility modifiers' pass in fd8baf0) - csharp_style_namespace_declarations = file_scoped:error (file-scoped namespaces are already the codebase pattern) - csharp_prefer_braces = false:silent (allow single-statement bodies without braces, e.g. for/if in the generator) Path-scoped silences: - src/TaskTupleAwaiter.Generator/**.cs: silence CS1591 (missing XML doc) because the public surface exists for [Generator] discovery, not external consumption - test/**.cs: silence IDE0005/IDE0040/IDE0044/IDE0046/IDE0051/IDE0055 (xUnit reflection-discovered methods look unused; adapter-pattern fields aren't readonly by intent; not worth chasing down across the test suite) Also drops the explicit GenerateDocumentationFile=false override from the generator csproj — required for IDE0005 to function under .NET 11 (https://github.com/dotnet/roslyn/issues/41640). Verified: full solution builds clean across all 9 TFM permutations; 2431 tests pass on net472/net8.0/net9.0/net10.0/net11.0 (net11.0 under runtime-async=on). AOT smoke-test managed compile + analyzer pass clean; native link still deferred to CI (Windows C++ toolchain missing locally). --- .editorconfig | 32 +++++++++++++++++++ .../TaskTupleAwaiter.Generator.csproj | 1 - 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 172eda6..2347c7f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -23,6 +23,9 @@ indent_style = tab # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true +# Omit accessibility modifiers when they match the language default (e.g. private on class members) +dotnet_style_require_accessibility_modifiers = omit_if_default:error + # Avoid "this." and "Me." if not necessary dotnet_style_qualification_for_field = false:error dotnet_style_qualification_for_property = false:error @@ -42,6 +45,12 @@ dotnet_style_explicit_tuple_names = true:error # CSharp code style settings: [*.cs] +# Prefer file-scoped namespace declarations (single `namespace Foo;`) +csharp_style_namespace_declarations = file_scoped:error + +# Allow single-statement bodies without braces (e.g. `if (x) F();`, `for (...) F();`) +csharp_prefer_braces = false:silent + # Prefer "var" everywhere csharp_style_var_for_built_in_types = true:error csharp_style_var_when_type_is_apparent = true:error @@ -71,3 +80,26 @@ csharp_new_line_before_catch = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true + +# Roslyn source generator project — types are public for [Generator] discovery, not for +# consumption (IsPackable=false). Doc generation is on so IDE0005 (unused usings) can run +# under .NET 11+; CS1591 (missing doc comment) is noise on this internal surface. +[src/TaskTupleAwaiter.Generator/**.cs] +dotnet_diagnostic.CS1591.severity = none + +# Test project: the codebase pre-dates several latest-Recommended rules that .NET 11 +# now elevates to warnings (and TreatWarningsAsErrors makes them build errors). Keep +# strict enforcement on the shipped library; relax on tests where the patterns are +# either intentional (xUnit reflection-discovered methods) or low-value to chase down. +# - IDE0040: explicit modifiers in adapter helpers matching the default +# - IDE0044: adapter pattern fields that are effectively immutable but not declared so +# - IDE0046: if/return preferred over ternary for clarity in test setup +# - IDE0051: xUnit discovers private [Fact]/[Theory] methods via reflection +# - IDE0055: legacy formatting in a few test files +[test/**.cs] +dotnet_diagnostic.IDE0005.severity = silent +dotnet_diagnostic.IDE0040.severity = silent +dotnet_diagnostic.IDE0044.severity = silent +dotnet_diagnostic.IDE0046.severity = silent +dotnet_diagnostic.IDE0051.severity = silent +dotnet_diagnostic.IDE0055.severity = silent diff --git a/src/TaskTupleAwaiter.Generator/TaskTupleAwaiter.Generator.csproj b/src/TaskTupleAwaiter.Generator/TaskTupleAwaiter.Generator.csproj index 1a0f05b..a220e99 100644 --- a/src/TaskTupleAwaiter.Generator/TaskTupleAwaiter.Generator.csproj +++ b/src/TaskTupleAwaiter.Generator/TaskTupleAwaiter.Generator.csproj @@ -5,7 +5,6 @@ true true false - false From 4b1df4dda10cdf03c69d68b184f20392019a55a8 Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 20:13:03 -0400 Subject: [PATCH 13/26] Speed up GHA --- .github/workflows/ci.yml | 2 -- test/Directory.Build.props | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29d47cc..8584e27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,8 +13,6 @@ jobs: uses: actions/setup-dotnet@v5 with: dotnet-version: | - 8.0.* - 9.0.* 10.0.* 11.0.* env: diff --git a/test/Directory.Build.props b/test/Directory.Build.props index e5e4e9c..9d2ade3 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -3,8 +3,7 @@ - - net11.0;net10.0;net9.0;net8.0;net472 + net11.0;net10.0;net472 Exe false true From d642c3e3a8c890fd375037ef70f891189d085831 Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 20:18:06 -0400 Subject: [PATCH 14/26] Add AotSmokeTest project to solution file TaskTupleAwaiter.slnx is what 'dotnet restore' (and build/test) auto-discovers when invoked at the repo root. Without listing the AotSmokeTest project in the solution, restore skipped it, so the AOT publish step in CI failed with NETSDK1004 ('project.assets.json not found') when invoked with --no-restore. --- TaskTupleAwaiter.slnx | 1 + 1 file changed, 1 insertion(+) diff --git a/TaskTupleAwaiter.slnx b/TaskTupleAwaiter.slnx index b741ce6..62f649d 100644 --- a/TaskTupleAwaiter.slnx +++ b/TaskTupleAwaiter.slnx @@ -23,4 +23,5 @@ + \ No newline at end of file From 714ec0df3ca043bef7764203774d8bbd910de8a9 Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 20:31:24 -0400 Subject: [PATCH 15/26] Light up C# syntax highlighting in generator raw-string literals Routes the generator's 15 sb.AppendLine(...) calls through a thin wrapper sb.AppendCSharp(...) whose parameter carries [StringSyntax("C#")]. Visual Studio 2022 and JetBrains Rider read the attribute and apply C# classification + IntelliSense inside the raw-string content at every call site, making the emitted template far easier to read and edit. Includes a single-file polyfill of System.Diagnostics.CodeAnalysis.StringSyntaxAttribute (added to the BCL in .NET 7) because the generator targets netstandard2.0. Roslyn's IDE classifiers match the attribute by namespace + type name, not by assembly identity, so an internal declaration in the project is enough. Generator output is byte-identical (AppendCSharp delegates to AppendLine); the full test suite still passes including the net11.0 target under runtime-async=on. --- src/TaskTupleAwaiter.Generator/CSharpEmit.cs | 13 ++++++++ .../StringSyntaxAttribute.cs | 13 ++++++++ .../TaskTupleExtensionsGenerator.cs | 30 +++++++++---------- 3 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 src/TaskTupleAwaiter.Generator/CSharpEmit.cs create mode 100644 src/TaskTupleAwaiter.Generator/StringSyntaxAttribute.cs diff --git a/src/TaskTupleAwaiter.Generator/CSharpEmit.cs b/src/TaskTupleAwaiter.Generator/CSharpEmit.cs new file mode 100644 index 0000000..3203f3b --- /dev/null +++ b/src/TaskTupleAwaiter.Generator/CSharpEmit.cs @@ -0,0 +1,13 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text; + +namespace TaskTupleAwaiter.Generator; + +static class CSharpEmit +{ + // Identical to StringBuilder.AppendLine at runtime; the [StringSyntax("C#")] annotation + // is what Visual Studio 2022 and JetBrains Rider use to syntax-highlight the raw-string + // content at each call site as C# instead of as opaque text. + public static StringBuilder AppendCSharp(this StringBuilder sb, [StringSyntax("C#")] string code) => + sb.AppendLine(code); +} diff --git a/src/TaskTupleAwaiter.Generator/StringSyntaxAttribute.cs b/src/TaskTupleAwaiter.Generator/StringSyntaxAttribute.cs new file mode 100644 index 0000000..78a3ef4 --- /dev/null +++ b/src/TaskTupleAwaiter.Generator/StringSyntaxAttribute.cs @@ -0,0 +1,13 @@ +// Polyfill for System.Diagnostics.CodeAnalysis.StringSyntaxAttribute (added in .NET 7). +// The generator project targets netstandard2.0, so the BCL definition isn't available. +// Roslyn's IDE classifiers recognise the attribute by namespace + type name (not assembly +// identity), so an internal declaration here drives the embedded-language hint in VS / Rider. +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Diagnostics.CodeAnalysis; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false)] +sealed class StringSyntaxAttribute(string syntax) : Attribute +{ + public string Syntax { get; } = syntax; +} diff --git a/src/TaskTupleAwaiter.Generator/TaskTupleExtensionsGenerator.cs b/src/TaskTupleAwaiter.Generator/TaskTupleExtensionsGenerator.cs index 4e287d7..05a8b6d 100644 --- a/src/TaskTupleAwaiter.Generator/TaskTupleExtensionsGenerator.cs +++ b/src/TaskTupleAwaiter.Generator/TaskTupleExtensionsGenerator.cs @@ -28,7 +28,7 @@ static string GenerateSource(bool hasAwaitOptions) { StringBuilder sb = new(); - sb.AppendLine( + sb.AppendCSharp( """ // using System.Runtime.CompilerServices; @@ -55,7 +55,7 @@ public static class TaskTupleExtensions AppendTypedArity(sb, arity, hasAwaitOptions); AppendNonGenericSection(sb, hasAwaitOptions); - sb.AppendLine("}"); + sb.AppendCSharp("}"); return sb.ToString(); } @@ -63,7 +63,7 @@ public static class TaskTupleExtensions static void AppendTypedArity1(StringBuilder sb, bool hasAwaitOptions) { - sb.AppendLine( + sb.AppendCSharp( """ #region (Task) @@ -78,7 +78,7 @@ public static ConfiguredTaskAwaitable ConfigureAwait(this ValueTupleThis type and its members are intended for use by the compiler. public static ConfiguredTaskAwaitable ConfigureAwait(this ValueTuple> tasks, ConfigureAwaitOptions options) => @@ -86,7 +86,7 @@ public static ConfiguredTaskAwaitable ConfigureAwait(this ValueTuple..Task) @@ -117,7 +117,7 @@ static void AppendTypedArity(StringBuilder sb, int arity, bool hasAwaitOptions) """); if (hasAwaitOptions) - sb.AppendLine( + sb.AppendCSharp( $""" /// This type and its members are intended for use by the compiler. public static TupleConfiguredTaskAwaitable<{tp}> ConfigureAwait<{tp}>(this {tupleType} tasks, ConfigureAwaitOptions options) => @@ -129,14 +129,14 @@ static void AppendTypedArity(StringBuilder sb, int arity, bool hasAwaitOptions) AppendTupleConfiguredTaskAwaitableStruct(sb, arity, tp, tupleType, optionsType); - sb.AppendLine( + sb.AppendCSharp( """ #endregion """); } - static void AppendTupleTaskAwaiterStruct(StringBuilder sb, int arity, string tp, string tupleType) => sb.AppendLine( + static void AppendTupleTaskAwaiterStruct(StringBuilder sb, int arity, string tp, string tupleType) => sb.AppendCSharp( $$""" /// This type and its members are intended for use by the compiler. public readonly record struct TupleTaskAwaiter<{{tp}}> : ICriticalNotifyCompletion @@ -174,7 +174,7 @@ public void UnsafeOnCompleted(Action continuation) => """); static void AppendTupleConfiguredTaskAwaitableStruct(StringBuilder sb, int arity, string tp, - string tupleType, string optionsType) => sb.AppendLine( + string tupleType, string optionsType) => sb.AppendCSharp( $$""" /// This type and its members are intended for use by the compiler. public readonly record struct TupleConfiguredTaskAwaitable<{{tp}}> @@ -232,7 +232,7 @@ public void UnsafeOnCompleted(Action continuation) => static void AppendNonGenericSection(StringBuilder sb, bool hasAwaitOptions) { - sb.AppendLine( + sb.AppendCSharp( """ #region Task @@ -247,7 +247,7 @@ public static ConfiguredTaskAwaitable ConfigureAwait(this ValueTuple tasks """); if (hasAwaitOptions) - sb.AppendLine( + sb.AppendCSharp( """ /// This type and its members are intended for use by the compiler. public static ConfiguredTaskAwaitable ConfigureAwait(this ValueTuple tasks, ConfigureAwaitOptions options) => @@ -260,7 +260,7 @@ public static ConfiguredTaskAwaitable ConfigureAwait(this ValueTuple tasks AppendNonGenericArity(sb, arity, hasAwaitOptions); } - sb.AppendLine( + sb.AppendCSharp( """ #endregion @@ -271,7 +271,7 @@ static void AppendNonGenericArity(StringBuilder sb, int arity, bool hasAwaitOpti { var tupleType = NonGenericTaskTupleType(arity); - sb.AppendLine( + sb.AppendCSharp( $""" /// This type and its members are intended for use by the compiler. public static TaskAwaiter GetAwaiter(this {tupleType} tasks) => @@ -285,7 +285,7 @@ public static ConfiguredTaskAwaitable ConfigureAwait(this {tupleType} tasks, boo if (!hasAwaitOptions) return; - sb.AppendLine( + sb.AppendCSharp( $""" /// This type and its members are intended for use by the compiler. public static ConfiguredTaskAwaitable ConfigureAwait(this {tupleType} tasks, ConfigureAwaitOptions options) => From bf0078738d9227e6c110a1162998db5004134fc1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 00:48:25 +0000 Subject: [PATCH 16/26] Restore net8/net9 test matrix coverage in tests and CI setup Agent-Logs-Url: https://github.com/buvinghausen/TaskTupleAwaiter/sessions/ab9a3590-b1e5-4f8f-849a-7a015000ac90 Co-authored-by: buvinghausen <1130210+buvinghausen@users.noreply.github.com> --- .github/workflows/ci.yml | 2 ++ test/Directory.Build.props | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8584e27..29d47cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,8 @@ jobs: uses: actions/setup-dotnet@v5 with: dotnet-version: | + 8.0.* + 9.0.* 10.0.* 11.0.* env: diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 9d2ade3..de75a6a 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -3,7 +3,7 @@ - net11.0;net10.0;net472 + net11.0;net10.0;net9.0;net8.0;net472 Exe false true From 8c94654373411b1b40506be44f6ba8398354d189 Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 20:55:41 -0400 Subject: [PATCH 17/26] Dump it back out to .net8 --- src/Directory.Build.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 8c82c6f..29ddbfa 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,7 +3,7 @@ - netstandard2.0;net462;net10.0;net11.0 + netstandard2.0;net462;net8.0;net11.0 Joseph Musser,Brian Buvinghausen Buvinghausen Solutions Copyright © $([System.DateTime]::UtcNow.ToString("yyyy")) Brian Buvinghausen @@ -20,7 +20,7 @@ ..\..\ - + true From 184b10c0df8525513a2ed0b6c07dd4d75bd5eaec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 01:18:17 +0000 Subject: [PATCH 18/26] Use version-based MSBuild condition for IsAotCompatible Agent-Logs-Url: https://github.com/buvinghausen/TaskTupleAwaiter/sessions/f76275ee-0a97-4581-a032-b628ba129cbd Co-authored-by: jnm2 <8040367+jnm2@users.noreply.github.com> --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 29ddbfa..e2ade42 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -20,7 +20,7 @@ ..\..\ - + true From 027125137dd94b505f216a2309ef12fb18adc10d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 01:28:01 +0000 Subject: [PATCH 19/26] Apply latest review feedback on TFM condition and README Agent-Logs-Url: https://github.com/buvinghausen/TaskTupleAwaiter/sessions/b28c1192-cd01-4b75-b5ca-f399b233b784 Co-authored-by: jnm2 <8040367+jnm2@users.noreply.github.com> --- README.md | 6 ++---- src/Directory.Build.props | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5ab5e89..2dbfd98 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ var (policy, preferences) = await ( ).ConfigureAwait(false); ``` -### ConfigureAwaitOptions (.NET 8+, also works under .NET 11 Runtime Async) +### ConfigureAwaitOptions (.NET 8+) ```csharp var (user, settings) = await ( @@ -100,9 +100,7 @@ TaskTupleAwaiter provides extension methods on `ValueTuple, ..., Task Consumers on .NET 8 or .NET 9 continue to work via the `netstandard2.0` target; the package still installs and behaves identically — just without the modern-TFM-only AOT analyzer guarantees. +| .NET | 8.0+ | ## Credits diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e2ade42..0e1cfb9 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -20,7 +20,7 @@ ..\..\ - + true From a5aa36137787dd927685ed5906389406447233c6 Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 21:29:02 -0400 Subject: [PATCH 20/26] Fix docs --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5ab5e89..4386d00 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ var (user, orders) = await (GetUserAsync(id), GetOrdersAsync(id)); - **`ConfigureAwait` support** — works with `ConfigureAwait(false)` and .NET 8+ `ConfigureAwaitOptions` - **Non-generic `Task` support** — await tuples of `Task` (not just `Task`) when you don't need return values - **Zero dependencies** — a single file, no external packages (except `System.ValueTuple` on .NET Framework 4.6.2) -- **Broad compatibility** — targets .NET Standard 2.0, .NET Framework 4.6.2, .NET 10, and .NET 11 +- **Broad compatibility** — targets .NET Standard 2.0, .NET Framework 4.6.2, .NET 8, and .NET 11 - **NativeAOT ready** — on .NET 10 and .NET 11 the package is marked `true`; a CI smoke-test publishes a downstream NativeAOT binary on every commit - **.NET 11 Runtime Async compatible** — verified by running the full test suite with `runtime-async=on` on the `net11.0` target @@ -63,7 +63,7 @@ var (policy, preferences) = await ( ).ConfigureAwait(false); ``` -### ConfigureAwaitOptions (.NET 8+, also works under .NET 11 Runtime Async) +### ConfigureAwaitOptions (.NET 8+) ```csharp var (user, settings) = await ( @@ -100,9 +100,7 @@ TaskTupleAwaiter provides extension methods on `ValueTuple, ..., Task Consumers on .NET 8 or .NET 9 continue to work via the `netstandard2.0` target; the package still installs and behaves identically — just without the modern-TFM-only AOT analyzer guarantees. +| .NET | 8.0, 11.0 | ## Credits From 5fc6699360748d5ad765ff77cedbf56c0dafb20b Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 21:30:58 -0400 Subject: [PATCH 21/26] AOT came w/ .NET 7 --- src/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 0e1cfb9..ac12cb2 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -20,7 +20,7 @@ ..\..\ - + true From 529cda4a54fb3575d78ddfce49ce78b546acdb84 Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 21:32:47 -0400 Subject: [PATCH 22/26] Smoke 8 --- src/Directory.Build.props | 2 +- .../TaskTupleAwaiter.AotSmokeTest.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index ac12cb2..e1dd1b7 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -20,7 +20,7 @@ ..\..\ - + true diff --git a/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj b/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj index 5607743..24a47a1 100644 --- a/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj +++ b/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj @@ -2,7 +2,7 @@ Exe - net10.0;net11.0 + net8.0;net11.0 true true false From 932328f99264a8f07625d8b3926b6e1e14f43441 Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 21:37:36 -0400 Subject: [PATCH 23/26] FIX CI --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29d47cc..fe6264a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: run: dotnet build --no-restore - name: Test run: dotnet test --no-build -v n /p:CollectCoverage=true /p:CoverletOutput='../../' /p:CoverletOutputFormat=opencover /p:Threshold=91 /p:SkipAutoProps=true /p:UseSourceLink=true - - name: AOT smoke-test (net10.0) - run: dotnet publish test/TaskTupleAwaiter.AotSmokeTest -c Release -f net10.0 --no-restore + - name: AOT smoke-test (net8.0) + run: dotnet publish test/TaskTupleAwaiter.AotSmokeTest -c Release -f net8.0 --no-restore - name: AOT smoke-test (net11.0) run: dotnet publish test/TaskTupleAwaiter.AotSmokeTest -c Release -f net11.0 --no-restore From 87136a2311440c673f727fa69ab3aa18cd716d6d Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Tue, 12 May 2026 21:39:40 -0400 Subject: [PATCH 24/26] last fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4386d00..f458f7d 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ TaskTupleAwaiter provides extension methods on `ValueTuple, ..., Task Date: Wed, 13 May 2026 19:27:52 +0000 Subject: [PATCH 25/26] Address remaining review threads on TFMs, AOT condition, and docs Agent-Logs-Url: https://github.com/buvinghausen/TaskTupleAwaiter/sessions/5f5d1708-7b9e-4c22-88b6-46bf49e9b056 Co-authored-by: jnm2 <8040367+jnm2@users.noreply.github.com> --- CLAUDE.md | 13 ++++++------- README.md | 5 ++--- src/Directory.Build.props | 7 ++----- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 752ada0..3a1b6b5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,15 +2,15 @@ ## Project Overview -TaskTupleAwaiter provides extension methods that allow you to `await` a `ValueTuple` of `Task` (or non-generic `Task`) instances and destructure the results in a single line. The implementation is emitted by a Roslyn incremental source generator (`src/TaskTupleAwaiter.Generator`) into the consumer's compilation under `namespace System.Threading.Tasks`, producing typed and non-generic overloads for arities 1–16. The library package itself ships no extension-method source — only the generator and the per-TFM build targets. +TaskTupleAwaiter provides extension methods that allow you to `await` a `ValueTuple` of `Task` (or non-generic `Task`) instances and destructure the results in a single line. In this repository, a Roslyn incremental source generator (`src/TaskTupleAwaiter.Generator`) generates the extension-method source during library build under `namespace System.Threading.Tasks`, and that generated code is compiled into `TaskTupleAwaiter.dll` for each target framework. Consumers install and reference the compiled package binaries; the generator is a private build-time implementation detail. ## Repository Layout ``` TaskTupleAwaiter/ ├── src/ -│ ├── TaskTupleAwaiter/ # Main library shell (netstandard2.0, net462, net10.0, net11.0) -│ │ # No .cs sources — generator-only package. +│ ├── TaskTupleAwaiter/ # Main library shell (netstandard2.0, net462, net8.0) +│ │ # No hand-authored .cs sources — code is generated at build and compiled into the library. │ └── TaskTupleAwaiter.Generator/ # Roslyn incremental source generator (netstandard2.0) │ └── TaskTupleExtensionsGenerator.cs ├── test/ @@ -22,7 +22,7 @@ TaskTupleAwaiter/ │ │ ├── DummyException.cs │ │ ├── On.cs │ │ └── SpySynchronizationContext.cs -│ └── TaskTupleAwaiter.AotSmokeTest/ # NativeAOT downstream-consumer smoke-test (net10.0, net11.0) +│ └── TaskTupleAwaiter.AotSmokeTest/ # NativeAOT downstream-consumer smoke-test (net8.0, net11.0) │ ├── TaskTupleAwaiter.AotSmokeTest.csproj │ └── Program.cs ├── docs/superpowers/ # Specs and implementation plans @@ -36,10 +36,9 @@ TaskTupleAwaiter/ | Concern | Choice | |---|---| | Language | C# 14.0 | -| Library TFMs | netstandard2.0, net462, net10.0, net11.0 | +| Library TFMs | netstandard2.0, net462, net8.0 | | Generator target | netstandard2.0 (Roslyn analyzer requirement) | -| AOT-compatible TFMs | net10.0, net11.0 (`true`) | -| Runtime Async verified on | net11.0 (`runtime-async=on` in the test project) | +| AOT-compatible TFMs | net8.0+ (`true` via `IsTargetFrameworkCompatible`) | | Generator framework | Roslyn `IIncrementalGenerator` | | Test framework | xUnit v3 | | Assertion library | Shouldly | diff --git a/README.md b/README.md index f458f7d..20f7296 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,8 @@ var (user, orders) = await (GetUserAsync(id), GetOrdersAsync(id)); - **`ConfigureAwait` support** — works with `ConfigureAwait(false)` and .NET 8+ `ConfigureAwaitOptions` - **Non-generic `Task` support** — await tuples of `Task` (not just `Task`) when you don't need return values - **Zero dependencies** — a single file, no external packages (except `System.ValueTuple` on .NET Framework 4.6.2) -- **Broad compatibility** — targets .NET Standard 2.0, .NET Framework 4.6.2, .NET 8, and .NET 11 -- **NativeAOT ready** — on .NET 10 and .NET 11 the package is marked `true`; a CI smoke-test publishes a downstream NativeAOT binary on every commit -- **.NET 11 Runtime Async compatible** — verified by running the full test suite with `runtime-async=on` on the `net11.0` target +- **Broad compatibility** — targets .NET Standard 2.0, .NET Framework 4.6.2, and .NET 8+ +- **NativeAOT ready** — the package sets `true` for .NET 8+ targets, and CI publishes downstream NativeAOT smoke tests ## Installation diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e1dd1b7..2ebe94d 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,7 +3,7 @@ - netstandard2.0;net462;net8.0;net11.0 + netstandard2.0;net462;net8.0 Joseph Musser,Brian Buvinghausen Buvinghausen Solutions Copyright © $([System.DateTime]::UtcNow.ToString("yyyy")) Brian Buvinghausen @@ -18,10 +18,7 @@ snupkg true ..\..\ - - - - true + true From 17592653ed8fa395601a43636b929aae5659730b Mon Sep 17 00:00:00 2001 From: Brian Buvinghausen Date: Wed, 13 May 2026 15:39:08 -0400 Subject: [PATCH 26/26] Remove editorconfig exclusions --- .editorconfig | 23 --------- Directory.Build.props | 5 +- src/Directory.Build.props | 47 +++++++++---------- .../TaskTupleAwaiter.Generator.csproj | 21 +++++---- test/Directory.Build.props | 11 +++-- test/TaskTupleAwaiter.AotSmokeTest/Program.cs | 2 - .../TaskTupleAwaiter.AotSmokeTest.csproj | 10 ++-- .../AwaiterAdapter.AwaiterAdapterAwaiter.cs | 2 +- ...terAdapter.ConfiguredTaskAwaiterAdapter.cs | 4 +- ...apter.ConfiguredTaskTupleAwaiterAdapter.cs | 34 +++++++------- .../AwaiterAdapter.TaskAwaiterAdapter.cs | 4 +- .../AwaiterAdapter.TaskTupleAwaiterAdapter.cs | 34 +++++++------- ....VoidResultConfiguredTaskAwaiterAdapter.cs | 4 +- ...terAdapter.VoidResultTaskAwaiterAdapter.cs | 4 +- .../Adapters/AwaiterAdapter.cs | 27 ++++------- .../BehaviorComparisonTests.cs | 32 ++++++------- test/TaskTupleAwaiter.Tests/On.cs | 4 +- .../TaskTupleAwaiter.Tests.csproj | 4 -- .../TaskTupleAwaiterTests.cs | 1 - 19 files changed, 118 insertions(+), 155 deletions(-) diff --git a/.editorconfig b/.editorconfig index 2347c7f..758e8ca 100644 --- a/.editorconfig +++ b/.editorconfig @@ -80,26 +80,3 @@ csharp_new_line_before_catch = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true - -# Roslyn source generator project — types are public for [Generator] discovery, not for -# consumption (IsPackable=false). Doc generation is on so IDE0005 (unused usings) can run -# under .NET 11+; CS1591 (missing doc comment) is noise on this internal surface. -[src/TaskTupleAwaiter.Generator/**.cs] -dotnet_diagnostic.CS1591.severity = none - -# Test project: the codebase pre-dates several latest-Recommended rules that .NET 11 -# now elevates to warnings (and TreatWarningsAsErrors makes them build errors). Keep -# strict enforcement on the shipped library; relax on tests where the patterns are -# either intentional (xUnit reflection-discovered methods) or low-value to chase down. -# - IDE0040: explicit modifiers in adapter helpers matching the default -# - IDE0044: adapter pattern fields that are effectively immutable but not declared so -# - IDE0046: if/return preferred over ternary for clarity in test setup -# - IDE0051: xUnit discovers private [Fact]/[Theory] methods via reflection -# - IDE0055: legacy formatting in a few test files -[test/**.cs] -dotnet_diagnostic.IDE0005.severity = silent -dotnet_diagnostic.IDE0040.severity = silent -dotnet_diagnostic.IDE0044.severity = silent -dotnet_diagnostic.IDE0046.severity = silent -dotnet_diagnostic.IDE0051.severity = silent -dotnet_diagnostic.IDE0055.severity = silent diff --git a/Directory.Build.props b/Directory.Build.props index 0c5d46e..75ad24b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,9 +1,10 @@ + latest-Recommended + true + true true latest - true - latest-Recommended true diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 2ebe94d..9f76723 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,30 +1,29 @@ - + - - netstandard2.0;net462;net8.0 - Joseph Musser,Brian Buvinghausen - Buvinghausen Solutions - Copyright © $([System.DateTime]::UtcNow.ToString("yyyy")) Brian Buvinghausen - MIT - README.md - https://github.com/buvinghausen/TaskTupleAwaiter/blob/master/README.md - C# 7.0;Value Tuple;Async Await Elegant Code - true - true - true - true - snupkg - true - ..\..\ - true - + + Joseph Musser,Brian Buvinghausen + Buvinghausen Solutions + true + Copyright © $([System.DateTime]::UtcNow.ToString("yyyy")) Brian Buvinghausen + true + true + true + MIT + ..\..\ + https://github.com/buvinghausen/TaskTupleAwaiter/blob/master/README.md + README.md + C# 7.0;Value Tuple;Async Await Elegant Code + true + snupkg + netstandard2.0;net462;net8.0 + - - - - - + + + + + diff --git a/src/TaskTupleAwaiter.Generator/TaskTupleAwaiter.Generator.csproj b/src/TaskTupleAwaiter.Generator/TaskTupleAwaiter.Generator.csproj index a220e99..0b17e95 100644 --- a/src/TaskTupleAwaiter.Generator/TaskTupleAwaiter.Generator.csproj +++ b/src/TaskTupleAwaiter.Generator/TaskTupleAwaiter.Generator.csproj @@ -1,14 +1,15 @@ - + - - netstandard2.0 - true - true - false - + + true + false + true + $(NoWarn);CS1591 + netstandard2.0 + - - - + + + diff --git a/test/Directory.Build.props b/test/Directory.Build.props index de75a6a..0297231 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -3,18 +3,21 @@ - net11.0;net10.0;net9.0;net8.0;net472 - Exe false + $(NoWarn);CS1591;IDE0051 + Exe + net11.0;net10.0;net9.0;net8.0;net472 true true - - + + + + diff --git a/test/TaskTupleAwaiter.AotSmokeTest/Program.cs b/test/TaskTupleAwaiter.AotSmokeTest/Program.cs index 944546f..0f2c408 100644 --- a/test/TaskTupleAwaiter.AotSmokeTest/Program.cs +++ b/test/TaskTupleAwaiter.AotSmokeTest/Program.cs @@ -1,5 +1,3 @@ -using System.Threading.Tasks; - // --- typed arity-1: ValueTuple> --- // The arity-1 GetAwaiter delegates directly to the inner task's TaskAwaiter. { diff --git a/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj b/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj index 24a47a1..6d604de 100644 --- a/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj +++ b/test/TaskTupleAwaiter.AotSmokeTest/TaskTupleAwaiter.AotSmokeTest.csproj @@ -1,14 +1,13 @@ + false Exe - net8.0;net11.0 true true - false - false + net8.0;net11.0 false - TaskTupleAwaiter.AotSmokeTest + false