Skip to content

Svelte 5: Variadic snippets #9672

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Rich-Harris opened this issue Nov 27, 2023 · 8 comments · Fixed by #9988
Closed

Svelte 5: Variadic snippets #9672

Rich-Harris opened this issue Nov 27, 2023 · 8 comments · Fixed by #9988
Milestone

Comments

@Rich-Harris
Copy link
Member

Describe the problem

Snippets take a single argument. This is for two reasons:

  • we initially thought it would be difficult to type variadic snippets
  • a vague notion that it might be easier to optimise snippets (e.g. hoisting them) if we know that they always have zero or one arguments

Describe the proposed solution

Hat-tip to @tcc-sejohnson for solving the typing issue:

type Snippet<Args = any, This = any> = Args extends any[] ? (this: This, ...args: Args) => unknown : (this: This, arg: Args) => unknown;

type MyCustomSnippet = Snippet<[firstName: string, lastName: string, dob: Date]>;
type MyCustomSnippetWithOneArg = Snippet<{ firstName: string, lastName: string, dob: Date }>;

I haven't thought deeply about the optimisation question. It may be a non-issue.

Alternatives considered

not doing this

Importance

nice to have

@elliott-with-the-longest-name-on-github
Copy link
Contributor

You can remove the This generic and just change this: This to this: void!

@dummdidumm dummdidumm added this to the 5.0 milestone Nov 30, 2023
@dummdidumm
Copy link
Member

There's one caveat with ambiguity: type MyCustomSnippetThatShouldHaveOneArg = Snippet<boolean[]> is interpreted as passing many arguments as booleans when the user likely meant "I want this to be one argument". The solution for this is type MyCustomSnippetThatShouldHaveOneArg = Snippet<[boolean[]]> - not sure how many people would run into this.

@Not-Jayden
Copy link
Contributor

Not-Jayden commented Dec 5, 2023

There's one caveat with ambiguity: type MyCustomSnippetThatShouldHaveOneArg = Snippet<boolean[]> is interpreted as passing many arguments as booleans when the user likely meant "I want this to be one argument". The solution for this is type MyCustomSnippetThatShouldHaveOneArg = Snippet<[boolean[]]> - not sure how many people would run into this.

Maybe it should just be simplified to always require an array generic?

type Snippet<Args extends unknown[] = []> = (this: void, ...argsArgs) => unknown;

type ObjectSnippet = Snippet<[{ namestring, agenumber }]>;
type TupleSnippet = Snippet<[string, number, boolean, ...boolean[]]>;
type BooleanRestSnippet = Snippet<[...boolean[]]>;
type BooleanArraySnippet = Snippet<[boolean[]]>;
type OptionalArgSnippet = Snippet<
  [string, number, boolean?]
>;

Reduces ambiguity as you'll always need to type the function arguments based on their position in the arguments list. Just has the tradeoff of being a bit less pretty for the single argument cases.

EDIT:
I guess this doesn't actually address the problem of being able to do this and expecting it to be a single arg:

type BooleanRestSnippet = Snippet<boolean[]>;

but it's still less ambiguous once you realise it's always an array of function args.

@elliott-with-the-longest-name-on-github
Copy link
Contributor

I raise you all the version that works with tuples, single-argument-typed-as-an-array, and objects + primitives as a single argument:

type Snippet<Args = any> = 
    Args extends any[]
        // it's an array
        ? number extends Args['length']
            // it's an array, not a tuple
            ? (this: void, arg: Args) => unknown
            // it's a tuple (because the type of Args['length'] is narrower than `number`)
            : (this: void, ...args: Args) => unknown
        // it's anything else
        : (this: void, arg: Args) => unknown;

type MyCustomSnippet = Snippet<[firstName: string, lastName: string, dob: Date]>;
type MyCustomSnippetWithOneArg = Snippet<{ firstName: string, lastName: string, dob: Date }>;
type MyPreviouslyBrokenSnippet = Snippet<boolean[]>

Playground

@Not-Jayden
Copy link
Contributor

@tcc-sejohnson this prevents being able to define snippets with single rest arguments AFAICT though.

e.g.

type MyBrokenSnippet = Snippet<[...boolean[]]>
//   ^? (this: void, arg: boolean[]) => unknown

I'd probably go with your original type over this one for that reason, though I'm still leaning towards the generic always being an array that maps to the function args as the most practical/unambiguous solution.

@elliott-with-the-longest-name-on-github
Copy link
Contributor

Rats 😆 yeah, guess I don't have a solution for that one. I kinda agree with you, there's something nice about just:

type Snippet<Args = any> = (this: void, ...args: Args) => unknown;

type MyCustomSnippet = Snippet<[todos: Todo[]]>;

It's an extra pair of brackets in the absolute simplest case, but it gives you just one way to do things...

@flo-at
Copy link

flo-at commented Jan 31, 2024

Good to see this is completed now!
Just to make sure, the type of children would be Snippet<[]>, right?

Edit: I just saw the updated documentation, Snippet seems to be the correct answer.

@dummdidumm
Copy link
Member

Snippet is equivalent to Snippet<[]> so they both work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants