Skip to content

Borrow-accessor groundwork: syntax, statics, and epoch-engine inlining (ADR-0062 phases 0–1) - #2077

Merged
steveklabnik merged 3 commits into
trunkfrom
steve/rue-662-borrow-accessors
Jul 29, 2026
Merged

Borrow-accessor groundwork: syntax, statics, and epoch-engine inlining (ADR-0062 phases 0–1)#2077
steveklabnik merged 3 commits into
trunkfrom
steve/rue-662-borrow-accessors

Conversation

@steveklabnik

@steveklabnik steveklabnik commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Toward RUE-662. Part of RUE-1015. Refs RUE-1016, RUE-1017.

Scope note: this lands the phase-1 groundwork — syntax, statics, epoch-engine inlining, formal core, spec chapter, and all coverage that runs through today's driver. One follow-up remains before RUE-662's acceptance criteria close: the provider-eligibility fallback, which per the maintainer ruling (recorded below) waits for RUE-1033 to land before touching engine eligibility. Deliberately uses no closing keyword until that follow-up completes the issue.

What this implements (ADR-0062, S2 surface, all behind --preview borrow_accessors)

  • Syntax through RIR: yield keyword (lexer + spec-visible token names); -> borrow T result position on fn/method declarations (Function.borrow_return / Method.borrow_return in the AST, FnDecl.returns_borrow in RIR); a Yield RIR instruction; lowering, printing, validation, remapping, and frontend-diff/artifact-view arms.
  • Statics — the (Accessor-Call) rule: an accessor call takes a (root(receiver), shared) loan whose extent is the enclosing full expression, tracked in a new expression_loans context set that the statement loop truncates per full expression. Exclusive uses inside that extent — inout arguments/receivers, moves, assignments — are rejected with the new E0259 (use(v.get_ref(i), g(inout v)) is the canonical rejection, covered by a unit test). Escape shapes get dedicated diagnostics: return E0250, store E0251, plain let E0252, aggregate capture E0253. Accessor bodies are well-formed iff every non-diverging path falls through to the single trailing yield of a place rooted at the receiver (E0254/E0255; return/? in a body and stray yield are rejected, E0256). Declarations require a borrow self receiver and by-value, trivially-shaped parameters (E0257/E0260) — re-checked in the shared body engine so they hold on every analysis host, including the incremental driver; reading a drop-glue value out of a result by value is E0258 (the RUE-651 aliasing-owner hole stays closed). Everything gates through require_preview() (E1100).
  • Lowering — mandatory inlining, no ABI: in the shared body engine a call expands to the accessor's guards plus the yielded place, composed as a caller-rooted place trace (guards ride as block-prefix statements; the result place projects/borrows/compares like any place). No call is emitted for accessor calls, and the standalone accessor compiles as guards + an unreachable trap, so CFG/codegen see no new instruction shapes — the RUE-1012 forward-compatibility contract. Accessor-body type inference runs on demand at the call site and overlays the caller's resolved types.
  • Formal core: §5.8 gains the (Accessor-Call) rule, accessor body well-formedness, and an inlined-call dynamics note, plus the §8 traceability row.
  • Spec + traceability: new chapter 6.6 (Borrow Accessors, rules 6.6:1–6.6:13, preview-marked), the yield row in the 2.4:2 keyword table, and result/yield_expr productions in appendix A. Spec cases cover every declaration/body rule, the preview gate, and the guard-trap dynamics as required coverage through today's driver; the remaining call-site cases (escapes, exclusivity, drop-glue read, in-place execution) are written and run as preview-allowed-to-fail, with matching KNOWN_UNCOVERED_NORMATIVE entries for 6.6:8–6.6:11 that the traceability gate will force us to retire when the cases flip.
  • Coverage at the engine layer: 15 rue-air unit tests exercise the inline expansion (no Call in the caller's AIR, guards present), the preview gate, every escape shape, the exclusivity conflict, and each body/declaration well-formedness rejection.

Driver status and the recorded ruling

The production driver analyzes bodies through the incremental provider path (ProviderBodyHost over the semantic query nucleus). Each body request receives a pruned, re-lowered RIR containing only that body — no struct declarations, no callee method bodies — so call-site inlining cannot happen on that path today: the call site can't learn the method is an accessor (the durable CallableSignature/DurableMethod schema doesn't carry returns_borrow), and the callee's guards/yield RIR isn't present to splice. Body-level diagnostics (gating, declaration shape, yield placement, rooting) do fire through the driver; the epoch engine implements the full semantics.

Maintainer ruling (2026-07-29): the epoch-engine fallback is approved as bounded preview scaffolding for RUE-662 — "a program that declares any accessor routes to the epoch engine" — with the CFG-threshold splice as the production mechanism and a hard prerequisite for RUE-1017 (tracked as RUE-1208, blocking RUE-1017). Sequencing: let RUE-1033 finish before touching provider eligibility, then land the eligibility change with a focused epoch/provider parity test demonstrating the fallback is intentional, flip the deferred spec cases to required coverage, retire the known-uncovered entries, and add the UI/CLI call-site coverage.

Also disclosed: two compile-forced mechanical edits in crates/rue-compiler (artifact_views.rs, parsed_modules.rs) — exhaustive-match arms for the new Yield AST/RIR variants; no query or session logic was touched.

Not included

The provider-eligibility change described above (waiting on RUE-1033), the CFG-threshold splice (RUE-1208), mutable accessors (RUE-1016), and std adoption (RUE-1017).

Testing

scripts/rue quick (30/30 targets), full spec suite (2211 passed, deferred accessor call-site cases running as preview-allowed-to-fail), the spec traceability gate, the full UI suite (209/209), and the oracle-diff spec corpus audit all pass; the tree builds clean across all crates.


Generated by Claude Code

@steveklabnik
steveklabnik marked this pull request as ready for review July 29, 2026 15:30
@steveklabnik
steveklabnik added this pull request to the merge queue Jul 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 29, 2026
@steveklabnik
steveklabnik enabled auto-merge July 29, 2026 15:36
@steveklabnik
steveklabnik added this pull request to the merge queue Jul 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 29, 2026
@steveklabnik
steveklabnik added this pull request to the merge queue Jul 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 29, 2026
@steveklabnik
steveklabnik added this pull request to the merge queue Jul 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 29, 2026
claude added 3 commits July 29, 2026 15:52
…och-engine inlining

Place-returning borrow accessors (ADR-0062, S2 surface) through the
epoch semantic engine, behind the borrow_accessors preview gate:

- yield keyword; -> borrow T result position on fn/method declarations
  (grammar-side only; free functions are rejected in sema, E0257)
- RIR: FnDecl.returns_borrow and InstData::Yield
- Sema statics: the (Accessor-Call) rule as an expression-scoped shared
  loan on the receiver root (ctx.expression_loans, truncated per full
  expression) checked against inout loans, moves, and assignments
  (E0259); dedicated escape diagnostics for return/store/let/capture
  (E0250-E0253); accessor-body well-formedness (single trailing yield,
  receiver-rooted place; E0254-E0256); declaration shape checks
  (borrow self receiver, by-value params; E0257, E0260); drop-glue
  value reads out of a result rejected (E0258); everything behind
  require_preview(borrow_accessors) (E1100 when off)
- Mandatory inlining at call sites in the shared body engine: an
  accessor call expands to its guards plus the yielded place, traced as
  a caller-rooted PlaceTrace (no call emitted, no ABI); the standalone
  accessor compiles its guards plus an unreachable trap so downstream
  stages see no new shapes
- Accessor-body inference runs on demand at expansion and overlays the
  caller's resolved types
- rue-air unit coverage for inlining, gating, every escape shape,
  exclusivity conflict, and body well-formedness

Known limitation, detailed in the PR: the incremental provider body
path prunes callee declarations from each body request's RIR, so
call-site expansion cannot see accessor bodies there; completing the
feature needs body-input changes in the query nucleus, which this
change deliberately does not touch.

Toward RUE-662. Part of RUE-1015.
…erage

Pull the documentation and coverage deliverables forward while the
provider-eligibility fallback waits on RUE-1033:

- Add spec chapter 6.6 (Borrow Accessors) with rules 6.6:1-6.6:13, the
  `yield` keyword row in 2.4:2, and `result`/`yield_expr` grammar
  productions in appendix A.
- Amend the formal core with the (Accessor-Call) rule, accessor body
  well-formedness, and the inlined-call dynamics note in section 5.8,
  plus the section 8 traceability row.
- Add spec cases for chapter 6.6: declaration/body rules and the
  guard-trap dynamics case pass through today's driver and are required
  coverage; the remaining call-site cases (escapes, exclusivity,
  drop-glue read, in-place execution) run as preview-allowed-to-fail
  until the eligibility change lands, with matching
  KNOWN_UNCOVERED_NORMATIVE entries for 6.6:8-6.6:11.
- Re-check the accessor declaration shape (E0257 receiver, E0260
  parameter modes) in the shared body engine so those rules hold on
  every analysis host, including the incremental driver.
- Point the ADR-0062 frontmatter at the `borrow_accessors` feature name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RwaF7SFHXcBuKdYrnwi5G
The oracle-diff audit classifies a spec case's expected trap from its
runtime_error string, and "index out of bounds" names the built-in
IndexOutOfBounds trap — but the accessor's guard is spelled
@Panic("index out of bounds"), which traps as UserPanic. Use the
established user-panic assertion shape (exit_code = 101 with
stderr_contains = "panic: ...") so the expected trap kind matches the
one the program actually raises.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RwaF7SFHXcBuKdYrnwi5G
@steveklabnik
steveklabnik force-pushed the steve/rue-662-borrow-accessors branch from 9f91a33 to da6a9dc Compare July 29, 2026 15:52
@steveklabnik
steveklabnik enabled auto-merge July 29, 2026 15:52
@steveklabnik
steveklabnik added this pull request to the merge queue Jul 29, 2026
Merged via the queue into trunk with commit 7fec828 Jul 29, 2026
24 checks passed
@steveklabnik
steveklabnik deleted the steve/rue-662-borrow-accessors branch July 29, 2026 16:04
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.

2 participants