Skip to content

bug: namespace-import-resolved member call dispatches receiver as TAG_TRUE — "X.fn is not a function" #636

Description

@proggeramlug

Summary

When a TypeScript file does import * as ns from "./mod.js" (or .ts) and calls a function exported from that module via ns.fn(args), perry resolves the receiver to TAG_TRUE and throws TypeError: (boolean).fn is not a function.

This is the followup to #611 noted in v0.5.716's CLAUDE.md changelog. The Effect framework hits this at Duration.ts:524 (order.make(...)).

Repro

```ts
// inner.ts
export const make = (s: string) => s.toUpperCase();

// index.ts
import * as inner from "./inner.js";
const result = inner.make("hello");
console.log("typeof inner:", typeof inner);
console.log("typeof inner.make:", typeof inner.make);
console.log("result:", result);
```

Pre-fix output:
```
Uncaught exception: TypeError: (boolean).make is not a function
```

Expected (Node):
```
typeof inner: object
typeof inner.make: function
result: HELLO
```

Root cause

For RESOLVED namespace imports (where the source IS a real .ts module — distinct from #629's UNRESOLVED node:* namespaces), the codegen path doesn't recognize the Call { callee: PropertyGet { ExternFuncRef("ns"), "fn" } } shape. The PropertyGet falls through to the generic method-dispatch path at lower_call.rs:2250, which calls lower_expr(ctx, object) against the namespace ExternFuncRef. The Expr::ExternFuncRef arm at expr.rs:9970 doesn't find the namespace name in class_ids or import_function_prefixes (the prefix map is keyed by the namespace's MEMBERS, not by the namespace name itself), falls through to TAG_TRUE, and js_native_call_method then dispatches "fn" against a boolean → typeerror.

Additionally, the CLI driver's namespace-import walk in compile.rs:2755-2836 populates import_function_prefixes for every namespace export but doesn't populate imported_vars — so even if codegen routes through the namespace member path, var-shaped exports (export const fn = (s) => ...) get treated as function-decls and the zero-arg getter symbol is called with N args.

Fix surface

  1. compile.rs::lower_module_decl namespace-import walk — populate imported_vars for namespace exports that are vars (mirror the named-import VAR path at line 3013).
  2. lower_call.rs::lower_call — add an early arm that detects Call { callee: PropertyGet { ExternFuncRef(ns), method } } where ns ∈ namespace_imports, and routes:
    • var-shaped → zero-arg getter + js_closure_callN
    • function-decl-shaped → direct perry_fn_<src>__<method>(args) with rest-bundling

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions