Skip to content

perry compile silently produces wrong output when import resolves through namespace re-export (export * as X from ...) #310

Description

@proggeramlug

What happened

When user code does import { X } from "pkg" and the package's entry point re-exports X via the namespace form export * as X from "./X.js", Perry's module collector does not traverse the re-export. The compile succeeds, the binary links cleanly, and every property access on X (e.g. X.someFn(...)) silently returns 0 — Perry's "unknown identifier" lowering fallback. There is no warning that X.someFn was unresolved.

Reproduced with Effect but the bug is general: any npm package whose index.ts uses export * as Foo from "./Foo.js" exhibits the same silent-zero behavior under import { Foo } from "pkg".

What you expected

Either:

  1. Preferred: traverse the re-export and pull ./X.js into the module graph the same way import * as X from "pkg/X" does.
  2. At minimum: emit a hard error or loud warning ("namespace re-export X from pkg/index.ts not traversed; calls on X will return 0"). Today the user gets a silently-broken binary.

Minimal reproduction

mkdir nsre-zero && cd nsre-zero
cat > package.json <<'JSON'
{
  "name": "nsre-zero",
  "version": "0.0.0",
  "type": "module",
  "perry": { "compilePackages": ["effect"] }
}
JSON
bun add effect
// test_minimal.ts
import { Effect } from "effect";

const program = Effect.succeed(42);
const result = Effect.runSync(program);
console.log("result:", result);

Command:

perry compile test_minimal.ts -o /tmp/test_minimal
/tmp/test_minimal

Expected output: result: 42
Actual output: result: 0 — exit 0, no errors, no warnings about Effect being unresolved

Diagnostic output

perry compile -v test_minimal.ts ... surfaces the smoking gun:

Collecting modules...
  Compile package: effect
Found 3 module(s): 3 native, 0 JavaScript
Module init order (2 modules):
  [0] node_modules/effect/src/Function.ts
  [1] node_modules/effect/src/index.ts

Only 2 of Effect's 152 modules were collected. effect/src/Effect.ts (29,102 lines, the actual implementation) was never visited despite being explicitly named in the index.ts re-export. By contrast, import * as Effect from "effect/Effect" correctly pulls 152 modules (then OOMs on a separate Perry-side bug — filed as #309).

Relevant source

effect/src/index.ts line 229:

export * as Effect from "./Effect.js"

This is a standard ES2020 namespace re-export and is the recommended pattern for "deep stdlib" packages (Effect, lodash-fp, ramda, etc.) — Perry should support it for compilePackages resolution.

Environment

  • Perry version: 0.5.399
  • Host OS: macOS 26.4 (arm64, Apple Silicon)
  • Target: native
  • Installed via: from source
  • Effect version: 3.21.2

Notes for triage

  • Likely fix site: crates/perry/src/commands/compile/collect_modules.rs and / or crates/perry/src/commands/compile/resolve.rs (the v0.5.340 split). The import { Foo } from "pkg" code path needs to consult the resolved pkg's exports for Foo — and when Foo is itself a namespace re-export pointing at another file, recurse into that file's module graph.
  • A scoped workaround that doesn't fix the silent-zero but stops the false success: when a user-side PropertyGet lowers to "unknown identifier X" AND X came from an import { X } from "pkg" binding, upgrade from "warn-and-lower-to-0" to a hard error. Today's "warn at lowering site, lower to 0" is the right default for genuinely-runtime-resolved globals (Intl, globalThis.foo) but completely wrong for an import that the resolver gave up on.

Why this matters

This is a silent-correctness bug — the binary produces wrong output and exits 0. Users with even modestly large dependency graphs will hit this without realizing it; the failure mode is "my Effect program prints all zeros" not "my Effect program won't compile." That is the worst possible failure mode for a compiler.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions