Skip to content

AOT: rest-parameter collection silently drops args inside @perryts/redis's Connection.command() (could not minimize) #672

Description

@proggeramlug

Summary

Under perry compile, calling conn.command('SET', 'k', 'v') on a @perryts/redis Connection instance reaches the encoder with args.length === 1 (instead of 2), and the single element is a denormal-double (same NaN-box symptom as #653, which fixed the spread-side variant). The bytes sent to Redis are then SET <denormal>-ERR wrong number of arguments for 'set' command.

Passing the args as an explicit array via commandArray(name, args) — same sendCommand callee, just no rest-collection — works correctly.

I could not minimize the repro outside the driver. Isolated tests with rest + generic + class + private-field + union-type + cross-module imports all collect args correctly. There's something specific to the actual @perryts/redis Connection class's shape that hits this path. Filing anyway because the symptom is reliable in-driver and the workaround indirection shouldn't be necessary.

Environment

  • perry 0.5.795
  • macOS 26.4 / Apple M1 Max

Reliable in-driver repro

cd /tmp && bun add @perryts/redis
# (or: git clone https://github.com/PerryTS/redis.git; cd redis; bun install)
// repro.ts
import { connect } from '@perryts/redis';

async function main() {
    const conn = await connect({ host: '127.0.0.1', port: 6379, protocol: 2 });

    // Rest form — fails under AOT, works on Node + Bun.
    try {
        const r = await conn.command<string>('SET', 'k', 'v');
        console.log('command(rest):     ', r.value);
    } catch (e: any) { console.log('command(rest) ERR:', e.message); }

    // Explicit-array form — works everywhere.
    const r2 = await conn.commandArray<string>('SET', ['k', 'v']);
    console.log('commandArray:      ', r2.value);

    await conn.close();
    process.exit(0);
}
main();
$ redis-server --port 6379 --daemonize yes --save '' --appendonly no
$ perry compile repro.ts -o repro && ./repro
connected
command(rest) ERR: ERR wrong number of arguments for 'set' command
commandArray:       OK

Node + Bun output (correct on both call shapes):

connected
command(rest):      OK
commandArray:       OK

What Connection.command() looks like

class Connection {
    private readonly id: number;
    // ...
    command<T = unknown>(name: string, ...args: CommandArg[]): Promise<CommandReply<T>> {
        return sendCommand<T>(this.id, name, args);
    }
    commandArray<T = unknown>(name: string, args: CommandArg[]): Promise<CommandReply<T>> {
        return sendCommand<T>(this.id, name, args);
    }
}
type CommandArg = string | Buffer | number | bigint;

The two methods differ only by ...args vs args. Same callee, same Promise shape. The rest form drops args under AOT; the array form doesn't.

Things I tried that did NOT reproduce

  • Generic async method with rest + union type + class + cross-module import (worked).
  • Same with a private numeric field on the class (worked).
  • Multi-level pass-through of the rest-collected array through three function calls (worked).
  • Class method with Map<number, State> lookup using the id field (worked).

So the trigger is something cumulative in the actual Connection class — possibly the number of other methods, interaction with the imports at the top of src/connection.ts (it imports from ./protocol/reader, ./protocol/writer, ./protocol/types, ./transport/net-socket, etc.), or interaction with the side-effect imports (./register-defaults, ./auth/handshake-hook). I'd be happy to bisect if you can suggest where to look.

Related

Workaround in @perryts/redis

Added commandArray(name, args) and pipeline.addArray(name, args) companions to the rest-form methods. Bun and Node ignore the indirection; AOT users have to call the array form until this is fixed.

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