Closed
Description
🔎 Search Terms
"variance", "object type", "interface", "higher order type", "type constraint", "generics"
🕗 Version & Regression Information
This is the behavior in every version I tried, and I reviewed the FAQ for entries about variance
⏯ Playground Link
💻 Code
type Foo<In, Out> = CreateFoo<{ in: In, out: Out }>
interface CreateFoo<P extends FooParams> {
in: (contra: P['in']) => void
out: () => P['out']
}
type FooParams = { in: any, out: any };
type IndirectFoo<T extends Foo<string, number>> = T;
type IFoo = IndirectFoo<Foo<string, 1>>
// ~~~~~~~~~~~~~~
// Type 'Foo<string, 1>' does not satisfy the constraint 'Foo<string, number>'.
// Type 'number' is not assignable to type '1'
/* fix */
type Expect<T> = { [K in keyof T]: T[K] }
type IndirectFooFixed<T extends Expect<Foo<string, number>>> = T;
type IFooOK = IndirectFooFixed<Foo<string, 1>> // OK
/* control */
type Bar<In, Out> = CreateBar<{ in: In }, Out>
interface CreateBar<P extends BarParams, Out> {
in: (contra: P['in']) => void
out: () => Out
}
type BarParams = { in: any };
type IndirectBar<T extends Bar<string, number>> = T;
type IBar = IndirectBar<Bar<string, 1>> // OK
/* same variance */
type CoFoo<In, Out> = CreateCoFoo<{ in: In, out: Out }>
interface CreateCoFoo<P extends FooParams> {
in: P['in']
out: P['out']
}
type IndirectCoFoo<T extends CoFoo<string, number>> = T;
type ICoFoo = IndirectCoFoo<CoFoo<'a', 2>> // OK
🙁 Actual behavior
Foo
has become invariant on In
and Out
(it would not accept Foo<unknown, number>
either).
🙂 Expected behavior
I would expect wrapping parameters in an object to be transparent. Foo
should remain contravariant on In
and covariant on Out
Additional information about the issue
It looks like variance information is not propagated correctly specifically when
- there is an interaction between an object type and an interface: when
Expect
is used to convert the interface into an object type, the variance check works properly; - the eventual variance of the object fields in the target interface is not the same:
CoFoo
is covariant on bothIn
andOut
as expected and aContraFoo
would behave similarly.