Design Meeting Notes 3/29/2019
Safeness of indexed access types (#30603)
This code is patently unsafe:
functionf2(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:
functionfoo<T,KextendskeyofT>(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 resolve T[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 to T[A] | T[B], which is only correct for reading - Similar: Given
(T | U)[K], the read simplification is T[K] | U[K] and the write simplification is T[K] & U[K]
- 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 where T extends { a: string, b: number } and K 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
- In the unconstrained
T case, what do we do?
- What about
T extends { [x: string]: any } ?
- Is the intent that any assignment is legal here?
- What about
T extends { [x: string]: number } ?
- All writes via constraints are suspect...
- Is
T[""] = 0 OK or no?
- 🤷
- This is a subtyping violating if it's inconsistent with
T[K] = 0
functionprint<T>(x: T): void{console.log(x.toString());}print(null);// A-OK!- This was OK because the default constraint of
T is {} and {} acquires the Object apparent members.
- (Non-SNC back compat accomplished by adding the Object apparent members back to
unknown)
- 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 or object or string | number | boolean | object | symbol) - "Goodbye
{}, we hardly knew ye" - Ron Buckton (@rbuckton)
Design Meeting Notes 3/29/2019
Safeness of indexed access types (#30603)
This code is patently unsafe:
We need to get different types for reading and writing an indexed access type.
Also has implications for constraints and assignability
Consider this code:
From a constraint perspective, the correct way to look at a reduction of
T[K]is:T extends C, then we try to resolveT[K]using the constraint (possibly recursively)T[A | B]simplifies toT[A] | T[B], which is only correct for reading(T | U)[K], the read simplification isT[K] | U[K]and the write simplification isT[K] & U[K]T[K] = SwhereT extends { a: string, b: number }andK extends keyof TK("a" | "b")and then distribute that (T['a'] | T['b']]) into the narrowest target valueTcase, what do we do?T extends { [x: string]: any }?T extends { [x: string]: number }?T[""] = 0OK or no?T[K] = 0#30637
Tis{}and{}acquires theObjectapparent members.unknown)print<T extends {}>which becomes meaningful.{}coming from broken inference (wow){}in DT (in files with sufficiently-high version)?unknownorobjectorstring | number | boolean | object | symbol){}, we hardly knew ye" - Ron Buckton (@rbuckton)