Closed
Description
Limiting Type Argument Inference from Binding Patterns
declare function f<T>(): T;
// Inference from binding patterns makes suspicious code like this work.
const { foo } = f(); // T: { foo: any }
// Motivation
declare function pick<T, K extends keyof T>(keys: K[], obj?: T): Pick<T, K>;
const _ = pick(["b"], { a: "a", b: "b", }); // T = "b"
const {} = pick(["b"], { a: "a", b: "b", }); // T = "b" | "a" (!?) (before fix)
-
Arguably, these
any
s should be implicitany
s that get an error undernoImplicitAny
. -
Inference from return types is generally suspicious, but binding patterns are kind of in the same domain.
-
Idea last time: binding patterns are useful for tuples, but not objects.
declare function f<T>(cb: () => T): T; const [e1, e2, e3] = f(() => [1, "hi", true]);
-
But then
declare function f<T>(): T; const [foo] = f(); // ?
-
-
So when does an object binding pattern provide a useful contextual type?
-
If the binding pattern has a default.
-
For literals it seems useless.
-
More complex defaults?
// Union of literals declare const oneOrZero: 0 | 1; const { b = oneOrZero } = f({ b: 0 }); // Contextually sensitive parameters function doSomething(x: string): string; const { func = doSomething } = f({ func: x => x });
-
-
Is this another level of inference priority?
- No, we just have two sets of inference contexts.
-
Conclusion: do it, get reviewed
Narrowing Type Parameters (un)Constrained to unknown
function deepEquals<T extends unknown>(a: T, b: T): boolean {
if (typeof a !== 'object' || typeof b !== 'object' || !a || !b) {
return false;
}
if (Array.isArray(a) || Array.isArray(b)) {
return false;
}
if (Object.keys(a).length !== Object.keys(b).length) { // Error here
return false;
}
return true;
}
- Given
someUnknown: unknown
,typeof someUnknown === "object
today narrows toobject | null
, but it doesn't narrow aT extends unknown
- Added some changes to do this, then added some changes so
keyof NonNullable<T>
→keyof T
succeeds.- Needed this when you index into these types, you need to preserve new behavior on higher order
NonNullable<T>
that you get (rather than on the previousT
).
- Needed this when you index into these types, you need to preserve new behavior on higher order
- Have a parallel effort to improve how intersections with
{}
operate - could unlock the same scenarios.- Ideally,
NonNullable<T>
would just beT & {}
, especially because intersections compose way better than conditional types. - With more aggressive
subtypesupertype reduction with{}
, we can get there. - Also, need to be able to allow
unknown
to be split betweenobject
,null
,undefined
, and{}
and remerged in CFA.
- Ideally,
- Can we do this without more aggressive subtype reduction?
- Actually, this isn't doing more aggressive subtype reduction, it's just supertype reduction.
- [[Above notes now reflect this]]
- Two approaches here, one focuses on the negative cases, the other on the positive cases.
- Both apply new narrowing
- Each PR has different "fallout", and has mitigations for the fallout.
- Conclusion: See if we can smush these changes together and take the best of each?