Dom Hastings
Dom Hastings

Reputation: 438

Inferring generic types in nested constructors

I'm hitting a problem with a TypeScript project. I have a generic Processor that performs validation and processes data by accepting Validator and Executor objects which are also both generic, inferring their arguments from the Processor:

type ExecutorFunction<Args extends any[], Return extends any> = (...args: Args) => Return;

class Validator<Args extends any[]> {
  private validators: ExecutorFunction<Args, boolean>[] = [];

  constructor(...validators: ExecutorFunction<Args, boolean>[]) {
    this.validators.push(...validators);
  }

  validate(...args: Args): boolean {
    return this.validators.every((validator) => validator(...args));
  }
}

class Executor<Args extends any[], Return extends any> {
  private executors: ExecutorFunction<Args, Return>[] = [];

  constructor(...executors: ExecutorFunction<Args, Return>[]) {
    this.executors.push(...executors);
  }

  execute(...args: Args): Return[] {
    return this.executors.map((executor) => executor(...args));
  }
}

class Processor<Args extends any[], Return extends any> {
  private validators: Validator<Args>[] = [];
  private executors: Executor<Args, Return>[] = [];

  constructor(...args: (Validator<Args> | Executor<Args, Return>)[]) {
    args.forEach((item) => {
      if (item instanceof Validator) {
        this.validators.push(item);

        return;
      }

      this.executors.push(item);
    });
  }

  process(...args: Args): Return[] {
    return this.executors.flatMap((executor) => executor.execute(...args));
  }

  validate(...args: Args): boolean {
    return this.validators.every((validator) => validator.validate(...args));
  }
}

This works fine for simple cases, but if I add a Validator that accepts other validators (an OrValidator for example) the child Validators lose their generic value:

class OrValidator<Args extends any[]> extends Validator<Args> {
  constructor(...childValidators: Validator<Args>[]) {
    super((...args) =>
      childValidators.some((validator) => validator.validate(...args))
    );
  }
}

class NumberProcessor extends Processor<[number, number, number], number> {}

const numberProcessor = new NumberProcessor(
  new Validator((a) => a > 0),
  new Validator((a, b) => b > 10),
  new Validator((a, b, c) => Number.isFinite(c)),
  new OrValidator(
    new Validator((a) => a > 0),
    new Validator((a, b) => a > b), // This and the line below error because it only expects 1 argument
    new Validator((a, b, c) => c < 99)
  ),
  new Executor((a, b, c) => (a + b) * c)
);

I'd like the OrValidator to correctly pick up the NumberProcessors generic values, am I doing something wrong/missing something obvious?

Link to codesandbox.


After fixing the missing ... spread operator, the examples where I manually add the generic types to each nested items do work as expected, but the original question still stands as to why this isn't inferred automatically for the nested items.

Upvotes: 0

Views: 74

Answers (1)

Nullndr
Nullndr

Reputation: 1827

This is a problem with the infer of the functions inside the constructor of OrValidator.

Change them to

const numberProcessor = new NumberProcessor(
  new Validator((a) => a > 0),
  new Validator((a, b) => b > 10),
  new Validator((a, b, c) => Number.isFinite(c)),
  new OrValidator(
    new Validator((a, b, c) => c < 99),
    new Validator((a) => a > 0),
    new Validator((a, b) => a > b),
  ),
  new Executor((a, b, c) => (a + b) * c)
);

Or type the new OrValidator:

const numberProcessor = new NumberProcessor(
  new Validator((a) => a > 0),
  new Validator((a, b) => b > 10),
  new Validator((a, b, c) => Number.isFinite(c)),
  new OrValidator<[number ,number, number]>(
    new Validator((a) => a > 0),
    new Validator((a, b) => a > b),
    new Validator((a, b, c) => c < 99),
  ),
  new Executor((a, b, c) => (a + b) * c)
);

Upvotes: 1

Related Questions