Skip to content

Error handling M1/M2: facade errors, HostFunction.parity, divergence register; pin core v0.110.0-dev.4 - #512

Merged
runyaga merged 20 commits into
mainfrom
fix/error-m1a-m2a
Oct 20, 2026
Merged

runyaga merged 20 commits into
mainfrom
fix/error-m1a-m2a

Conversation

@runyaga

@runyaga runyaga commented Oct 20, 2026

Copy link
Copy Markdown
Owner

Implements the dart_monty part of milestones M1 and M2 of issue 9 of the dart_monty_extras repository (error handling), and pins dart_monty_core v0.110.0-dev.4. Upstream pydantic-monty 1.1.0 is the specification. Every change is additive.

Changes

  • Pin dart_monty_core v0.110.0-dev.4 (e962659) in the root and example pubspecs, pages.yaml, the docs and the demo pages. The pubspec comment states that core is released as git tags only.
  • The facade re-exports Frame, MontyRuntimeError, MontySyntaxError, MontyTypingError, MontyCrashedError, MontyExceptionValue and FrameTooLargeException, so a consumer reads traceback frames and the error subtype from the dart_monty import alone.
  • BridgeRunError gains displayMessage, cause, causeStack and throwCause(). extractBridgeResult rethrows through throwCause().
  • HostFunction.parity registers a host function whose arguments and result follow 1.1.0. Its handler may return any Dart object: null becomes None, and an object with no Monty form raises Python TypeError with upstream's "Cannot convert ..." text. A call that its parameters cannot bind raises Python TypeError with the CPython message, in upstream's order: keywords in call order (unexpected keyword, multiple values), then too many positional, then missing. The original HostFunction constructor is unchanged.
  • docs/reference/upstream-divergences.md lists the behaviours that differ from 1.1.0: D-1 (the legacy binder keeps RuntimeError), D-2 (an opaque return is impossible through the legacy handler type) and D-3 (dart_monty checks argument types against the schema). It inherits core's C-1 to C-12. The pinning tests name their row id.
  • DCM baselines: unnecessarily-public 71 to 75 (owner-approved: +3 for the BridgeRunError getters, +1 for HostFunction.parity). The lib ratchet drops 9 to 0 and the test ratchet 844 to 834.

Test-count floors are unchanged.

Tests

Each behaviour change was committed as a failing test before its fix.

Unit Red on the base Green after
Error parity test/integration/error_parity_test.dart: 32 cases recorded from pydantic-monty 1.1.0 (tool/record_error_parity.py), comparing exception type, message, every display format, traceback frames, data and external calls. Fails to compile on the base: the error types are not exported VM, dart2js, dart2wasm
Facade errors and BridgeRunError the error types and getters do not exist VM, dart2js, dart2wasm
HostFunction.parity the constructor does not exist; then two(1, 2, a=4) binds silently; then two(1, 2, 3, a=4) reports the positional count instead of multiple values test/integration/host_function_parity_test.dart, test/src/host/schema_test.dart, all backends

Local results at this head:

Cell integration acceptance portable
vm 709 12 69
chrome-dart2js 547 12 69
chrome-dart2wasm 547 12 69

dart analyze --fatal-infos is clean, bash tool/dcm_host_gate.sh is green, and check_core_ref, check_page_versions and check_demo_pages pass. The VM cell's native workspace tests need the build_worker: true opt-in from docs/contributor/testing.md, as CI adds it.

Pre-PR review

Each unit had a design review and a code review from a second reviewer. Findings fixed:

  • the parity binder let a keyword overwrite a positional argument;
  • the parity binder checked the positional count before keywords, which reverses upstream's precedence (confirmed against pydantic-monty 1.1.0);
  • missing tests for a parity handler that throws a Dart exception and for the legacy binder on DispatchMode.future;
  • unnecessarily public parity handler fields.

Declined:

  • throwCause() adds one stack frame to the StateError for a BridgeRunError with no cause. Type and message are unchanged.
  • The parity handler's FutureOr result is awaited unconditionally. Dispatch is asynchronous on both paths, so Python cannot observe the difference.

Follow-ups

  • dart_monty_extras repins to the dev tag cut from this merge and adds its lowered-tool parity test.
  • The remaining M1 and M2 rules (diagnostics envelope, callback history) are later milestones of issue 9 of the dart_monty_extras repository.

tool/bake_error_parity.dart bakes test/fixtures/error_parity/cases.json
into error_parity.g.dart, so VM, dart2js and dart2wasm tests read the same
bytes. error_parity.dart reads the baked cases, and
error_parity_fixture_test checks the bake is current.
…cade

error_facade_test imports only package:dart_monty and catches
MontyRuntimeError with its traceback frame. bridge_run_error_test checks
BridgeRunError's display, traceback, exception value, prints, cause and
cause stack for a Python error, a crash, an infrastructure failure and a
manually built event. Fails on the base: the facade does not export the
error types and BridgeRunError has none of the getters.
…getters

dart_monty re-exports Frame, MontyRuntimeError, MontySyntaxError,
MontyTypingError, MontyCrashedError, MontyExceptionValue and
FrameTooLargeException. BridgeRunError gains displayMessage, traceback,
exceptionValue, cause, causeStack, prints and throwCause(); its const
constructor, fields and message are unchanged. SessionBridge records the
(MontyError, StackTrace) pair with recordRunFailure, so cause and
causeStack work for Python and infrastructure failures. The surface
goldens and facade tests follow the new names; six negative compile
cases for now-exported types are removed, and tests drop core imports the
facade now covers.
error_parity_test imports only package:dart_monty and runs each fixture
case through MontyRuntime, comparing the error class, exception type and
message, display(), traceback frames, prints and values with the
recorded pydantic-monty 1.1.0 outcome. All 32 pass on VM, dart2js and
dart2wasm. On the base before the facade exports it fails to compile:
MontyRuntimeError and the other error types are not reachable.
The memory-limit case was recorded with digit runs written as N; the
fixture marks it normalize_numbers and the test applies the same rewrite.
…d throwCause

prints duplicated printOutput, exceptionValue is exception?.exception and
traceback is derivable from exception, so they are dropped.
extractBridgeResult's infrastructure branch calls throwCause(), which
rethrows the recorded failure with its stack or throws
StateError(message), as before. The fixture test names its repeated
matcher (DCM prefer-moving-to-variable).
displayMessage, cause and causeStack are deliberate public API for facade
consumers (error-handling spec dm E2) and have no in-package caller.
Approved by the owner after trimming the duplicate getters; every other
count is unchanged.
tool/record_error_parity.py re-recorded the 32 cases from pydantic-monty
1.1.0. Every field recorded before is unchanged; each exception now also
carries str, type-msg, msg, the exception value, structured data and
frame filename and source line.
… getters

The facade comment and the facade behaviour test name 25 core names, the
count after the withdrawal of CrashCause and ChainWireStatus.
…s and binding failures

Covers an opaque return (uncaught and caught as TypeError), direct
invokeHostFunction, a null return, the x66_02 call with and without a
registered tag parameter, missing and surplus positional arguments,
schema type mismatch (RuntimeError, unchanged), the legacy binder
(D-1, RuntimeError) and a future-dispatch parity function. Fails on the
base: HostFunction.parity does not exist.
…and binding failures

HostFunction.parity takes a ParityHostFunctionHandler that may return any
Dart object. Dispatch registers it with core's MontyHostFunction.parity or
parityCoroutine, so an unconvertible return reaches Python as core's
TypeError and null becomes None. Handler evaluation and result
serialization are separate catch blocks. The bridge event and a direct
invokeHostFunction carry the same TypeError text from one message
function. A parity binder gives CPython's TypeError texts for an
unexpected keyword, missing arguments and too many positional arguments.
Schema type validation and the legacy constructor and binder are
unchanged.
…ror getters

parity_dispatch_test drives HostFunction.parity through binding, handler
invocation, result conversion and the error paths with fakes, and covers
the browser handler slot in a browser-only test. bridge_run_error_getters_test
covers displayMessage, cause, causeStack and throwCause. The integration
suite already runs these paths against the real worker.
@runyaga
runyaga marked this pull request as ready for review October 20, 2026 03:48
@runyaga
runyaga merged commit e2a92cd into main Oct 20, 2026
15 checks passed
@runyaga
runyaga deleted the fix/error-m1a-m2a branch October 20, 2026 03:48
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.

1 participant