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
TypeScript incorrectly considers variable as possibly null inside condition that checks variable #619
Comments
Don't know what can we do to deal with it. svelte2tsx transform it into this: () => (<>
{() => {if (test){<>
<div>
{() => {if (test.author){<>
<div>Author: {test.author}</div>
</>}}}
</div>
</>}}}</>); because it's wrapped in a function so that the control flow type analysis won't take into effect. And we can't remove the function or wrapped into a immediately-invoked function because the expression would re-run when it's dependency cahnged, we definitely needs to invalidate some of the control flow type. |
Could we turn this into ternaries? If there's not else block we just use an empty string for the else case. |
Maybe allow ! syntax? |
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. {() => {if (test && test.author){<>
<div>Author: {test.author}</div>
</>}}} |
Temporary workaround: <script lang="typescript">
interface TestObject {
author?: string;
}
export let test: TestObject | null;
let requitedTest: Required<TestObject>;
$: {
requiredTest = test as unknown as Required<TestObject>;
}
</script>
{#if test}
<div>
{#if requiredTest.author}
<div>Author: {requiredTest.author}</div>
{/if}
</div>
{/if} Copying variable, assigning different type and using it in nested condition. It is ugly and needs to be used carefully, but it does get rid of warnings. |
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} |
Seems to work here. |
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 |
Thanks! I always assumed it was TS feature similar to non-null statement Always learning something new :) |
I did some tests in that direction and I think this will not work for all cases. It may silence errors for |
Maybe the "append upper-level if-blocks"-thing is correct after all. It is not safe to assume that the conditions are still true inside each/await blocks. For example this will throw a runtime error ( <script>
let name = 'name';
function setNameToNumber(){
name = 1
return ['a']
}
</script>
{#if name}
<h1>Hello {name.toUpperCase()}!</h1>
{#each setNameToNumber() as item}
item
{/each}
{/if} The same can be constructed for an await-block. |
sveltejs#619 This way the TypeScript control flow will be broken in less instances and nested if-blocks like ``` {#if a} {#if a.a} .. {/if} {/if} ``` will not throw "a could be undefined" in the inner if anymore in strict mode. This will not shut down all instances of control flow breakage, because for example slots/each/await are still transformed to lambda-functions.
#619 This way the TypeScript control flow will be broken in less instances and nested if-blocks like ``` {#if a} {#if a.a} .. {/if} {/if} ``` will not throw "a could be undefined" in the inner if anymore in strict mode. This will not shut down all instances of control flow breakage, because for example slots/each/await are still transformed to lambda-functions.
Small update/summary:
|
Now that #493 is fixed in a way that declares In
Out <>{__sveltets_each((true, items), (item) => (somecondition && anothercondition) && <>
...
</>)}</> The tricky part is to get elseif/else and nesting logic right. |
This adds an IfScope to svelte2tsx which keeps track of the if scope including nested ifs. That scope is used to prepend the resulting condition check to all inner lambda functions that svelte2tsx needs to create for slot, each, await. This way, TypeScript's control flow can determine the type of checked variables and no longer loses that info due to the lambda functions. sveltejs#619
This adds two new scopes to svelte2tsx: IfScope, which keeps track of the if scope including nested ifs, and TemplateScope, which tracks all initialized variables at certain AST levels (await, each, let:slot).. These scopes are used to prepend a repeated if-condition check to all inner lambda functions that svelte2tsx needs to create for slot, each, await. This way, TypeScript's control flow can determine the type of checked variables inside these lambda functions and no longer loses that info. #619
This should be fixed with VS Code version 104.6.3 / |
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} |
That's just the regular TS behavior though. Your value is referenced in a function that could be called any time in the future where value might be back to null. |
Both are valid views. One could say "since |
I'll open a different issue for that so we can discuss this seperately. |
I don't think it's worth your time to discuss TS's own, old choices :p |
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 😄 |
Hi, These doesn't seems to work completely. Dependencies versions: "svelte": "^3.48.0", and Svelte for VS Code v105.16.1 <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> |
Describe the bug
TypeScript error "Object is possibly null" is shown for nested conditional checks.
To Reproduce
Simple test case:
TypeScript throws error
Object is possibly null
on line{#if test.author}
, not specifically ontest
inside that line.Expected behavior
Expected not to throw any errors.
System (please complete the following information):
Additional context
If nested conditional statement is removed and in TestObject interface
author?
is replaced withauthor
(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 thattest
is not null.The text was updated successfully, but these errors were encountered: