Skip to content

Latest commit

 

History

History
92 lines (79 loc) · 33.7 KB

File metadata and controls

92 lines (79 loc) · 33.7 KB

Lessons Learned — Structured Reference

This file is a decision aid for future Hew work. It is not a changelog.

Use it in this order:

  1. Match the current change against the trigger column.
  2. Start with the P0 rows before applying any matching P1 or P2 rows.
  3. Treat the apply column as the minimum pre-merge checklist for that lesson.
  4. When two lessons conflict, keep the stricter fail-closed, ownership-preserving, or parity-preserving rule.

Schema

Column Meaning
id Stable short key for referencing the lesson.
area Subsystem or seam where the lesson usually applies.
trigger Smell or change pattern that should activate the lesson.
rule Distilled invariant to preserve.
apply Concrete check or action to take now.
evidence Main PRs or JOURNEY/original-LESSONS workstreams that produced the lesson.

Priority bands

Band Use it for
P0 Silent wrong-code, memory safety, ownership, cleanup, or cross-language boundary risk.
P1 Parity, tests, diagnostics, toolchain, or user-surface trust.
P2 Architecture, cleanup, deduplication, and maintenance decisions that prevent repeat bugs.

P0 - Compiler/runtime correctness and boundary safety

id area trigger rule apply evidence
serializer-fail-closed serialization/codegen A Rust-to-C++ or wire boundary must encode types, schema data, or unsupported shapes. Boundary code must either serialize an explicit supported form or stop with context; silent omission is a bug. Reject unsupported nested forms, carry span/context through conversion, turn warning-and-skip paths into hard errors, and version or contract-test high-risk seams. PR #812/#811/#808/#795; historical quality/correctness and remediation waves.
type-info-survival typecheck/enrichment/codegen A later phase is tempted to re-infer receiver, type, or visibility facts that the checker already resolved. Checker-authoritative information must survive into downstream consumers. Thread inferred call args, receiver authority, Self generics, visibility, and module-aware names through side tables or serialization; fail if a downstream phase cannot see the authoritative answer. PR #814/#806; implicit monomorphization and receiver-authority fixes.
checker-output-boundary typecheck/serialize/codegen A checker-owned type or metadata table is about to cross into serialization or codegen. Checker output must be concrete and complete; unresolved Ty::Var or missing metadata are boundary failures, not downstream reconstruction work. Reject or strip unresolved signature, type-def, and expr-type entries in check_program, emit the boundary diagnostic there, and make serialize/codegen consume checker-authoritative metadata instead of re-inferring from AST fallbacks. PR #838/#848/#849/#852/#853.
assignment-target-authority typecheck/codegen MLIR assignment lowering is tempted to rediscover field/index target validity after the checker already classified the target. Once assignment-target metadata crosses the boundary, stale or contradictory field facts are invariants, not user-facing backend errors. Keep checker-owned assignment kind/shape data authoritative, turn impossible field-lowering fallbacks into fail-closed invariant diagnostics, and add regression tests by mutating typed AST metadata directly instead of re-testing the frontend path. JOURNEY 2026-04-11 fix/field-assign-codegen-invariant.
generated-narrowing-guards astgen/generated-code A generator emits exact-width integer reads for the Rust→C++ msgpack boundary. Generated numeric narrowing must stay fail-closed; regeneration must not silently replace checked conversions with static_cast truncation. Centralize checked-width helpers in the shared generator preamble, route generic u32 parses and special-cased schema metadata through them, and re-run both generator freshness and reader-lane tests after regeneration. JOURNEY 2026-04-11 fix/astgen-u32-overflow-guards.
module-qualified-rewrite-authority typecheck/serialize An imported stdlib module call can be rewritten directly to a runtime C symbol. Module-qualified stdlib rewrites belong to checker metadata, not serializer registry re-resolution. Record direct-call rewrite metadata during check_method_call, consume it in enrich_method_call before any registry fallback, and test that the rewrite preserves the original argument list without receiver injection. JOURNEY 2026-04-10 module-qualified stdlib rewrite metadata lane.
codegen-import-path-consistency codegen/modules A new codegen pass, pre-registration table, or lookup map handles imported items. Imported definitions must use defining-module mangling consistently in every pass; one pass using current-module mangling creates silent table mismatches. Audit every name-keyed codegen table for imported-item lookups, swap to defining-module paths where required, and add focused regressions for each independent pass that builds or consumes such tables. PR #386; later module-system reliability fixes.
exhaustive-traversal-and-lowering analysis/codegen A new AST variant, lowering branch, or supported type is added. Visitors and dispatch chains must be exhaustive; wildcard or default fallthrough hides semantic gaps. Maintain explicit variant/type lists, build an operation x type matrix for typed lowerings, cover narrow widths and uncommon shapes, and prefer errors over warnings when a case is unsupported. Original LESSONS audit matrix/visitor/lowering lessons; PR #798/#786/#797.
match-fail-closed typecheck/match Match checking or lowering changes. Match expressions and statements need identical validation and fail-closed runtime behavior. Forbid cross-enum fallback, treat guarded wildcards as non-exhaustive, exclude Never and Error from result inference, wrap HashMap.get() at the expression boundary, and trap on runtime fallthrough. Original LESSONS match lessons; historical quality/correctness wave.
cleanup-all-exits control-flow/cleanup Code stages setup work or mutates side-channel state before success is guaranteed. Cleanup must mirror setup on every exit path, not just the happy path. Evaluate return expressions before drops, reset pending state on failure, deactivate all intermediate loops, and clean partial select/join/ask setup before panicking, timing out, or returning early. When bounded teardown cannot wait for a live worker, transfer ownership to a reaper that performs the final join and free path instead of dropping the join handle or leaking the allocation graph, and treat an outstanding join handle as the worker-liveness authority even if task state already says Done. For runtime-level reset: use a session hook registry so every subsystem's cleanup is unconditionally registered at init and fires from every shutdown path — not just the paths that existed when the subsystem was added. Original LESSONS cleanup/control-flow lessons; PR #801/#794; historical remediation wave; JOURNEY 2026-04-11 task-scope cancelled worker reclamation lane; PR #1271 (hew_trace_reset and clear_dispatch_registry gaps in hew_sched_shutdown).
ffi-ownership-contracts runtime/ffi A boundary returns pointers, diagnostics, reply-channel status, or an opaque handle with a user-visible close() / free() method. FFI contracts need owned results, explicit submit or cancel status, caller-visible diagnostics, and exactly one canonical release path per owned handle. Return owned strings instead of internal pointers, expose thread-local last-error APIs, surface ask submission failure directly, cancel select losers, document who frees what, and when a stdlib handle exposes manual close() / free(), keep that as the only release path unless the lower layer is provably idempotent and regression-tested against double free. Audit every Hew call site before removing an impl Drop, then add Hew-level tests that exercise explicit release followed by scope exit. Original LESSONS FFI/runtime lessons; node-runtime and remediation waves; PR #1292 superseded by the explicit-release fix for #1281; PR #1314 migrated Server/Pattern and issue #1310 finished the migration for http.Request and json.Value.
raii-null-after-move codegen/raii Adding drops, auto-close behavior, or ownership transfer for owned values. Null-after-move is a prerequisite for safe automatic drops. Store owned handles in dominating allocas, null them after consuming calls, returns, match destructuring, or explicit close, null-check before drop, and use runtime-visible function names instead of rewritten aliases when classifying consumers. Stream/sink RAII rollout and parameter-drop follow-up work.
field-alias-fail-closed codegen/ownership A call expression passes a struct field as an arg, or a function returns a field of an owned parameter that receives a callee-side drop. Without partial-move / field-alias tracking, field accesses in consuming positions cause double-frees or leaks; reject at compile time. Scan call args for ExprFieldAccess before emitting any call IR (not after — void calls swallow return nullptr); scan function bodies for field-of-param returns after pendingFunctionParamDrops is populated; always increment errorCount_ alongside emitError so the module is rejected even when the diagnostic alone would not halt codegen. fix/function-param-drop-alias-fail-closed; MLIRGenExpr.cpp pre-scan, MLIRGen.cpp return scanner.
ownership-over-locks runtime/concurrency Shared mutable state crosses threads, actors, or background workers. Fix ownership and causality first; locks cannot rescue a broken lifetime model. Prefer Arc, atomics, owned routing tables, and mailbox-carried context, never pass stack pointers across threads, then add the smallest lock surface still required. Historical runtime audit findings; PR #784; original LESSONS ownership lesson.

P1 - Parity, tests, diagnostics, and toolchain trust

id area trigger rule apply evidence
native-wasm-parity runtime/wasm/tests A runtime, scheduler, timer, channel, or actor behavior changes. New runtime behavior needs native/WASM parity or an explicit tracked gap. Implement both lanes or add a named TODO, add contract tests for timeout, cancel, and budget edges, and document intentional divergence where parity cannot land yet. For session-lifecycle hooks: session_reset must be called from both hew_sched_shutdown paths (scheduler_wasm.rs and scheduler.rs) so cleanup fires symmetrically. PR #818/#787/#785/#754/#729; project WASM-parity rule; PR #1271 (hew_trace_reset parity gap closed).
spec-surface-alignment docs/specs/checker-runtime/stdlib A spec example, API signature, or stdlib module uses i32/i64 where the user-facing type should be int. Specs and pub fn signatures must describe the shipped surface, not an aspirational or ABI-typed name. int is the canonical user-facing integer in std/**/*.hew; width-specific types belong only inside extern "C" {} blocks or on lines marked // INTERNAL-ABI:. Verify examples against hew-types/src/check/registration.rs, hew-types/src/check/methods.rs, and the owning stdlib module; run bash scripts/lint-stdlib-int-surface.sh before pushing any stdlib change; see docs/stdlib-style-contract.md for the full contract. JOURNEY 2026-04-10 docs wire codec lane; foundations-lane-6 stdlib-int-surface migration.
global-test-isolation runtime/tests Tests share globals, phase state, supervisor state, or rely on execution order. The real contract is order independence under the full suite, not isolated correctness. Add reset helpers and test-only serialization or mutexes where needed, run the full suite in parallel, and size the test to the actual race or nesting depth of the bug. When a test in module A touches globals owned by module B, expose a pub(crate) static B_TEST_LOCK and pub(crate) fn reset_b_for_test() from module B and acquire them in A before touching A's own lock; private per-module locks do not serialize across modules. Lock acquisition order must be consistent across all test sites that hold multiple locks simultaneously. Original LESSONS shutdown and review-depth lessons; PR #1257 bridge/tracing cross-module lock; PR #1271 session/tracing SESSION_TEST_LOCK pattern.
concurrent-test-final-invariant runtime/tests A concurrent or async test observes lifecycle state or cleanup across a race window. Concurrency tests should assert the final stable invariant, not one exact transient state sequence. Allow known valid intermediate states, require the eventual terminal state, and bind race windows to named scheduler or timeout constants at the assertion site. When a test dispatch must remain Running until a controlling function (drain, stop) has acted on the actor, use a release-flag spin loop (matching the DRAIN_BUSY_LOOP_RELEASE pattern) not a fixed sleep; a fixed sleep can expire before the controlling function fires under load, letting the actor reach Idle→Stopped instead of Running→Crashed and changing the observed outcome. PR #813/#738/#751; PR #1484 (drain-crashed flake fix, #1482).
network-smoke-readiness integration-tests/network A smoke test launches a server and client or asserts teardown ordering across processes. Cross-process smoke tests need an explicit readiness handshake and should not treat teardown ordering as a fixed contract. Wait for the server to bind or signal readiness before launching clients, loosen teardown assertions to the durable contract, and document intentionally unordered events. PR #763.
shared-user-surface-paths cli/repl/eval Two user-facing entry points do almost the same thing. User surfaces should share execution and diagnostic paths instead of drifting through near-duplicate implementations. Reuse shared file or session execution, align import roots and diagnostic envelopes, and add end-to-end coverage at the public command level rather than only unit-level helpers. PR #816/#804/#803/#800/#806.
diagnostic-source-attribution types/cli/diagnostics Diagnostics may be recorded in one module and rendered in another. Source attribution must be stamped when the diagnostic is recorded, not guessed later during rendering. Snapshot source_module on immediate and deferred diagnostics, tag unattributed diagnostics after each module slice, and add e2e tests that assert filenames and underlines point at the correct source file. PR #806.
error-count-exit-code codegen/cli/diagnostics A new error path emits a diagnostic in C++ codegen or a CLI/doc command. A visible diagnostic is not enough; the process must also record failure and exit nonzero. Whenever you add emitError or other diagnostics, increment the owning error counter or propagate failure immediately, then add a reject test where that error fires alone and must exit nonzero. PR #808/#746.
test-runner-trust cli/test-runner Discovery, reporting, or timeout behavior changes. A runner is only trustworthy if it surfaces real errors, preserves stable order, and exposes testable control seams. Fail on discovery parse errors, distinguish empty-suite modes clearly, keep stable ordering, and expose timeout behavior through the CLI so it can be tested end to end. Original LESSONS 2026-03-15 hew test pass.
test-toolchain-match tests/build/llvm A CTest script invokes clang or another LLVM tool by bare name via find_program. Test infrastructure must use the same LLVM toolchain the project was built against; Apple clang and upstream LLVM diverge on coroutine intrinsic signatures (e.g. llvm.coro.end returns i1 in Apple clang 21, void in upstream LLVM 22+). Pass the project-configured clang (resolved from LLVM_TOOLS_BINARY_DIR or CMAKE_C_COMPILER) into test CMake scripts instead of relying on PATH-based find_program. JOURNEY 2026-04-12 fix/llvm22-coro-end-return-contract; coro.end return-type mismatch between Apple clang 21 and upstream LLVM 22.
regen-checks infra/generated Adding a --check, generator validator, or repo-wide drift audit. A drift check must prove what regeneration would change in the real repo layout, not whether the checkout already happens to be dirty. Walk the real input tree, regenerate in an isolated snapshot, diff the produced outputs, and make downstream repo roots configurable so worktrees and fixture repos can run the same check. Original LESSONS remediation passes.
build-link-authority build/linking Embedding, relinking, or packaging the native backend across platforms. One build system must own native linkage and toolchain resolution, and mismatches must fail loudly. Let CMake author link directives consumed by Cargo, resolve compiler and runtime paths explicitly, fail on cross-arch or toolchain mismatch, and reset staged or generated dirs when packaging rewrites them. PR #809/#775/#771; single-binary and packaging fixes.
tracing-boundaries runtime/observe Observability exists but data is incomplete, causality breaks, or activation is awkward. Tracing belongs at scheduler and mailbox boundaries and needs operator-friendly activation semantics. Emit begin and end in the dispatch loop, carry trace context in mailbox payloads, and tie activation to the operator-facing profiler or UI switch. Original LESSONS observe pass.
link-gate-expansion-test-hygiene build/linking/tests A can_link_with_host_tools() or similar acceptance gate is expanded to accept new target classes. Rejection tests must use targets that are genuinely foreign under the new gate definition, not just under the old one; and same-OS cross-arch gating on Linux requires matching the ABI env (gnu vs musl) in addition to the OS. After expanding a link gate, grep every test helper that constructs a "foreign" target and verify each target remains rejected under the new rules. For Linux same-OS cross-arch, both acceptance-path helpers (e.g. linux_cross_target()) and rejection-path tests must mirror the host env using cfg!(target_env = "musl"); a hardcoded gnu triple in the acceptance helper causes spurious failures on musl hosts. PR #978 Phase 2 / #254 Phase 3; foreign_native_target() fix in cross_target_e2e.rs; linux_cross_target() and linux_cross_sysroot_path() musl-aware fix in Phase 3 follow-up.
preflight-gate-no-bypass build/hooks/ci A local pre-push or pre-commit gate fails and the change is pushed anyway via an environment-variable escape hatch, --no-verify, SKIP_HOOKS=1, or a "just this once" bypass — typically rationalized as "offline", "missing local deps", "failure is in main not my diff", or "unrelated to this lane". Local gates are the last pre-CI signal. Never route around a failing gate per-branch. If the gate fails because main itself is red, the correct action is to stop and fix main (revert, cherry-pick, or coordinate a fix-forward PR) so every contributor's branch turns green at once — not to accumulate branches that individually compensate. Bypass hatches in hooks silently normalize red baselines. Never add an environment-variable bypass branch to scripts/pre-push-ci-preflight.sh, scripts/ci-preflight-dispatcher.sh, or any git hook; never pass --no-verify / SKIP_HOOKS=1 / ALLOW_SKIP_*=1 on git push or git commit. Before dispatching a fleet of branches from the root worktree, run git status + git branch --show-current — if the root is on a detached HEAD or on a stale SHA, fix that before pushing anything (a detached HEAD means you are not in a clean state to be making policy-bending calls). When make ci-preflight fails: if the failure reproduces on a fresh origin/main checkout, freeze further dispatch and fix main; if it is only on your diff, fix your diff. Reverts of bad PRs use a new commit and a new PR — never force-push over main. PR #1466.
preflight-perf-discipline build/ci/tests make ci-preflight becomes slow (≥5 min), hangs indefinitely, or always falls back to the comprehensive lane. Preflight performance depends on the dispatcher's narrow-lane classification working correctly. That requires clean .gitignore entries (no untracked tooling artefacts triggering fallback scans), per-test timeouts on every integration suite (no indefinite hangs), and slow JIT-compile-and-execute suites gated out of the default make test target into test-all. Audit .gitignore for machine-local artefacts (clangd cache, LSP test artefacts, gate output dirs); add timeout/TIMEOUT properties to every test that can hang; move inherently slow suites into opt-in targets; verify sub-minute narrow lanes and ≤2-minute fallback locally before pushing. PR #1537; issues #1532, #1538, #1539, #1540.
repl-piped-stdout-flush cli/repl/interactive An interactive REPL or shell accepts user input and produces output per submission, and is invoked with stdin/stdout piped (non-TTY). Buffered output must be flushed per submission boundary so the REPL is usable in pipes and scripts; otherwise libc block-buffers stdout and output arrives in bulk at process exit. After each user-submitted statement or expression evaluation in the REPL loop, call flush() on stdout before reading the next submission. Flush unconditionally in both isatty and piped paths — the cost of a flush is negligible compared to the user-visible breakage when missed. PR #1553 (hew-cli REPL flush + jit-mode wiring; closed #1542, #1547).
formatter-comment-flush-audit formatter/comments A new block-shaped construct (enum variant, struct field, match arm, extern fn, else-if branch, etc.) is added to the formatter, OR comment-positioning anomalies appear in round-trip tests. The formatter must flush comments at every per-element semantic boundary; orphaned comments cause user-visible documentation loss. For each top-level AST construct that can contain comments, ensure flush_comments_before(span.start) runs before emitting each element, set prev_source_pos = span.start, format the construct, then flush_comments_before(span.end) (or the next element's start). Add a round-trip fixture test with leading and trailing comments on each construct type. Anchor on a real Span field on the AST node — not a find_keyword_after heuristic — so identifier-content collisions don't misanchor. PR #1535 (three formatter comment-positioning bugs across enum/struct/match arms, else-if branches, extern blocks; closed #1529, #1534, #1536).
check-pass-does-not-imply-run-pass examples/tests/release-validation An example, doc snippet, or test fixture is "fixed" by adding type annotations, pattern adjustments, or other changes that make hew check pass. Type-checker success does not imply runtime correctness. A previously-failing-at-check input may have been masking a deeper codegen, runtime, or wire-format bug; satisfying the checker can newly expose it. After any change that flips a previously-failing hew check to pass, run hew run (or the equivalent runtime smoke for the surface — hew eval, hew test, downstream consumer) and verify success. If runtime fails, file the runtime/codegen issue immediately and either revert the check-fix or proceed with a clear comment pointing at the runtime bug. Never declare an example "fixed" on hew check exit 0 alone. Treat new abort/SIGSEGV/SIGABRT signals on the previously-failing input as serious — those are the bug the type-error was hiding. PR #1602 (examples-triage); issue #1600 (Result<int, String> match SIGABRT newly surfaced when annotations were added to enums_and_options.hew and showcase.hew).
static-cpp-object-libcxx-boundary codegen/build/release A C++ function adds a static const local holding a std::regex, std::locale, std::string, or similar object that uses the allocator at construction time — especially inside a function called from the embedded codegen (e.g. MLIRGen::generate()). Function-local C++ statics that hold Homebrew/LLVM-allocated objects survive until process exit, where the system libc++ tries to destroy them via a different allocator, causing SIGABRT. The bug is invisible in debug builds; only release binaries on macOS with split libc++ ABI are affected. Remove static from the declaration so the object is a per-call local destroyed within the function's stack frame. Add scripts/test-release-binary.sh, wire it into make test-release-binary, and hook it into the ci-preflight dispatcher for hew-codegen/* diffs so the regression cannot silently ship. Before adding any static const C++ local in hew-codegen/src/, verify the object does not hold a Homebrew LLVM libc++ allocator reference. Exception classes: (a) std::optional<std::string> holding no value (disengaged) — destructor is trivial, no allocation; (b) llvm::StringSet<> — uses LLVM BumpPtrAllocator, not libc++, different bug class. Issue #1606; hew-codegen/src/mlir/MLIRGen.cpp three static regex fix; audit 2026-05-04: unordered_set/map<std::string> statics at MLIRGenExpr.cpp:1825/4132/4693 probed on macOS release binary — no SIGABRT; std::optional empty statics (MLIRGenWire.cpp:631/636/641/646) safe by inspection; llvm::StringSet (MLIRGenExpr.cpp:1080) uses LLVM allocator, out of scope.

P2 - Architecture, cleanup, and maintenance decisions

id area trigger rule apply evidence
dedup-semantic-boundary parser/runtime/helpers Multiple helpers look identical but differ at one semantic edge. Share mechanics, but stop deduplication at the point where ownership of semantics diverges. Extract common parse, decode, or update scaffolding while leaving lossy conversion, interpolation-only escapes, auto-numbering, and other owner-specific semantics at the call site. Original LESSONS C string, parser escape, wire parser, and adze-cli passes.
collection-traversal-owner typecheck/admissibility Vec, HashSet, and HashMap admissibility rules repeat the same structural Ty recursion. Centralise structural traversal, but keep per-collection policy and aggregate entry points separate so nested invalid collections still report the right container-level diagnostic. Extract one recursive walker that delegates named collection nodes to the existing validators, and keep validate_concrete_collection_types calling each collection lane explicitly instead of relying on a single short-circuiting pass. JOURNEY 2026-04-09 checker collection capability dedup lane.
ty-recursion-owner hew-types/typecheck A checker helper is only recursively rebuilding or scanning Ty values without checker state. Put structural Ty recursion on Ty itself and keep checker modules as call sites, not duplicate owners of the walk. Compare candidate helper bodies for semantic identity first, then move the shared traversal onto Ty and delete leftover wrappers or duplicate walkers in checker-local modules. JOURNEY 2026-04-10 refactor/ty-subst-dedup lane.
dead-surface-sweep cleanup/cli/workspace A flag, helper, crate, placeholder, or tool looks unused. Dead surfaces usually survive through tests, docs, completions, specs, or workspace membership rather than production callers. Classify production callers separately from self-tests and placeholders, then sweep docs, completions, specs, and workspace entries before deciding the surface is still real. Original LESSONS dead-helper, dead CLI, QUIC, and export-tooling passes.
integration-over-rewrite architecture/runtime Existing modules already cover most of a new runtime or protocol feature, but the wiring between them is missing. Prefer a thin integration owner over rewrites or new abstraction layers that hide working modules. Add one orchestrating owner that composes the existing transport, cluster, registry, or runtime pieces, keep handshakes and protocol records fixed-shape, and land the work in focused slices that can still be tested independently. Original LESSONS audit integration lessons.
mlir-local-type-hints codegen/mlir A collection literal or empty block needs surrounding type information during MLIR lowering. Prefer a local explicit type hint threaded through the active lowering call over mutable side-channel state like pendingDeclaredType. Pass an optional MLIR type hint from the enclosing let/var expression into array, map, and empty-block lowering, keep resolvedTypeOf as the fallback when checker-authoritative expression types already exist, and clear any consumed pendingDeclaredType before recursively lowering nested collection elements so outer hints cannot bleed into inner builtins. refactor/mlir-typehint-array-hashmap; hew-codegen/src/mlir/MLIRGenExpr.cpp, MLIRGenStmt.cpp.
resolver-restartability package-manager/lockfile Dependency resolution or lockfile staleness logic changes. Resolution must be restartable and manifest-aware, not a one-pass name comparison. Revisit packages when constraints tighten and compare manifest requirements against direct locked dependencies instead of raw package-name sets. Original LESSONS resolver and lockfile lessons.
completion-transparent-containers analysis/completions The cursor sits inside wrapper expressions around nested blocks. Transparent AST containers need span-guarded descent only into the child that covers the cursor. Centralize wrapper descent helpers and update them whenever new pass-through expression nodes are added. Original LESSONS 2026-03-25 completion fix.
tail-position-context parser/optimization Tail-call or tail-expression analysis changes across blocks, match arms, or wrapper expressions. Tail position depends on enclosing context, not node shape alone. Carry an explicit tail-position flag through match arms and nested containers instead of marking every trailing expression as tail position. Original LESSONS 2026-03-25 tail-call fix.
codegen-abi-authority codegen/streams A lowering helper selects an ABI variant (bytes vs. string) by querying a span-keyed type map built by the checker. Span-keyed checker lookups at codegen time are unreliable: they can be absent, stale from a different function scope, or contradict metadata the lowering pass already tracked explicitly. Route ABI selection through an explicit authority hierarchy: type annotation → known C-call name → chained combinator propagation → tracked binding → safe default. Remove resolvedTypeOf calls from ABI decision paths and add tests that erase the relevant expr_types entries to prove the explicit path is load-bearing. fix/bytes-stream-abi-authority; resolveStreamHandleInfo in hew-codegen/src/mlir/MLIRGen.cpp.
sub-body-scoped-traversal analysis/lsp A multi-body item (Actor, Impl, TypeDecl, Trait) is queried by sub-body name and the result is fed into a whole-item traversal. The scope of the walk must match the scope of the lookup; a container-level match check followed by a full-container walk conflates the two and bleeds sibling bodies into the result. Replace any(sub.name == name) && walk_item(container) with a helper that finds the named sub-body and calls collect_calls_in_block on it alone. Add regression tests for same-container sibling non-bleed. PR #967; find_outgoing_calls regression in hew-lsp/src/server.rs.
wasm-diagnostic-json-contract hew-wasm/serialization A new structured field is added to the WASM analysis JSON (e.g. notes, suggestions on WasmDiagnostic). Adding fields to a WASM-boundary struct is not enough; the serialized JSON contract must be locked down by tests that assert both field presence (even when empty) and field population for known-triggering inputs. After adding any field to WasmDiagnostic or AnalysisResult, add: (1) a field-presence test that checks every diagnostic in both the parse-error and type-error paths, (2) a population test for each field using a Hew input known to trigger a non-empty value. JOURNEY 2026-04-11 test/wasm-diagnostic-notes-suggestions; PR #967.
checker-codegen-pattern-contract types/codegen A new kind of if let / while let pattern is permitted by the parser but not yet lowered by codegen. The checker, not codegen, must own the contract for which top-level patterns are legal in if let / while let. Codegen should be a consumer of pre-validated input; encountering an unsupported pattern in codegen always means a checker omission. Add a pre-validation step in the checker at each if let / while let call site (not inside bind_pattern, which is used for match arms too) that rejects unsupported patterns with a clear diagnostic. In codegen, increment errorCount_ and emit an explicit error message instead of silently skipping. Wildcard and identifier patterns are trivially supported without any tag-test; only delay dereferencing the scrutinee to the constructor branch. fix/iflet-whilelet-pattern-contract; hew-types/src/check/diagnostics.rs, MLIRGenIfLet.cpp, MLIRGenWhileLet.cpp.
ffi-stub-generator-ceiling astgen/stdlib Adding or evaluating a generator that produces Hew façade stubs from #[no_mangle] extern "C" Rust sources. A generator can mechanically produce correct extern "C" blocks and prefix-stripped wrapper shells; it cannot derive public function names, user-facing docstrings, or bool-vs-i32 return semantics without human input. Treat extern "C" block emission as zero-gap for modules with only *mut c_char/*const c_char and i32 returns; treat public name, docstring, and return-type annotation as semantic gap requiring hand-edit. Modules with opaque handles (e.g. json, compress) exceed the generator ceiling regardless. Mark generator-emitted // INTERNAL-ABI: lines the same way json.hew does when the ABI type diverges from the user surface. PR #1249; calibration against std/misc/uuid/uuid.hew; counter-example: std/encoding/json/json.hew.