TypeScript: Uninitialized variables work around strictNullChecks
TypeScript Version: 2.2.0
--strictNullChecks
must be used.
Code
interface Foo {
name: () => string;
}
let foo: Foo;
setTimeout((() => foo.name()), 0);
Expected behavior:
Error at let foo: Foo
line since the compiler is implicitly doing let foo: Foo = undefined;
.
If I were to write the = undefined
initializer myself, the compiler properly errors with:
Type 'undefined' is not assignable to type 'Foo'.
Actual behavior:
The code compiles and errors at runtime:
TypeError: foo is undefined
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 4
- Comments: 17 (6 by maintainers)
@RyanCavanaugh Why was this closed? Those fixes don’t cover this particular issue.
I think it’s especially important to revisit this issue now that
--strictPropertyInitialization
is available. With--strictPropertyInitialization
, this code correctly emits a compile error:Link
But this code compiles successfully:
Link
Shouldn’t there be a
--strictLocalInitialization
mode to catch errors like this? I imagine it would work similarly to--strictPropertyInitialization
: if a local variable is not synchronously initialized before it’s used, a compile error would be emitted. To resolve it, one would need to:A) Initialize the variable in a way that the TypeScript compiler understands (e.g. immediately, or before referencing it in a function somewhere).
-OR-
B) Use a definite assignment assertion. (It seems that this issue would come up naturally when discussing definition assignment assertions for local variables.)
-OR-
C) Explicitly mark the variable as potentially
undefined
.Expanding upon option (B), this code was given as an example in the linked-to blog:
I think that this is a misleading example. That code - in an ideal world where the compiler performs flow analysis - should not need the definite assignment assertion.
However, this code should (and currently does not):
Thoughts?
Sure, but I’m not enabling
--strictNullChecks
and using TypeScript for the practicality and free-wheeling spirit of initializing variables however I please 😉 . When I’ve declared a variable to be of a particular type, I’ve explicitly expressed the desire forundefined
to not be considered a member of that type, and I assignundefined
to that variable somewhere in my program (regardless of if I potentially re-assign to a valid type elsewhere, such as in yourif/else
example), I expect a reasonable compiler to provide me with an error.I’m sure there are cases where this necessitates additional
!
operators throughout the code, but that’s to be expected if indeed a particular Object “is possibly ‘undefined’”.How is this not a compile error in strict mode? How can i make it error?
let num_or_undefined:number|undefined
works as intended, but i have to remember to put |undefined everywhere.I feel like maybe line 1 should be an error? Something like
Type 'undefined' is not assignable to type 'number'.
(it would be better if it could just be automatically considered ‘possibly undefined’ though)(typescript’s not making me feel very type safe)
@gcnew I’m not sure I follow. The following code seems perfectly reasonable to me:
self
would have type{x: Number, doSomething: () => Number}
, which the() => self.x + self.x
expression captures and everything should typecheck cleanly.Here’s a thornier example that currently breaks even with
const
:This results in a runtime
TypeError
becausefoo
isundefined
when the callback forb
is invoked.Here’s how I would expect things to work. I might’ve gotten things horribly wrong so please correct me if so:
strictNullChecks
, given a declarationlet x: T
orconst x: T
whereundefined ∉ T
,x
must be immediately initialized.x
, function definitions that capturex
are split into 2 types:x: T
in their body. This means that thedoSomething: () => self.x + self.x
from your example hasself: {x: Number, doSomething: () => Number}
.x: T | undefined
. For my example,b: indirection(() => foo.a)
, inside the functionfoo: {a: Number, b: Number} | undefined
, at which point the compiler can complain aboutfoo.a
because it no longer typechecks.Point 1 can be made palatable with sugar for IIFE’s for complicated initialization.
Point 2.1 should capture the bulk of self referencing function definitions and things just work. This is because the compiler knows that those functions cannot be invoked until after the variable has been initialized.
Point 2.2 is necessary to ensure correctness. The current version of the compiler breaks with a runtime
TypeError
, which seems to fly in the face of TypeScript’s primary goal.