Skip to content

feat: warn (AWT106) when a factory body hides an IAsyncInitializable/IDisposable capability behind its declared return type#35

Merged
vbreuss merged 2 commits into
mainfrom
feat/factory-capability-lint
Jun 29, 2026
Merged

feat: warn (AWT106) when a factory body hides an IAsyncInitializable/IDisposable capability behind its declared return type#35
vbreuss merged 2 commits into
mainfrom
feat/factory-capability-lint

Conversation

@vbreuss

@vbreuss vbreuss commented Jun 29, 2026

Copy link
Copy Markdown
Member

Taint and disposability for a factory registration are read off the producer method's declared return type, so a concrete type that implements IAsyncInitializable or IDisposable but is returned behind a plainer interface is invisible to the container: its async initialization never runs, or its disposable is never disposed. This adds a best-effort, zero-false-positive lint that inspects the factory method's own return expressions (expression-bodied and block-bodied, never descending into nested lambdas or local functions) and fires only when the statically determined returned type is provably a non-abstract, non-interface named type carrying a capability the declared return type does not expose. Metadata-only factories and unanalyzable returns are left silent; helper-returned or runtime-selected implementations are accepted false negatives.

Severity Warning, since the lint has false negatives by design. One descriptor with a {capability} placeholder (async-initializable / disposable) keeps the AWT numbering tight.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

Test Results

   18 files  ± 0     18 suites  ±0   59s ⏱️ ±0s
  248 tests +13    247 ✅ +13  1 💤 ±0  0 ❌ ±0 
1 232 runs  +39  1 231 ✅ +39  1 💤 ±0  0 ❌ ±0 

Results for commit 144b59e. ± Comparison against base commit 14c9c20.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

🚀 Benchmark Results

Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.301
[Host] : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Resolve Size Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 8 9.945 ns 0.0056 ns 0.0044 ns 0.71 - NA
Awaiten 8 13.937 ns 0.0158 ns 0.0132 ns 1.00 - NA
MsDI 8 8.170 ns 0.5996 ns 0.5608 ns 0.59 - NA
Autofac 8 113.541 ns 1.0272 ns 0.8578 ns 8.15 656 B NA
Jab 8 2.652 ns 0.0035 ns 0.0027 ns 0.19 - NA
PureDI 8 5.434 ns 0.0088 ns 0.0078 ns 0.39 - NA
DryIoc 8 9.079 ns 0.0151 ns 0.0134 ns 0.65 - NA
SimpleInjector 8 11.234 ns 0.0080 ns 0.0075 ns 0.81 - NA
baseline* 256 360.425 ns 0.7201 ns 0.6014 ns 0.86 - NA
Awaiten 256 420.991 ns 0.1860 ns 0.1452 ns 1.00 - NA
MsDI 256 7.687 ns 0.2130 ns 0.1888 ns 0.02 - NA
Autofac 256 116.171 ns 2.3662 ns 2.2134 ns 0.28 656 B NA
Jab 256 42.786 ns 0.0584 ns 0.0488 ns 0.10 - NA
PureDI 256 7.644 ns 0.0055 ns 0.0043 ns 0.02 - NA
DryIoc 256 10.355 ns 0.0111 ns 0.0086 ns 0.02 - NA
SimpleInjector 256 14.867 ns 0.0900 ns 0.0842 ns 0.04 - NA
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.301
[Host] : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Realistic Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 242.5 ns 2.52 ns 2.36 ns 1.00 560 B 1.00
Awaiten 243.3 ns 1.63 ns 1.44 ns 1.00 560 B 1.00
MsDI 591.7 ns 6.06 ns 5.67 ns 2.43 1104 B 1.97
Autofac 8,123.8 ns 129.50 ns 121.13 ns 33.40 13696 B 24.46
Jab 176.1 ns 3.26 ns 3.05 ns 0.72 432 B 0.77
DryIoc 392.0 ns 7.77 ns 7.27 ns 1.61 944 B 1.69
SimpleInjector 723.8 ns 5.72 ns 5.35 ns 2.98 1096 B 1.96
PureDI 178.5 ns 1.72 ns 1.61 ns 0.73 632 B 1.13
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.301
[Host] : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Build Size Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 8 17.30 ns 0.412 ns 0.366 ns 1.06 136 B 1.00
Awaiten 8 16.28 ns 0.181 ns 0.160 ns 1.00 136 B 1.00
MsDI 8 1,456.55 ns 22.952 ns 21.469 ns 89.46 5688 B 41.82
Autofac 8 28,667.86 ns 118.665 ns 99.091 ns 1,760.66 33098 B 243.37
Jab 8 10.59 ns 0.662 ns 0.619 ns 0.65 96 B 0.71
PureDI 8 14.95 ns 0.422 ns 0.394 ns 0.92 128 B 0.94
DryIoc 8 645.69 ns 7.548 ns 7.060 ns 39.66 1472 B 10.82
SimpleInjector 8 11,692.77 ns 170.573 ns 142.436 ns 718.12 24760 B 182.06
baseline* 256 98.35 ns 2.402 ns 2.247 ns 1.15 2120 B 1.00
Awaiten 256 85.50 ns 2.595 ns 2.167 ns 1.00 2120 B 1.00
MsDI 256 14,597.65 ns 141.723 ns 132.568 ns 170.84 61016 B 28.78
Autofac 256 714,923.82 ns 8,759.137 ns 8,193.302 ns 8,366.90 741509 B 349.77
Jab 256 75.45 ns 2.183 ns 2.042 ns 0.88 2080 B 0.98
PureDI 256 88.57 ns 2.235 ns 2.090 ns 1.04 2112 B 1.00
DryIoc 256 44,365.43 ns 199.622 ns 166.694 ns 519.22 79167 B 37.34
SimpleInjector 256 348,008.02 ns 5,859.271 ns 5,480.766 ns 4,072.81 572992 B 270.28

baseline* rows show the corresponding Awaiten benchmark from the most recent successful main branch build with results, for regression comparison.

@github-actions

Copy link
Copy Markdown

👽 Mutation Results

Mutation testing badge

Awaiten

Details
File Score Killed Survived Timeout No Coverage Ignored Compile Errors Runtime Errors Total Detected Total Undetected Total Mutants

The final mutation score is NaN%

Coverage Thresholds: high:80 low:60 break:0

@vbreuss vbreuss force-pushed the feat/factory-capability-lint branch 2 times, most recently from 24e03e4 to 4b1ddbe Compare June 29, 2026 11:02
@vbreuss vbreuss changed the title feat: warn (AWT122) when a factory body hides an IAsyncInitializable/IDisposable capability behind its declared return type feat: warn (AWT106) when a factory body hides an IAsyncInitializable/IDisposable capability behind its declared return type Jun 29, 2026
@vbreuss vbreuss force-pushed the feat/factory-capability-lint branch from 4b1ddbe to 1c4f583 Compare June 29, 2026 15:14
…izable type behind its declared return type

A synchronous factory whose declared return type is plainer than the concrete type it builds hides that type's asynchronous initialization from the container: the container reads async-init taint off the declared return type, so a concrete IAsyncInitializable returned behind an interface is never initialized and is handed out uninitialized. This adds a best-effort, zero-false-positive lint that inspects the factory method's own return expressions (expression-bodied and block-bodied, never descending into nested lambdas or local functions) and fires only when the statically determined returned type is provably a non-abstract, non-interface named type that is async-initializable while the declared return type is not. Metadata-only factories and unanalyzable returns are left silent; helper-returned or runtime-selected implementations are accepted false negatives.

The lint is scoped to the one capability with no other compile-time signal and no runtime safety net. A hidden IDisposable is not reported: the container already disposes factory outputs behind a runtime is-IDisposable check, so it does not leak. An asynchronous Task<T> / ValueTask<T> factory is not reported either: it is the explicit manual-initialization escape hatch, so the container awaits the factory and trusts it to initialize its result - matching how the container declines to drive InitializeAsync on an async factory's produced type.

Severity Warning, since the lint has false negatives by design. The id reuses the retired AWT106 slot (the old per-registration disposable-transient check, removed before it ever shipped), filling the numbering gap.
@vbreuss vbreuss force-pushed the feat/factory-capability-lint branch from 1c4f583 to 47fa1f1 Compare June 29, 2026 15:20
@vbreuss vbreuss enabled auto-merge (squash) June 29, 2026 17:02
@sonarqubecloud

Copy link
Copy Markdown

@vbreuss vbreuss merged commit 89a0188 into main Jun 29, 2026
14 checks passed
@vbreuss vbreuss deleted the feat/factory-capability-lint branch June 29, 2026 17:04
github-actions Bot added a commit that referenced this pull request Jun 29, 2026
… hides an IAsyncInitializable/IDisposable capability behind its declared return type (#35) by Valentin Breuß
github-actions Bot added a commit that referenced this pull request Jun 29, 2026
… hides an IAsyncInitializable/IDisposable capability behind its declared return type (#35) by Valentin Breuß
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.

1 participant