Repository navigation
Cross-module inlining copies Enum.Member references into the importer: "enum member X.Y not found in enums table" #10417
Description
Found by the package audit (compiling real npm packages from source instead of Perry's native bindings) on
Perry e6dcb62 (v0.5.1587), Linux x64. When an exported function or class method reads a TypeScript enum member,
cross-module inlining copies the body into the importing module. That module's enum table has no such enum, so codegen
fails with enum member CharacterCode.Lt not found in enums table.
Reproduction
utils.ts:
enum CharacterCode { LowerA = 97, LowerZ = 122, Lt = 60 } // `const enum` and `export enum` fail the same way
export function isHtml(str: string): boolean {
const c = str.charCodeAt(1);
return str.charCodeAt(0) === CharacterCode.Lt && c >= CharacterCode.LowerA && c <= CharacterCode.LowerZ;
}main.ts:
import { isHtml } from "./utils.ts";
console.log(isHtml("<div>"), isHtml("hello"));package.json: {"type":"module"}
perry compile main.ts -o main && ./mainExpected (Node 26.5.1)
Node 26.5.1 cannot run enum in strip-only mode (ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX: TypeScript enum is not supported in strip-only mode). The files were therefore transpiled with TypeScript 5.8.2 transpileModule and the output was run
under Node 26.5.1:
true false
Actual (Perry)
Same result with and without PERRY_NO_AUTO_OPTIMIZE=1:
Error compiling module 'main.ts' (.../main.ts) with --backend llvm: lowering specialized entry for function '__perry_xmod_inline_1_isHtml': lowering body of '__perry_xmod_inline_1_isHtml': perry-codegen: enum member CharacterCode.Lt not found in enums table
✗ ENTRY MODULE FAILED TO COMPILE — REFUSING TO LINK
Impact
- cheerio 1.2.0 ships
src/*.ts, and Perry compiles that source instead ofdist/.src/utils.ts:63declares
const enum CharacterCode, andisHtml(src/utils.ts:82-96) reads it.src/load.tsand
src/api/manipulation.tsimportisHtml, so both fail with
lowering body of '__perry_xmod_inline_11_isHtml': enum member CharacterCode.LowerA not found in enums table.
Workaround: hidenode_modules/cheerio/srcso Perry compilesdist/esm. - This likely affects any TS-source package (compiled via
compilePackages) or multi-file TS app where a small exported
helper or method reads an enum it does not re-export under the same name.
Notes
Results (compiled with PERRY_NO_AUTO_OPTIMIZE=1; the plain-enum and method variants also fail in auto mode):
| variant | Perry |
|---|---|
const enum / enum / export enum, importer imports only the function |
compile error |
string enum (enum Kind { A = "a" }, return x > 0 ? Kind.A : Kind.B) |
compile error (Kind.A) |
| enum read in a non-exported helper called by the exported function (graph inlining) | compile error |
class method reads the enum; importer does const p = new Probe(); p.isLt("<a") |
compile error, in lowering init statements of module 'main.ts' |
export enum, importer imports the enum under an alias: import { isHtml, CharacterCode as CC } |
compile error |
export enum, importer also imports it under its own name: import { isHtml, CharacterCode } |
works (true false 60) |
exported function contains a for loop (not inlinable) |
works |
| same-module call | works |
-
Cause (verified by reading the code; the fix direction is inferred):
Expr::EnumMember { enum_name, member_name }
names the enum as written in the source module. Codegen resolves it throughctx.enums
(crates/perry-codegen/src/expr/bigint_set.rs:573-583). That table holds only the current module's own enums plus
opts.imported_enums, keyed by the importer's local binding name (crates/perry-codegen/src/codegen/mod.rs:559-579).
The cross-module safety checks rejectFuncRef,GlobalGet,NativeModuleRef, and similar nodes, but they do not
rejectEnumMember, so it falls through to the generic child walk. Affected checks:cross_function_expr_is_safe: free-function graphs,crates/perry-transform/src/inline/cross_module.rs:350-406is_cross_module_safe: methods,cross_module.rs:8-40, used at:1017is_cross_module_safe_with_externs:cross_module.rs:1146-1170, used at:1085
No file in
crates/perry-transform/src/inline/mentionsEnumMember. Possible fixes: rejectEnumMemberin the
three checks, or fold members to literals (numeric or string) before harvesting candidates. -
Closed issues about imported enums in other positions: codegen: imported enum member reads undefined in array/object/template/builtin-arg positions #6879 (imported enum member reads undefined in
array/object/template positions), web target: importedKeyconst enum values reach the FFI bridge asundefined#1991 (web target const enum). Neither covers inlining.
Activity
proggeramlug commentedon Sep 21, 2026
Fixed in v0.5.1632 (merge train 252,
841b605c97) by #10866 —fix(transform): keep enum references out of cross-module inlining. Regression testcrates/perry/tests/source_graph_export_regressions/issue_10417.rs.Train validation: 173 gap fixtures across seven areas with zero regressions, re-run against the current base after v0.5.1631 changed
canonical_dir's path resolution. All ratchets,cargo fmt, andcargo check --workspace --all-targetsunder-D warningsclean.