「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.

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.

github.com

Webページ

コンテンツ文字数:0 文字

見出し数(H2/H3タグ):0 個

閲覧数:110 件

2022-04-06 11:08:05

オリジナルページを開く

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