Skip to content

Commit 0e7125b

Browse files
committed
docs(d5): correct the unresolved-log contract; test return extern boundary
CodeRabbit review: the solve_with_log / module docstrings claimed the extern boundary is the ONLY source of `unknown`. It isn't — resolve_return also yields `unknown` (without logging) for a return-forward cycle, an un-remappable aliasOf, and an unrecognised return kind, and lookup does for a missing param index. Those are intrinsic, precision-safe, deterministic-from-input degradations, not "gave up on something external", so they are intentionally not logged. Soften the wording: the log covers extern (outside-the-analyzed-set) boundaries, not every `unknown`. Also add a forward-return extern-boundary log assertion — the return chase is a separate code path from the param-forward case the existing test covered, so a regression in return-boundary logging would otherwise slip through. Docs/test only — no behaviour change. ownership 44/44, ownir 194/194, mypy --strict, ruff all green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019846YSZ35c7CdkWQ1qX5gm
1 parent e2bb4ca commit 0e7125b

2 files changed

Lines changed: 18 additions & 7 deletions

File tree

‎ownlang/ownership.py‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@
2828
at the lattice bottom (⊥, "no evidence yet") rather than at a spurious `no`, so a
2929
method that disposes on its base case and recurses otherwise resolves to `must`
3030
(every *terminating* path disposes), and mutual recursion that never disposes
31-
settles at `no`. A forward to an unsummarized (extern) callee is the only residual
32-
`unknown`, and `solve_with_log` surfaces every such boundary — never a guessed
33-
`must`. That keeps the project's precision-first stance: we only ever *claim*
34-
transfer when we can prove it.
31+
settles at `no`. A residual `unknown` is never a guessed `must`: it comes either
32+
from an extern (unsummarized) callee — which `solve_with_log` surfaces in its log —
33+
or from a deliberate precision-safe degradation intrinsic to the input (a
34+
return-forward cycle, an `aliasOf` that cannot be re-mapped without the call's arg
35+
mapping, an unrecognised or sparse shape), which is not logged. That keeps the
36+
project's precision-first stance: we only ever *claim* transfer when we can prove it.
3537
"""
3638

3739
from __future__ import annotations
@@ -243,9 +245,12 @@ def solve_with_log(skeletons: Iterable[MethodSkeleton]) -> tuple[
243245
"""Resolve every method's MOS by a summary fixpoint over the call graph's SCC
244246
condensation.
245247
246-
Returns (summaries-by-key, unresolved-log). The log names every forward that
247-
crosses an extern (unsummarized) boundary — the only place a transfer or return
248-
degrades to `unknown`. There is no depth cap and so no silent truncation: the
248+
Returns (summaries-by-key, unresolved-log). The log names every forward — param
249+
or return — that crosses an extern (unsummarized) boundary: the `unknown`s that
250+
come from *outside* the analyzed set. It is NOT a log of all `unknown`s — the
251+
intrinsic, precision-safe degradations (a return-forward cycle, an un-remappable
252+
`aliasOf`, a missing param index or unrecognised shape) are deterministic from
253+
the input and not logged. There is no depth cap, so no work is truncated: the
249254
condensation makes the work linear, and recursion is solved, not cut off."""
250255
sk: dict[str, MethodSkeleton] = {}
251256
for s in skeletons:

‎tests/test_ownership.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ def expect(cond, msg):
139139
_, log2 = solve_with_log([_m("Caller", _p(0, PathAction("forward", "Extern", 0)))])
140140
expect(any("Extern#0" in e for e in log2), "extern forward is logged, not silent")
141141

142+
# The return chase logs its extern boundary too (a separate code path from the
143+
# param forward above) — a forward-return to an unsummarized callee.
144+
s3, log3 = solve_with_log([_m("Caller", ret=ReturnSkeleton("forward", callee="Extern"))])
145+
expect(s3["Caller"].returns == "unknown", "forward-return to extern -> unknown")
146+
expect(any("return Extern" in e for e in log3), "extern forward-return is logged")
147+
142148
# A deep but acyclic chain must not blow Python's recursion limit (the solver is
143149
# iterative end to end; a RecursionError here would, via the bridge's catch-all,
144150
# drop the WHOLE input's MOS to empty over one long chain). 3000 > default limit.

0 commit comments

Comments
 (0)