Skip to content

fix(emit-js): don't emit runtime imports for macro-only refers#3

Merged
tompassarelli merged 1 commit into
mainfrom
fix/elide-macro-imports
Jun 16, 2026
Merged

fix(emit-js): don't emit runtime imports for macro-only refers#3
tompassarelli merged 1 commit into
mainfrom
fix/elide-macro-imports

Conversation

@tompassarelli

Copy link
Copy Markdown
Owner

A :refer'd name that resolves to a macro is compile-time only — it's expanded away and never referenced at runtime, and the target module emits no runtime export for it. Emitting it in import { … } produces ESM that throws does not provide an export named X in any consumer that isn't bundled (e.g. test files loaded via dynamic import()).

Until now this was masked: the chrome layer's bundler tree-shakes the dead import. But an unbundled consumer (the integration-test runner loads compiled test files directly) hits the error.

emit-module-header now filters each require's refer list against program-macros (which includes imported macros), dropping macro names. If a require's refers are all macros, no import line is emitted at all.

Verification

  • active suite 1377/1377
  • gjoa chrome bundle compiles + bundles unchanged; integration 42/42
  • import { by_id, mi, … } from './macros.js' (dead, was bundler-stripped) is now simply not emitted

A `:refer`'d name that resolves to a macro is compile-time only — it's
expanded away and never referenced at runtime, and the target module
emits no runtime export for it. Emitting it in `import { … }` produces
ESM that throws "does not provide an export named X" in any consumer
that isn't bundled (e.g. test files loaded via dynamic import). The
chrome layer only worked because its bundler tree-shook the dead import.

emit-module-header now drops macro refers (checked against program-macros)
and omits the import line entirely when a require's refers are all macros.
Verified: active suite 1377/1377; gjoa chrome bundle + tests unaffected.
@tompassarelli tompassarelli merged commit e9e14aa into main Jun 16, 2026
@tompassarelli tompassarelli deleted the fix/elide-macro-imports branch June 16, 2026 07:13
tompassarelli added a commit that referenced this pull request Jun 16, 2026
Turtle #3. Adds the claims->datum->source reverse path that did not exist
anywhere in beagle, and a gate proving a program round-trips through its CNF
claim graph losslessly:
- datum<->claims identity: 1100/1100 forms across the gjoa corpus
- text-is-a-view: 97/97 files render to idiomatic beagle that re-reads identically
  (un-desugars [..]->(#%brackets ..), {..}->(#%map ..))
- type annotations (:- T) survive for free as reader tokens

bin/beagle-roundtrip exposes the gate + --emit-edn (reader claims for Fram) and
--verify modes. The reader-datum projection is verbose-but-lossless (source of
truth); emit-claims stays compact-but-lossy (queries).
tompassarelli added a commit that referenced this pull request Jun 18, 2026
…ol return, quasiquote quote, variant-delete, self-doc delete)

rename.sh §9: parameterized defunion name renames + annotations cascade; defprotocol
method return type cascades; (quote ..) inside a quasiquote stays inert data.
delete.sh §4-5: a defunion variant is refused (not a top-level form, no false success);
a self-documented def is deletable (its own doc-comment is pruned, not an orphan).
tompassarelli added a commit that referenced this pull request Jun 21, 2026
…scape-across-fn)

Closes soundness hole-class #3 (escape across the fn boundary) from beagle-2's
adversarial boundary map: a fn RETURNING a compound-keyed map (or a `(set var)`
over a compound-typed param) was read NATIVE by the caller because rep-selection
was intraprocedural + per-node-table only (bare var-refs and call results carry
no rep).

Two additions to classify-rep:
  - TYPE-DIRECTED dispatch: an expression whose node-type is a concrete
    compound-keyed Map / compound-elem Set classifies hmap/hset directly. This
    catches annotated fn returns (`build-index :- (Map (Vec Int) Keyword)`) and
    any typed expr — the caller's get/contains/count now route to HAMT ops.
  - BINDING-TYPE ENV (current-type-env): a var-ref resolves to its declared type
    (param `:-` annotation / let value type) via arg-type, so `(set paths)` with
    paths :- (Vec (Vec Int)) sees the compound element and routes to hamtSet.
    Seeded at defn/fn/arity params (with-param-envs) + composes with rep-env.
  - rep-env now also seeded from param types (a param :- (Map compound ..) reads
    as hmap inside the body).

Real-program evidence: bjs-bench/programs/structdiff.bjs (pure-value diff/merge
= Eddy skip-if-unchanged at lib scale) goes 5/7 -> 7/7 — distinct-paths (value
dedup over a compound param) and cross-fn compound-map lookup both flip GREEN.
No regression: active 1474/1474, gated JS (emit-js 147, fixtures, jst,
exec-oracle) + behavioral 78 all green. Still provably-compound predicate; the
Any/records/unions flip (#1,#2,#5,#6) is the next commit.
tompassarelli added a commit that referenced this pull request Jun 21, 2026
…HAMT

Closes the last soundness hole-class (#4 runtime dedup) from beagle-2's boundary
map. The runtime collection builders no longer emit native ARRAYS for sets (a
pre-existing set-builder bug) and route to value-dedup HAMT when the element type
is compound:

  coll-kind helper: 'set | 'vec | 'map | 'unknown, seeing through conj/into/disj
  to the underlying collection (so a native Set/conj-result isn't mistaken for
  an array).
  conj  : value-set -> hamtSetAdd fold ; native set -> new Set([...c, x]) (a Set,
          not [...c,x]) ; vec -> [...c,x].
  into  : value-set -> xs.reduce(hamtSetAdd, target) ; native set -> new Set([...
          target, ...xs]) ; vec -> spread.
  count : native branch now dispatches on coll-kind -> set:.size / map:Object.keys
          ().length / else:.length, so (count (conj #{..} x)) is .size not .length
          (was undefined).
  frequencies : compound elements -> value-keyed hamtMap (was native object ->
          "[object Object]" collisions). group-by left native (its key type comes
          from f's return, not reliably inferable — documented residual).

Verified vs Clojure shape: conj/into on scalar+compound sets (3/1/4/2),
frequencies compound/scalar (2/2). rep-soundness +4 = 24/24; active 1484/1484,
gated JS (emit-js 147, exec-oracle 7, ...) + behavioral 78 green.

ALL 6 hole-classes now closed: #1 records, #2 unions, #3 escape-across-fn,
#4 runtime dedup, #5 heterogeneous, #6 cross-rep equality.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant