-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Uninitialized variables work around strictNullChecks #13884
Comments
Ping! This still feels broken to me. |
I've been bitten by this too, this seems broken that It's not even that TS is unaware that it's potentially undefined as the below snippet does error.
|
Have you tried |
Using |
You can add |
Ah, my example was a bit unclear. I'm fine with TS being unable to know when a variable has been initialized in a closure. I'm advocating for the same expected behavior as the OP, no error at line 6 but an error at line 5 |
Well, that's kind of unpractical for deferred initializations such as in |
Sure, but I'm not enabling I'm sure there are cases where this necessitates additional |
Coming from a CoffeeScript background, this seems like a non-sequitur to me. I consider deferred initialization to be a bit of a code smell and tend to rely on immediately invoked function expressions for complicated initialization:
CoffeeScript has syntax sugar for IIFE's with The point is, instead of forcing the compiler to reason about variable initialization (which isn't complete because it can't deal with closures), just do complicated initialization via IIFE's. Syntax sugar is nice but it's workable without. |
@rraval But then, how do you express self referential initializations? let self = {
x: 4,
doSomething: () => self.x + self.x
} It can be made a special case, however I think the current situation strikes a good compromise:
|
@gcnew I'm not sure I follow. The following code seems perfectly reasonable to me: let self = {
x: 4,
doSomething: () => self.x + self.x
};
Here's a thornier example that currently breaks even with function indirection(func: () => Number): Number {
return func();
}
const foo: {a: Number, b: Number} = {
a: 1,
b: indirection(() => foo.a),
}; This results in a runtime Here's how I would expect things to work. I might've gotten things horribly wrong so please correct me if so:
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 |
Ping! We might as well close this issue if it's just going to languish like this. |
let num_or_undefined:number
function runtimeerror() { num_or_undefined.toString() }
runtimeerror() How is this not a compile error in strict mode? How can i make it error?
I feel like maybe line 1 should be an error? (typescript's not making me feel very type safe) |
@bterlson we discussed this at Edge summit way back in September, any chance you followed up? :) |
@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 class Foo {
private x: number; // Compile-time error!
foo() {
// `x` isn't initialized!
// But it's OK, compile-time error above.
return this.x.toString();
}
} But this code compiles successfully: function fooFactory() {
let x: number;
return {
foo() {
// `x` isn't initialized!
// Run-time error!
return x.toString();
}
};
} Shouldn't there be a 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 Expanding upon option (B), this code was given as an example in the linked-to blog: let x!: number[];
initialize();
x.push(4);
function initialize() {
x = [0, 1, 2, 3];
} 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): let x: number[]; // Doesn't require definite assignment assertion.
function foo() {
x.toString();
}
foo(); // No compile-time error, but throws an exception. Thoughts? |
@agopshi you can log a new suggestion for that |
TypeScript Version: 2.2.0
--strictNullChecks
must be used.Code
Expected behavior:
Error at
let foo: Foo
line since the compiler is implicitly doinglet foo: Foo = undefined;
.If I were to write the
= undefined
initializer myself, the compiler properly errors with:Actual behavior:
The code compiles and errors at runtime:
The text was updated successfully, but these errors were encountered: