sveltejs / svelte

web development for the rest of us
https://svelte.dev
MIT License
80.31k stars 4.28k forks source link

`$state(undefined)` is typed as never on svelte-check #14435

Open ykrods opened 1 week ago

ykrods commented 1 week ago

Describe the bug

On a project generated by create-vite@5.5.5, running a type check to the following code will get a result that p is typed as never.

<script lang="ts">
  type Person = { name: string }

  let p: Person | undefined = $state(undefined)

  $effect(() => {
    // load person
    setTimeout(() => {
      p = { name: "bob" }
    }, 1000);
  });

  let displayName = $derived(p?.name ?? "anon") // => Error: Property 'name' does not exist on type 'never'. (ts)
</script>
<div>{ displayName }</div>

This is considered a bug because it does not occur when the following changes are made.

-  let p: Person | undefined = $state(undefined) // NG
+  let p: Person | undefined = $state() // OK

Reproduction

https://github.com/ykrods/svelte-reproduction-state-type

Logs

$ npm run check

> svelte-reproduction-state-type@0.0.0 check
> svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json

====================================
Loading svelte-check in workspace: /home/ykrods/work/svelte-reproduction-state-type
Getting Svelte diagnostics...

/home/ykrods/work/svelte-reproduction-state-type/src/App.svelte:13:33
Error: Property 'name' does not exist on type 'never'. (ts)

  let displayName = $derived(p?.name ?? "anon")
</script>

====================================
svelte-check found 1 error and 0 warnings in 1 file

System Info

System:
    OS: Linux 6.10 Manjaro Linux
    CPU: (16) x64 AMD Ryzen 7 5700U with Radeon Graphics
    Memory: 8.43 GB / 14.98 GB
    Container: Yes
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 22.8.0 - /usr/bin/node
    npm: 10.8.3 - /usr/bin/npm
  Browsers:
    Chromium: 131.0.6778.85
  npmPackages:
    svelte: ^5.1.3 => 5.2.8

Severity

annoyance

ykrods commented 1 week ago

I don't know where in the tool chain the problem is, so sorry if this is not the proper place to report it. Thanks for your hard work on Svelte!

bhuynhdev commented 1 week ago

I believe the correct way to declare types for $state is:

let p = $state<Person | undefined>(undefined);

This will fix it

Unsure why your way errors out though - The Typescript magic works in mysterious way

dummdidumm commented 1 week ago

From a TypeScript perspective this works as expected, because TypeScript does not see that p is assigned to anything else until it comes across p inside the $derived. Therefore moving the type definition onto the generic fixes this.

brunnerh commented 6 days ago

Essentially duplicate of:

(Appeal n to move that kit issue.)

ykrods commented 6 days ago

Thanks for responses!

Based on the received advise, I have confirmed that the following code fixes the error.

let p1 = $state<Person>()
let p2 = $state<Person | undefined>()
let p3 = $state<Person | undefined>(undefined)
let p4: Person | undefined = $state()
let p5: Person | undefined = $state<Person | undefined>(undefined)
ykrods commented 6 days ago

Please let me know if it is better to close this issue and discuss it further in another issue.

I understand now that if the type is not specified in generics, an error occurs due to a mismatch between the return type ( Person | undefined ) and the argument type ( undefined ) in the type inference (More precisely, the type of p is undefined when type-checking is performed because of the syntax of $derived, and the type of p becomes never since it never reaches the end of the optional chain), so how about define another generics variable for the argument?

As an example, changing the type of $state as follows could also fix the error.

declare function $state<T, U extends T = T>(initial: U): T;

( I am not used to typescript, so I can't judge if there is a problem with the code :skull: )

I think that maintainability, robustness, or other important features should take precedence over this, but I think it is simpler to write a state without explicit generics.

type definition

https://github.com/sveltejs/svelte/blob/19d80ad63c545bff2b84d29563cfab8e63b5bf68/packages/svelte/types/index.d.ts#L2337-L2338