fix(codegen): default-export-of-named-fn wrapper forwards to body (#967)#968
Merged
Conversation
When `function add(a,b){…}; export default add;` is compiled as a native
package, the consumer's `const fn = add; fn(2,3)` returned undefined
because sub-bug-B's wrapper-alias loop emitted a no-op
`__perry_wrap_perry_fn_<src>__default` for every `local==exported`
named export whose local wasn't a HIR function name — but the canonical
default-export shape lowers to `Export::Named { local: "default",
exported: "default" }` where the function's HIR name is `add`. Consult
`hir.exported_functions` for an alias entry before falling through;
when found, emit a forwarding wrapper to the real function body.
This is the npm-package barrel idiom used by ramda, date-fns, lodash-es,
and many others. The fix unblocks closure-form access of those default
imports (callback passing, alias-then-call). Direct calls already worked
via the separate `perry_fn_<src>__default` alias loop, so this fixes the
"function returns undefined when called through a local" wedge specifically.
e64bade to
cf611b6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
function add(a,b){…}; export default add;is the canonical npm-packagebarrel idiom (ramda, date-fns, lodash-es, hundreds more). When that
shape was compiled as a native
perry.compilePackagesmodule, theconsumer's
const fn = add; fn(2,3)returnedundefinedinstead of5. Directadd(2,3)worked — the bug was specific to closure-formaccess (alias to local, callback passing, indirect call).
Root cause: codegen's sub-bug-B wrapper-alias loop emitted a NO-OP
__perry_wrap_perry_fn_<src>__defaultwrapper for everyExport::Named { local, exported }wherelocal == exportedandlocalwasn't a HIR function name. The branch's original purpose wasto make
import * as z; export { z };(re-exported namespace) andexport { SomeClass }link by emitting an→ undefinedthunk. But thedefault-export shape ALSO satisfies
local == exported == "default"with
func_by_local_name.contains_key("default") == false— becausethe HIR function's name is
add, notdefault. So the no-op firedand short-circuited every closure-form call through the default
import.
The HIR lowerer at
perry-hir/src/lower.rs:5601correctly registersthe alias in
module.exported_functions.push(("default", add_id)).The fix consults that map: when a
local==exportedno-function-by-local-name export resolves to an aliased real function, emit a
forwarding wrapper (
closure_ptr → tail-call perry_fn_<src>__add)instead of the no-op. Mirrors the
local != exportedbranch at L2792which already handles renamed exports correctly.
Test plan
test-files/test_default_import_as_value.ts+_helper.tscover four value-position shapes: local alias,function-parameter pass-through, closure-capture, direct call.
Output matches
node --experimental-strip-typesbyte-for-byte(
5 / 30 / 15 / 5).test-files/test_lodash_default_import_methods.ts(issue feat(security): #498 — pin SHA-256 ofperry.nativeLibraryprebuilt archives inperry.lock#957regression) still passes.
test-files/test_issue_678_reexport_default.tsstill passes.lint/cargo-test/parity/compile-smoke/api-docs-drift/security-auditonce green.Out of scope (related but separate bugs)
With this fix applied, two packages from the original compat sweep
still don't fully work:
R.sum([1..5])returns[object Object]becauseFunction.prototype.applyhas no runtime handler —fn.apply(this, args)returns the receiver as a stub instead of invokingfn.Triggers inside ramda's
_curry1/_curry2/_curry3machinery.Untriaged.
format(new Date(), 'yyyy-MM-dd')throwsRangeError: Invalid time valuebecauseDate.prototype.constructorreturns
undefined, breakingconstructFrom'snew date.constructor(value)cloning. Untriaged.Both packages exercise the default-import-as-value shape internally
many times, so this fix is a prerequisite for any future work on
them — without it the closure-dispatch would short-circuit before
reaching the
apply/constructorfailures.Version
0.5.980 → 0.5.981