language-tools icon indicating copy to clipboard operation
language-tools copied to clipboard

TypeScript incorrectly considers variable as possibly null inside condition with array index access

Open KuSh opened this issue 2 years ago • 2 comments

Describe the bug

TypeScript error "Object is possibly 'undefined' ts(2532)" is shown for nested conditional checks. This is a special case of fixed #619

Reproduction

<script lang="ts">
  export let content: {
    list: {
      image?: {
        src: string;
        alt: string;
      };
    }[];
  };
  export let item = 0;
</script>

<div>
  {#if content.list[item]?.image}
    <img
      src={content.list[item].image.src}
      alt={content.list[item].image.alt}
    />
  {/if}
</div>

Expected behaviour

No error should be thrown

System Info

Dependencies versions:

"svelte": "^3.48.0", "svelte-check": "^2.7.2", "svelte-preprocess": "^4.10.7", "tslib": "^2.4.0", "typescript": "^4.7.4"

and

Svelte for VS Code v105.18.1

Which package is the issue about?

svelte-check

Additional Information, eg. Screenshots

No response

KuSh avatar Jun 30 '22 12:06 KuSh

This is a TypeScript limitation due to the array index access, so there's nothing we can do about it. You get the same error in a regular TS file:

let content: {
	list: {
		image?: {
			src: string;
			alt: string;
		};
	}[];
} = null as any;
let item = 0;

if (content.list[item]?.image) {
	content.list[item].image.src; // <- error
}

dummdidumm avatar Jun 30 '22 13:06 dummdidumm

Hi @dummdidumm, thanks for your answer

The difference is that in TypeScript I can mark them as defined ;)

if (content.list[item]?.image) {
	content.list[item]!.image!.src; // <- no error
}

But this is not possible in the markup. I have used another workaround where I store the indexed access in a variable in the code and check that variable instead of the array in the markup 👍

KuSh avatar Jun 30 '22 15:06 KuSh

Having a similar problem:

<script lang="ts">
	import List from '$components/forms/List.svelte'

	type Item = { key: string; value: string }

	export let items: Item[]
</script>

<List bind:items let:index>
	{#if items[index]} <!-- this check has no effect on the type -->
		<!-- Error: Object is possibly 'undefined'. -->
		<input bind:value={items[index].key} />
	{/if}
</List>

I would expect that:

  1. the if check would work like it does in regular TypeScript code
    items[12].key // Error
    
    	if (items[12]) {
    	items[12].key // no Error
    }
    
  2. we can use the ! operator inside the markup to tell the compiler "I know what I do"
    items[12]!.key // no Error in TypeScript
    
    <input bind:value={items[index]!.key} /> <!-- Error: Expected }-->
    

Becasue of the two-way-binding in a loop I can't really store the value in a variable and mark it as defined inside the script tag.

Current workaround: Instead of marking the items as an array, make them a dynamic tuple with at least a single fixed item. Then I can cast the index to the fixed value 0 and I can get rid of the error.

<script lang="ts">
	import List from '$components/forms/List.svelte'

	type Item = { key: string; value: string }

	export let items: [Item, ...Item[]] // `tuple` instead of `array`

	const typedAs0 = (value: number) => value as 0 // cast to fixed value `0`
</script>

<List bind:items let:index>
	{#if items[index]}
		<input bind:value={items[typedAs0(index)].key} />
	{/if}
</List>

ivanhofer avatar Nov 24 '22 06:11 ivanhofer