-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix multiple issues with indexed access types applied to mapped types #47109
Conversation
@typescript-bot perf test faster |
Heya @ahejlsberg, I've started to run the abridged perf test suite on this PR at d42e4c2. You can monitor the build here. Update: The results are in! |
Heya @ahejlsberg, I've started to run the extended test suite on this PR at d42e4c2. You can monitor the build here. |
Heya @ahejlsberg, I've started to run the parallelized Definitely Typed test suite on this PR at d42e4c2. You can monitor the build here. |
@typescript-bot user test this inline |
Heya @ahejlsberg, I've started to run the inline community code test suite on this PR at d42e4c2. You can monitor the build here. Update: The results are in! |
@ahejlsberg Here they are:Comparison Report - main..47109
System
Hosts
Scenarios
Developer Information: |
@ahejlsberg |
I'm kind of surprised at how much of this already works in TS4.5 without these fixes. Both processRecord({ kind: 'n', v: 42, f: v => v.toExponential() }); // error fails outright, and that's just because of unfortunate contextual typing of processRecord<'n'>({ kind: 'n', v: 42, f: v => v.toExponential() }); // okay
processRecord({ kind: 'n', v: 42, f: (v: number) => v.toExponential() }); // okay Am I right in thinking the current fix is mostly helping inference for |
Yes, the nice thing is that this approach isn't really a new feature. The core capabilities were all there, they just didn't work quite right in a few cases. This in contrast to a whole new feature like existential types, which would be a very complex undertaking.
The PR has three fixes in it. Two of them relate to inference, specifically that we would fail to infer from object literals that contain contextually typed arrow functions. The third fix relates to indexing non-generic mapped types with generic index types. For example: type RecordFuncMap = { [P in keyof RecordMap]: (x: RecordMap[P]) => void };
const recordFuncs: RecordFuncMap = {
n: n => n.toFixed(2),
s: s => s.length,
b: b => b ? 1 : 0,
}
function callRecordFunc<K extends keyof RecordMap>(rec: UnionRecord<K>) {
const fn = recordFuncs[rec.kind]; // RecordFuncMap[K]
fn(rec.v); // Would fail, but works with this PR
} The call in There are more examples of this pattern in the tests I just added to the PR. See here. |
Performance is unaffected by this PR and all tests look clean. I think this is ready to merge. |
Anders Hejlsberg fixed issues of version 4.5.4 in microsoft/TypeScript#47109 so I revert typescript version until it will be released Hejlsberg saved my day ❤️
I have run into an oddity that might be related to this PR. Consider the following code: enum MyEvent {
One,
Two
}
type Callback<T> = (arg: T) => void;
interface CallbackTypes {
[MyEvent.One]: number
[MyEvent.Two]: string
}
type Callbacks = { [E in MyEvent]: Callback<CallbackTypes[E]>[] };
class CallbackContainer {
callbacks: Callbacks = {
[MyEvent.One]: [],
[MyEvent.Two]: []
};
constructor() { }
setListener<E extends MyEvent>(event: E, callback: Callback<CallbackTypes[E]>) {
const callbacks = this.callbacks[event];
callbacks.push(callback); // okay
}
getListeners<E extends MyEvent>(event: E) {
return this.callbacks[event];
}
} Thanks to this PR above code now works! But when I change Why then, is |
This does some hacky things to get inference that works for the mapping types. Based on: microsoft/TypeScript#47109 Also adds two comments where the expect error cannot reasonably be removed (as far as I can see). Experiment with it here: https://www.typescriptlang.org/play?ts=5.3.3#code/C4TwDgpgBAYglgJwgEwPKQQQ2HA9gOygF4oBvKUSALigCIAzRFWgGigGMIAbLm-AVwC2AIwgIoAXwBQlaAFlcANxTox2PIRLlZNWoKXM2nHnyGiEbMLgDOcHARoBtASLFsX5gLqSZ4aKqx7TVgmNAx1AigAHygFZTC1IKlkgHoUij8oAIjg8kYkZBpUQTsAHngC7KC2WllaAD42fXiikuBSuJVw6ro6+skAbl9ILO6NABVMkiqNR1q-Wk9k2VHEjTlMMFKAaSgIAA9gCHxka1XAib9+rShHAAUoOEJtzxpSKShPjOooO5YpCRQABkWTapQAoocsOx2jMCGxSDpfhJGr0FvUAY4XkNhtAYPx8DD1psdntDsdTuccpNINcyB8vvdHs9XlAABS4MYOKlBDZbO71ACUxH6ilwcGQAOS7AI1mAUHoBKJBD5NHxhN5JLh+BpEDp7y+CtCNDZ+RQAH1OWtuRUutb8IKaGKJSL6YbDWbkJaufgAHTGLgMz4Sf6G5ooE3hr1Wi7czoJWMOp3i5Cug3uz5R732-3cQMZzMGaM+31WWxJQ3SaRSRUajRQTZgLggbWkg5HE5nbW6+ocn1FH18nZC5Mu9OfGX4OVQfbEKAxnK+2RBjiy+W19hzjeasCOfZLD1Kvv2wVSoA Signed-off-by: anderium <[email protected]>
This PR is linked from TypeScript 4.6 release notes but the implementation changed soon after it here: #47370 |
This does some hacky things to get inference that works for the mapping types. Based on: microsoft/TypeScript#47109 Also adds two comments where the expect error cannot reasonably be removed (as far as I can see). Experiment with it here: https://www.typescriptlang.org/play?ts=5.3.3#code/C4TwDgpgBAYglgJwgEwPKQQQ2HA9gOygF4oBvKUSALigCIAzRFWgGigGMIAbLm-AVwC2AIwgIoAXwBQlaAFlcANxTox2PIRLlZNWoKXM2nHnyGiEbMLgDOcHARoBtASLFsX5gLqSZ4aKqx7TVgmNAx1AigAHygFZTC1IKlkgHoUij8oAIjg8kYkZBpUQTsAHngC7KC2WllaAD42fXiikuBSuJVw6ro6+skAbl9ILO6NABVMkiqNR1q-Wk9k2VHEjTlMMFKAaSgIAA9gCHxka1XAib9+rShHAAUoOEJtzxpSKShPjOooO5YpCRQABkWTapQAoocsOx2jMCGxSDpfhJGr0FvUAY4XkNhtAYPx8DD1psdntDsdTuccpNINcyB8vvdHs9XlAABS4MYOKlBDZbO71ACUxH6ilwcGQAOS7AI1mAUHoBKJBD5NHxhN5JLh+BpEDp7y+CtCNDZ+RQAH1OWtuRUutb8IKaGKJSL6YbDWbkJaufgAHTGLgMz4Sf6G5ooE3hr1Wi7czoJWMOp3i5Cug3uz5R732-3cQMZzMGaM+31WWxJQ3SaRSRUajRQTZgLggbWkg5HE5nbW6+ocn1FH18nZC5Mu9OfGX4OVQfbEKAxnK+2RBjiy+W19hzjeasCOfZLD1Kvv2wVSoA Signed-off-by: anderium <[email protected]>
This PR fixes multiple issues related to indexed access types applied to mapped types. The fixes enable most of the problems outlined in #30581 to be solved using an approach that involves "distributive object types" created using indexed access types applied to mapped types.
First, a summary of the issue we're trying to solve:
The
UnionRecord
type is a union of records with two correlated properties,v
andf
, where the type ofv
is always the same as the type off
's parameter. In theprocessRecord
function we're attempting to take advantage of the correlation by callingrec.f(rec.v)
for some record. By casual inspection this seems perfectly safe, but the type checker doesn't see it that way. From its viewpoint, the type ofrec.v
isstring | number | boolean
and the type ofrec.f
is(v: never) => void
, thenever
originating from the intersectionstring & number & boolean
. The type is basically trying to check if av
from some record can be passed as an argument to anf
from some record--but not necessarily the same record.In the following I will outline an approach to writing types for this kind of pattern. Key to the approach is that the union types in question contain a discriminant property with a type that can itself serve as a property name (e.g. a string literal type or a unique symbol type). Note that the examples below only compile successfully with the fixes in this PR.
The following formulation of the
UnionRecord
type encodes the correspondence between kinds and types and the correlated properties:The manual step of creating a union of
RecordType<K>
for each key inRecordMap
can instead be automated:The pattern of applying an indexed access type to a mapped type is effectively a device for distributing a type (in this case
RecordType<P>
) over a union (in this casekeyof RecordMap
). We can even go one step further and allow aUnionRecord
to be created for any arbitrary subset of keys:We can then merge
RecordType<K>
intoUnionRecord<K>
, which removes the possibility of creating non-distributed records. This leaves us with the final formulation:And, using this formulation, we can now write types for the
processRecord
function that reflect the correlation and work for singletons as well as unions:And, because everything is expressed in terms of the mapping in
RecordMap
, new kinds and data types can be added in a single place, nicely conforming to the DRY principle.Fixes #30581.