Problem
Institute APIs currently can impose @Sendable on closure values even when the actual law is one-shot ownership transfer, scoped borrowing, or region-based isolation. That unnecessarily requires concurrent reusability, blocks ~Copyable and ~Escapable designs, and encourages shallow constraint cascades instead of the deeper ownership refactor. The Institute rule is: prefer scoped borrowing and non-escape, then consuming/sending ownership transfer, then isolated/region transfer; reserve @Sendable for values genuinely reusable concurrently or stored across independently concurrent isolation, with an explicit local justification.
This policy is mechanically enforceable as an Institute Memory-family rule. The portable swift-linter engine already owns SwiftSyntax-based diagnostics, severities, fix refusal, and swift-linter:disable infrastructure; this repository is the exact owner of the Institute rule and its source-law fixtures.
Proposed outcome
Add an Institute Memory-family rule, tentatively identified as sendable sharing requirement, and enable it through Lint.Rule.Bundle.institute. Its structural contract is:
-
Diagnose public or package API closure parameters, stored/computed closure properties, closure-valued results, and public closure typealiases whose type carries @Sendable when the declaration expresses a one-shot boundary that can instead be represented by scoped non-escape/borrowing, consuming or sending, or isolated/region transfer. Report the declaration and the preferred next ownership spelling; do not claim semantic equivalence when syntax alone cannot prove it.
-
Emit a stronger diagnostic when the same declaration or enclosing generic surface carries ~Copyable, ~Escapable, consuming, or sending, yet @Sendable still constrains a value that crosses the boundary only once. These are direct signals that the API is single-use or region-transferred rather than concurrently reusable.
-
Accept justified sharing cases: closures stored and executable by multiple concurrent tasks; cancellation callbacks that may run concurrently; genuinely shared Sendable endpoints; and actor-independent reusable operations. Acceptance requires an adjacent local suppression in the existing engine format:
// swift-linter:disable:next sendable sharing requirement
// REASON: CATEGORY: <multi-task-storage|concurrent-cancellation|shared-endpoint|actor-independent-reuse>; SHARING: <concise proof naming the independently concurrent users or storage boundary>.
The rule must mechanically reject a suppression whose REASON: lacks either a recognized CATEGORY: or a non-empty SHARING: proof. A generic statement such as “needed for concurrency” is not a proof.
-
Offer no automatic rewrite unless SwiftSyntax can prove the replacement is mechanically equivalent. A fix-it may be offered only for such proven cases; all other findings must explain the ownership preference and leave the deeper refactor to the author.
-
Encode the preference order in the diagnostic and rule documentation: scoped borrowing/non-escaping → consuming/sending ownership transfer → isolated/region transfer → @Sendable only when concurrent sharing is real.
Source-law fixtures must include both diagnostic and accepted controls for:
~Copyable and ~Escapable closure/value boundaries;
sending parameters and results;
consuming closures and values;
- actor and isolated-region transfer;
- each accepted justified
@Sendable category;
- missing, unknown, and empty suppression category/proof fields;
- the rule’s own positive control, proving the diagnostic fires.
The observable completion contract is: the structural fixtures pass in both directions; the rule is present in the Institute bundle; an audit run produces an adjudicable inventory; every false positive is either fixed in the predicate or locally suppressed with the required category and sharing proof; and the default advances from warning to error once that exact non-zero baseline is adjudicated. Warning is a staged rollout state, not the end-state.
After the rule lands and graduates, universal Institute CI consumes it through the existing generated leaf caller → universal reusable → verified ci-binaries swift-linter path. Do not add or edit repository workflow files for this issue; the existing one-hop integration is the contract.
Alternatives considered
- Documentation only: rejected because it leaves a mechanically detectable ownership-law regression unenforced.
- A blanket ban on
@Sendable: rejected because concurrently reusable closures and independently concurrent storage genuinely require it.
- Permanent advisory-only lint: rejected because the programme end-state is CI enforcement after false-positive adjudication.
- Automatic conversion to
sending or consuming: rejected except where SwiftSyntax can prove mechanical equivalence; ownership and escape semantics generally require design judgment.
- Implementing the rule in the
swift-linter engine or CI workflow: rejected because portable execution/fix mechanics and Institute policy have separate owners.
Compatibility and migration impact
The initial warning/audit stage will inventory affected public and package APIs across Institute packages without silently changing source. Findings will require either a deeper ownership/region refactor or the explicit category-plus-sharing proof above. Move-only and lifetime-dependent APIs should receive the strongest benefit because unnecessary @Sendable constraints currently exclude their intended forms.
Promotion to error follows this repository’s standing graduation discipline: a structural SwiftSyntax predicate, both-direction fixtures for every carve-out, and a non-zero fleet inventory whose findings have all been adjudicated. The final CI contract is enforcing, universal, and uses the existing one-hop swift-linter mechanism; this issue authorizes no workflow edits, CI reads, builds, tests, resolves, compiler probes, or source changes.
Problem
Institute APIs currently can impose
@Sendableon closure values even when the actual law is one-shot ownership transfer, scoped borrowing, or region-based isolation. That unnecessarily requires concurrent reusability, blocks~Copyableand~Escapabledesigns, and encourages shallow constraint cascades instead of the deeper ownership refactor. The Institute rule is: prefer scoped borrowing and non-escape, thenconsuming/sendingownership transfer, then isolated/region transfer; reserve@Sendablefor values genuinely reusable concurrently or stored across independently concurrent isolation, with an explicit local justification.This policy is mechanically enforceable as an Institute Memory-family rule. The portable
swift-linterengine already owns SwiftSyntax-based diagnostics, severities, fix refusal, andswift-linter:disableinfrastructure; this repository is the exact owner of the Institute rule and its source-law fixtures.Proposed outcome
Add an Institute Memory-family rule, tentatively identified as
sendable sharing requirement, and enable it throughLint.Rule.Bundle.institute. Its structural contract is:Diagnose public or package API closure parameters, stored/computed closure properties, closure-valued results, and public closure typealiases whose type carries
@Sendablewhen the declaration expresses a one-shot boundary that can instead be represented by scoped non-escape/borrowing,consumingorsending, or isolated/region transfer. Report the declaration and the preferred next ownership spelling; do not claim semantic equivalence when syntax alone cannot prove it.Emit a stronger diagnostic when the same declaration or enclosing generic surface carries
~Copyable,~Escapable,consuming, orsending, yet@Sendablestill constrains a value that crosses the boundary only once. These are direct signals that the API is single-use or region-transferred rather than concurrently reusable.Accept justified sharing cases: closures stored and executable by multiple concurrent tasks; cancellation callbacks that may run concurrently; genuinely shared Sendable endpoints; and actor-independent reusable operations. Acceptance requires an adjacent local suppression in the existing engine format:
// swift-linter:disable:next sendable sharing requirement// REASON: CATEGORY: <multi-task-storage|concurrent-cancellation|shared-endpoint|actor-independent-reuse>; SHARING: <concise proof naming the independently concurrent users or storage boundary>.The rule must mechanically reject a suppression whose
REASON:lacks either a recognizedCATEGORY:or a non-emptySHARING:proof. A generic statement such as “needed for concurrency” is not a proof.Offer no automatic rewrite unless SwiftSyntax can prove the replacement is mechanically equivalent. A fix-it may be offered only for such proven cases; all other findings must explain the ownership preference and leave the deeper refactor to the author.
Encode the preference order in the diagnostic and rule documentation: scoped borrowing/non-escaping →
consuming/sendingownership transfer → isolated/region transfer →@Sendableonly when concurrent sharing is real.Source-law fixtures must include both diagnostic and accepted controls for:
~Copyableand~Escapableclosure/value boundaries;sendingparameters and results;consumingclosures and values;@Sendablecategory;The observable completion contract is: the structural fixtures pass in both directions; the rule is present in the Institute bundle; an audit run produces an adjudicable inventory; every false positive is either fixed in the predicate or locally suppressed with the required category and sharing proof; and the default advances from warning to error once that exact non-zero baseline is adjudicated. Warning is a staged rollout state, not the end-state.
After the rule lands and graduates, universal Institute CI consumes it through the existing generated leaf caller → universal reusable → verified
ci-binariesswift-linter path. Do not add or edit repository workflow files for this issue; the existing one-hop integration is the contract.Alternatives considered
@Sendable: rejected because concurrently reusable closures and independently concurrent storage genuinely require it.sendingorconsuming: rejected except where SwiftSyntax can prove mechanical equivalence; ownership and escape semantics generally require design judgment.swift-linterengine or CI workflow: rejected because portable execution/fix mechanics and Institute policy have separate owners.Compatibility and migration impact
The initial warning/audit stage will inventory affected public and package APIs across Institute packages without silently changing source. Findings will require either a deeper ownership/region refactor or the explicit category-plus-sharing proof above. Move-only and lifetime-dependent APIs should receive the strongest benefit because unnecessary
@Sendableconstraints currently exclude their intended forms.Promotion to error follows this repository’s standing graduation discipline: a structural SwiftSyntax predicate, both-direction fixtures for every carve-out, and a non-zero fleet inventory whose findings have all been adjudicated. The final CI contract is enforcing, universal, and uses the existing one-hop swift-linter mechanism; this issue authorizes no workflow edits, CI reads, builds, tests, resolves, compiler probes, or source changes.