Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Fix multiple issues with indexed access types applied to mapped types#47109

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ahejlsberg merged 2 commits intomainfromfix30581
Dec 14, 2021

Conversation

@ahejlsberg
Copy link
Member

@ahejlsbergahejlsberg commentedDec 10, 2021
edited
Loading

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:

typeUnionRecord=|{kind:"n",v:number,f:(v:number)=>void}|{kind:"s",v:string,f:(v:string)=>void}|{kind:"b",v:boolean,f:(v:boolean)=>void};functionprocessRecord(rec:UnionRecord){rec.f(rec.v);// Error, 'string | number | boolean' not assignable to 'never'}

TheUnionRecord 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 fromsome record can be passed as an argument to anf fromsome record--but not necessarily thesame 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 theUnionRecord type encodes the correspondence between kinds and types and the correlated properties:

typeRecordMap={n:number,s:string,b:boolean};typeRecordType<KextendskeyofRecordMap>={kind:K,v:RecordMap[K],f:(v:RecordMap[K])=>void};typeUnionRecord=RecordType<'n'>|RecordType<'s'>|RecordType<'b'>;

The manual step of creating a union ofRecordType<K> for each key inRecordMap can instead be automated:

typeUnionRecord={[PinkeyofRecordMap]:RecordType<P>}[keyofRecordMap];

The pattern of applying an indexed access type to a mapped type is effectively a device for distributing a type (in this caseRecordType<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:

typeUnionRecord<KextendskeyofRecordMap=keyofRecordMap>={[PinK]:RecordType<P>}[K];

We can then mergeRecordType<K> intoUnionRecord<K>, which removes the possibility of creating non-distributed records. This leaves us with the final formulation:

typeRecordMap={n:number,s:string,b:boolean};typeUnionRecord<KextendskeyofRecordMap=keyofRecordMap>={[PinK]:{kind:P,v:RecordMap[P],f:(v:RecordMap[P])=>void}}[K];

And, using this formulation, we can now write types for theprocessRecord function that reflect the correlation and work for singletons as well as unions:

functionprocessRecord<KextendskeyofRecordMap>(rec:UnionRecord<K>){rec.f(rec.v);// Ok}declareconstr1:UnionRecord<'n'>;// { kind: 'n', v: number, f: (v: number) => void }declareconstr2:UnionRecord;// { kind: 'n', ... } | { kind: 's', ... } | { kind: 'b', ... }processRecord(r1);processRecord(r2);processRecord({kind:'n',v:42,f:v=>v.toExponential()});

And, because everything is expressed in terms of the mapping inRecordMap, new kinds and data types can be added in a single place, nicely conforming to the DRY principle.

Fixes#30581.

authereal, CYBAI, ibezkrovnyi, mrwensveen, danvk, karlhorky, r-cyr, karol-majewski, avery-radmacher, reaktor-esakoskinen, and 54 more reacted with heart emojijcalz, peterjuras, CYBAI, gabritto, reaktor-esakoskinen, JowoScript, Bnaya, svieira, captain-yossarian, kiprasmel, and 11 more reacted with rocket emoji
@typescript-bottypescript-bot added Author: Team For Uncommitted BugPR for untriaged, rejected, closed or missing bug labelsDec 10, 2021
@ahejlsberg
Copy link
MemberAuthor

@typescript-bot perf test faster
@typescript-bot test this
@typescript-bot user test inline
@typescript-bot run dt

@typescript-bot
Copy link
Collaborator

typescript-bot commentedDec 10, 2021
edited
Loading

Heya@ahejlsberg, I've started to run the abridged perf test suite on this PR atd42e4c2. You can monitor the buildhere.

Update:The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commentedDec 10, 2021
edited
Loading

Heya@ahejlsberg, I've started to run the extended test suite on this PR atd42e4c2. You can monitor the buildhere.

@typescript-bot
Copy link
Collaborator

typescript-bot commentedDec 10, 2021
edited
Loading

Heya@ahejlsberg, I've started to run the parallelized Definitely Typed test suite on this PR atd42e4c2. You can monitor the buildhere.

@ahejlsberg
Copy link
MemberAuthor

@typescript-bot user test this inline

@typescript-bot
Copy link
Collaborator

typescript-bot commentedDec 10, 2021
edited
Loading

Heya@ahejlsberg, I've started to run the inline community code test suite on this PR atd42e4c2. You can monitor the buildhere.

Update:The results are in!

@typescript-bot
Copy link
Collaborator

@ahejlsberg
The results of the perf run you requested are in!

Here they are:

Comparison Report - main..47109

Metricmain47109DeltaBestWorst
Angular - node (v14.15.1, x64)
Memory used331,241k (± 0.01%)331,232k (± 0.01%)-9k (- 0.00%)331,147k331,307k
Parse Time1.95s (± 0.46%)1.94s (± 1.45%)-0.01s (- 0.67%)1.92s2.05s
Bind Time0.86s (± 0.64%)0.86s (± 0.52%)-0.00s (- 0.46%)0.85s0.87s
Check Time5.41s (± 0.36%)5.40s (± 0.40%)-0.01s (- 0.15%)5.37s5.47s
Emit Time6.21s (± 0.65%)6.17s (± 0.65%)-0.04s (- 0.69%)6.11s6.28s
Total Time14.44s (± 0.34%)14.38s (± 0.30%)-0.06s (- 0.42%)14.28s14.46s
Compiler-Unions - node (v14.15.1, x64)
Memory used191,776k (± 0.60%)191,902k (± 0.62%)+126k (+ 0.07%)190,235k193,521k
Parse Time0.81s (± 1.00%)0.81s (± 0.94%)-0.01s (- 0.86%)0.79s0.83s
Bind Time0.56s (± 0.59%)0.56s (± 0.65%)-0.00s (- 0.71%)0.55s0.56s
Check Time7.44s (± 0.76%)7.48s (± 0.77%)+0.04s (+ 0.52%)7.36s7.66s
Emit Time2.48s (± 0.75%)2.48s (± 0.84%)-0.00s (- 0.08%)2.44s2.54s
Total Time11.29s (± 0.51%)11.32s (± 0.60%)+0.03s (+ 0.25%)11.19s11.50s
Monaco - node (v14.15.1, x64)
Memory used324,384k (± 0.00%)324,393k (± 0.01%)+9k (+ 0.00%)324,357k324,431k
Parse Time1.51s (± 0.62%)1.52s (± 1.26%)+0.00s (+ 0.20%)1.49s1.59s
Bind Time0.76s (± 0.90%)0.76s (± 0.48%)0.00s ( 0.00%)0.75s0.76s
Check Time5.33s (± 0.61%)5.31s (± 0.52%)-0.02s (- 0.32%)5.27s5.40s
Emit Time3.26s (± 0.46%)3.25s (± 0.66%)-0.01s (- 0.37%)3.21s3.32s
Total Time10.86s (± 0.44%)10.83s (± 0.44%)-0.02s (- 0.21%)10.73s10.95s
TFS - node (v14.15.1, x64)
Memory used289,204k (± 0.01%)289,192k (± 0.01%)-12k (- 0.00%)289,155k289,236k
Parse Time1.24s (± 0.72%)1.24s (± 0.89%)+0.00s (+ 0.32%)1.22s1.28s
Bind Time0.73s (± 0.41%)0.73s (± 0.76%)-0.00s (- 0.00%)0.72s0.75s
Check Time4.95s (± 0.45%)4.96s (± 0.57%)+0.00s (+ 0.10%)4.90s5.03s
Emit Time3.50s (± 1.22%)3.49s (± 0.63%)-0.02s (- 0.49%)3.44s3.55s
Total Time10.43s (± 0.48%)10.41s (± 0.43%)-0.01s (- 0.13%)10.30s10.49s
material-ui - node (v14.15.1, x64)
Memory used448,288k (± 0.06%)448,440k (± 0.00%)+152k (+ 0.03%)448,386k448,488k
Parse Time1.84s (± 0.36%)1.85s (± 2.15%)+0.00s (+ 0.27%)1.80s2.00s
Bind Time0.68s (± 0.65%)0.68s (± 0.76%)-0.00s (- 0.15%)0.67s0.70s
Check Time12.96s (± 0.97%)12.87s (± 0.63%)-0.09s (- 0.70%)12.76s13.11s
Emit Time0.00s (± 0.00%)0.00s (± 0.00%)0.00s ( NaN%)0.00s0.00s
Total Time15.49s (± 0.81%)15.40s (± 0.60%)-0.09s (- 0.59%)15.27s15.62s
xstate - node (v14.15.1, x64)
Memory used532,835k (± 0.00%)532,865k (± 0.00%)+30k (+ 0.01%)532,843k532,913k
Parse Time2.55s (± 0.23%)2.56s (± 0.56%)+0.01s (+ 0.51%)2.53s2.59s
Bind Time1.15s (± 0.65%)1.16s (± 0.91%)+0.00s (+ 0.35%)1.13s1.18s
Check Time1.48s (± 0.54%)1.48s (± 0.87%)-0.00s (- 0.13%)1.46s1.52s
Emit Time0.07s (± 0.00%)0.07s (± 0.00%)0.00s ( 0.00%)0.07s0.07s
Total Time5.26s (± 0.24%)5.27s (± 0.42%)+0.01s (+ 0.19%)5.22s5.32s
System
Machine Namets-ci-ubuntu
Platformlinux 4.4.0-210-generic
Architecturex64
Available Memory16 GB
Available Memory8 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v14.15.1, x64)
Scenarios
  • Angular - node (v14.15.1, x64)
  • Compiler-Unions - node (v14.15.1, x64)
  • Monaco - node (v14.15.1, x64)
  • TFS - node (v14.15.1, x64)
  • material-ui - node (v14.15.1, x64)
  • xstate - node (v14.15.1, x64)
BenchmarkNameIterations
Current4710910
Baselinemain10

Developer Information:

Download Benchmark

@typescript-bot
Copy link
Collaborator

@ahejlsberg
Great news! no new errors were found between main..refs/pull/47109/merge

@jcalz
Copy link
Contributor

I'm kind of surprised at how much of this already works in TS4.5 without these fixes. BothprocessRecord(r1); andprocessRecord(r2); are fine. Sure, theK parameter isn't inferred, but it falls back tokeyof RecordMap which is fine for a distributive object type. Only

processRecord({kind:'n',v:42,f:v=>v.toExponential()});// error

fails outright, and that's just because of unfortunate contextual typing ofv asRecordMap[keyof RecordMap] . In current TS I'd be inclined to fix that with either of these:

processRecord<'n'>({kind:'n',v:42,f:v=>v.toExponential()});// okayprocessRecord({kind:'n',v:42,f:(v:number)=>v.toExponential()});// okay

Am I right in thinking the current fix is mostly helping inference forK succeed here so that these are not necessary? I'm definitely interested in playing around with this. Thanks for working on it!!

Playground link

amakhrov, dragomirtitian, craigphicks, and nickzoum reacted with thumbs up emojicaptain-yossarian and nickzoum reacted with heart emoji

@ahejlsberg
Copy link
MemberAuthor

ahejlsberg commentedDec 11, 2021
edited
Loading

I'm kind of surprised at how much of this already works in TS4.5 without these fixes.

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.

Am I right in thinking the current fix is mostly helping inference for K succeed here so that these are not necessary?

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:

typeRecordFuncMap={[PinkeyofRecordMap]:(x:RecordMap[P])=>void};constrecordFuncs:RecordFuncMap={n:n=>n.toFixed(2),s:s=>s.length,b:b=>b ?1 :0,}functioncallRecordFunc<KextendskeyofRecordMap>(rec:UnionRecord<K>){constfn=recordFuncs[rec.kind];// RecordFuncMap[K]fn(rec.v);// Would fail, but works with this PR}

The call incallRecordFunc failed to compile because we transformed typeRecordFuncMap[K] intoRecordFuncMap[typeof RecordMap] by constraint substitution when obtaining what we call the apparent type offn. With the fix in the PR, we instead transformRecordFuncMap[K] into(x: RecordMap[K]) => void.

There are more examples of this pattern in the tests I just added to the PR. Seehere.

jcalz and svieira reacted with thumbs up emojithorn0 reacted with eyes emoji

@ahejlsberg
Copy link
MemberAuthor

Performance is unaffected by this PR and all tests look clean. I think this is ready to merge.

@ahejlsbergahejlsberg merged commit3d3825e intomainDec 14, 2021
@ahejlsbergahejlsberg deleted the fix30581 branchDecember 14, 2021 19:51
zerobias added a commit to effector/effector that referenced this pull requestDec 14, 2021
Anders Hejlsberg fixed issues of version 4.5.4 inmicrosoft/TypeScript#47109 so I revert typescript version until it will be releasedHejlsberg saved my day ❤️
@quixot1c
Copy link

I have run into an oddity that might be related to this PR. Consider the following code:

enumMyEvent{One,Two}typeCallback<T>=(arg:T)=>void;interfaceCallbackTypes{[MyEvent.One]:number[MyEvent.Two]:string}typeCallbacks={[EinMyEvent]:Callback<CallbackTypes[E]>[]};classCallbackContainer{callbacks:Callbacks={[MyEvent.One]:[],[MyEvent.Two]:[]};constructor(){}setListener<EextendsMyEvent>(event:E,callback:Callback<CallbackTypes[E]>){constcallbacks=this.callbacks[event];callbacks.push(callback);// okay}getListeners<EextendsMyEvent>(event:E){returnthis.callbacks[event];}}

Thanks to this PR above code now works! But when I changeconst callbacks = this.callbacks[event] toconst callbacks: Array<Callback<CallbackTypes[E]>> = this.callbacks[event] the compiler throwsType 'Callbacks[E]' is not assignable to type 'Callback<CallbackTypes[E]>[]'. But when (in both versions) examining the type ofcallbacks.push, the Array is typed asArray<Callback<CallbackTypes[E]>>.

Why then, isCallbacks[E] not assignable toCallback<CallbackTypes[E]>[] while in thepush call theCallbacks[E] is automagically converted to that very same typeArray<Callback<CallbackTypes[E]>>?

alextompkins reacted with thumbs up emoji

@jfet97jfet97 mentioned this pull requestJun 29, 2023
anderium added a commit to anderium/Arroost that referenced this pull requestJan 23, 2024
This does some hacky things to get inference that works for the mapping types.  Based on:microsoft/TypeScript#47109Also 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+W19hzjeasCOfZLD1Kvv2wVSoASigned-off-by: anderium <33520919+anderium@users.noreply.github.com>
@jcalzjcalz mentioned this pull requestJul 5, 2024
6 tasks
@Andarist
Copy link
Contributor

This PR is linked fromTypeScript 4.6 release notes but the implementation changed soon after it here:#47370

TodePond pushed a commit to TodePond/Arroost that referenced this pull requestNov 10, 2024
This does some hacky things to get inference that works for the mapping types.  Based on:microsoft/TypeScript#47109Also 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+W19hzjeasCOfZLD1Kvv2wVSoASigned-off-by: anderium <33520919+anderium@users.noreply.github.com>
@jcalzjcalz mentioned this pull requestJan 24, 2025
6 tasks
@microsoftmicrosoft locked asresolvedand limited conversation to collaboratorsOct 16, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.

Reviewers

@RyanCavanaughRyanCavanaughRyanCavanaugh approved these changes

@DanielRosenwasserDanielRosenwasserAwaiting requested review from DanielRosenwasser

@weswighamweswighamAwaiting requested review from weswigham

Assignees

@ahejlsbergahejlsberg

Labels

Author: TeamFor Uncommitted BugPR for untriaged, rejected, closed or missing bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wishlist: support for correlated union types

7 participants

@ahejlsberg@typescript-bot@jcalz@quixot1c@Andarist@RyanCavanaugh

[8]ページ先頭

©2009-2025 Movatter.jp