5.7 KiB
Pending output ownership source map — proposal only
Source inspected: a629ae01d49bb3cb482a047eb41832ad4246a5c6; no compilation,
source modification or acceptance claim. This refines only the lifetime seam in
ACCEPTANCE-PROPOSAL.md, not equality/scratch policy.
Paths below are relative to .local/as3batch at that commit.
Existing authority
crates/oa-core/src/language/analysis_source/mixed/project/ordinary_index/rename/emission.rs:6
owns PendingRename's bundle, exact Limits/ProjectLimits, retained Slots, original
expression count and immutable desired Project reference. Its constructor at the
end of emit_changed_unverified follows output reservation and explicit old-owner
drop; no public bundle/slots constructor exists. Those private fields currently
prevent sibling carried code from reading authority.
.../mixed/carried.rs:40 consumes CarriedInput into a bundle reference, one policy
value and a slim guard. :63 derives C from that guard; :116 selects both producer
modes. .../mixed.rs:39 stores the guard alongside the borrowed bundle and Budget;
:308 is the shared parser. Consumed resolution at .../mixed/project.rs:453
retains that syntax. None of these owners currently needs desired's lifetime.
Recommend Candidate A with a complete pending reference
The smallest variant avoids projecting authority down to a bare &Slots at all:
// Private variants in the existing enums; no new allocating wrapper.
SnapshotBacking::Pending(&'s PendingRename<'s>)
GuardBacking::Pending(&'s PendingRename<'s>)
impl<'s> CarriedInput<'s> {
pub(super) fn from_pending<'t: 's>(p: &'s PendingRename<'t>) -> Self {
Self { backing: SnapshotBacking::Pending(p) }
}
}
This is a proposed Rust shape, not a compiler-tested patch. PendingRename is
covariant in its sole lifetime: the only use is desired: &'t Project, and all
other fields own data without that lifetime. Consequently the longer desired
borrow can be shortened to the pending borrow 's when storing p. The original
pending value retains its original type/lifetime; no data moves or self-reference
is created. Parser, resolved and indexed owner types keep their single existing
's. Immutable &PendingRename itself prevents mutation, consumption or dropping
its bundle while syntax/guard remain usable. No trait object or PhantomData proof
is needed, and future invariant lifetime-bearing fields would require revisiting
this coercion rather than silently bypassing it.
Add narrowly visible immutable Pending methods bundle() -> &SourceBundle,
policy() -> (Limits, ProjectLimits) and retained() -> Slots, restricted to
pub(in crate::language::analysis_source::mixed). CarriedInput accepts only the
complete pending reference, never those components as independent arguments.
into_parts derives bundle/policy from its stored pending and transfers the same
reference into GuardBacking::Pending; slots() calls that pending's retained
method. Policy is copied once into the existing preparse transfer value and then
Budget/syntax limits; the guard contains only one pointer, not another policy.
Pending necessarily retains its original immutable policy as owner provenance.
No recomputation of C, tuple authentication or runtime identity comparison helps.
Candidate B and visibility
Embedding an owned RetainedSnapshot in Pending would remove the cross-reference between carried and emission modules and also avoid desired lifetime covariance. It adds no necessary heap allocation, but replaces existing pending fields, changes emission construction and existing direct inspection tests, and requires a controlled production snapshot constructor or wider sibling field authority. That larger migration has no present ownership benefit over the complete pointer. Prefer B only if later genuinely shared owning snapshot behavior justifies it.
The module paths are already visible: ordinary_index.rs:433 exposes rename to
analysis_source, and rename.rs:2 exposes emission likewise. Pending type itself
has that visibility. Keep carried private to mixed; descendants can name its
module. The needed changes are just its from_pending entry point and a named
strong parse entry with visibility up to mixed, plus the three immutable pending
views. Keep SnapshotBacking/GuardBacking variants and fields private to carried;
do not export SnapshotPolicy or add arbitrary production RetainedSnapshot inputs.
Consuming verification and finite type checks
Implement verify in emission, where it can consume self and eventually move its private bundle. A local block calls the strong parser with from_pending(&self), consumes syntax into resolve, performs separately specified bounded equality, then drops resolved owner/guard before leaving that block. Only afterward move self.bundle into an owning accepted result. No parsed witness or desired borrow escapes, so accepted output has no lifetime parameter or self-reference. Failure returns diagnostics and drops self; successful no-op remains the separate old owner branch. This note does not select accepted-result extraction/installation.
Compiler acceptance must demonstrate: long-lived desired + local pending can produce a short scoped syntax; resolved owner cannot outlive pending; moving or mutating pending while the guard is subsequently used is rejected; dropping all verification borrows permits moving the bundle into the accepted owner. Positive runtime tests should drop original input and desired after acceptance and still inspect returned output. Preserve exact inherited policies and C once throughout; no new carrying-mode bypass or policy mutation. Any compile-fail examples should be narrow external snippets/appropriate harness, not runtime unsafe tricks.