Skip to content
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

Each Block Typing Issue #732

Closed
JAD3N opened this issue Jan 3, 2021 · 12 comments · Fixed by #1218
Closed

Each Block Typing Issue #732

JAD3N opened this issue Jan 3, 2021 · 12 comments · Fixed by #1218
Labels
bug Something isn't working Fixed Fixed in master branch. Pending production release.

Comments

@JAD3N
Copy link

JAD3N commented Jan 3, 2021

I'm trying to iterate over a properties values by using {#each...}, and I have two typings (this is a TypeScript project) in my Svelte component.

<script lang="ts">
    import Transition from './Transition/Transition.svelte';

    type Options = { text: string, value?: string }[];
    type OptionsGroup = Options[];

    export let options: Options | OptionsGroup = [];
</script>

{#each options as option}
    <div>example</div>
{/each}

I would expect the type of option in the each statement to be Options | OptionsGroup but VS Code is giving me an error saying:

Argument of type 'Options | OptionsGroup' is not assignable to parameter of type 'ArrayLike<{ text: string; value?: string; }>'.

I'm unsure of what exactly is going wrong as the build and dev tasks are working fine with no errors at all, it only seems to be an issue with VS Code.

System:

  • OS: Manjaro (Linux)
  • IDE: VSCode
  • Plugin/Package: Svelte for VSCode
@JAD3N JAD3N added the bug Something isn't working label Jan 3, 2021
@dkzlv
Copy link

dkzlv commented Jan 5, 2021

I also found a strange bug with {#each}.

Smallest repro possible:

<script lang="ts">
  let data: { name: string } | null = null,
    someArr = [1, 2, 3];
</script>

{#if data}
  <!-- Works fine -->
  {data.name}
  {#each someArr as _}
    <!-- Highlights an error! -->
    {data.name}
  {/each}
{/if}

Mac, VS Code, running the latest plugin. I have a gut feeling it is somewhat connected to this issue.

Proof of bug

@dummdidumm
Copy link
Member

@dkzlv that's #619

@dummdidumm
Copy link
Member

dummdidumm commented Jan 6, 2021

I fear this is not possible to fix on our side, it seems it's a TypeScript issue. You would get the same error when you do this:

function each<T>(
    array: ArrayLike<T>, // or T[], doesnt matter for the error
    callbackfn: (value: T, index: number) => any
): any{ /* doesnt matter */ };

each(options, a => a); // The same TS error

that each function is basically what is used to type-check the each-block. I did not find a way to silence the error without losing type safety.

@jasonlyu123
Copy link
Member

jasonlyu123 commented Jan 6, 2021

You can do this instead. I think I have this typescript error before and got it works by trying some random combination lol

<script lang="ts">
  type Option = { text: string, value?: string };
  type OptionsGroup = Option[];

  export let options: Array<Option | OptionsGroup> = [];
</script>

{#each options as option}
  <div>example</div>
{/each}

Edit:
Noted these two types are not the same, but you probably don't care about the difference most of the time. If you do care, you probably need to write some custom type guard to cast it.

@dummdidumm dummdidumm added question Further information is requested bug Something isn't working and removed bug Something isn't working labels Jan 6, 2021
@JAD3N
Copy link
Author

JAD3N commented Jan 6, 2021

Thank you for the clarification, I've amended my typings as recommend and that has pretty much solved it.

@JAD3N JAD3N closed this as completed Jan 6, 2021
@ITenthusiasm
Copy link
Contributor

ITenthusiasm commented Oct 25, 2021

that each function is basically what is used to type-check the each-block. I did not find a way to silence the error without losing type safety.

@dummdidumm I'm running into this same issue. Does Svelte use an ES array method (eg. map) to determine the type? Or does it define its own type? I think that would clarify whether this is a bug in Svelte's language tools or an issue in TS.

I agree with @jasonlyu123 that the two TS types are not the same, so I prefer not to have to do Array<T | U>. From the code, I'm imagining this is coming from the use case of a custom select component, with the code being oversimplified to drill into the specific problem. My code looks (almost) the exact same as JAD3N's when it comes to this use case, with similar types and type names.

In such a use case, the consumers of the component will pass in some kind of options prop. But Array<T | U> suggests to consumers that mixed arrays are supported by the component when in fact they are not.


Alternatively, is there some way to type cast within the HTML portion of a Svelte component?

@ITenthusiasm
Copy link
Contributor

ITenthusiasm commented Oct 26, 2021

Much sad. 😔 I'll open an issue on the TS repo. Thanks!

@ITenthusiasm
Copy link
Contributor

ITenthusiasm commented Oct 26, 2021

@dummdidumm Okay in the meantime, though, hear me out:

// TS v4.4.4

/** This is used by language-tools for #each */
declare function __sveltets_1_each<T>(
  array: T[],
  callbackfn: (value: T, index: number) => any
): any;

/** This is a potential fix for `language-tools`'s #each */
declare function __sveltets_2_each<T>(
  array: T,
  callbackfn: (value: T extends ArrayLike<infer U> ? U : never, index: number) => any
): any;

type Option = { text: string, value?: string };

const options: string[] | Option[] = ["1", "2", "3"];
const options2: string[] | Option[] = [{text: "1", value: "2"}];
let optionsX: string[] | Option[] = [];


// TS errors here
__sveltets_1_each(optionsX, e => e);

// No TS Errors here
__sveltets_2_each(options, e => e); // arg type = string[]
__sveltets_2_each(options2, e => e); // arg type = Object[]
__sveltets_2_each(optionsX, e => e); // arg type = string[] | Object[]

Here, the approach is changed. Instead of T being assumed to be the inner type, T is assumed to be the Array. (Only arrays are supposed to be passed in anyway.) This array type is immediately assigned to the first argument. And the second argument unpacks the array to discover the true inner type. This approach requires no updates to the TS repo, and it immediately resolves this bug.

Thoughts? Would this fit in to how the rest of this repo uses language-tools?

@ITenthusiasm
Copy link
Contributor

ITenthusiasm commented Oct 26, 2021

If we absolutely want the ArrayLike in the mix, then we should be able to get away with enforcing T extends ArrayLike<any>.

Here's a TS Playground link.

I'm not sure if ArrayLike or Array/[] is the preference. But both are definitely options. Here's an alternative using just an Array constraint:

declare function __sveltets_2_each<T extends any[]>(
  array: T,
  callbackfn: (value: T extends (infer U)[] ? U : never, index: number) => any
): any;

You get the exact same (good) pass/fail cases in the playground link with this approach.

Edit: Linking this part of the TS docs to show that this isn't a hack. 😅 So we can use this approach legitimately.

@ITenthusiasm
Copy link
Contributor

@dummdidumm @jasonlyu123 Any thoughts on this solution? If it doesn't work, I can accept that, move on, and try to open a TS issue. But from the code I've added, this does seem actionable, does it not?

If so, could we re-open this issue?

@dummdidumm dummdidumm reopened this Oct 27, 2021
@dummdidumm dummdidumm removed the question Further information is requested label Oct 27, 2021
dummdidumm pushed a commit that referenced this issue Oct 28, 2021
Example use case:

```
const array: string[] | SomeInterface[] = [];
```
#732
@dummdidumm dummdidumm added the Fixed Fixed in master branch. Pending production release. label Oct 28, 2021
@dummdidumm dummdidumm reopened this Oct 28, 2021
@ITenthusiasm
Copy link
Contributor

Awesome! @JAD3N if you ever want to go back to the previous syntax you were using, it's doable now. It's working for me on VS Code. 👌🏾

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working Fixed Fixed in master branch. Pending production release.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants