Skip to content

Improve variance measurement #36261

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ namespace ts {

const emptySymbols = createSymbolTable();
const identityMapper: (type: Type) => Type = identity;
const arrayVariances = [VarianceFlags.Covariant];

const compilerOptions = host.getCompilerOptions();
const languageVersion = getEmitScriptTarget(compilerOptions);
Expand Down Expand Up @@ -15403,6 +15404,9 @@ namespace ts {
source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol &&
!(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) {
const variances = getAliasVariances(source.aliasSymbol);
if (variances === emptyArray) {
return Ternary.Maybe;
}
const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState);
if (varianceResult !== undefined) {
return varianceResult;
Expand Down Expand Up @@ -15599,6 +15603,12 @@ namespace ts {
// type references (which are intended by be compared structurally). Obtain the variance
// information for the type parameters and relate the type arguments accordingly.
const variances = getVariances((<TypeReference>source).target);
// We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This
// effectively means we measure variance only from type parameter occurrences that aren't nested in
// recursive instantiations of the generic type.
if (variances === emptyArray) {
return Ternary.Maybe;
}
const varianceResult = relateVariances(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), variances, intersectionState);
if (varianceResult !== undefined) {
return varianceResult;
Expand Down Expand Up @@ -16418,8 +16428,7 @@ namespace ts {
// a digest of the type comparisons that occur for each type argument when instantiations of the
// generic type are structurally compared. We infer the variance information by comparing
// instantiations of the generic type for type arguments with known relations. The function
// returns the emptyArray singleton if we're not in strictFunctionTypes mode or if the function
// has been invoked recursively for the given generic type.
// returns the emptyArray singleton when invoked recursively for the given generic type.
function getVariancesWorker<TCache extends { variances?: VarianceFlags[] }>(typeParameters: readonly TypeParameter[] = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type): VarianceFlags[] {
let variances = cache.variances;
if (!variances) {
Expand Down Expand Up @@ -16462,9 +16471,9 @@ namespace ts {
}

function getVariances(type: GenericType): VarianceFlags[] {
// Arrays and tuples are known to be covariant, no need to spend time computing this (emptyArray implies covariance for all parameters)
// Arrays and tuples are known to be covariant, no need to spend time computing this.
if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) {
return emptyArray;
return arrayVariances;
}
return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference);
}
Expand Down

This file was deleted.

27 changes: 0 additions & 27 deletions tests/baselines/reference/recursiveTypeComparison.errors.txt

This file was deleted.

122 changes: 122 additions & 0 deletions tests/baselines/reference/varianceMeasurement.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
tests/cases/compiler/varianceMeasurement.ts(10,7): error TS2322: Type 'Foo1<string>' is not assignable to type 'Foo1<"a">'.
Type 'string' is not assignable to type '"a"'.
tests/cases/compiler/varianceMeasurement.ts(21,7): error TS2322: Type 'Foo2<string>' is not assignable to type 'Foo2<"a">'.
Types of property 'x' are incompatible.
Type 'string' is not assignable to type '"a"'.
tests/cases/compiler/varianceMeasurement.ts(22,7): error TS2322: Type 'Foo2<string>' is not assignable to type 'Foo2<unknown>'.
The types of 'y.x' are incompatible between these types.
Type '(arg: string) => void' is not assignable to type '(arg: unknown) => void'.
Types of parameters 'arg' and 'arg' are incompatible.
Type 'unknown' is not assignable to type 'string'.
tests/cases/compiler/varianceMeasurement.ts(33,7): error TS2322: Type 'Foo3<string>' is not assignable to type 'Foo3<"a">'.
Type 'string' is not assignable to type '"a"'.
tests/cases/compiler/varianceMeasurement.ts(44,7): error TS2322: Type 'Foo4<string>' is not assignable to type 'Foo4<"a">'.
Types of property 'x' are incompatible.
Type 'string' is not assignable to type '"a"'.
tests/cases/compiler/varianceMeasurement.ts(45,7): error TS2322: Type 'Foo4<string>' is not assignable to type 'Foo4<unknown>'.
The types of 'y.x' are incompatible between these types.
Type '(arg: string) => void' is not assignable to type '(arg: unknown) => void'.
Types of parameters 'arg' and 'arg' are incompatible.
Type 'unknown' is not assignable to type 'string'.
tests/cases/compiler/varianceMeasurement.ts(57,7): error TS2322: Type 'Fn<string, number>' is not assignable to type 'Fn<unknown, number>'.
Type 'unknown' is not assignable to type 'string'.
tests/cases/compiler/varianceMeasurement.ts(62,7): error TS2322: Type 'Fn<string, number>' is not assignable to type 'Fn<string, 0>'.
Type 'number' is not assignable to type '0'.


==== tests/cases/compiler/varianceMeasurement.ts (8 errors) ====
// The type below should be invariant in T but is measured as covariant because
// we don't analyze recursive references.

interface Foo1<T> {
x: T;
y: Foo1<(arg: T) => void>;
}

declare const f10: Foo1<string>;
const f11: Foo1<'a'> = f10;
~~~
!!! error TS2322: Type 'Foo1<string>' is not assignable to type 'Foo1<"a">'.
!!! error TS2322: Type 'string' is not assignable to type '"a"'.
const f12: Foo1<unknown> = f10;

// The type below is invariant in T and is measured as such.

interface Foo2<T> {
x: T;
y: { x: (arg: T) => void, y: Foo2<(arg: T) => void>; }
}

declare const f20: Foo2<string>;
const f21: Foo2<'a'> = f20;
~~~
!!! error TS2322: Type 'Foo2<string>' is not assignable to type 'Foo2<"a">'.
!!! error TS2322: Types of property 'x' are incompatible.
!!! error TS2322: Type 'string' is not assignable to type '"a"'.
const f22: Foo2<unknown> = f20;
~~~
!!! error TS2322: Type 'Foo2<string>' is not assignable to type 'Foo2<unknown>'.
!!! error TS2322: The types of 'y.x' are incompatible between these types.
!!! error TS2322: Type '(arg: string) => void' is not assignable to type '(arg: unknown) => void'.
!!! error TS2322: Types of parameters 'arg' and 'arg' are incompatible.
!!! error TS2322: Type 'unknown' is not assignable to type 'string'.

// The type below should be invariant in T but is measured as covariant because
// we don't analyze recursive references.

type Foo3<T> = {
x: T;
y: Foo3<(arg: T) => void>;
}

declare const f30: Foo3<string>;
const f31: Foo3<'a'> = f30;
~~~
!!! error TS2322: Type 'Foo3<string>' is not assignable to type 'Foo3<"a">'.
!!! error TS2322: Type 'string' is not assignable to type '"a"'.
const f32: Foo3<unknown> = f30;

// The type below is invariant in T and is measured as such.

type Foo4<T> = {
x: T;
y: { x: (arg: T) => void, y: Foo4<(arg: T) => void>; }
}

declare const f40: Foo4<string>;
const f41: Foo4<'a'> = f40;
~~~
!!! error TS2322: Type 'Foo4<string>' is not assignable to type 'Foo4<"a">'.
!!! error TS2322: Types of property 'x' are incompatible.
!!! error TS2322: Type 'string' is not assignable to type '"a"'.
const f42: Foo4<unknown> = f40;
~~~
!!! error TS2322: Type 'Foo4<string>' is not assignable to type 'Foo4<unknown>'.
!!! error TS2322: The types of 'y.x' are incompatible between these types.
!!! error TS2322: Type '(arg: string) => void' is not assignable to type '(arg: unknown) => void'.
!!! error TS2322: Types of parameters 'arg' and 'arg' are incompatible.
!!! error TS2322: Type 'unknown' is not assignable to type 'string'.

// Repro from #3580

interface Fn<A, B> {
(a: A): B;
then<C>(next: Fn<B, C>): Fn<A, C>;
}

declare const fn: Fn<string, number>;

// Contravariant in A
const fn1: Fn<unknown, number> = fn; // Error
~~~
!!! error TS2322: Type 'Fn<string, number>' is not assignable to type 'Fn<unknown, number>'.
!!! error TS2322: Type 'unknown' is not assignable to type 'string'.
const fn2: Fn<'a', number> = fn;

// Covariant in B
const fn3: Fn<string, unknown> = fn;
const fn4: Fn<string, 0> = fn; // Error
~~~
!!! error TS2322: Type 'Fn<string, number>' is not assignable to type 'Fn<string, 0>'.
!!! error TS2322: Type 'number' is not assignable to type '0'.

83 changes: 83 additions & 0 deletions tests/baselines/reference/varianceMeasurement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//// [varianceMeasurement.ts]
// The type below should be invariant in T but is measured as covariant because
// we don't analyze recursive references.

interface Foo1<T> {
x: T;
y: Foo1<(arg: T) => void>;
}

declare const f10: Foo1<string>;
const f11: Foo1<'a'> = f10;
const f12: Foo1<unknown> = f10;

// The type below is invariant in T and is measured as such.

interface Foo2<T> {
x: T;
y: { x: (arg: T) => void, y: Foo2<(arg: T) => void>; }
}

declare const f20: Foo2<string>;
const f21: Foo2<'a'> = f20;
const f22: Foo2<unknown> = f20;

// The type below should be invariant in T but is measured as covariant because
// we don't analyze recursive references.

type Foo3<T> = {
x: T;
y: Foo3<(arg: T) => void>;
}

declare const f30: Foo3<string>;
const f31: Foo3<'a'> = f30;
const f32: Foo3<unknown> = f30;

// The type below is invariant in T and is measured as such.

type Foo4<T> = {
x: T;
y: { x: (arg: T) => void, y: Foo4<(arg: T) => void>; }
}

declare const f40: Foo4<string>;
const f41: Foo4<'a'> = f40;
const f42: Foo4<unknown> = f40;

// Repro from #3580

interface Fn<A, B> {
(a: A): B;
then<C>(next: Fn<B, C>): Fn<A, C>;
}

declare const fn: Fn<string, number>;

// Contravariant in A
const fn1: Fn<unknown, number> = fn; // Error
const fn2: Fn<'a', number> = fn;

// Covariant in B
const fn3: Fn<string, unknown> = fn;
const fn4: Fn<string, 0> = fn; // Error


//// [varianceMeasurement.js]
"use strict";
// The type below should be invariant in T but is measured as covariant because
// we don't analyze recursive references.
var f11 = f10;
var f12 = f10;
var f21 = f20;
var f22 = f20;
var f31 = f30;
var f32 = f30;
var f41 = f40;
var f42 = f40;
// Contravariant in A
var fn1 = fn; // Error
var fn2 = fn;
// Covariant in B
var fn3 = fn;
var fn4 = fn; // Error
Loading