Closed ooflorent closed 3 years ago
I actually abuse this bug to have type-only assertions written as unreachable runtime code 😅
If I write code intended to assert types inside an if (false)
, typescript completely ignores the branch, not even typechecking it. If I do, however, write an if (!true)
, typescript will look inside the branch, and I get my type assertions ignored at runtime and stripped by the minifier.
I say this is related because the typeof
of const foo = !true
is false
, but is not treated the same as the literal false
, which is just the false
version of this bug.
// subscribe cc @DanielRosenwasser also :-)
Background: The control flow graph creator can't resolve the types of expressions (doing so would create a circular dependency between passes of the compiler), but does have some logic for detecting if (true) {
. Basically, TS thinks if (value === null) {
might not always execute, because during the construction of the control flow graph, it is not yet known that __TRUE__
is true
.
What's the reason for writing code like this? It seems like you'd only want to do this if you wanted __TRUE__
to sometimes not be true
(in which case, the error is correct)?
What's the reason for writing code like this?
The goal is to be able to remove those checks using a minification pass at your own risk. I understand that the output code would break the type contract.
The above snippet could be written as follow:
function ensureValue<T>(value: T | null): T {
if (__TRUE__) {
if (value === null) {
throw new Error();
}
}
return value!;
// ^
}
But I tried to see if it was possible to achieve the same result without using TypeScript syntax extension.
Moreover, since __TRUE__
was typed as true
and not boolean
, it is strange that it doesn't behave like true
literals.
It does seem odd that boolean
isn't inherently composed of two smaller types, false
and true
, separate from this use case - I'd expect type narrowing to also apply to something like:
function f(x: boolean) {
if (!x) { return; }
// here `x` is known to be `true`
}
@ljharb boolean
is just a union type.
type MapBool<T> = T extends true ? "true" : T extends false ? "false" : never;
type B = MapBool<boolean>; // "true" | "false";
Your example narrows correctly for me.
function f(x: boolean) {
if (!x) {
const f: false = x;
return;
}
const t: true = x;
}
You need --strictNullChecks
for the false
case to work correctly.
In that case, why wouldn’t the OP’s type of true
follow the variable around? I’m sure there’s something I’m missing.
@ljharb
The control flow graph handles type narrowing, and is used to essentially answer the question:
"Is the test (value === null)
reachable? Always/Sometimes/Never"
As @RyanCavanaugh already described, there are no types involved when building the graph. The reason the true
type doesn't follow the variable around is because the compiler hasn't actually got to type checking yet.
From the point of view of the graph, and trying to answer the "Always/Sometimes/Never" reachability question, the identifier __TRUE__
might aswell read:
__I_AM_SOME_IDENTIFIER_WITH_ANY_ARBITRARY_VALUE__
.
The graph doesn't know the identifier will always evaluate to true
, so it marks (value === null)
as sometimes reachable, rather than always. When the literal is used the graph can recognise the special syntactic form (without having to typecheck) and mark the conditional as always reachable.
(Please correct me if I'm wrong!)
@jack-williams 💯%
TypeScript Version: 3.3.0-dev.20190108
Search Terms:
Code
Expected behavior:
ensureValue
has a correct return type whenstrictNullChecks
is enabled.In other words, if a constant is typed as
true
, I expect it to behave liketrue
literal.In the above example, both
if
statements should infer the types the same way.Actual behavior:
There is a type error when
strictNullChecks
is enabled:Please note that
if (true)
works as expected.Playground Link:
http://www.typescriptlang.org/play/#src=function%20ensureValue%3CT%3E(value%3A%20T%20%7C%20null)%3A%20T%20%7B%0D%0A%20%20if%20(true)%20%7B%0D%0A%20%20%20%20if%20(value%20%3D%3D%3D%20null)%20%7B%0D%0A%20%20%20%20%20%20throw%20new%20Error()%0D%0A%20%20%20%20%7D%0D%0A%20%20%7D%0D%0A%20%20return%20value%20%2F%2F%20OK%0D%0A%7D%0D%0A%0D%0Adeclare%20const%20__TRUE__%3A%20true%0D%0A%2F%2F%20Same%20error%20using%20variable%20declaration%3A%0D%0A%2F%2F%20const%20__TRUE__%3A%20true%20%3D%20true%0D%0Afunction%20ensureValueWithConstant%3CT%3E(value%3A%20T%20%7C%20null)%3A%20T%20%7B%0D%0A%20%20if%20(__TRUE__)%20%7B%0D%0A%20%20%20%20if%20(value%20%3D%3D%3D%20null)%20%7B%0D%0A%20%20%20%20%20%20throw%20new%20Error()%0D%0A%20%20%20%20%7D%0D%0A%20%20%7D%0D%0A%20%20return%20value%20%2F%2F%20Hmm%0D%0A%7D
Related Issues: