Skip to content

Variance annotations in type aliases for interfaces do not affect assignability #59790

@bgenia

Description

@bgenia

🔎 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions