To provide an explanation for your query, let's start by revisiting the concept of "variance." In this context, I will be referring to definitions outlined in Microsoft's .NET documentation (with the exception of bivariance, which is not documented). Here is a summary of the different types of variance and their implications:
Variance |
Definition |
Allowed Substitutions |
Bivariance |
Covariance and Contravariance simultaneously |
Supertype -> Subtype, Subtype -> Supertype |
Covariance |
Allows usage of a more derived type than specified |
Supertype -> Subtype |
Contravariance |
Allows usage of a less derived type than specified |
Subtype -> Supertype |
Invariance |
Restricted to the originally specified type |
none |
Now, let's identify whether your types are considered as supertypes or subtypes:
type T1 = SubOptions extends ParentOptions ? true : false; // false
type T2 = ParentOptions extends SubOptions ? true : false; // true
Based on the above, we conclude that ParentOptions
is a subtype of SubOptions
, with the latter being its supertype. This scenario signifies attempting to assign a subtype when a supertype is anticipated when assigning parentFlow
labeled as Store<ParentOptions>
to subFlow
designated as Store<SubOptions>
.
Referring back to the variance table, this situation aligns with the requirement for covariance. However, encountering an error implies involvement of either contravariance or invariance. Upon analyzing the assignment of subFlow
to
parentFlow</code, where a <em>supertype expectation meets a subtype provision</em>, the conclusion reveals the application of <em>invariant</em> behavior. The assertion made by <a href="https://stackoverflow.com/users/8495254/captain-yossarian">@captain-yossarian</a> in this <a href="https://stackoverflow.com/questions/67482793/typescript-narrowing-type-with-generic-error#comment119279210_67482793">comment</a> proves valid:</p>
<blockquote>
<p>I believe that it is because subFlow and parentFlow are invariant to each other.</p>
</blockquote>
<p>Nonetheless, this enforced design limitation highlighted in TypeScript (as referenced in Anders Hejlsberg's <a href="https://github.com/microsoft/TypeScript/issues/32674#issuecomment-716178615" rel="nofollow noreferrer">comment</a> addressing a related concern) sacrifices flexibility for reliability. By eliminating the <code>[keyof Options]
indexing component, the contravariant assignment becomes feasible.
Regarding the proposed resolution strategy, restructuring the positioning of Params
outward results in the conversion of parameter types to covariant attributes due to the absence of aliasing within
T[keyof T]</code here. It's important to note that at its core structure, the type <code>Param
essentially represents:
type Param<Options> = Options[keyof Options]
, merely mapped
1.
An illustrative example0 of the solution process can be observed as follows:
type Param<Options> = {
[K in keyof Options]: Readonly<{
id: K,
options: Options[K],
}>
}[keyof Options];
interface Store<Options> {
exec: (nextState: Options) => void
}
type SuperOptions = { 'b': { b: number } }
type SubOptions = { 'a': { a: string } } & SuperOptions
const test1 = (subtype: Store<Param<SubOptions>>) => subProcess1(subtype); // OK, Subtype -> Supertype, covariance
const test2 = (supertype: Store<Param<SuperOptions>>) => subProcess2(supertype); // error, Supertype -> Subtype, contravariance
const subProcess1 = (supertype: Store<Param<SuperOptions>>) => supertype.exec({ id: 'b', options: { b: 3 } }); // ok
const subProcess2 = (subtype: Store<Param<SubOptions>>) => subtype.exec({ id: 'b', options: { b: 3 } }); // ok
Playground
0 Your naming convention adds a layer of complexity to an already intricate issue: designating a subtype as
ParentOptions</code and a supertype as <code>SubOptions</code, despite representing the reverse relationship. To enhance clarity, I have renamed them <code>SubOptions
and
SuperOptions
accordingly.
1 As discussed in the comments, the relationship between
Store<Param<SubOptions>>
and
Store<Param<SuperOptions>></code portrays a <em>covariant</em> nature within the solution approach. However, the essence of <code>T[keyof T]</code in this context demonstrates a <em>contravariant</em> aspect, as elucidated in Anders's <a href="https://github.com/microsoft/TypeScript/issues/32674#issuecomment-716192565" rel="nofollow noreferrer">comment</a>—the <code>SuperOptions
supertype contains fewer properties than the
SubOptions
subtype without any distinctive features).