「Optional variance annotations by ahejlsberg · Pull Request #48240 · microsoft/TypeScript · GitHub」
With this PR we introduce optional declaration site variance annotations for type parameters of classes, interfaces and type aliases. Annotations take the form of an in and/or out keyword immediately preceding the type parameter name in a type parameter declaration. An out annotation indicates that a type parameter is covariant. An in annotation indicates that a type parameter is contravariant. An in out annotation indicates that a type parameter is invariant. Generally, type parameter variance is simply a function of how a type parameter is used in its generic subject type. Indeed, when generic type instantiations are related structurally, variance annotations serve no purpose. This is why TypeScript strictly doesn't need variance annotations. However, variance annotations are useful to assert desired type relations of their subject generic types. Specifically, given a generic type G<T> and any two type arguments Super and Sub for which Sub is a subtype of Super, if T is covariant (declared as out T), G<Sub> is a subtype of G<Super>, if T is contravariant (declared as in T), G<Super> is a subtype of G<Sub>, and if T is invariant (declared as in out T), neither G<Super> nor G<Sub> is a subtype of the other. Intuitively, covariance restricts a type parameter to output (read) positions and contravariance restricts a type parameter to input (write) positions--hence the in and out modifiers. For example: type Provider<out T> = () => T; type Consumer<in T> = (x: T) => void; type Mapper<in T, out U> = (x: T) => U; type Processor<in out T> = (x: T) => T; Covariance and contravariance annotations are checked by structurally relating representative instantiations of their subject generic types. For example, in the following generic type, the type parameter T is used in both input and output positions and T is thus invariant. Attempting to mark T covariant type Foo<out T> = { x: T; f: (x: T) => void; } reports the following error on out T: Type 'Foo<sub-T>' is not assignable to type 'Foo<super-T>' as implied by variance annotation. Types of property 'f' are incompatible. Type '(x: sub-T) => void' is not assignable to type '(x: super-T) => void'. Types of parameters 'x' and 'x' are incompatible. Type 'super-T' is not assignable to type 'sub-T'. Likewise, attempting to mark T contravariant type Foo<in T> = { x: T; f: (x: T) => void; } reports the following error on in T: Type 'Foo<super-T>' is not assignable to type 'Foo<sub-T>' as implied by variance annotation. Types of property 'x' are incompatible. Type 'super-T' is not assignable to type 'sub-T'. Notice how the error elaborations reveal where and how variance is breached. Invariance annotations (in out T) are never checked but simply assumed to hold. Thus, it is possible to assert invariance even when the actual usage of a type parameter is co- or contravariant. When multiple interface declarations are merged, or when a class declaration and one or more interface declarations are merged, variance annotations are aggregated. In the example interface Bar<T> { // ... } interface Bar<out T> { // ... } interface Bar<in T> { // ... } the aggregate variance of T is in out, and T is thus assumed to be invariant. When variance annotations are present, the type checker doesn't need to measure variance. Thus, variance annotations can help improve the performance of checking complex and interdependent types. In particular, marking a type parameter invariant means that no measurement or checking is necessary for that type parameter. In addition, variance annotation can help establish correct variance for multiple circularly dependent generic types. Specifically, when measuring variance, TypeScript limits the structural search space in order to avoid runaway recursion. In the example type Foo<T> = { x: T; f: Bar<T>; } type Bar<U> = (x: Baz<U[]>) => void; type Baz<V> = { value: Foo<V[]>; } declare let foo1: Foo<unknown>; declare let foo2: Foo<string>; foo1 = foo2; // Should be an error but isn't foo2 = foo1; // Error the compiler measures T to be covariant even though it is actually invariant due to variance reversal in Bar and the circular reference in Baz. The compiler could establish that by continuing to structurally relating nested circular references until some fixed point, but this gets exponentially expensive and isn't feasible in complex scenarios. Adding an in out annotation to T establishes the correct variance and produces the expected errors. We're marking #1394 and #10717 fixed by this PR, although the feature implemented isn't exactly what is suggested in those issues. Fixes #1394. Fixes #10717.
コンテンツ文字数:0 文字
見出し数(H2/H3タグ):0 個
閲覧数:110 件
2022-04-06 11:08:05