TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Variance annotations in type aliases for interfaces do not affect assignability

Open bgenia opened this issue 1 year ago • 1 comments
trafficstars

🔎 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

bgenia avatar Aug 29 '24 03:08 bgenia

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.

Andarist avatar Aug 29 '24 07:08 Andarist

Will discuss in #56390

RyanCavanaugh avatar Sep 03 '24 23:09 RyanCavanaugh