Skip to content

strictNullChecks mode - Object is possibly 'undefined'. #10642

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

Closed
aliai opened this issue Aug 31, 2016 · 7 comments
Closed

strictNullChecks mode - Object is possibly 'undefined'. #10642

aliai opened this issue Aug 31, 2016 · 7 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@aliai
Copy link

aliai commented Aug 31, 2016

In strictNullChecks mode only:

TypeScript Version: nightly (2.0.2)

Code

interface IRoute {
  children?: IRoute[];
}

function register(route: IRoute) {
  if (route.children) {                                   // <-- route.children is of type IRoute|undefined
    Object.keys(route.children).forEach(routeKey => {     // <-- route.children is of type IRoute
      register(route.children[routeKey]);  // <-- Error: Object 'route.children' is possibly 'undefined'.
    });
  }
}

Expected behavior:

There shouldn't be an error here, because route.children is being checked 2 lines above.

Actual behavior:

TypeScript fails to recognize route.children cannot be undefined anymore, because of the nested function. It is the same behavior with both function declaration and arrow functions.

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Aug 31, 2016
@RyanCavanaugh
Copy link
Member

This is the intended behavior because we don't know that route.children is still not-undefined at the point when the callback executes. It is the case that forEach executes its callback immediately, but this is not true of all functions and we don't know that someone isn't going to remove route.children after you leave the scope of register.

A good solution is to capture the property in a const, which we can statically know will still be not-undefined

function register(route: IRoute) {
  const children = route.children;
  if (children) {                                   // <-- children is of type IRoute|undefined
    Object.keys(children).forEach(routeKey => {     // <-- children is of type IRoute
      register(children[routeKey]);  // <-- OK
    });
  }
}

@aliai
Copy link
Author

aliai commented Aug 31, 2016

Thanks @RyanCavanaugh for the fast feedback :) That's true! I never thought of that possibility.

I just enabled strictNullChecks for our project and found these 3 issues (2 I posted earlier) that I couldn't wrap my head around. Truly amazing work 👍

@unional
Copy link
Contributor

unional commented Nov 21, 2016

@RyanCavanaugh for some reason this is not working for 2.0.10 and 2.1+ for me right now.

There got to be something on my side as it was working....

  let deferred = defer()
  setTimeout(() => {
    if (deferred) {
      deferred.resolve(1) // <-- still error with Object is possibly 'undefined'
    }
  }, 1)

Need some help. 🙇

@RyanCavanaugh
Copy link
Member

@unional please post question on Stack Overflow and/or provide a self-contained example

@unional
Copy link
Contributor

unional commented Nov 21, 2016

Yes. I can reproduce it:

// index.ts
export function defer() {
  let resolve
  let reject
  let promise = new Promise((r, j) => {
    resolve = r;
    reject = j;
  });
  return {
    resolve: resolve,
    reject: reject,
    promise: promise
  };
}

let deferred = defer()
setTimeout(() => {
  if (deferred !== undefined) {
    deferred.reject(1)  // <-- in VSC there is no error, but compiles fail
  }
}, 1)
// tsconfig.json
{
  "compilerOptions": {
    "lib": [
      "es2015",
      "dom"
    ],
    "strictNullChecks": true,
    "target": "es5"
  },
  "include": [
    "index.ts"
  ]
}

error:

index.ts(18,5): error TS2532: Object is possibly 'undefined'.

Tested with [email protected]

And it turns out the issue is fixed in [email protected] and does not exist in 2.0.10

@syberkitten
Copy link

Have the same issue with typescript (tsc) version 2.3.4
with the process module.

process.on('message', (msg) => {
  console.log('Message from parent:', msg);
});

but doing:

process.send()

does not compile saying:
error TS2532: Object is possibly 'undefined'.

adrienjoly added a commit to signaux-faibles/opensignauxfaibles that referenced this issue Jun 18, 2020
- [x] rendre optionnelles les props de `BatchValue`
- [x] effectuer les modifications demandées par TS suite à ce changement
- [x] retirer les conversions de type (`as`) qui étaient nécessaires en attendant ce changement (cf `TODO`s dans le code)

## Erreurs TS rencontrées suite au changement du type

- `ts(2532)`: "Object is possibly 'undefined'"
- `ts(2769)`: "No overload matches this call"

(liste non exhaustive)

## Méthodes de résolution employées

### 1. Réutilisation / introduction de variables ayant un type inféré

Quand un propriété de variable est affectée à une variable, et que cette dernière fait l'objet d'un _type guard_ (ex: condition vérifiant qu'elle ne vaut pas `undefined`), TS infère un type plus précis pour cette variable, mais pas pour la propriété qui lui a été affectée.

Mentionner cette variable au lieu de la propriété d'origine permet de bénéficier de cette inférence, et donc d'éviter une deuxième vérification.

Exemple: https://github.com./signaux-faibles/opensignauxfaibles/pull/67/files#diff-e110d168b746f33dff086ac08b3e622aR22

### 2. Introduction de _type guards_

Quand c'était possible mais pas encore fait, j'ai ajouté des conditions permettant de vérifier qu'une propriété de batch utilisée dans un block de code n'était pas `undefined`, afin de bénéficier de l'inférence de type de TS.

Exemple: https://github.com./signaux-faibles/opensignauxfaibles/pull/67/files#diff-365ef303242f26b37cc462f43bd58544R11

### 3. Utilisation de boucles `for` au lieu de `forEach`

Après l'usage d'un _type guard_, TypeScript est capable d'inférer le type de la variable considérée dans le block courant, mais pas dans les fonctions définies dans ce block. (ex: une fonction passée en paramètre de `forEach`)

Sources:
  - Flow analysis does not cross function boundaries, so since you are in another function you need to test again. (https://stackoverflow.com/a/49611195/592254)
  - This is the intended behavior because we don't know that `batch.compact` is still not-undefined at the point when `forEach`'s function executes. (microsoft/TypeScript#10642 (comment))

Contrairement à `forEach`, l'usage de boucle `for` permet de bénéficier de l'inférence de type.

Exemple: https://github.com./signaux-faibles/opensignauxfaibles/pull/67/files#diff-365ef303242f26b37cc462f43bd58544R12

### 4. De/re-structuration d'objets

Dans certains cas, notre code affecte des propriétés dans un objet qui pourrait s'avérer être `undefined`. La déstructuration et restructuration d'objets permet d'ignorer ce cas, car `{ ...undefined, [key]: val }` se contente de retourner un objet valide, alors que `undefined[key] = val` échouera.

Exemple: https://github.com./signaux-faibles/opensignauxfaibles/pull/67/files#diff-338ee2d75c428c416407680b35e229f1R202

### 5. Valeurs par défaut

Enfin, et en dernier recours, j'ai intégré des valeurs par défaut, lorsqu'une expression pouvait être `undefined`.

Exemple: https://github.com./signaux-faibles/opensignauxfaibles/pull/67/files#diff-a8438853384b3701fc9b6f0f76527127R109
@subins2000
Copy link

subins2000 commented Jul 4, 2020

Happens to me too :

Vue.directive('click-outside', {
  bind: (el, binding, vnode) => {
    const event = ..a function
    if (vnode) {
      vnode.context[binding.expression](event) // Object is possibly 'undefined'.ts(2532)
    }
  }
}

Both codium & compile shows the error. Issue being tracked at #10642

@microsoft microsoft locked as resolved and limited conversation to collaborators Jul 8, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

5 participants