You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Typescript incorrectly resolves discriminated unions of indexed types #37052
Actual behavior:
The following error Type '"B"' is not assignable to type '"A"'
Code
typeTinyModel={"A": "A"|"B";"B": "B";}typeTinyId<TextendskeyofTinyModel>={type: TinyModel[T];}leta1: TinyId<"A">;letb1: TinyId<"B">;// Should be semantically equivalent to a1 & b1leta2: {type: "A"|"B"};letb2: {type: "B"};functionfn1(){a1=b1;}// this should work, but doesn'tfunctionfn3(){a2=b2;}
Output
"use strict";leta1;letb1;// Should be semantically equivalent to a1 & b1leta2;letb2;functionfn1(){a1=b1;}// this should work, but doesn'tfunctionfn3(){a2=b2;}
Haven't checked if this is a duplicate yet but whatever the issue is doesn't seem to have to do with discriminated unions (or even necessarily unions) or indexed access either per se:
typeFoo={"a": unknown"b": string;}typeBar<TextendskeyofFoo>={type: Foo[T];}consterr: Bar<"a">=null!asBar<"b">;// error// Bar<"b"> is not assignable to Bar<"a">constokay: {type: Foo["a"]}=null!as{type: Foo["b"]};
I believe the issue here is that we measure indexed access types such as TinyModel[T] as invariant, and since TinyId<T> contains a property of such a type, we consider it to be invariant in T. Effectively our variance measurement is more conservative than a structural check in this case. We do have provisions for tagging measurements as unreliable (which causes a follow-up structural check in the failure case), but we only do that for certain mapped types and rest parameters. We could try to do it for indexed access types as well, but it may have undesirable performance impact.
TinyModel[T] is only covariant in T - but "a" and "b" are obviously not covariantly related; the issue is that variance reasoning about how T is possibly related between instances does not include any information from the constraint of TinyModel[T] as a whole (which requires information about TinyModel to be known). I don't think I've directly experimented with it, but adding Unreliable markers to access positions when the object type is non-generic should allow the structural fallback, yeah.
It's also worth mentioning that the compiler behavior changed at some point. The code in the playground example compiles in TS 3.3.3, but not in TS 3.5.1. (Also, thanks for the workaround, Ryan Cavanaugh (@RyanCavanaugh)!)
Wesley Wigham (@weswigham) I does seem that when a type variable is used as the index type in an indexed access type, its variance effectively becomes unmeasurable (because a relation between property names implies nothing about a relation between the types of those properties).
I've found a simpler workaround that doesn't involve conditional types: the type indexing must occur external to the definition of the object's shape. As an example, here's a slight modification to fix Joe Calzaretta (@jcalz) 's example:
typeFoo={"a": unknown"b": string;}typeBarInner<T>={type: T};typeBar<TextendskeyofFoo>=BarInner<Foo[T]>;consterr: Bar<"a">=null!asBar<"b">;// no longer an error!
I'm closing this as a design limitation. The core issue is that we conservatively measure most indexed access types as invariant, where structural comparisons might be less restrictive. For example, see my comment here.
Activity
jcalz commentedon Feb 26, 2020
Haven't checked if this is a duplicate yet but whatever the issue is doesn't seem to have to do with discriminated unions (or even necessarily unions) or indexed access either per se:
Playground
RyanCavanaugh commentedon Feb 26, 2020
Not sure why we're not falling back to structural comparison here.
Workaround
ahejlsberg commentedon Feb 26, 2020
I believe the issue here is that we measure indexed access types such as
TinyModel[T]as invariant, and sinceTinyId<T>contains a property of such a type, we consider it to be invariant inT. Effectively our variance measurement is more conservative than a structural check in this case. We do have provisions for tagging measurements as unreliable (which causes a follow-up structural check in the failure case), but we only do that for certain mapped types and rest parameters. We could try to do it for indexed access types as well, but it may have undesirable performance impact.ahejlsberg commentedon Feb 26, 2020
Wesley Wigham (@weswigham) Don't recall if you've experimented with this before.
weswigham commentedon Feb 27, 2020
TinyModel[T]is only covariant inT- but"a"and"b"are obviously not covariantly related; the issue is that variance reasoning about howTis possibly related between instances does not include any information from the constraint ofTinyModel[T]as a whole (which requires information aboutTinyModelto be known). I don't think I've directly experimented with it, but addingUnreliablemarkers to access positions when the object type is non-generic should allow the structural fallback, yeah.mmorearty commentedon Feb 27, 2020
It's also worth mentioning that the compiler behavior changed at some point. The code in the playground example compiles in TS 3.3.3, but not in TS 3.5.1. (Also, thanks for the workaround, Ryan Cavanaugh (@RyanCavanaugh)!)
ahejlsberg commentedon Feb 28, 2020
Wesley Wigham (@weswigham) I does seem that when a type variable is used as the index type in an indexed access type, its variance effectively becomes unmeasurable (because a relation between property names implies nothing about a relation between the types of those properties).
jchurchill commentedon Mar 8, 2020
I've found a simpler workaround that doesn't involve conditional types: the type indexing must occur external to the definition of the object's shape. As an example, here's a slight modification to fix Joe Calzaretta (@jcalz) 's example:
ahejlsberg commentedon Oct 26, 2020
I'm closing this as a design limitation. The core issue is that we conservatively measure most indexed access types as invariant, where structural comparisons might be less restrictive. For example, see my comment here.