Skip to content

Layer 3 of #626: CodegenSkip infrastructure + convert 104 SILENT_SKIPs#658

Merged
aallan merged 12 commits into
mainfrom
layer3-codegen-skip-infra
May 11, 2026
Merged

Layer 3 of #626: CodegenSkip infrastructure + convert 104 SILENT_SKIPs#658
aallan merged 12 commits into
mainfrom
layer3-codegen-skip-infra

Conversation

@aallan

@aallan aallan commented May 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • Layer 3 of Cross-cutting: convert 'translate returns None → silent skip' failures into loud diagnostics #626 ("silent-translator-skip" campaign). Introduces CodegenSkip / CodegenInvariantError infrastructure in vera/skip.py with catch handlers at the _compile_fn / _compile_lifted_closure boundaries.
  • Audited all 372 return None sites in vera/codegen/** and vera/wasm/** and classified each into SILENT_SKIP / PROPAGATE / OPTIONAL_RETURN / INVARIANT_DEFENSIVE buckets.
  • Converted 104 SILENT_SKIP sites across 6 files to raise CodegenSkip(node, reason). Each now surfaces as a source-located [E602] pointing at the unsupported AST node, rather than the generic enclosing-function-level [E602] the legacy propagate-None path produced.

Closes #626.

What's in this PR

Built up across 7 commits, each independently green so git bisect works:

  1. bf30a0fPhase 1: infrastructure (vera/skip.py, catch handlers in _compile_fn + _compile_lifted_closure, new E699 error code). Behaviourally inert: no translator raises yet.
  2. 41c5c7cPhase 3a: convert 55 SILENT_SKIPs in calls_arrays.py. Introduces a shared _array_elem_triad_or_skip(arr_arg, *, role) helper that centralises the (_infer_concat_elem_type, _element_mem_size, _element_wasm_type) triad every array combinator runs upfront. Also fixes a circular import discovered during this commit — vera/codegen/skip.pyvera/skip.py.
  3. a10d202Phase 3b: convert 24 SILENT_SKIPs in data.py (constructors / patterns / let-destruct / match arms / array literals / index expressions).
  4. 1290e35Phase 3c: convert 11 SILENT_SKIPs in calls_containers.py (all Map<K, Array> / Set<Array> rejection points — the # Map<K, Array<T>> not supported (#475 finding 5) markers).
  5. 5523db4Phase 3d: convert 9 SILENT_SKIPs in calls_handlers.py (show/hash dispatch + State/Exn handle expressions).
  6. f9da64bPhase 3e+3f: convert 5 SILENT_SKIPs in context.py + calls.py (central dispatcher fallthroughs + call-target guard rail). Also documents one borderline site (functions.py L29) reclassified as PROPAGATE since _is_compilable already emits its own diagnostics.
  7. dffbc3e — Phase 4 docs: CHANGELOG entry, ROADMAP cleanup (delete Cross-cutting: convert 'translate returns None → silent skip' failures into loud diagnostics #626 row, renumber).

The 39 INVARIANT_DEFENSIVE sites and the 154 PROPAGATE sites that may now be unreachable are tracked in #657.

Test plan

Design notes

Why exceptions, not return-value Union types. The catch handler at _compile_fn needs to attach enclosing-function context (name, declaration span) to the diagnostic, but the translator that detects the unsupported shape doesn't know that context. Exceptions let the translator carry just the local information (node + reason) and let the catch handler enrich it. A Result[list[str], CodegenSkip] return type would force every translator-call site to either propagate or explicitly handle the skip, which is what the legacy if x is None: return None propagation chain already does and the bug class #626 is about.

Why vera/skip.py and not vera/errors.py. Every VeraError subclass carries a fully-formed Diagnostic at construction time. Codegen skips need to defer diagnostic construction until the catch handler can attach enclosing-function context. Different lifecycle, different module. Also avoids a circular import: vera.wasm.calls_arrays raises CodegenSkip, and vera.codegen.__init__ eagerly imports vera.codegen.core which imports from vera.wasm. Top-level vera/skip.py breaks the cycle cleanly.

Why not just unify with _warning(...) calls. Half the legacy "silent skip" sites are several translator-call layers deep inside the WASM mixin chain. Calling _warning(...) and then return None works at the leaf, but the warning has the wrong span (the leaf doesn't know the enclosing-function context). The exception mechanism lets the leaf say "I couldn't translate this specific AST node, here's why" and the catch handler builds the structured diagnostic from both pieces of information.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Unsupported constructs now emit precise, source‑located diagnostics ([E602]) instead of being silently dropped.
    • Many previously‑silent translator failure paths now surface proper diagnostics across closures, functions, arrays, containers, WASM translation, type inference and pattern matching.
  • New Features

    • New internal‑compiler error diagnostic ([E699]) reported with error severity and propagated to CLI exit.
  • Tests

    • Added tests to verify node‑level skip reasons and accurate diagnostic locations.
  • Documentation

    • Changelog and roadmap updated to reflect these changes and test counts.

Review Change Stack

aallan and others added 7 commits May 11, 2026 16:28
…Phase 1)

Layer 3 of the silent-translator-skip campaign converts the
implicit `return None` propagation chain across codegen into
explicit, source-located diagnostics.  This commit introduces
the infrastructure — exception classes and catch handlers — and
makes no behavioural change: no translator raises CodegenSkip
yet, so the try/except wrappers are inert.

What changed
- New `vera/codegen/skip.py` with two exception classes:
  - `CodegenSkip(node, reason)` — raised by a translator when an
    AST node shape isn't yet supported by the WASM backend.
    Caught at the `_compile_fn` / `_compile_lifted_closure`
    boundary and converted to a structured [E602] diagnostic
    with the unsupported-node's source span (rather than the
    enclosing-function's, which is all the legacy `return None`
    path can give us).
  - `CodegenInvariantError(msg, node=None)` — raised when codegen
    sees a state that type-check should have rejected.  Surfaced
    as [E699] "Internal compiler error" with a "please file a
    bug" rationale.
- `vera/codegen/functions.py::_compile_fn` — wraps
  `ctx.translate_block(decl.body, env)` in a try/except for both
  exception types.  Legacy `body_instrs is None` branch stays as
  the catch-all until Phase 3 converts the remaining call sites.
- `vera/codegen/closures.py::_compile_lifted_closure` — same
  pattern around the closure-body translate call.  The
  enclosing-fn drop is handled by Layer 2 (#636); these handlers
  just emit the closure-level [E602] before returning None.
- `vera/errors.py` — adds the `E699` code.

Why these aren't in `vera/errors.py`
Every `VeraError` subclass carries a fully-formed `Diagnostic`
at construction time.  The codegen-skip path needs to defer
diagnostic construction: the translator that detects an
unsupported shape doesn't know the enclosing function name or
its declaration span (both needed for the [E602] message).  The
catch handler at the `_compile_fn` boundary has that context,
so these exceptions carry raw `(node, reason)` and let the
catch handler build the structured `Diagnostic`.

What's next
Phase 2 (audit): walk the ~367 `return None` sites across
`vera/codegen/` and `vera/wasm/`, classify each into one of
SILENT_SKIP / OPTIONAL_RETURN / INVARIANT_DEFENSIVE / KNOWN_GAP.

Phase 3 (convert): rewrite the ~30 SILENT_SKIP sites identified
by the audit to `raise CodegenSkip(node, reason)`.

Phase 4 (follow-up): the OPTIONAL_RETURN / KNOWN_GAP buckets
remain in a new follow-up issue with the audit table attached.

Verification
- `mypy vera/` clean (59 source files)
- `pytest tests/ -q` passes (3789 passed, 14 skipped) — confirms
  the infrastructure addition is behaviourally inert

Refs #626

Co-Authored-By: Claude <noreply@anthropic.invalid>
…se 3a)

55 silent-skip sites in `vera/wasm/calls_arrays.py` converted to
`raise CodegenSkip(node, reason)`.  Each now surfaces as a
source-located [E602] pointing at the unsupported AST node, rather
than the generic enclosing-function-level [E602] the legacy
`return None` propagation produced.

What changed
- New helper `_array_elem_triad_or_skip(arr_arg, *, role)` on
  `CallsArraysMixin`: centralises the `(_infer_concat_elem_type,
  _element_mem_size, _element_wasm_type)` triad that every array
  combinator runs upfront.  Pre-conversion this was 9 lines × 14
  combinators of identical bail-out shape.  Post-conversion it's
  one helper call per combinator + per-leg CodegenSkip raises in
  the helper.  All 6 `t_type = _infer_concat_elem_type(arr_arg);
  if t_type is None: return None; t_size = _element_mem_size(...);
  if t_size is None: return None; ...` blocks (filter, fold,
  reverse, any_all, find, sort_by) collapse to one line.
- `_translate_array_map` / `_translate_array_mapi` use the helper
  for the input element triad; the closure-return triad (b_type)
  still has its own raises since it uses a different inference
  helper (`_infer_closure_return_vera_type`).
- `_translate_array_append` raises on element-type / size / store-op
  failures.  Doesn't use the shared helper since its arg is the
  element value, not the array container.
- `_translate_array_concat` and `_translate_array_slice` retain
  their "provably-empty literal" fallback paths; the non-fallback
  branches raise CodegenSkip.
- `_translate_array_flatten` raises on the two AST-walk failures
  (input isn't Array<Array<T>>; inner type couldn't be recovered).
- Body-internal `_element_load_op` / `_element_store_op` checks
  inside every combinator's loop body raise CodegenSkip with a
  per-combinator role label.
- `_translate_array_sort_by`'s inner-loop `t_load2 / t_store2`
  defensive check raises on real failure (the `not t_is_pair` guard
  is preserved — pair-typed elements legitimately have no load_op).

Module path move
`vera/codegen/skip.py` → `vera/skip.py`.  The previous location
triggered a circular import: `vera.wasm.calls_arrays` imports from
`vera.codegen.skip`, but `vera.codegen.__init__` eagerly imports
`vera.codegen.core` which imports from `vera.wasm`.  Moving skip
out of both packages (to top-level `vera/`) breaks the cycle —
both `vera/codegen/` and `vera/wasm/` can now import it freely.
Import-statement updates in `vera/codegen/functions.py` and
`vera/codegen/closures.py`.

Verification
- mypy clean (59 source files)
- pytest 3789 passed, 14 skipped — confirms behaviour unchanged
  on every well-typed input the test suite covers (no test
  currently exercises an unsupported-element-type path that
  would now raise; those paths exist as code, not as tests, and
  the [E602] warning emission is the observable change there).

Refs #626

Co-Authored-By: Claude <noreply@anthropic.invalid>
24 silent-skip sites in `vera/wasm/data.py` converted to
`raise CodegenSkip(node, reason)`.

Sites by translator
- `_translate_nullary_constructor`: unknown ctor lookup.
- `_translate_constructor_call`: unknown ctor lookup + per-arg
  inference failure (now raises with the offending arg's span,
  not the whole ctor call's).
- `_translate_let_destruct`: type-slot resolution + WASM-type
  resolution per binding.
- `_translate_match`: scrutinee inference failure + empty-arms
  guard.  The empty-arms case is audit-classed as borderline
  (type-check rejects empty match, but a cross-module import
  could still hand us one); converted with a top-level guard in
  `_translate_match` that runs before `_compile_match_arms` so
  the diagnostic carries the MatchExpr's span.
- `_translate_match_condition`: nullary/constructor pattern ctor
  lookup, plus nested-tag-check propagation.
- `_setup_match_arm_env`: BindingPattern slot-name failure +
  ConstructorPattern ctor lookup + unsupported pattern fallthrough.
- `_extract_constructor_fields`: BindingPattern slot-name +
  field WASM-type + nested ctor lookup + unknown sub-pattern
  fallthrough.
- `_sub_pattern_wasm_type`: BindingPattern slot-name (the only
  branch that's a silent skip; wildcard / no-field branches stay
  as `return None` since callers handle the legitimate "no type
  here" case).
- `_collect_nested_tag_checks`: per-field WASM-type failure +
  nested ctor lookup.
- `_translate_array_lit`: element-type inference + memory size +
  store-op.
- `_translate_index_expr`: index element-type inference + memory
  size + load-op.

Verification
- mypy clean
- pytest 3789 passed, 14 skipped

Refs #626

Co-Authored-By: Claude <noreply@anthropic.invalid>
… Phase 3c)

11 silent-skip sites in `vera/wasm/calls_containers.py` converted
to `raise CodegenSkip(call, reason)`.

All 11 sites follow the same pattern: the translator calls
`_map_wasm_tag(type)` on a key/value/element vera_type and bails
when the helper returns None (which is the case for Array-typed
K, V, or element — #475 finding 5 area).  Pre-conversion every
site was a one-line `return None` with a `# Map<K, Array<T>> not
supported` marker comment; post-conversion each raises CodegenSkip
anchored to the relevant FnCall so the [E602] diagnostic points
at the call expression rather than the enclosing function.

Sites covered (one per Map/Set operation that touches K/V/elem):
- `_translate_map_insert` — key + value type tagging
- `_translate_map_get` — key type tagging (×2: K and V independently)
- `_translate_map_contains` — key type tagging
- `_translate_map_remove` — key type tagging
- `_translate_map_keys` — key type tagging
- `_translate_map_values` — value type tagging
- `_translate_set_add` — element type tagging
- `_translate_set_contains` — element type tagging
- `_translate_set_remove` — element type tagging
- `_translate_set_to_array` — element type tagging

The helper `_map_wasm_tag` (line 380) continues to return None for
the Array case; the audit classified it as OPTIONAL_RETURN since
its callers handle None and the empty-collection paths still need
the helper's None-allowing branch (uninferred-type fallthrough to
the ``"b"`` tag is the documented behaviour for `set_new()` /
`map_keys(map_new())` round-trips).

Verification
- mypy clean
- pytest 3789 passed, 14 skipped

Refs #626

Co-Authored-By: Claude <noreply@anthropic.invalid>
…hase 3d)

9 silent-skip sites in `vera/wasm/calls_handlers.py` converted to
`raise CodegenSkip(node, reason)`.

Sites by translator
- `_translate_show`: arg-type inference failure + unsupported
  show target (vera_type not in _SHOW_DISPATCH and not String/
  Unit/Decimal).
- `_translate_hash`: arg-type inference failure + unsupported
  hash target.
- `_translate_handle_expr`: effect isn't an EffectRef + handler
  type not State/Exn.
- `_translate_handle_state`: State<T> type_arg isn't a NamedType.
- `_translate_handle_exn`: Exn<E> type_arg isn't a NamedType +
  zero-clauses guard.

The "arg-instrs is None" propagate at line 87 (now post-conversion)
is preserved as `return None` since it's pure forwarding.  Same
for the post-translate-block None checks in `_translate_handle_state`
and `_translate_handle_exn`.

Verification
- mypy clean
- pytest 3789 passed, 14 skipped

Refs #626

Co-Authored-By: Claude <noreply@anthropic.invalid>
… 3 Phase 3e+3f)

5 silent-skip sites converted across the two central dispatcher
files in vera/wasm/.

`vera/wasm/context.py` (4 sites)
- `translate_expr` — dispatch fallthrough at the end of the
  ``isinstance(...)`` chain.  Pre-conversion an unknown AST node
  (e.g. a new expr kind added to grammar but not wired up here)
  silently fell through to `return None`.  Now raises CodegenSkip
  with the expression's source span.
- `translate_block` LetStmt — `_type_expr_to_slot_name` returned
  None for the binding's declared type.
- `translate_block` LetStmt — `_slot_name_to_wasm_type` returned
  None for the resolved slot name.
- `translate_block` — unknown statement type at the end of the
  ``isinstance(stmt, ...)`` chain.

`vera/wasm/calls.py` (1 site)
- `_translate_call` — the "call target not in module" guard rail.
  Reachable in cross-module / prelude-mangling edge cases (e.g.
  the #604 option_map mono-suffix mismatch where mono rewrites the
  callee name but the rewritten name isn't in `_known_fns`).

Borderline site NOT converted: `vera/codegen/functions.py::_compile_fn`
line 29 (`_is_compilable(decl)` rejection).  The audit flagged this
as borderline; on inspection `_is_compilable` itself calls
`self._warning(...)` in every False-returning branch, so the
diagnostic is already emitted upstream.  This is PROPAGATE — the
caller's `return None` after a False is pure forwarding.  No
conversion needed.

Total SILENT_SKIPs converted so far: 104 (55 + 24 + 11 + 9 + 5).
105 was the audit estimate; the borderline functions.py site was
re-classified post-audit as PROPAGATE, leaving 104.  All
SILENT_SKIPs identified by the audit are now loud.

Verification
- mypy clean
- pytest 3789 passed, 14 skipped

Refs #626

Co-Authored-By: Claude <noreply@anthropic.invalid>
CHANGELOG: add an Unreleased entry for the Layer 3 work
(infrastructure + 104 SILENT_SKIP conversions, with the
follow-up #657 reference for the remaining INVARIANT_DEFENSIVE
and PROPAGATE buckets).

ROADMAP: delete the row for #626 from the stabilisation tier
since Layer 3 closes it.  Renumber the remaining stabilisation
rows (#596 → 2, #597 → 3, etc.) and the agent-integration tier
rows (#222 → 8, #523 → 9, #370 → 10).  #597's "Pairs with #626"
note adjusted to past tense since #626 has closed.

Refs #626 #657

Co-Authored-By: Claude <noreply@anthropic.invalid>
@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds vera/skip.py (CodegenSkip, CodegenInvariantError), converts many return None translator paths to raise these exceptions, catches them at function/closure boundaries to emit source-anchored [E602]/[E699] diagnostics, updates tests/docs and roadmap, and adds a CI check to track unexpected skips.

Changes

Silent-Skip Exception Infrastructure and Top-Level Integration

Layer / File(s) Summary
Exception Type Definitions
vera/skip.py
Introduces CodegenSkip(node, reason) and CodegenInvariantError(msg, node=None).
Error Code Registration
vera/errors.py
Adds E699: "Internal compiler error" to the error registry.
Codegen Core Helper
vera/codegen/core.py
Adds CodeGenerator._error(...) to record error-severity diagnostics for E699.
Core Translator Context
vera/wasm/context.py
translate_expr/translate_block now raise CodegenSkip for unsupported exprs, missing slot/WASM types, and unsupported statements.
Data / Constructor / Match / Array
vera/wasm/data.py
Converts many silent failures (unknown constructor/layout, constructor-arg WASM inference, match scrutinee/empty-arms, pattern binding failures, nested-pattern lookups, array literal/index issues) to raise CodegenSkip.
Array Combinator Helper & Updates
vera/wasm/calls_arrays.py
Adds _array_elem_triad_or_skip() and updates array combinators (append, concat, slice, map, filter, fold, reverse, mapi, any, all, find, flatten, sort_by) to raise CodegenSkip on failures.
Calls (unknown target)
vera/wasm/calls.py
Unknown function-target guard now raises CodegenSkip instead of returning None.
Map/Set Container Translators
vera/wasm/calls_containers.py
Map/Set call translators now raise CodegenSkip for unsupported key/value/element inference.
Show/Hash/Handler Translators
vera/wasm/calls_handlers.py
show, hash, and effect-handler translation now raise CodegenSkip on inference/unsupported-type failures and for zero-clause exn handlers.
Function Boundary
vera/codegen/functions.py
Wraps ctx.translate_block() in try/except catching CodegenSkip (emit E602) and CodegenInvariantError (emit E699); legacy body_instrs is None fallback remains.
Closure Boundary
vera/codegen/closures.py
Wraps closure body translation in try/except to catch CodegenSkip/CodegenInvariantError, harvest interpolation failures, emit E602/E699, and return None.
Docs / Tests / Roadmap
CHANGELOG.md, ROADMAP.md, TESTING.md, tests/test_codegen.py
Document changes, renumber roadmap, update test counts, and add tests asserting node-specific E602 reason text and source location.
Tests
tests/test_codegen.py
New tests verify E602 descriptions include node-specific reasons and diagnostics point to offending call sites.

Sequence Diagram

sequenceDiagram
  participant Boundary as _compile_fn/_compile_lifted_closure
  participant Translator as ctx.translate_block (wasm translators)
  participant Skip as CodegenSkip
  participant Invariant as CodegenInvariantError
  participant Diagnostic as emit (CodeGenerator._warning/_error)

  Boundary->>Translator: request translation of body
  alt translator cannot translate
    Translator->>Skip: raise CodegenSkip(node, reason)
    Skip->>Boundary: exception
    Boundary->>Diagnostic: emit E602 with node.span
    Boundary-->>Boundary: return None
  else translator hits invariant
    Translator->>Invariant: raise CodegenInvariantError(msg, node?)
    Invariant->>Boundary: exception
    Boundary->>Diagnostic: emit E699 with inv.node.span via _error()
    Boundary-->>Boundary: return None
  else success
    Translator-->>Boundary: return body_instrs
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

  • #657: Follow-up to convert remaining invariant/propagate return None sites to CodegenSkip/CodegenInvariantError.

Possibly related PRs

  • aallan/vera#550: Touches _translate_call dispatch logic; relevant to unknown-target handling changes.
  • aallan/vera#598: Related closure-lifting changes intersecting _compile_lifted_closure.
  • aallan/vera#567: Changes in array/map translators and unsupported-type guards overlap with these edits.

Suggested labels

compiler, tests, docs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: Layer 3 infrastructure for CodegenSkip plus conversion of 104 silent-skip sites into explicit exceptions.
Linked Issues check ✅ Passed All coding requirements from #626 Layer 3 are met: CodegenSkip and CodegenInvariantError introduced, 104 silent-skip sites audited and converted, catch handlers installed at compile boundaries, and node-level diagnostics implemented.
Out of Scope Changes check ✅ Passed All changes are in-scope for Layer 3 of #626. Documentation updates (CHANGELOG, ROADMAP, TESTING) appropriately accompany code changes. Two new tests verify E602 node-level attachment, consistent with objectives.
Docstring Coverage ✅ Passed Docstring coverage is 96.43% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch layer3-codegen-skip-infra

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented May 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 40.17857% with 67 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.03%. Comparing base (637cddd) to head (9bead69).

Files with missing lines Patch % Lines
vera/wasm/data.py 11.53% 23 Missing ⚠️
vera/wasm/calls_arrays.py 60.00% 10 Missing ⚠️
vera/wasm/calls_containers.py 16.66% 10 Missing ⚠️
vera/wasm/calls_handlers.py 10.00% 9 Missing ⚠️
vera/codegen/core.py 16.66% 5 Missing ⚠️
vera/codegen/closures.py 42.85% 4 Missing ⚠️
vera/skip.py 75.00% 3 Missing ⚠️
vera/wasm/context.py 40.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #658      +/-   ##
==========================================
+ Coverage   90.95%   91.03%   +0.08%     
==========================================
  Files          59       60       +1     
  Lines       23197    23134      -63     
  Branches      259      259              
==========================================
- Hits        21098    21060      -38     
+ Misses       2092     2067      -25     
  Partials        7        7              
Flag Coverage Δ
javascript 57.36% <ø> (ø)
python 94.83% <40.17%> (+0.10%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@vera/codegen/functions.py`:
- Around line 228-243: The CodegenInvariantError except-block is missing the
interpolation-failure harvest that the CodegenSkip handler performs; before
calling self._warning in the CodegenInvariantError handler, invoke
self._harvest_interp_inference_failures(ctx) (using the same ctx variable used
in the CodegenSkip path) so any prior interpolation inference failures are
collected, then proceed to call self._warning with the same parameters
(inv.node/decl, message, rationale, error_code="E699") and return None as
before.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e509ffb7-69b5-440e-b9f3-0a72260d3463

📥 Commits

Reviewing files that changed from the base of the PR and between 637cddd and dffbc3e.

⛔ Files ignored due to path filters (1)
  • docs/llms-full.txt is excluded by !docs/**
📒 Files selected for processing (12)
  • CHANGELOG.md
  • ROADMAP.md
  • vera/codegen/closures.py
  • vera/codegen/functions.py
  • vera/errors.py
  • vera/skip.py
  • vera/wasm/calls.py
  • vera/wasm/calls_arrays.py
  • vera/wasm/calls_containers.py
  • vera/wasm/calls_handlers.py
  • vera/wasm/context.py
  • vera/wasm/data.py

Comment thread vera/codegen/functions.py Outdated
aallan and others added 2 commits May 11, 2026 19:12
CodeRabbit symmetry catch: the `CodegenInvariantError` catch-block
in `_compile_fn` emits the `[E699]` warning without first calling
`self._harvest_interp_inference_failures(ctx)`, whereas the
parallel `CodegenSkip` catch-block does call it before the
`[E602]` warning.

Empirically invariant violations fire early — before any
interpolation translation has populated
`ctx._interp_inference_failures` — so this is mostly insurance.
But keeping the two handlers structurally identical is the right
principle: any time we add a new failure side-channel above
either path, we want both to drain it.  Added the harvest call
plus a comment explaining the symmetry reasoning.

Refs #626 #658

Co-Authored-By: Claude <noreply@anthropic.invalid>
The Layer 3 audit (`#626`) classified `if helper_returned_None:
raise CodegenSkip` checks as SILENT_SKIP without verifying that
the helpers can actually return None for the codepaths reaching
them.  On inspection a number of these defensive guards are
unreachable: `_element_mem_size` / `_element_wasm_type` /
`_element_load_op` / `_element_store_op` all fall back to a
non-None default for non-pair types, and every call site is
inside an `else:` branch of `if X_is_pair:` (so the helper's only
None-returning input — pair types — never reaches it).

The raises themselves remain in place — they correctly become
defensive guards that fire only if a helper signature is ever
loosened.  But they shouldn't count against patch coverage, so
each gets `# pragma: no cover` with an inline rationale pointing
at the specific helper's fallback behaviour.

Pragmas added (all in `vera/wasm/calls_arrays.py`):

- 6 sites: `if X_size is None:` after `_element_mem_size(...)` —
  the helper has a `return 4` final fallback for any non-primitive
  non-pair type, so this branch is unreachable.
- 3 sites: `if X_wasm is None:` after `_element_wasm_type(...)` —
  the helper has a `return "i32"` final fallback, same shape.
- 9 sites: `if X_load is None:` / `if X_store is None:` inside
  `else:` branches of `if X_is_pair:` — load/store helpers return
  None only for pair types, guarded above.
- 4 sites: `if X_load is None or X_store is None:` combined checks
  with the same guarding.
- 2 sites inside `_array_elem_triad_or_skip` helper itself: legs
  2 (size) and 3 (wasm-type) are dead by the same reasoning.  Leg
  1 (`_infer_concat_elem_type` returns None) remains coverable.

Total: 24 pragma annotations.  No code paths removed — all raises
remain as defensive guards.  `calls_arrays.py` per-file coverage:
93% → 94%.

Discovery noted in the follow-up `#657` audit table as Track 1
input: these dead guards become candidates for removal once
`_element_mem_size` / `_element_wasm_type` signatures tighten
from `int | None` / `str | None` to `int` / `str` (which would
accurately reflect their actual return contracts).

Refs #626 #658 #657

Co-Authored-By: Claude <noreply@anthropic.invalid>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@vera/wasm/calls_arrays.py`:
- Around line 1686-1688: The skip-reason currently uses the internal role string
"array_any_all" so diagnostics show helper names instead of source-level
builtins; modify the callers (array_any and array_all) to pass their actual
names (e.g., role="array_any" or role="array_all") into
_array_elem_triad_or_skip and ensure the helper uses that role value when
composing skip reasons (and similarly for the shared common helper path
currently using "array_any_all_common") so [E602] messages mention array_any or
array_all rather than the internal helper name.
- Around line 504-507: The CodegenSkip raised for arr_arg in the array_slice
path currently tells users a non-empty array literal is required; change the
diagnostic text to a neutral inference-failure message so it covers both literal
and non-literal cases (this is the branch that can fail when
_infer_concat_elem_type() cannot recover the element type). Update the raise
CodegenSkip(...) message for array_slice to something like "could not infer
array_slice element type (provide an explicit element type or ensure elements
are recoverable)" so it no longer implies only literals are accepted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: df54f2fe-5083-4295-960f-8df2c72a18bc

📥 Commits

Reviewing files that changed from the base of the PR and between f1765bb and f48dcda.

📒 Files selected for processing (1)
  • vera/wasm/calls_arrays.py

Comment thread vera/wasm/calls_arrays.py Outdated
Comment thread vera/wasm/calls_arrays.py
Both findings are reason-string quality on user-facing [E602]
diagnostics — the same lens the comment-analyzer flagged in the
PR review, refined to specific sites.

CR-2 (line 1688): `_translate_array_any_all_common` is a shared
implementation helper for `array_any` and `array_all`.  It was
passing `role="array_any_all"` into `_array_elem_triad_or_skip`
and emitting `"no load op for array_any_all_common element"` in
its body load-op raise.  Both strings leaked the internal helper
name into user-visible diagnostics.

Fix: thread a `name: str` kwarg through the helper from each
caller (`name="array_any"` / `name="array_all"`), use it for the
triad helper's `role=` and the body load-op raise.  Diagnostics
now mention `array_any` or `array_all` — the source-level builtin
the user wrote — rather than the internal dispatch helper.

CR-3 (lines 504-507): `_translate_array_slice`'s inference-failure
raise used to read "could not infer array_slice element type
(non-empty array literal required)".  The parenthetical implied
the fix was "use a literal", but the branch fires whenever
`_infer_concat_elem_type()` can't recover the element type — that
includes non-literal expressions that happen to be inference-opaque,
not just non-empty literals.

Fix: rephrase to "(provide an explicit element type or ensure
elements are recoverable from the expression)".  Neutral on the
literal/non-literal distinction; covers both reachable paths.

Validation
- mypy clean
- pytest 3789 passed, 14 skipped

Refs #626 #658

Co-Authored-By: Claude <noreply@anthropic.invalid>
@aallan

aallan commented May 11, 2026

Copy link
Copy Markdown
Owner Author

Multi-agent review summary

Ran the pr-review-toolkit:review-pr skill (5 agents in parallel: code-reviewer, comment-analyzer, silent-failure-hunter, type-design-analyzer, pr-test-analyzer) plus folded in CodeRabbit's two latest inline findings. Posting the synthesis + disposition here for traceability.

TL;DR

  • No critical issues found — all 5 agents agree the PR is fundamentally correct. Catch-handler placement is right, no state leaks, no broken catches, pragma rationales accurately match helper return contracts in vera/wasm/helpers.py:317-407.
  • 3 of 10 important findings already addressed in this PR (CR-1 in f1765bb, CR-2 + CR-3 in 09bb9e4).
  • 7 important findings outstanding, listed below with proposed disposition.

Findings already addressed in this PR

# Finding Source Status
CR-1 _compile_fn's CodegenInvariantError handler missing _harvest_interp_inference_failures(ctx) for symmetry with the CodegenSkip handler CodeRabbit Fixed in f1765bb
CR-2 _translate_array_any_all_common leaked the internal helper name into [E602] text (role="array_any_all", "no load op for array_any_all_common element") CodeRabbit + comment-analyzer Fixed in 09bb9e4 — threaded name: str kwarg through; reasons now say array_any / array_all
CR-3 / F7 array_slice raise text "(non-empty array literal required)" was misleading — it implied a user-side fix that doesn't exist for the non-literal-non-empty path CodeRabbit + comment-analyzer Fixed in 09bb9e4 — neutral text covering both reachable paths

Findings outstanding — awaiting disposition

# Finding Source Effort Disposition
F1 vera/codegen/functions.py:206 comment says "See vera/codegen/skip.py." — module is at vera/skip.py code-reviewer, comment-analyzer trivial Fix in this PR
F2 _compile_lifted_closure's CodegenInvariantError handler in vera/codegen/closures.py:397-406 missing harvest call (parallel to the fix f1765bb made on the fn-boundary side) code-reviewer trivial Fix in this PR for symmetry
F3 CodegenInvariantError is defined + caught but never raised in this PR. The two E699 catch blocks are uncovered code code-reviewer small Fix in this PR# pragma: no cover + inline rationale on both catch blocks (canary raise would expand scope)
F4 E699 emitted at severity="warning" — wrong for a compiler bug. vera compile exits 0 with a soft message that CI logs can miss. skip.py docstring says "never caught, always crash" but production code catch-and-warns silent-failure-hunter medium Needs maintainer judgment — escalate to severity="error"? Changes the CLI exit-code contract for any program that triggers an E699
F5 vera/skip.py:41-43 says "the follow-up issue (filed when #626 closes)" — #657 is filed, name it comment-analyzer trivial Fix in this PR
F6 vera/skip.py:35-39 says "~30 silent-skip sites out of ~367 return None" — stale pre-audit estimate; actual is 104/372 comment-analyzer trivial Fix in this PR
F8 vera/codegen/compilability.py:60-63 has two silent-skip paths (non-EffectRef inside EffectSet; not-PureEffect-not-EffectSet) that don't call _warning(...). Pre-existing but contradicts the audit's PROPAGATE reclassification of functions.py:29 silent-failure-hunter medium Note on #657, don't expand this PR's scope — pre-existing behaviour, audit-classification fix
S1 Add 2 targeted tests: one reason-text assertion + one span-attachment. Locks in the user-visible E602 improvement (which is the whole point of the PR) pr-test-analyzer ~40 lines Fix in this PR — cheap, locks user-facing contract

Type-design suggestions — deferred to polish follow-up

These are valid but not blocking; rolling them into the #657 Track 1 follow-up would be coherent:

  • S2: tighten _element_mem_size / _element_wasm_type signatures ... | None → non-Optional (removes ~13 of the 24 pragma annotations mechanically)
  • S3: rename CodegenSkipCodegenSkipError for symmetry with CodegenInvariantError
  • S4: force node non-None on CodegenInvariantError; drop the decl fallback
  • S5: add shared CodegenControlFlow(Exception) marker base for a top-level backstop except
  • S6: narrow node: ast.Nodeast.Expr | ast.Stmt | ast.Pattern
  • S7: trim the meta-narrative comment in closures.py:421-425 about the empirically-disproved # pragma: no cover claim
  • S8: closures.py:382 "Layer 2" attribution is inconsistent with the rest of the PR's "Layer 3" naming
  • S9: in data.py:217-226, move the empty-arms check before scrutinee translation so empty-arms takes precedence over inference-failure

Strengths flagged

  • Two-tier exception design well-justified; type-design-analyzer rated CodegenSkip Usefulness 5/5
  • Pragma rationales accurately match helper return contracts (silent-failure-hunter cross-checked against helpers.py:317-407)
  • No state leaks — _compile_fn catch correctly preserves legacy drop-function semantics; _lift_pending_closures rollback contract intact
  • Reason strings are short, specific, free of WASM-internal jargon
  • ROADMAP renumbering verified clean — no stale row references

Next step

Awaiting direction on whether to proceed with F1, F2, F3, F5, F6 + S1 as a single follow-up commit (all small, total ~80 lines including the 2 tests), and how to handle F4 (E699 severity). F8 will be noted in #657 regardless.

Multi-agent PR review (code-reviewer + comment-analyzer +
silent-failure-hunter + type-design-analyzer + pr-test-analyzer)
surfaced 9 important findings.  Three were already addressed in
prior commits (CR-1 in f1765bb, CR-2/CR-3 in 09bb9e4).  Eight
addressed here; one (F8) noted on #657 as pre-existing.

F1 (code-reviewer + comment-analyzer)
Stale module path comment in `vera/codegen/functions.py:206`.
The module lives at `vera/skip.py` (top-level, deliberately, to
break a circular import — see #658 description).  Comment now
points at the correct path.  Also tightened the Phase 3 reference
to name #657 instead of the vaguer "audit-and-convert pass".

F2 (code-reviewer)
`_compile_lifted_closure`'s `CodegenInvariantError` handler in
`vera/codegen/closures.py:397-406` was missing the
`_harvest_interp_inference_failures(ctx)` call that f1765bb added
to the parallel `_compile_fn` handler.  Symmetry restored — both
boundaries now drain `ctx._interp_inference_failures` before the
warning/error emission so no E615s get silently dropped if a
future invariant fires mid-interp-translation.

F3 (code-reviewer)
`CodegenInvariantError` is defined and caught but never raised in
this PR — the catch blocks are uncovered code that confuses future
readers about whether the handlers are reachable.  Added
`# pragma: no cover` with an inline rationale to both catch blocks
(`functions.py` + `closures.py`) noting that no production code
raises yet and the handler exists as the catch-side contract for
the Track 2 work in #657.

F4 (silent-failure-hunter) — ESCALATED
The `[E699]` handlers were emitting at `severity="warning"`,
which is wrong for a compiler-bug diagnostic: `vera compile`
would exit 0 with a soft message that CI logs can mask.  Added
a new `_error()` method on `vera/codegen/core.py::CodeGenerator`
(parallel to the existing `_warning()`, same signature, hardcodes
`severity="error"`).  Both `_compile_fn` and `_compile_lifted_closure`
now route `CodegenInvariantError` through `_error` for [E699].
The skip.py docstring's "never caught, always crash" claim is now
backed by an error-severity diagnostic, not a warning.

This is a user-visible CLI contract change for any program that
ever triggers an `[E699]` (none currently exist in production —
no `raise CodegenInvariantError` site has shipped — but the catch
handler is now contract-correct for when Track 2 wires them).
CHANGELOG entry updated.

F5 (comment-analyzer)
`vera/skip.py` docstring said "the follow-up issue (filed when
#626 closes)" — #657 is now filed, named directly.

F6 (comment-analyzer)
`vera/skip.py` docstring claimed "~30 silent-skip sites out of
~367 return None".  Stale pre-audit estimate.  Replaced with the
audit's actual aggregate counts (372 total: 105 SILENT_SKIP, 154
PROPAGATE, 74 OPTIONAL_RETURN, 39 INVARIANT_DEFENSIVE — bucket
status per cleanup track).

F8 (silent-failure-hunter)
`vera/codegen/compilability.py:60-63` has two pre-existing silent
`return False` paths that don't call `self._warning(...)`.  Audit
classification of `functions.py:29` (PROPAGATE) assumed
`_is_compilable` always emits a diagnostic on failure; those two
branches violate that assumption.  Noted in #657 as Phase 4
cleanup — not introduced by this PR, so not in scope here.

S1 (pr-test-analyzer)
Added new test class `TestE602NodeLevelReasons626Layer3` in
`tests/test_codegen.py` with two tests pinning the user-visible
Layer 3 contract:

* `test_e602_description_contains_node_specific_reason` — asserts
  the `[E602]` description carries the CodegenSkip's `reason`
  text and the AST-node-type label (`FnCall`), not the legacy
  generic "function body contains unsupported expressions".
* `test_e602_location_points_at_offending_call_not_fn_header` —
  asserts the diagnostic's `location.line` points at the
  offending call (line 5 in the test fixture) rather than the
  enclosing function declaration (line 2).

Both tests use the existing `_compile()` helper + the existing
`Map<Nat, Array<Nat>>` fixture (same as `TestMapArrayValueRejected475`).
The fixture passes type-check, fails at codegen, and the assertions
verify the post-#658 diagnostic shape.

Validation
- mypy clean (59 source files)
- pytest 3791 passed, 14 skipped (was 3789 + 2 new tests)
- The 2 new tests fail against the pre-Layer-3 codepath, which
  is the locking-in property — any regression that drops the
  per-node span or generic-reason text will break them.

Refs #626 #657 #658

Co-Authored-By: Claude <noreply@anthropic.invalid>
@aallan

aallan commented May 11, 2026

Copy link
Copy Markdown
Owner Author

Update: all review findings addressed in 0205f9c

Following the review synthesis comment above, all 8 outstanding important findings now resolved in commit 0205f9c. F4 (E699 severity) escalated to error per direction.

# Finding Disposition Where
F1 Stale vera/codegen/skip.pyvera/skip.py path comment Fixed 0205f9c vera/codegen/functions.py:206
F2 _compile_lifted_closure's CodegenInvariantError handler missing harvest call Fixed (parallel to f1765bb on the fn-boundary) 0205f9c vera/codegen/closures.py:397-417
F3 CodegenInvariantError never raised; uncovered catch blocks Fixed — # pragma: no cover + inline rationale on both catches 0205f9c vera/codegen/functions.py:228, vera/codegen/closures.py:397
F4 E699 emitted at severity="warning" — wrong for a compiler bug Escalated to severity="error". New _error() method on CodeGenerator (parallel to _warning); both E699 catch handlers route through it. vera compile now exits non-zero on CodegenInvariantError instead of emitting a maskable warning. No production code raises CodegenInvariantError yet, so no existing programs see new errors — but the catch contract is now correct for when Track 2 of #657 wires raises. 0205f9c vera/codegen/core.py:233-263 (new _error), vera/codegen/functions.py:242, vera/codegen/closures.py:407
F5 vera/skip.py "filed when #626 closes" → "#657" Fixed (named explicitly) 0205f9c vera/skip.py:46
F6 vera/skip.py "~30/~367" stale pre-audit counts Fixed — replaced with the audit aggregate (372 total: 105 SILENT_SKIP / 154 PROPAGATE / 74 OPTIONAL_RETURN / 39 INVARIANT_DEFENSIVE) + per-bucket status 0205f9c vera/skip.py:31-50
F8 _is_compilable (compilability.py:60-63) has 2 pre-existing silent return False paths that contradict the audit's PROPAGATE reclassification Noted on #657 as Phase 4 cleanup — pre-existing behaviour, audit-classification fix. Comment thread (no code change in this PR)
S1 Add tests for the user-visible E602 improvement Added new TestE602NodeLevelReasons626Layer3 class with 2 tests: one for reason-text + AST-node-type label in the description, one for span-attachment. Both pass; both would fail against the pre-Layer-3 codepath, locking in the regression-detection contract. 0205f9c tests/test_codegen.py:15835-15931

Validation

  • mypy clean (59 source files)
  • pytest 3791 passed, 14 skipped (was 3789 + 2 new)
  • doc-count hook satisfied (TESTING.md / ROADMAP.md updated for the +2 tests)

Type-design suggestions S2-S9

Still deferred to a polish follow-up per the earlier framing — these don't surface correctness issues and 0205f9c is the natural stopping point for this PR. Happy to take direction if you'd rather fold them in.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 13: Update the CHANGELOG bullet to remove the contradictory "No existing
call sites change" by clarifying that the new _error() API
(vera/codegen/core.py) is used by the internal handlers _compile_fn and
_compile_lifted_closure (the [E699] catch paths) so internal diagnostics now
emit severity="error", and note explicitly that no external/user-facing call
sites changed (if that is true) or else state that only those internal handlers
were updated; reference _error(), _warning(), _compile_fn,
_compile_lifted_closure and [E699] in the revised sentence.

In `@tests/test_codegen.py`:
- Around line 15934-15939: The assertion for the E602 diagnostic span is too
loose (assert loc_line > 2) and should be tightened to assert the exact
offending line; update the test that checks loc_line (the E602 assertion in the
test around the `main` fixture that triggers `map_insert(...)`) to assert
loc_line == 5 and update the failure message accordingly so the test pins the
diagnostic to the `map_insert(...)` call rather than any later statement.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fe1438fc-951e-4b14-891f-ac3d6d054369

📥 Commits

Reviewing files that changed from the base of the PR and between 09bb9e4 and 0205f9c.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • ROADMAP.md
  • TESTING.md
  • tests/test_codegen.py
  • vera/codegen/closures.py
  • vera/codegen/core.py
  • vera/codegen/functions.py
  • vera/skip.py

Comment thread CHANGELOG.md Outdated
Comment thread tests/test_codegen.py Outdated
Both findings are precision-tightening on artefacts added in
`0205f9c`.

CR-4 (CHANGELOG line 13)
The new `_error()` bullet ended with "No existing call sites
change", which contradicted the rest of the bullet — the [E699]
catch handlers in `_compile_fn` and `_compile_lifted_closure`
both DO change (route through `_error()` instead of `_warning()`).
The intended meaning was "no user-facing API surface changes", but
the loose shorthand was misleading.

Fix: rewrite to be explicit about what changed (the two named
internal handlers, both routed through the new `_error()` method),
and qualify the "no other call sites" claim to "no user-facing
API surface or other `_warning()` call sites".

CR-5 (test_codegen.py line 15939)
The span assertion in
`test_e602_location_points_at_offending_call_not_fn_header` used
`assert loc_line > 2` — too loose.  A regression that drifted the
catch-handler's span onto `IO.print(...)` (line 6) instead of
`map_insert(...)` (line 5) would slip past the test silently.

Fix: tighten to `assert loc_line == 5`.  Verified by running the
fixture through `_compile()` and reading `d.location.line`
directly (5, column 31 — the start of the `map_insert(...)` call).
Updated the assertion failure message to call out both regression
paths the loose check allowed: dropping back to the legacy
enclosing-fn span (line 2) AND drifting to any later call.

Validation
- pytest tests/test_codegen.py::TestE602NodeLevelReasons626Layer3 -v
  → 2 passed
- The full suite was last verified green in 0205f9c; this commit
  only touches comment text + an assertion bound, no production
  code

Refs #626 #658

Co-Authored-By: Claude <noreply@anthropic.invalid>
@aallan aallan merged commit f6c8cc5 into main May 11, 2026
22 checks passed
@aallan aallan deleted the layer3-codegen-skip-infra branch May 11, 2026 20:05
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.

Cross-cutting: convert 'translate returns None → silent skip' failures into loud diagnostics

1 participant