Table of Contents

Class NDScopedCoveredAttribute

Namespace
NumSharp
Assembly
NumSharp.dll

Marks a method (or property accessor) as covered by an ambient NDScope opened by its caller — a [NDScoped] OR [NDScopedAsync] boundary (or a hand-written scope) — so its NDArray temporaries are reclaimed by that caller's scope, and the NDW012 leak analyzer must treat the method as covered instead of flagging its transients.

[AttributeUsage(AttributeTargets.Method|AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class NDScopedCoveredAttribute : Attribute
Inheritance
NDScopedCoveredAttribute
Inherited Members

Remarks

What it is — and is NOT. Unlike NDScopedAttribute / NDScopedAsyncAttribute, this attribute is NOT a scope boundary: the IL weaver never weaves it, no NDScope is opened here, and it is completely inert at runtime. It exists ONLY to inform the compile-time leak analyzer (NumSharp.Build.Analyzer): NDW012 is a per-method dataflow pass with no call-graph, so it cannot see that a helper's transients are reclaimed by a [NDScoped]/[NDScopedAsync] (or hand-scoped) CALLER's ambient scope — the documented "scope the boundary, helpers ride the ambient scope" pattern (DISPOSAL-GUIDELINES.md). This attribute is the author's assertion of exactly that, so the analyzer exempts the method.

The coverage contract the author asserts. Because NDScope tracks every array constructed while a scope is open on the current thread (the constructor funnel NDScope.Track), a helper is genuinely covered iff EVERY call path that reaches it does so synchronously, on the same thread, while an ambient scope is open. Marking a method [NDScopedCovered] asserts that invariant holds — typically because its only callers are [NDScoped]/[NDScopedAsync] boundary methods (or other [NDScopedCovered]s below them). If the method is ever invoked WITHOUT an ambient scope (a public entry point that was not scoped, a call from a lambda/Task.Run that runs after the scope closed, another thread), its temporaries fall back to the finalizer backstop — a real leak the analyzer will no longer report. The assertion is the author's responsibility, the same way a wrong hand-written using is.

Async boundaries ([NDScopedAsync]) are covered too. An [NDScopedAsync] method holds ONE invocation scope across its awaits (the state-machine seam re-installs it on the resuming thread at every MoveNext), so a helper it calls synchronously within a segment allocates under that installed scope and is reclaimed exactly as under a synchronous [NDScoped] caller — this attribute applies identically to both. Two async-specific caveats follow from the seam and are the author's to honour: (1) the scope is UNINSTALLED across an await (suspended before the continuation is scheduled), so a covered helper that is ITSELF async only rides the caller's scope for the work it does BEFORE its first await — allocations after an await (possibly on another thread) are not tracked; and (2) an awaited callee keeps the caller's tracked temps alive until the async method completes, which is the intended behaviour, not a leak. Mark a helper [NDScopedCovered] when it does its NDArray allocation synchronously under the boundary.

Why not just mark it [NDScoped]? That also silences the analyzer, but it WEAVES a nested scope into the helper (a per-call Open() + Returns<T>(T)). Nested scopes compose correctly, but they are not free — for a hot helper called under a boundary that already owns a scope, the nested scope is pure overhead. [NDScopedCovered] is the zero-runtime-cost choice when the caller's scope already does the reclamation.

No weaver, no NDW013. The weaver collects targets by the exact type names NumSharp.NDScopedAttribute / NumSharp.NDScopedAsyncAttribute, so this attribute is never a weave target and never a target-gate (NDW002–011) subject. The "you used [NDScoped] but the weaver is absent" guard (NDW013) and the weaver's own usage pre-scan match those two names precisely (not the shared NDScoped prefix), so a project using only [NDScopedCovered] — which needs no weaver — draws neither.

Inheritance, and the opt-out role. Like the two scope attributes this one is inherited along the override/implementation chain: a virtual/abstract/interface member marked [NDScopedCovered] asserts coverage for every override too. And because the nearest scope-family attribute up the chain wins, placing it ON an override whose base declaration is [NDScoped]/[NDScopedAsync] is the way to opt that override OUT of the inherited weave — the override then rides its caller's ambient scope, on the same author's-assertion terms as any covered helper.

PUBLIC for the same reason as the scope attributes: a consumer that adopts the [NDScoped] pattern can annotate its own always-ambient helpers to keep its build clean.