Parent: #10107 (OpenCode v1.18.30 native bring-up).
Summary
When a module has two aliased imports, and the local alias of one is the same identifier as the exported name of the other's source module, perry binds the alias to the wrong module — but only for calls made during module initialization. The call gets the other module's value, which is typically not a function, so it throws TypeError: value is not a function with no callee name.
This is the shape minifiers emit constantly. Short aliases like t, e, n get reused across import statements in the same file, so any minified dependency that calls an imported function at module scope can hit it.
Repro (4 files)
idx.js
var cat = { br: 1 }
function extend(o) { Object.assign(cat, o) }
function getCat() { return cat }
export { getCat, extend, cat }
leafT.mjs — exports a binding literally named t
const t = { kind: "obj-not-a-function" }
export { t }
consO.mjs — imports t under the alias e, and extend under the alias t
import { t as e } from "./leafT.mjs"
import { extend as t } from "./idx.js"
function n() { t({ spinner: e }) }
n()
export { n as reg }
mainO.ts
import { getCat } from "./idx.js"
import { reg } from "./consO.mjs"
console.log("typeof:", typeof reg, "catalogue:", JSON.stringify(getCat()))
bun 1.3.14 and node:
typeof: function catalogue: {"br":1,"spinner":{"kind":"obj-not-a-function"}}
perry 0.5.1571 (+ PRs 10279/10280/10282/10283), perry compile mainO.ts --platform bun:
TypeError: value is not a function
at <anonymous>
t inside consO.mjs resolves to leafT's t — the object — instead of idx's extend.
Control matrix
All four controls pass under perry, which pins the trigger to the conjunction of three conditions:
| variant |
change from the repro |
perry |
| repro |
— |
fails |
| P |
alias extend as q instead of as t |
passes |
| Q |
leafT exports z instead of t |
passes |
| R |
the leafT import moved out of this module |
passes |
| S |
leafT imported without an alias, extend as u |
passes |
| T |
identical, but the call happens after init rather than during it |
passes |
So all three are required: a local alias colliding with another import's exported name, both imports in the same module, and the call executing during module initialization. T is the interesting one — the same binding is correct once initialization is over, which points at init-time resolution rather than a permanently wrong symbol.
Where to look
crates/perry/src/commands/compile/run_pipeline.rs builds import_function_prefixes / import_function_origin_names keyed by identifier. There is already a comment there (marked "Issue #", so apparently never filed) describing a sibling collision:
exported_name is whatever the ORIGIN module happens to call it, so it can collide with an unrelated LOCAL alias elsewhere in the same file. Concrete repro: import { a as n, c as a } from "./x"; import { a as t } from "./y"
That fix only inserts the aliased case under local_name. This repro is aliased on both sides, so it should already be keyed by local name — meaning the collision is somewhere else on the init path, not in that map. Same family as #9847/#9857 (name-keyed instead of local-id-keyed registration).
Why it matters
This is the last blocker for OpenCode's terminal interface under perry. The real case is opentui-spinner@0.0.7/dist/solid.mjs, four minified statements:
import{t as e}from"./src-DjeqLSfu.mjs";import{extend as t}from"@opentui/solid/components";function n(){t({spinner:e})}n();export{n as registerSpinner};
extend is a plain function declaration in @opentui/solid. A module-body trace of the subgraph under both engines shows perry running 17 modules against bun's 147, stopping immediately after @opentui/solid/components.js — the module right before this one. The at n / at n frames in the original stack are this file's minified n.
Parent: #10107 (OpenCode v1.18.30 native bring-up).
Summary
When a module has two aliased imports, and the local alias of one is the same identifier as the exported name of the other's source module, perry binds the alias to the wrong module — but only for calls made during module initialization. The call gets the other module's value, which is typically not a function, so it throws
TypeError: value is not a functionwith no callee name.This is the shape minifiers emit constantly. Short aliases like
t,e,nget reused across import statements in the same file, so any minified dependency that calls an imported function at module scope can hit it.Repro (4 files)
idx.jsleafT.mjs— exports a binding literally namedtconsO.mjs— importstunder the aliase, andextendunder the aliastmainO.tsbun 1.3.14 and node:
perry 0.5.1571 (+ PRs 10279/10280/10282/10283),
perry compile mainO.ts --platform bun:tinsideconsO.mjsresolves toleafT'st— the object — instead ofidx'sextend.Control matrix
All four controls pass under perry, which pins the trigger to the conjunction of three conditions:
extend as qinstead ofas tleafTexportszinstead oftleafTimport moved out of this moduleleafTimported without an alias,extend as uSo all three are required: a local alias colliding with another import's exported name, both imports in the same module, and the call executing during module initialization. T is the interesting one — the same binding is correct once initialization is over, which points at init-time resolution rather than a permanently wrong symbol.
Where to look
crates/perry/src/commands/compile/run_pipeline.rsbuildsimport_function_prefixes/import_function_origin_nameskeyed by identifier. There is already a comment there (marked "Issue #", so apparently never filed) describing a sibling collision:That fix only inserts the aliased case under
local_name. This repro is aliased on both sides, so it should already be keyed by local name — meaning the collision is somewhere else on the init path, not in that map. Same family as #9847/#9857 (name-keyed instead of local-id-keyed registration).Why it matters
This is the last blocker for OpenCode's terminal interface under perry. The real case is
opentui-spinner@0.0.7/dist/solid.mjs, four minified statements:extendis a plain function declaration in@opentui/solid. A module-body trace of the subgraph under both engines shows perry running 17 modules against bun's 147, stopping immediately after@opentui/solid/components.js— the module right before this one. Theat n / at nframes in the original stack are this file's minifiedn.