language-tools: TypeScript incorrectly considers variable as possibly null inside condition that checks variable

Describe the bug TypeScript error “Object is possibly null” is shown for nested conditional checks.

To Reproduce Simple test case:

<script lang="typescript">
	interface TestObject {
		author?: string;
	}
	export let test: TestObject | null;
</script>

{#if test}
    <div>
        {#if test.author}
	        <div>Author: {test.author}</div>
	    {/if}
    </div>
{/if}

TypeScript throws error Object is possibly null on line {#if test.author}, not specifically on test inside that line.

Expected behavior Expected not to throw any errors.

System (please complete the following information):

  • OS: OSX 10.15.7
  • IDE: VSCode
  • Plugin/Package: “Svelte for VSCode”

Additional context If nested conditional statement is removed and in TestObject interface author? is replaced with author (to make it required), it would be logical for TypeScript to throw the same error for {test.author}, but it doesn’t. So looks like error is triggered by nested conditional statement, without it TypeScript knows that test is not null.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 10
  • Comments: 21 (13 by maintainers)

Commits related to this issue

Most upvoted comments

Small update/summary:

  • control flow is reset as soon as the code is inside one of these constructs: #await, #each, let:X. This is due to the way we have to transform the code. We haven’t forgotten about this issue and are still looking for ways to solves this in a good way.
  • nested if-conditions now should work as expected as long as they are not interrupted by one of the things mentioned in the first bullet point

Right now you can’t use typescript in markup. There’s no way to preprocess markup or let compiler parse typescript syntax. So non-null assertion is not possible. Haven’t tried the viability buy maybe we could copy the upper-level condition and append it before the nested condition

 {() => {if (test && test.author){<>
	        <div>Author: {test.author}</div>
	    </>}}}

I’ve found an issue - This works correctly inside templates now, but not in event listeners. I’m not sure if I should open a new issue for it, but here’s a repro:

<script lang="ts">
  let value: string | null = null;

  function acceptsString(value: string) {
    console.log(value);
  }
</script>

{#if value}
  <!-- Argument of type 'string | null' is not assignable to parameter of type 'string'.
  Type 'null' is not assignable to type 'string'. -->
  <button on:click={() => acceptsString(value)} />
{/if}

Thanks! I always assumed it was TS feature similar to non-null statement foo!. That makes it much simpler.

Always learning something new 😃

if you mean optional chaining, that’s a new javascript feature, not just a typescript feature. It’s available in svelte after 3.24.0

A more pragmatic workaround for now would be

{#if test}
    <div>
        {#if test?.author}
	        <div>Author: {test.author}</div>
	    {/if}
    </div>
{/if}

It’s quite annoying, especially when it’s not just drilling inside the same object but making separate conditions:

{#if bigSwitch}
    <div>
        {#if smallSwitch}
	        <MyComponent {bigSwitch} />
	    {/if}
    </div>
{/if}

Strictly speaking the control flow was correct before these relaxing changes because one could modify other variables within an each loop or an await.

<script lang="ts">
  let foo: string | null = 'bar';
  function getItems() {
     foo = null;
     return [''];
  }
</script>

{#if foo}
  {#each getItems() as item}
    {foo.toUpperCase()} <!-- boom -->
  {/each}
{/if}

… but who does this? You can fabricate the same runtime errors in TS, too, btw. So it’s more of a tradeoff in DX than “this is 100% correct”.

But before we discuss this further in here, please take this discussion to #876 😄