This file is a decision aid for future Hew work. It is not a changelog.
Use it in this order:
- Match the current change against the trigger column.
- Start with the P0 rows before applying any matching P1 or P2 rows.
- Treat the apply column as the minimum pre-merge checklist for that lesson.
- When two lessons conflict, keep the stricter fail-closed, ownership-preserving, or parity-preserving rule.
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |