Typescript: Type 'undefined' is not assignable to type 'string' (reduced from a null-able function parameter)

Created on 2 Mar 2017  ·  8Comments  ·  Source: microsoft/TypeScript

TypeScript Version: 2.2.1

Code

function foo(bar: string | undefined = '') {
    bar = undefined;
//  ^ Type 'undefined' is not assignable to type 'string'.
}

Possibly related to #13826.

Fixed

Most helpful comment

We have talked in the past about distinguishing yet another "empty" value called missing, which would allow us to distinguish between things like f() and f(undefined) or {} and { x: undefined }. That's the missing piece here. It would allow the compiler to distinguish these cases:

function f(x: string = '') {
  x.length // ok
  x = undefined // error!
}
function g(y: string | undefined = '') {
  y.length // ok, undefined was removed by narrowing
  y = undefined // ok
}
f(undefined) // error!
g(undefined) // ok!

By letting the assigned type of x: string | missing and y: string | undefined | missing. This additional type would let the compiler disallow the call f(undefined) but allow g(undefined). And inside the function the default initialiser would remove both undefined and missing, so the type of both would be string. However, in g, the assigned type of y would still allow assignment of undefined.

If we added missingType, default initialisers and ? would add it; I'm not sure if there would be syntax in the language for it. There probably wouldn't be any values at all of type missing because it signifies the absence of values.

We did not add this feature for a few reasons.

  1. It is complex: the compiler would need yet another null-like type and control flow would have to correctly narrow it when used in parameters and object literals.
  2. It only applies to a couple of cases — default initialisers and object literal assignability. These cases are both of limited scope; common usage won't encounter the distinction very often.
  3. The intent of ? vs | undefined in object literals [1] is a lot less clear. This change would break code and it's not clear that the object literal break is a desired one.

The mixture of complexity, limited applicability and possibility of unwanted breakage sank this feature. We might re-visit this in the future, but I don't think our decision will change.

[1] To be consistent object literal ? should add missing instead of undefined as well:

type A = {
  a?: number,

  other1: number | undefined,
  other2?: number | undefined,
  other3: number
}
let o: A = {
  a: undefined // error! Previously ok

  // missing other1: error before and after
  // missing other2: ok
  other2: undefined // also ok before and after
  // missing other3: error before and after

All 8 comments

This is related to #14406

you can work around it by doing function foo(bar = <string | undefined>'') { ...

@krisselden Nice try but that will make the variable string | undefined when entering the function.

This is really a design limitation in the existing system. See https://github.com/Microsoft/TypeScript/pull/12033 for more details on why and how it was implemented.

The crux here is outside the function the type is string | undefined. in side the function, since there is a default, the undefined is stripped out of the type leaving you with only string. This means that the type annotation is not really changing any thing, it is still string | undefined for the caller, and string within the function body.

@mhegazy But can't we just do something similar with something like:

let p: string | undefined = '';
// here `p` has type `string`
p = undefined; // no error

I understand that sometimes if we write (p = '') in parameter list we would want the type of p to be string, but maybe we should treat p: string | undefined = '' differently as it explicitly specifies the type of p.

Yes, I think there is a difference between "I said this could be undefined for callers" vs "this can always be undefined". It's not clear what the limitations are from the linked discussion. @sandersn

We have talked in the past about distinguishing yet another "empty" value called missing, which would allow us to distinguish between things like f() and f(undefined) or {} and { x: undefined }. That's the missing piece here. It would allow the compiler to distinguish these cases:

function f(x: string = '') {
  x.length // ok
  x = undefined // error!
}
function g(y: string | undefined = '') {
  y.length // ok, undefined was removed by narrowing
  y = undefined // ok
}
f(undefined) // error!
g(undefined) // ok!

By letting the assigned type of x: string | missing and y: string | undefined | missing. This additional type would let the compiler disallow the call f(undefined) but allow g(undefined). And inside the function the default initialiser would remove both undefined and missing, so the type of both would be string. However, in g, the assigned type of y would still allow assignment of undefined.

If we added missingType, default initialisers and ? would add it; I'm not sure if there would be syntax in the language for it. There probably wouldn't be any values at all of type missing because it signifies the absence of values.

We did not add this feature for a few reasons.

  1. It is complex: the compiler would need yet another null-like type and control flow would have to correctly narrow it when used in parameters and object literals.
  2. It only applies to a couple of cases — default initialisers and object literal assignability. These cases are both of limited scope; common usage won't encounter the distinction very often.
  3. The intent of ? vs | undefined in object literals [1] is a lot less clear. This change would break code and it's not clear that the object literal break is a desired one.

The mixture of complexity, limited applicability and possibility of unwanted breakage sank this feature. We might re-visit this in the future, but I don't think our decision will change.

[1] To be consistent object literal ? should add missing instead of undefined as well:

type A = {
  a?: number,

  other1: number | undefined,
  other2?: number | undefined,
  other3: number
}
let o: A = {
  a: undefined // error! Previously ok

  // missing other1: error before and after
  // missing other2: ok
  other2: undefined // also ok before and after
  // missing other3: error before and after

As the error says, localStorage.getItem() can return either a string or null. JSON.parse() requires a string, so you should test the result of localStorage.getItem() before you try to use it.

For example:
this.currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}');

or perhaps:

const userJson = localStorage.getItem('currentUser');
this.currentUser = userJson !== null ? JSON.parse(userJson) : new User();
Was this page helpful?
0 / 5 - 0 ratings

Related issues

MartynasZilinskas picture MartynasZilinskas  ·  3Comments

dlaberge picture dlaberge  ·  3Comments

fwanicka picture fwanicka  ·  3Comments

remojansen picture remojansen  ·  3Comments

blendsdk picture blendsdk  ·  3Comments