Skip to content

runtime: fix String.fromCharCode wrapping and fromCodePoint RangeErrors #2788

Description

@andrewtdiz

Summary

Perry has runtime helpers for String.fromCharCode and String.fromCodePoint, and HIR already expands multi-argument valid calls. The remaining edge semantics diverge from Node:

  • String.fromCharCode should convert each argument with ToUint16, so negative / out-of-range values wrap modulo 65536 instead of returning "".
  • String.fromCodePoint should throw RangeError for invalid code points, negative values, and non-integers instead of returning "".
  • Zero-argument calls should return "".

Node behavior

Local Node v25.9.0 probe:

fromCharCode:[65,66,67]="ABC":41+42+43
fromCharCode:[128512]="":f600
fromCharCode:[-1]="�":ffff
fromCharCode:[65.9]="A":41
fromCharCode:[]="":
fromCodePoint:[128512,65]="😀A":1f600+41
fromCodePoint:[1114112]=RangeError:Invalid code point 1114112
fromCodePoint:[-1]=RangeError:Invalid code point -1
fromCodePoint:[3.14]=RangeError:Invalid code point 3.14
fromCodePoint:[]="":

Probe shape:

String.fromCharCode(65, 66, 67)
String.fromCharCode(0x1F600)
String.fromCharCode(-1)
String.fromCharCode(65.9)
String.fromCharCode()
String.fromCodePoint(0x1F600, 65)
String.fromCodePoint(0x110000)
String.fromCodePoint(-1)
String.fromCodePoint(3.14)
String.fromCodePoint()

Perry evidence

  • crates/perry-hir/src/lower/expr_call/module_static.rs handles one-or-more String.fromCharCode(...) / String.fromCodePoint(...) arguments by lowering each argument to a single-code helper and concatenating multi-argument calls.
  • crates/perry-codegen/src/expr/string_regex_proc.rs lowers both helpers by converting the argument with raw fptosi(DOUBLE -> I32) before calling the runtime helper.
  • crates/perry-runtime/src/string/char_ops.rs::js_string_from_char_code() returns an empty string for values outside 0..=0xFFFF instead of applying ToUint16 wrapping.
  • crates/perry-runtime/src/string/char_ops.rs::js_string_from_code_point() returns an empty string for values outside 0..=0x10FFFF / invalid scalar values instead of throwing RangeError.
  • test-files/test_gap_string_methods.ts covers valid multi-argument fromCodePoint and zero, but not invalid code points or fromCharCode wrapping.

Duplicate check

Searched issues and PRs for:

  • String.fromCharCode variadic
  • String.fromCodePoint RangeError
  • fromCharCode fromCodePoint
  • PR search for fromCharCode OR fromCodePoint OR String.fromCodePoint

No focused tracker showed up. PR #1043 was a WASM-specific fix for String.fromCharCode returning undefined, not these runtime edge semantics.

Expected fix shape

  • Implement ToUint16 semantics for String.fromCharCode, including negative and out-of-range wrapping.
  • Make String.fromCodePoint validate integer code points and throw RangeError for negative, non-integer, and > 0x10FFFF values.
  • Avoid raw fptosi for non-finite / non-integer values where JS coercion or validation must be observable.
  • Ensure zero-argument static calls return "".
  • Add regression coverage for wrapping, invalid code points, non-integers, and zero-argument calls.

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