Skip to content

runtime: make AggregateError consume iterable errors and reject non-iterables #2838

Closed

Description

@andrewtdiz

Summary

new AggregateError(errors, message) should synchronously consume the errors iterable into the .errors array and throw TypeError when errors is omitted or non-iterable. Perry currently lowers the first argument as an array pointer and defaults missing errors to an empty array, so non-array iterables and invalid inputs do not match Node.

Node behavior

A local Node v25.9.0 probe shows:

new AggregateError([1, 2], "m").errors;          // [1, 2]
new AggregateError(new Set([1, 2]), "m").errors; // [1, 2]
new AggregateError("ab", "m").errors;           // ["a", "b"]
function* g() { yield "x"; yield "y"; }
new AggregateError(g(), "m").errors;             // ["x", "y"]

new AggregateError(undefined, "m");              // throws TypeError
new AggregateError(1, "m");                      // throws TypeError
new AggregateError();                             // throws TypeError

Current Perry evidence

  • crates/perry-hir/src/lower/expr_new.rs handles AggregateError specially as a two-argument form. It takes the first arg as errors, the second as message, and defaults missing errors to Expr::Array(vec![]) rather than a runtime TypeError.
  • The IR variant Expr::AggregateErrorNew { errors, message } has no way to represent the iterable conversion or invalid-input throw; it just carries an expression expected to become an array-like value.
  • crates/perry-codegen/src/expr/array_methods.rs lowers AggregateErrorNew by unboxing errors with unbox_to_i64 and passing it directly to js_aggregateerror_new(errors_handle, msg_handle).
  • crates/perry-runtime/src/error.rs::js_aggregateerror_new() accepts errors: *mut ArrayHeader and stores that pointer directly in the ErrorHeader. It does not consume the iterable protocol, convert strings/generators/Sets, or validate non-iterables.

Suggested test surface

Add node-suite tests for:

function show(label: string, fn: () => unknown) {
  try { console.log(label + ":", JSON.stringify(fn())); }
  catch (err: any) { console.log(label + ":", err?.name); }
}

show("array", () => new AggregateError([1, 2], "m").errors);
show("set", () => new AggregateError(new Set([1, 2]), "m").errors);
show("string", () => new AggregateError("ab" as any, "m").errors);
show("generator", () => {
  function* g() { yield "x"; yield "y"; }
  return new AggregateError(g(), "m").errors;
});
show("undefined", () => new AggregateError(undefined as any, "m"));
show("number", () => new AggregateError(1 as any, "m"));
show("omitted", () => new (AggregateError as any)());

Expected output should materialize the iterable values for valid iterables and print TypeError for invalid inputs.

Scope / non-goals

This issue is about the AggregateError constructor's errors argument. It does not cover Promise.any combinator input handling (#2822) or AggregateError { cause } options (#2836).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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