Language-tools: TypeScript considère à tort la variable comme possiblement nulle à l'intérieur de la condition qui vérifie la variable

Créé le 18 oct. 2020  ·  20Commentaires  ·  Source: sveltejs/language-tools

Décrivez le bogue
L'erreur TypeScript «L'objet est peut-être nul» s'affiche pour les vérifications conditionnelles imbriquées.

Reproduire
Cas de test simple:

<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 renvoie l'erreur Object is possibly null sur la ligne {#if test.author} , pas spécifiquement sur test intérieur de cette ligne.

Comportement prévisible
On s'attend à ne pas lancer d'erreurs.

Système (veuillez compléter les informations suivantes):

  • Système d'exploitation: OSX 10.15.7
  • IDE: VSCode
  • Plugin / Package: "Svelte for VSCode"

Contexte supplémentaire
Si l'instruction conditionnelle imbriquée est supprimée et que dans l'interface TestObject author? est remplacée par author (pour la rendre obligatoire), il serait logique que TypeScript renvoie la même erreur pour {test.author} , mais ce n'est pas le cas. On dirait donc qu'une erreur est déclenchée par une instruction conditionnelle imbriquée, sans elle, TypeScript sait que test n'est pas nul.

Fixed bug

Commentaire le plus utile

Petite mise à jour / résumé:

  • le flux de contrôle est réinitialisé dès que le code est à l'intérieur de l'une de ces constructions: #await , #each , let:X . Cela est dû à la façon dont nous devons transformer le code. Nous n'avons pas oublié ce problème et nous cherchons toujours des moyens de le résoudre d'une bonne manière.
  • Les conditions if imbriquées devraient maintenant fonctionner comme prévu tant qu'elles ne sont pas interrompues par l'une des choses mentionnées dans le premier point

Tous les 20 commentaires

Je ne sais pas ce que nous pouvons faire pour y faire face. svelte2tsx le transforme en ceci:

() => (<>

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

car il est enveloppé dans une fonction afin que l'analyse du type de flux de contrôle ne prenne pas effet. Et nous ne pouvons pas supprimer la fonction ou être enveloppée dans une fonction immédiatement invoquée car l'expression serait réexécutée lorsque sa dépendance était cahnged, nous devons définitivement invalider une partie du type de flux de contrôle.

Pourrions-nous transformer cela en ternaires? S'il n'y a pas de bloc else, nous utilisons simplement une chaîne vide pour le cas else.
Mais le plus gros problème pourrait être que nous ne sommes pas autorisés à utiliser des fonctions comme celle-là dans aucune des transformations - je ne sais pas si c'est le cas actuellement

Permettez peut-être! syntaxe? {#if test!.author} . C'est une syntaxe TypeScript valide, mais pour le moment, elle ne peut pas être utilisée dans un modèle.

Pour le moment, vous ne pouvez pas utiliser de texte dactylographié dans le balisage. Il n'y a aucun moyen de prétraiter le balisage ou de laisser le compilateur analyser la syntaxe typographique. Une assertion non nulle n'est donc pas possible.
Je n'ai pas essayé l'achat de viabilité, nous pourrions peut-être copier la condition de niveau supérieur et l'ajouter avant la condition imbriquée

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

Solution provisoire:

<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}

Copier la variable, attribuer un type différent et l'utiliser dans une condition imbriquée. Il est moche et doit être utilisé avec précaution, mais il supprime les avertissements.

Une solution de contournement plus pragmatique pour le moment serait

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

C'est assez ennuyeux, surtout quand il ne s'agit pas seulement de percer à l'intérieur du même objet, mais de créer des conditions distinctes:

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

Cela ne fonctionnera pas. TypeScript n'est pas autorisé dans les modèles.

Semble fonctionner ici. test && test.author alors.

si vous voulez dire le chaînage facultatif, c'est une nouvelle fonctionnalité javascript, pas seulement une fonctionnalité dactylographiée. Il est disponible en svelte après 3.24.0

Merci! J'ai toujours supposé que c'était une fonctionnalité TS similaire à une déclaration non nulle foo! . Cela rend les choses beaucoup plus simples.

Toujours apprendre quelque chose de nouveau :)

Je n'ai pas essayé l'achat de viabilité, nous pourrions peut-être copier la condition de niveau supérieur et l'ajouter avant la condition imbriquée

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

J'ai fait quelques tests dans ce sens et je pense que cela ne fonctionnera pas dans tous les cas. Cela peut faire taire les erreurs pour #if , mais elles réapparaîtront dès que vous utiliserez un bloc #each ou #await intérieur. Ces erreurs n'apparaîtront pas si la variable est un const , car le flux de contrôle TS sait alors que la condition doit toujours être vraie car la variable ne peut pas être réaffectée. La meilleure façon de résoudre ce problème serait donc de tout transformer en variables const , ou de réaffecter toutes les variables à d'autres variables temporaires pour le modèle. Cela apportera ses propres problèmes pour les informations de survol, le changement de nom, la déclaration de passage, etc. Peut-être que cela pourrait être contourné en utilisant des expressions de virgule et transformer {#if foo} en {#if (foo, generatedConstFoo)} . Bottom line: cela nécessitera un certain piratage.

Peut-être que le "ajout de blocs if de niveau supérieur" est correct après tout. Il n'est pas sûr de supposer que les conditions sont toujours vraies à l'intérieur de chaque bloc / await. Par exemple, cela lancera une erreur d'exécution ( toUpperCase is not a function ):

<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}

La même chose peut être construite pour un bloc d'attente.

Petite mise à jour / résumé:

  • le flux de contrôle est réinitialisé dès que le code est à l'intérieur de l'une de ces constructions: #await , #each , let:X . Cela est dû à la façon dont nous devons transformer le code. Nous n'avons pas oublié ce problème et nous cherchons toujours des moyens de le résoudre d'une bonne manière.
  • Les conditions if imbriquées devraient maintenant fonctionner comme prévu tant qu'elles ne sont pas interrompues par l'une des choses mentionnées dans le premier point

Pour le moment, vous ne pouvez pas utiliser de texte dactylographié dans le balisage. Il n'y a aucun moyen de prétraiter le balisage ou de laisser le compilateur analyser la syntaxe typographique. Une assertion non nulle n'est donc pas possible.
Je n'ai pas essayé l'achat de viabilité, nous pourrions peut-être copier la condition de niveau supérieur et l'ajouter avant la condition imbriquée

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

Maintenant que # 493 est corrigé d'une manière qui déclare $store -variables, nous pouvons revoir cette idée. Mon idée est de préfixer toutes les conditions if comme celle-ci ( #each titre d'exemple):

Dans

{#if somecondition && anothercondition}
   {#each ..}
     ..
   {/each}
{/if}

En dehors

<>{__sveltets_each((true, items), (item) => (somecondition && anothercondition) && <>
    ...
</>)}</>

La partie la plus délicate est de bien comprendre elseif / else et la logique d'imbrication.

Cela devrait être corrigé avec VS Code version 104.6.3 / svelte-check version 1.2.4. S'il vous plaît laissez-moi savoir si cela fonctionne pour vous maintenant.

J'ai trouvé un problème - Cela fonctionne correctement dans les modèles maintenant, mais pas dans les écouteurs d'événements. Je ne sais pas si je devrais ouvrir un nouveau numéro pour cela, mais voici un 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}

J'ai trouvé un problème - Cela fonctionne correctement dans les modèles maintenant, mais pas dans les écouteurs d'événements. Je ne sais pas si je devrais ouvrir un nouveau numéro pour cela, mais voici un 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}

C'est juste le comportement habituel de TS. Votre valeur est référencée dans une fonction qui pourrait être appelée à tout moment dans le futur où value pourrait être de retour à null.

Les deux sont des vues valides. On pourrait dire "puisque value n'est pas une const, cela pourrait changer", mais d'un autre côté, on pourrait aussi dire "Je n'arrive jamais à ce gestionnaire de clics si value vaut null ". Rusé ..

Je vais ouvrir un autre numéro pour cela afin que nous puissions en discuter séparément.

Je ne pense pas que cela vaille la peine de discuter des anciens choix de TS: p
Le bouton ne resterait-il pas avec la valeur === null s'il y a une transition de sortie à leur niveau ou à celui de leurs parents par exemple?

À proprement parler, le flux de contrôle était correct avant ces changements relaxants car on pouvait modifier d'autres variables dans une boucle ou une attente.

<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}

.. mais qui fait ça? Vous pouvez également fabriquer les mêmes erreurs d'exécution dans TS, btw. Donc c'est plus un compromis dans DX que "c'est 100% correct".

Mais avant de discuter de cela plus en détail ici, veuillez porter cette discussion au n ° 876 😄

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

matthewmueller picture matthewmueller  ·  3Commentaires

non25 picture non25  ·  5Commentaires

AlexGalays picture AlexGalays  ·  5Commentaires

JAD3N picture JAD3N  ·  5Commentaires

vatro picture vatro  ·  3Commentaires