1

I am looking for a way to not provide an empty array for a generic function Parameter<F>-typed parameter when F does not receive parameters. The following working example shows the current state

type A<F extends (...args: any[]) => any> = {
    shouldPrintHello: boolean;
    params: Parameters<F>;
};

const wrappingFunction = <F extends (...args: any[]) => any>(sentFunction: F, defaultParams: A<F>) => {
    const innterFunction = (...args: Parameters<F>) => {
        if (defaultParams.shouldPrintHello) console.log("hello");
        sentFunction(args);
        return;
    };

    const defaultValue = sentFunction(defaultParams);
    return innterFunction;
};

const f1 = wrappingFunction(
    (arg0: string) => {
        return;
    },
    { shouldPrintHello: true, params: ["defaultString"] }
);

const f2 = wrappingFunction(
    () => {
        return;
    },
    { shouldPrintHello: true, params: [] }
);

f1("a string");
f2();

Desired (pseudo) code changes:

type A<F extends (...args: any[]) => any> = {
    shouldPrintHello: boolean;
    params: Parameters<F> === [] ? undefined : Parameters<F>;
};

const f2 = wrappingFunction(
    () => {
        return;
    },
    { shouldPrintHello: true }
);

f2();
9
  • Have you checked out the extends keyword? Commented Oct 11, 2022 at 13:42
  • yes, but I don't see how it can be of use here sadly Commented Oct 11, 2022 at 14:05
  • How? It functions very similarly to equality. Although it's not "equality" but more of "<:", it still works here. Commented Oct 11, 2022 at 14:07
  • Does this approach meet your needs? If so I could write up an answer; if not, what am I missing? (Please say @jcalz in your comment to notify me) Commented Oct 11, 2022 at 16:00
  • @jcalz thank you but this approach doesn't work for me. I'm trying to implement it as follows but without success: link Commented Oct 12, 2022 at 7:13

2 Answers 2

1

There's no built-in way to express that a property is optional if and only if some condition holds of its value, so if you want a type like that you have to write it yourself as a conditional type:

type OptionalParamsIfAllowedToBeEmpty<P> =
  [] extends P ? { params?: P } : { params: P }

And then you can intersect it into the rest of your type definition:

type A<F extends (...args: any[]) => any> = {
  shouldPrintHello: boolean;
} & OptionalParamsIfAllowedToBeEmpty<Parameters<F>>;

declare const wrappingFunction: <F extends (...args: any[]) => any>(
  sentFunction: F,
  defaultParams: A<F>) => (...args: Parameters<F>) => void

Let's test it out. First, let's make sure it behaves as desired for functions with at least one required parameter:

wrappingFunction(
  (arg0: string) => {
    return;
  },
  { shouldPrintHello: true, params: ["defaultString"] }
);    

wrappingFunction(
  (arg0: string) => {
    return;
  },
  { shouldPrintHello: true } // error! missing params
);

Looks good, params is not optional. Then let's test it for functions without any parameters:

wrappingFunction(
  () => {
    return;
  },
  { shouldPrintHello: true }
);

wrappingFunction(
  () => {
    return;
  },
  { shouldPrintHello: true, params: [] } // okay
);

wrappingFunction(
  () => {
    return;
  },
  { shouldPrintHello: true, params: undefined } // okay
);

wrappingFunction(
  () => {
    return;
  },
  { shouldPrintHello: true, params: ["defaultString"] } // error
);

Also looks good; you can leave out params, or pass in undefined or an empty array [], but it won't let you pass in the wrong parameters. Finally, let's see how it behaves with a function with parameters that are all optional:

function f(x?: string) { }
wrappingFunction(f, { shouldPrintHello: false }) // okay
wrappingFunction(f, { shouldPrintHello: false, params: ["abc"] }) // okay

That's also what you want, I think. Since you can call f(), you shouldn't be required to pass in params in wrappingFunction, but since you can also call f("abc"), you should be allowed to pass in params as ["abc"]. This is an important difference between the [] extends P check in OptionalParamsIfAllowedToBeEmpty<P> and a seemingly similar P extends [] check; if P is [x?: string], then [] extends [x?: string] is true, but [x?: string] extends [] is false.

Playground link to code

Sign up to request clarification or add additional context in comments.

Comments

0

Don't be afraid to use extends to check for "equality":

type A<F extends (...args: any[]) => any> = {
    shouldPrintHello: boolean;
    params: Parameters<F> extends [] ? undefined : Parameters<F>;
};

Alternatively, you can check the length:

params: Parameters<F>["length"] extends 0 ? undefined : Parameters<F>;

1 Comment

Thanks, but after applying your suggestion I still must use { shouldPrintHello: true, params: undefined }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.