Summary
Symbol and BigInt are callable factory/coercion functions in Node, but neither is constructable. Perry implements call lowering for both, yet new Symbol(...) and new BigInt(...) appear to fall through the generic built-in new placeholder path instead of throwing.
Node behavior
Observed with local Node v25.9.0:
console.log(typeof Symbol("x")); // "symbol"
console.log(typeof BigInt("42"), BigInt("42").toString()); // "bigint", "42"
try { new Symbol("x"); } catch (e) { console.log(e.name, e.message); }
// TypeError Symbol is not a constructor
try { new BigInt("1"); } catch (e) { console.log(e.name, e.message); }
// TypeError BigInt is not a constructor
Perry evidence
crates/perry-hir/src/lower/expr_call/globals.rs lowers Symbol() / Symbol(description) to Expr::SymbolNew(...) and BigInt(value) to Expr::BigIntCoerce(...), so ordinary calls are covered.
crates/perry-runtime/src/symbol.rs implements js_symbol_new_empty, js_symbol_new, js_symbol_for, and related runtime support. crates/perry-runtime/src/bigint.rs implements the BigInt allocation/coercion runtime.
crates/perry-runtime/src/object/global_this.rs::GLOBAL_THIS_BUILTIN_CONSTRUCTORS and crates/perry-codegen/src/expr/helpers.rs::is_global_this_builtin_name() include Symbol and BigInt as function-valued globals.
crates/perry-hir/src/lower/expr_new.rs has no special class_name == "Symbol" or class_name == "BigInt" rejection path.
crates/perry-codegen/src/lower_call/new.rs::lower_new() calls lower_builtin_new() first; there is no Symbol / BigInt arm there. For built-in/native class names without a dedicated handler and no user class, it lowers args for side effects and allocates an empty object placeholder via js_object_alloc(0, 0). That means new Symbol("x") / new BigInt("1") can produce an ordinary object instead of throwing.
Expected fix direction
Add a new-expression rejection path for these non-constructable built-ins so both direct and obvious rebound constructor forms throw a TypeError rather than allocating placeholders. Ordinary Symbol(...), Symbol.for(...), Symbol.keyFor(...), and BigInt(...) call behavior should remain unchanged.
Related but not duplicate: #2852 tracks missing well-known Symbol.* constants, not new Symbol construction semantics.
Summary
SymbolandBigIntare callable factory/coercion functions in Node, but neither is constructable. Perry implements call lowering for both, yetnew Symbol(...)andnew BigInt(...)appear to fall through the generic built-innewplaceholder path instead of throwing.Node behavior
Observed with local Node v25.9.0:
Perry evidence
crates/perry-hir/src/lower/expr_call/globals.rslowersSymbol()/Symbol(description)toExpr::SymbolNew(...)andBigInt(value)toExpr::BigIntCoerce(...), so ordinary calls are covered.crates/perry-runtime/src/symbol.rsimplementsjs_symbol_new_empty,js_symbol_new,js_symbol_for, and related runtime support.crates/perry-runtime/src/bigint.rsimplements the BigInt allocation/coercion runtime.crates/perry-runtime/src/object/global_this.rs::GLOBAL_THIS_BUILTIN_CONSTRUCTORSandcrates/perry-codegen/src/expr/helpers.rs::is_global_this_builtin_name()includeSymbolandBigIntas function-valued globals.crates/perry-hir/src/lower/expr_new.rshas no specialclass_name == "Symbol"orclass_name == "BigInt"rejection path.crates/perry-codegen/src/lower_call/new.rs::lower_new()callslower_builtin_new()first; there is noSymbol/BigIntarm there. For built-in/native class names without a dedicated handler and no user class, it lowers args for side effects and allocates an empty object placeholder viajs_object_alloc(0, 0). That meansnew Symbol("x")/new BigInt("1")can produce an ordinary object instead of throwing.Expected fix direction
Add a
new-expression rejection path for these non-constructable built-ins so both direct and obvious rebound constructor forms throw aTypeErrorrather than allocating placeholders. OrdinarySymbol(...),Symbol.for(...),Symbol.keyFor(...), andBigInt(...)call behavior should remain unchanged.Related but not duplicate: #2852 tracks missing well-known
Symbol.*constants, notnew Symbolconstruction semantics.