Skip to content

Incorrect variance inference when parameters of an interface with eventually different variances are bundled in an object type #56069

Closed
@geoffreytools

Description

@geoffreytools

🔎 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

https://www.typescriptlang.org/play?ts=5.2.2#code/C4TwDgpgBAYg9nAPASQHYBooHkCuwB8UAvFAMIBOEAhsBPEgN5QCWqAXFGpnHh7sFAC++AFAjWtcgDMqAY2gVqteogAKUCAA9aqACYBnWAlVVyVALb7CDEVBbsoACllxUwMx1UBtAOSsfALoAlMSEAG5wzLq2UDzAHI4hRITePnGBIoJioJBGcCZmlsRQTKwcVKgg3LxQFSBCANzZ4NBousyUssAqACoa2hB6hir67qwA5pioOOYARhDk+IQkPU0iOa30xW0dEF0qI2Ook1AAjEsiAPSXdrd39w92AH4vr29vVzc9LVA+h+QTTDnHxQXRwCCGVBwAT6GjMfRSerAAAW0BcqFGZgkv3+gKg0zmC3wPgAdJ87N9cj4CfNyCD4fjobV9PpmONUFRZgAbaDAOBQDa-U4+MRXABUUCkzE0UDFl2auQAoppIF1ED1liUoF4ANL2KAAawgIDgUigPQCHB6uoCQgVrT0u32CBg0ogunV-R0BigytVwEQuOOUxmtKWmtW9s49CwepIO063Rdbo9QZO50I12wOrElwl6PccC5svl6x+ACFTCgMNg8JrFDQIJXyIhSg40EJMPxROI3AsZPIyJRG821F7Bj7mwULPou3WSjEyk4Cx4oKl-MFQlAIlEYnEEklCPxMlGp6YZ8U2+VKo0owm9sBR30tN7DKPMXiaUSI2tBchm9sjqJqO75HOmSxQFmsainmUCwuY0BhKYzAVIOcpRqQcAqFwtYEMUDa0JhKhXpwNb7rhQg9hI-ZyAow6EVhCBji+E7DMY56WNYi4OOuqAZHY5GpOkAQnmWuT3l0RFMc+AxDGQjFIB+wb4qG37FJGYmtFJ-LxkBD7aYgBk+FQPiYAATBBUE6kAA

💻 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 both In and Out as expected and a ContraFoo would behave similarly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions