Close #11/#12: defer leading-dot member args to the callee; make bridge errors catchable - #13
Merged
Merged
Conversation
…able Closes #11, #12 — the two follow-on blockers from the XCUITest work in #9. #11 (implicit member): a leading-dot member in argument position (`app.descendants(matching: .any)`, `element.typeKey(.escape)`) no longer errors before any type context is consulted. In evaluateArg it resolves against the parameter's context type when one is known (a bridge static-let like `.utf8`/`.whitespaces`, a user enum case), and otherwise defers to the callee as an unresolved `.enumValue(typeName: "", caseName:)` marker — a bridged parameter has no declared type for the interpreter to consult, so the receiving bridge decides what the case means and a bridge expecting something else raises its own clearer error. The deferral is scoped to argument position; a stray `.foo` in general expressions stays a hard error. #12 (catchable bridge errors): a RuntimeError (or raw host error) raised inside a .method/.computed/.subscriptGet body used to fly past every catch clause and end the script. execute(do:) and evaluate(try:) now surface any non-control-flow error as a catchable `.opaque(typeName: "Error", …)` value — `catch { print(error) }` binds it, `try?` yields nil, and an unmatched clause re-raises the original so an unhandled error still ends the script with its message. Control-flow signals (return/break/continue/fallthrough/exit) are re-thrown explicitly rather than swept up by the catch-all, via an allowlist as the issue asks. 15 new tests (implicit-member deferral incl. a fake element-query bridge reading the marker; catchable bridge errors incl. control-flow bypass and exit); 531 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7ebc71b730
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
An internal review found both #11 and #12 over-reached; narrowed both. #11: the leading-dot deferral fired even when the receiver was a builtin container, so `[1,2,3].contains(.foo)` silently returned false (the marker never equals a real element) instead of erroring. evaluateArg now only defers to the marker when the receiver is a bridged (opaque) type — the only receiver with a bridge that can interpret the case name. A bare `.foo` to a builtin method, or in general position, stays the hard "no such member" error. #12: the do/catch catch-all surfaced EVERY non-control-flow error as catchable, which swallowed Swift's uncatchable traps (fatalError / precondition / assert / division-by-zero) and programming errors (undefined identifier, no-such-member) — masking script bugs. Reverted the do/catch change and instead wrap at the bridge-invocation boundary (callingBridge): only an error raised *inside* a bridge body becomes a catchable UserThrowSignal. Traps, dispatch failures, and operator errors are raised by the interpreter itself, outside any bridge, so they keep terminating the script exactly as stock Swift traps. Control-flow signals still pass through untouched. New boundary tests lock it in: builtin-container bare members stay fatal; fatalError / precondition / division-by-zero / undefined-id / no-such-member stay uncatchable; try? doesn't suppress a trap; an embedder bridge that throws is caught. 541 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 9, 2026
Closed
odrobnik
added a commit
that referenced
this pull request
Aug 10, 2026
Codex review: a custom host error thrown from a `registerGlobal` closure escaped `invoke` raw — neither dispatcher catch matched it, so it rendered as a bare `error:` line with no call site, unlike the same error thrown from a bridge body. Route `.builtin` / `.builtinMethod` invocation through a new `callingBuiltin` wrapper: an arbitrary host error becomes a catchable `ScriptError` stamped with the invoking call's offset (the same #13 contract bridges follow), while a `RuntimeError` still passes through raw so the diagnostic builtins (`fatalError`, `precondition`, `assert`) keep terminating the script trap-style — positioned by the dispatcher on the way out. Control-flow signals and `ScriptExit` pass through untouched. Also wrap the three `.staticComputed` bridge invocation sites in `callingBridge` — same gap, same fix as instance computed properties. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #11, #12 — the two follow-on blockers from the XCUITest work in #9.
#11 — leading-dot member access defers to the callee
A leading-dot member in argument position (
app.descendants(matching: .any),element.typeKey(.escape)) no longer errors before any type context is consulted. InevaluateArgit resolves against the parameter's context type when one is known (a bridgestatic letlike.utf8/.whitespaces, a user enum case), and otherwise — only when the receiver is a bridged (opaque) type — defers to the callee as an unresolved.enumValue(typeName: "", caseName:)marker. A bridged parameter has no declared type for the interpreter to consult, so the receiving bridge decides what the case means; a bridge expecting something else raises its own clearer error.Deferral is deliberately narrow: a bare member passed to a builtin method (
[1,2,3].contains(.foo)) or used in general position (let x = .any) stays a hard error, matching stock Swift's "no such member".#12 — errors thrown from bridges are catchable
An error raised inside a bridge body (
.method/.computed/.subscriptGet/…) used to fly past everycatchand end the script. Bridge invocations now surface a non-control-flow error as a catchable.opaque(typeName: "Error", …)value, socatch { print(error) }binds it andtry?yieldsnil.Crucially this is scoped to the bridge-invocation boundary, not a blanket
do/catchchange: Swift's uncatchable traps (fatalError/precondition/assert, division-by-zero, out-of-bounds) and programming errors (undefined identifier, no-such-member) are raised by the interpreter itself — outside any bridge closure — so they keep terminating the script as they must. Control-flow signals (return/break/continue/fallthrough/exit) are re-thrown untouched.Tests
New suites for both: implicit-member deferral (incl. a fake element-query bridge that reads the marker, plus builtin-collection and general-position stay-fatal cases) and catchable bridge errors (incl. trap/programming errors staying fatal, control-flow bypass, and
exit).🤖 Generated with Claude Code