Repository navigation
feat(analyzers): suggest state and a static lambda for capturing generators - #248
cosmin-staicu wants to merge 1 commit into
Conversation
|
🔎 Maintainer heads-up: automated triage flagged this PR as potentially material, so it may need a signed CLA in addition to the DCO sign-off. Strong signals
Other signals
This is advisory only — the bot does not decide. Please judge against the CLA criteria (material, product-critical, patent-sensitive, corporate contributor, broad commercial use). Note that thresholds can be gamed by splitting PRs, so use your judgement.
|
401e714 to
50b4f5d
Compare
0330101 to
0cc19f9
Compare
50b4f5d to
1c90388
Compare
0cc19f9 to
ebf697a
Compare
1c90388 to
5fd4a97
Compare
ebf697a to
fed3eaf
Compare
5fd4a97 to
8993858
Compare
fed3eaf to
1333496
Compare
8993858 to
da7c321
Compare
1333496 to
424077f
Compare
424077f to
549c8b5
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Several code-fix paths can change runtime behavior or produce uncompilable code.
9 open findings
Dynamic captured state produces invalid generator calls · New Incorrect declaring class retained for static extension state overloads · New Nested anonymous types produce uncompilable state arguments · New Conditional calls mishandle generic state overloads · New Duplicate generated parameters for omitted batch delegate parameters · New Instance method conversion loses eager null-check behavior · New Parenthesized assignment targets bypass write detection · New Missed mutations through captured value-type receivers · New Unvalidated generic constraints allow invalid state overload fixes · New
What changed in this PR
Adds UIPCACHE001 to identify allocating cache generators and suggest state-based static lambdas.
Changes:
- Adds and packages the analyzer and code fix.
- Converts synchronous wrappers to state overloads.
- Adds tests, documentation, and repository-wide analyzer configuration.
| File | Description |
|---|---|
| UiPath.Caching.slnx | Registers analyzer and test projects. |
| tests/UiPath.Caching.Tests/SyncExtensionsTests.cs | Tests synchronous wrappers. |
| tests/UiPath.Caching.Analyzers.Tests/Verify.cs | Provides analyzer test harness. |
| tests/UiPath.Caching.Analyzers.Tests/UiPath.Caching.Analyzers.Tests.csproj | Configures analyzer tests. |
| tests/UiPath.Caching.Analyzers.Tests/CapturingGeneratorTests.cs | Tests diagnostics and rewrites. |
| src/UiPath.Caching.Analyzers/UiPath.Caching.Analyzers.csproj | Configures analyzer assembly. |
| src/UiPath.Caching.Analyzers/Sibling.cs | Describes matching state overloads. |
| src/UiPath.Caching.Analyzers/PublicAPI.Unshipped.txt | Records new analyzer API. |
| src/UiPath.Caching.Analyzers/PublicAPI.Shipped.txt | Initializes API baseline. |
| src/UiPath.Caching.Analyzers/GeneratorCaptures.cs | Identifies captured values. |
| src/UiPath.Caching.Analyzers/CapturingGeneratorAnalyzer.cs | Implements UIPCACHE001 diagnostics. |
| src/UiPath.Caching.Analyzers/CacheMethods.cs | Recognizes cache methods and overloads. |
| src/UiPath.Caching.Analyzers/AnalyzerReleases.Unshipped.md | Registers the new diagnostic. |
| src/UiPath.Caching.Analyzers/AnalyzerReleases.Shipped.md | Initializes release tracking. |
| src/UiPath.Caching.Analyzers.CodeFixes/UiPath.Caching.Analyzers.CodeFixes.csproj | Separates IDE dependencies. |
| src/UiPath.Caching.Analyzers.CodeFixes/StateRewrite.cs | Generates state-based static lambdas. |
| src/UiPath.Caching.Analyzers.CodeFixes/PublicAPI.Unshipped.txt | Records code-fix API. |
| src/UiPath.Caching.Analyzers.CodeFixes/PublicAPI.Shipped.txt | Initializes API baseline. |
| src/UiPath.Caching.Analyzers.CodeFixes/PassStateCodeFixProvider.cs | Registers fixes and Fix All. |
| src/UiPath.Caching.Abstractions/UiPath.Caching.Abstractions.csproj | Packages analyzer assemblies. |
| src/UiPath.Caching.Abstractions/HashCacheSyncExtensions.cs | Passes hash generators as state. |
| src/UiPath.Caching.Abstractions/CacheSyncExtensions.cs | Passes cache generators as state. |
| docs/how-to/choosing-a-get-or-add-overload.md | Documents diagnostics and fixes. |
| Directory.Packages.props | Adds Roslyn dependencies. |
| Directory.Build.props | Wires repository analyzer references. |
| CHANGELOG.md | Announces analyzer support. |
| .editorconfig | Sets diagnostic severity by scope. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
549c8b5 to
0775f45
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Remaining code-fix cases can produce uncompilable code or change generator behavior, and consumer extension detection is incomplete.
8 open findings
Reduced extension rewrite misses statically imported sibling · New Pointer captures cannot be used as generic state · New Trimmed underscores produce invalid generated identifiers · New Escaped contextual keyword parameter loses its escape · New Rewriting reordered generic parameters breaks type bindings · New Tuple captures are invalid inside expression trees · New Mutable struct property getter mutation is missed · New Consumer-defined cache extensions are not discovered · New
9 resolved since last review
Unvalidated generic constraints allow invalid state overload fixes Missed mutations through captured value-type receivers Parenthesized assignment targets bypass write detection Instance method conversion loses eager null-check behavior Duplicate generated parameters for omitted batch delegate parameters Conditional calls mishandle generic state overloads Nested anonymous types produce uncompilable state arguments Incorrect declaring class retained for static extension state overloads Dynamic captured state produces invalid generator calls
🧠 Review effort: Balanced
0775f45 to
19815df
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved rewrite-safety, capture-detection, packaging, and release-tracking issues affect correctness and delivery.
5 open findings
Sibling matching ignores required return type compatibility · New Release-tracking files are missing from AdditionalFiles · New Analyzer assets are not transitive through the published package · New Missed primary-parameter mutations can cause unsafe snapshotting · New Cross-file spans misidentify primary parameters as lambda-local · New
8 resolved since last review
Rewriting reordered generic parameters breaks type bindings Escaped contextual keyword parameter loses its escape Trimmed underscores produce invalid generated identifiers Pointer captures cannot be used as generic state Reduced extension rewrite misses statically imported sibling Consumer-defined cache extensions are not discovered Mutable struct property getter mutation is missed Tuple captures are invalid inside expression trees
🧠 Review effort: Balanced
19815df to
857756b
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The code fix can alter observable mutations and compiler-supplied caller-expression strings.
2 open findings
5 resolved since last review
Release-tracking files are missing from AdditionalFiles Sibling matching ignores required return type compatibility Cross-file spans misidentify primary parameters as lambda-local Missed primary-parameter mutations can cause unsafe snapshotting Analyzer assets are not transitive through the published package
🧠 Review effort: Balanced
22c49e7 to
1d48db9
Compare
…rators UIPCACHE001 (category UiPath.Caching, Info) reports a generator that allocates on every call, hits included, passed to GetOrAddAsync or GetOrAddWithExpirationAsync on ICache, ICache<T>, IHashCache or IHashCache<T>, an extension of one, or a class member that implements one: a lambda that captures this, a local or a parameter, itself or through a local function it calls, an instance method group, or a local function that captures; and a capturing lambda passed to a state overload. A name used only in nameof is not a capture. It reports only where a state overload exists for the call, the single-key state or the batch argument, found by comparing parameter symbols with the closure overload's, the sibling's own type parameters bound as it goes. The fix moves the call to that overload with a static lambda: one capture is the state (this becomes self, implicit member access goes through it), several become a named tuple, a method group's target is passed, and spelled type arguments get the state's type. It is not offered when the state, read once at the call, could differ from what the lambda read when it ran (a captured variable assigned in the method, a primary constructor parameter assigned in the type, base, a capturing local function, a captured local named by inference or nameof), for a call on a class whose state overload is only on the interface (a class of the caller's own; the library's public caches declare theirs), or below C# 9; and only when the rewritten call, compiled in place, binds to the state overload without an error. Fix All uses the batch fixer; a generator inside another is fixed on the next pass. The rule has a category of its own, so bulk configuration of Performance does not raise it. The analyzer and the fix (apart, so the analyzer does not reference Workspaces) target netstandard2.0 on Microsoft.CodeAnalysis 4.8, the .NET 8 SDK's compiler, and ship in the UiPath.Caching.Abstractions package under analyzers/dotnet/cs, packed from the analyzer projects' own outputs; NuGet carries a dependency's analyzers to every package built on it. The blocking GetOrAdd wrappers pass their generator as state. The repository runs the analyzer on itself: src and samples reference both projects as analyzers, with UIPCACHE001 a warning there. Signed-off-by: Cosmin Staicu <cosmin.staicu@uipath.com>
1d48db9 to
fa11c9f
Compare
|





UIPCACHE001 (category UiPath.Caching, Info) reports a generator that allocates on every
call, hits included, passed to GetOrAddAsync or GetOrAddWithExpirationAsync on
ICache, ICache, IHashCache or IHashCache, an extension of one, or a class
member that implements one: a lambda that captures this, a local or a
parameter, itself or through a local function it calls, an instance method
group, or a local function that captures; and a capturing lambda passed to a
state overload. A name used only in nameof is not a capture. It reports only
where a state overload exists for the call, the single-key state or the batch
argument, found by comparing parameter symbols with the closure overload's,
the sibling's own type parameters bound as it goes.
The fix moves the call to that overload with a static lambda: one capture is
the state (this becomes self, implicit member access goes through it), several
become a named tuple, a method group's target is passed, and spelled type
arguments get the state's type. It is not offered when the state, read once at
the call, could differ from what the lambda read when it ran (a captured
variable assigned in the method, a primary constructor parameter assigned in
the type, base, a capturing local function, a captured local named by
inference or nameof), for a call on a class whose state overload is only on the
interface (a class of the caller's own; the library's public caches declare
theirs), or below C# 9; and only when the rewritten call, compiled in place,
binds to the state overload without an error. Fix All uses the batch fixer; a
generator inside another is fixed on the next pass. The rule has a category of
its own, so bulk configuration of Performance does not raise it.
The analyzer and the fix (apart, so the analyzer does not reference
Workspaces) target netstandard2.0 on Microsoft.CodeAnalysis 4.8, the .NET 8
SDK's compiler, and ship in the UiPath.Caching.Abstractions package under
analyzers/dotnet/cs, packed from the analyzer projects' own outputs; NuGet carries a dependency's analyzers to every package
built on it. The blocking GetOrAdd wrappers pass their generator as state.
The repository runs the analyzer on itself: src and samples reference both
projects as analyzers, with UIPCACHE001 a warning there.