TypeScript
TypeScript copied to clipboard
Variance annotations in type aliases for interfaces do not affect assignability
🔎 Search Terms
variance annotations, type aliases, interfaces, assignability
🕗 Version & Regression Information
Previously noticed here for #56390 #56418, it still seems very strange. Is this behavior really intended?
This behavior exists since #56418 and looks like a bug.
⏯ Playground Link
https://www.typescriptlang.org/play/?ts=5.7.0-dev.20240828#code/JYOwLgpgTgZghgYwgAgGIHt0ElzXkgHnQFcxkAVAPmQG8AoZR5AfQC5kAKASmQF5rydAL506YAJ4AHFBnTkpEIqQrVetBkzace-CsNEg4AWwgBnSYhQ5IsSwCEIACzgA3YOijqmyQyfOXkAHVgMEdrPAD6b29QG3wrEBc4KGA4cFkCUGQSMipkCAAPSBAAE1M0THDbQjyaEQ1o5BKIBAAbZJRWiDJkqHYcJJS0sAyARmQAH2QAJkoG6Oa2juQusgAjKD7kAeTU9MwCUbn5716+ZA3PAHor7cTdtKRkR3RWsuQAWmoztJKLzeQyxA6B6plMwAA5oY1l1kGB0PlEI5sqFoCcmJdzr1RI1GBJpHDxmoANrIG7IOwg5HLeCtUwQdGMcmMAB6AH5GdF8RB0DBAQDCsV3tzef9PGy4VBiCh2LT6QAaTneEV8zGCiClcoq-niyXS5CyuB0hm45AAXXm9W8vjMFiewVC8mkAEFWqlylFcbEIk8dkN9uhMiBsso8urNRU5AoCLUrbjFu0oJ1ujr+vd-SMDuMprNOQnlqsxWnBntM4GjjjcWc1JjyX69k8Xm9yl8dYDSmL+ShgaDwVC4DCUPDEQhkVS0abMWpsZzuYTzqTyZTQl3kHKTbjmch2UqmNqzuHhQpRZiJWApTK10aFbu8cfVQKihqj9JRWczxeDVfjZyLd4RPUNr+E8ToQK67oOM4bgeF4TBAXaKAOmEuDVCgnqNHO9bDBkWQ5Co5yyFU8Qxscpr5kmKwpr0xYPAGhyTDMcxkS0ibJusmw0RmYykVWAI1gC5IYJ4pjoCYyBJnAInBqi9KrqYYDAK0rTIAg6AlsMHKTnxOqVhhCjziSZK3MS67ynqEB-putyspppp7vebaHlqDmnuZX6mbecIuY+QrOa+fLvm5ho-qalmMHGjDwQESGgeBkmwXpBJYXRuGhqokagSRukLCxBZURxdzqXR2aMXmuUUYWlycaW3HZac2m1rcyWNq87ytj8HaYkCIKAmCkLQrCw4QEiKKOBOuJTjps76WARLIIutzLtSFHrpyW47nZ9n+Y5T4RtqrnnvqwU3ptd7bWqu0vjyAUAh+R3fvSv6WvodBAA
💻 Code
interface FooInterface<out T> {
_: () => T
}
type FooType<out T> = {
_: () => T
}
namespace InterfaceBehavior {
namespace WithInterface {
interface InvariantFoo<in out T> extends FooInterface<T> {}
declare let arr: InvariantFoo<1 | 2>
declare let brr: InvariantFoo<1>
arr = brr // Invariance holds -> arr and brr are not assignable to each other
brr = arr
type t1 = [ // Both are false
// ^?
typeof arr extends typeof brr ? true : false,
typeof brr extends typeof arr ? true : false
]
}
namespace WithTypeAlias {
interface InvariantFoo<in out T> extends FooType<T> {}
declare let arr: InvariantFoo<1 | 2>
declare let brr: InvariantFoo<1>
arr = brr // Invariance holds -> arr and brr are not assignable to each other
brr = arr
type t1 = [ // Both are false
// ^?
typeof arr extends typeof brr ? true : false,
typeof brr extends typeof arr ? true : false
]
}
}
namespace TypeAliasBehavior {
namespace WithInterface {
type InvariantFoo<in out T> = FooInterface<T>
declare let arr: InvariantFoo<1 | 2>
declare let brr: InvariantFoo<1>
arr = brr // For some reason these are still covariant?
brr = arr
type t1 = [ // [false, true]
// ^?
typeof arr extends typeof brr ? true : false,
typeof brr extends typeof arr ? true : false
]
}
namespace WithTypeAlias {
type InvariantFoo<in out T> = FooType<T>
declare let arr: InvariantFoo<1 | 2>
declare let brr: InvariantFoo<1>
arr = brr // Invariance holds -> arr and brr are not assignable to each other
brr = arr
type t1 = [ // Both are false
// ^?
typeof arr extends typeof brr ? true : false,
typeof brr extends typeof arr ? true : false
]
}
}
🙁 Actual behavior
Restricting variance with annotations works in all cases EXCEPT for a type alias for an interface.
🙂 Expected behavior
I don't expect any difference between these cases. This is extremely unintuitive if intended.
Additional information about the issue
No response
This is a type merely with an alias symbol. It gets normalized to the contained type by getNormalizedType using createTypeReference almost at the beginning of isRelatedTo. It gets normalized to the innermost target.symbol in VarianceDeepShape test case from https://github.com/microsoft/TypeScript/pull/56418 (so to Level3of3Shape). This never had a chance to work correctly and I'm inclined to say that https://github.com/microsoft/TypeScript/pull/56418 was merged in incorrectly. This is probably the reason why it originally wasn't allowed.
Will discuss in #56390