Closed
Description
Design Meeting Notes 3/29/2019
Safeness of indexed access types (#30603)
This code is patently unsafe:
function f2(obj: { a: number, b: string }, key: 'a' | 'b') {
obj[key] = 123;
}
We need to get different types for reading and writing an indexed access type.
Also has implications for constraints and assignability
Consider this code:
function foo<T, K extends keyof T>(obj: T, key: K) {
obj[key] = 123;
}
From a constraint perspective, the correct way to look at a reduction of T[K]
is:
- If
T extends C
, then we try to resolveT[K]
using the constraint (possibly recursively)- Better form: During simplification, specify whether this is a simplification for reading or writing
- Simplification today:
T[A | B]
simplifies toT[A] | T[B]
, which is only correct for reading - Similar: Given
(T | U)[K]
, the read simplification isT[K] | U[K]
and the write simplification isT[K] & U[K]
- Simplification today:
- During assignability checks, simplify the source for reading and the target for writing
- Once the unions, etc are removed via simplification, we still might have constraints
- e.g.
T[K] = S
whereT extends { a: string, b: number }
andK extends keyof T
- We need to pick the widest
K
("a" | "b")
and then distribute that (T['a'] | T['b']
]) into the narrowest target value - Seems to work pretty well
- e.g.
- In the unconstrained
T
case, what do we do?- Disallow assignment
- What about
T extends { [x: string]: any }
?- Is the intent that any assignment is legal here?
- Seems bad?
- Is the intent that any assignment is legal here?
- What about
T extends { [x: string]: number }
?- All writes via constraints are suspect...
- Equally suspect?
- Is
T[""] = 0
OK or no?- 🤷
- This is a subtyping violating if it's inconsistent with
T[K] = 0
- All writes via constraints are suspect...
- Better form: During simplification, specify whether this is a simplification for reading or writing
#30637
function print<T>(x: T): void {
console.log(x.toString());
}
print(null); // A-OK!
- This was OK because the default constraint of
T
is{}
and{}
acquires theObject
apparent members.- (Non-SNC back compat accomplished by adding the Object apparent members back to
unknown
)
- (Non-SNC back compat accomplished by adding the Object apparent members back to
- Now you have to write
print<T extends {}>
which becomes meaningful. - Some breaks in react-redux
- lodash has some breaks where they seem to rely on
{}
coming from broken inference (wow)- Most changes are actually just different error spans
- Back compat means what
- Fix lodash and react-redux
- Circular constraints keep hobbling on
- Should we lint-ban
{}
in DT (in files with sufficiently-high version)?- Yes (use
unknown
orobject
orstring | number | boolean | object | symbol
) - "Goodbye
{}
, we hardly knew ye" - @rbuckton
- Yes (use