Description
Type-Unreachable Code
-
Trend over the last few months - people keep asking us to flag code that can never be reached.
-
The analyses are unrelated, but they are all similar in spirit.
-
One example
interface Range { isEmpty(): boolean; } function isNotEmpty(r1: Range): boolean { return !r1.isEmpty // oops - forgot to call this }
- We catch these in
if
, but we don't catch this in other conditions.
// right side is unreachable let check = this.isEmpty || that.isEmpty; // also fishy? let result: boolean = !check;
declare const a: { b: number }; // why are you using a '?.'? const bar = a?.b;
- We catch these in
-
These are to support defensive checks, but unclear if it carries its weight.
-
We have some checks for
Promise
s and uncalled functions in conditions. -
For nullish-coalescing, we have a non-null assertion operator (postfix
!
), but we don't have a "possibly null assertion operator".- We use
node.parent
, and it's more convenient for us to say "this property is neverundefined
", so we declare it asparent: Node
which is not technically valid.
- We use
-
Ideally a lot of these would go into a linter - but
- Seems like type lint rules are slow.
- Also, do people actually run these lint rules by default?
-
The complexity comes from the fact that "these are not accurate".
-
One issue is you're able to say "I can use this as if it's not nullable" - could have something like "I can use this if it's not nullable - but don't warn me if I try to check if it's nullish."
-
We have suggestion diagnostics - why not leverage that?
- They show up in the editor, not at compilations.
-
Need an implementation - maybe "strict boolean checking".
- Feels like there's an existing issue for this, can't find it offhand.
Correlating Union Members Between Each Other
-
Want to be able to grab the type of one property and know that its type "corresponds" to the underlying consistuent of the union.
-
Kind of need to create a type variable to describe this - don't have that except in conditional types.
-
Very "dependent type"y.
-
Could imagine
type FindArg = { readonly fn: (x: infer T) => void; readonly arg: infer T; }
-
How does this tie in with usage?
const dispatchTable: FnAndArg[] = [ // ... ]; const entry1 = dispatchTable[someValue]; const { fn, arg } = dispatchTable[x]; fn(arg); // want this to succeed
- Could say that if these are all
readonly
andconst
, we can analyze these right.
- Could say that if these are all
-
Why not have a local type variable and make
FindArg
generic?type FindArg = { readonly fn: (x: infer T) => void; readonly arg: infer T; } const dispatchTable: FnAndArg[] = [ // ... ]; const entry1 = dispatchTable[someValue]; const { fn, arg } = dispatchTable[x]; fn(arg); // want this to succeed
- The type variable doesn't live at the use-site, it's local to each type.
-
Can sort of emulate this today
<T>() => ({ arg: T, callback: (x: T) => void }
-
Pretty much associated types - should revisit that issue too!
Overriding Subtype Reduction Between any
and unknown
-
Lots of cases where people want stricter results for places that return
any
such asJSON.parse
,fetch
,axios
, stricter DOM types, etc. -
Idea - during subtype reduction of a union, we would have
unknown
take precedence overany
. -
Feel like we had an experiment of this at some point.
What's happening with
strictAny
? #24737- This mode didn't work because
any
is a sledgehammer to say "lemme do it!"
- This mode didn't work because
-
Problem with this is that a lot of places will assume the types are reduced.
- The proposal is that it always reduces - this is doable.
-
Concern: people might use
any | unknown
everywhere - not always desirable when using contravariant positions- e.g.
(...args: any[]) => any
can be assigned any function (...args: unknown[]) => unknown
) doesn't work because you flip assignability for the parameters(...args: never[]) => unknown
is the "safe any function", but nobody can build the right intuition about what it means and whether it's useful - also barely buys you anything over(...args: any[]) => any
.
- e.g.
-
Another concern: instantiation assumes
T | U
could instantiate tounknown
instead ofany
whereas it would have previously beenunknown
. That's a break. -
A new type that's different from a union?
- Could be persuaded of that.