Skip to content

Commit

Permalink
Add unmeasurable variance kind for marking types whose variance resul…
Browse files Browse the repository at this point in the history
…t is unreliable (#30416)

* Add unmeasurable variance kind for marking types whose variance result is unreliable

* Remove now-unneeded nongeneric checks

* Add rule allowing `Readonly<any>` to be `any` instead of `{readonly [index: string]: any}`

* All Unmeasurable variances to still shortcut structural comparisons in some cases

* Separate unmeasurable from unreliable to reduce the impact of this change, for now

* Fix lint

* Remove Readonly<any> -> any callout

* Add fix for circularity error triggered by deep signature return type comparisons with `this` types
  • Loading branch information
weswigham committed May 3, 2019
1 parent fc88a1c commit b365e65
Show file tree
Hide file tree
Showing 23 changed files with 1,092 additions and 57 deletions.
106 changes: 75 additions & 31 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

20 changes: 12 additions & 8 deletions src/compiler/types.ts
Expand Up @@ -3735,7 +3735,7 @@ namespace ts {
specifierCache?: Map<string>; // For symbols corresponding to external modules, a cache of incoming path -> module specifier name mappings
extendedContainers?: Symbol[]; // Containers (other than the parent) which this symbol is aliased in
extendedContainersByFile?: Map<Symbol[]>; // Containers (other than the parent) which this symbol is aliased in
variances?: Variance[]; // Alias symbol type argument variance cache
variances?: VarianceFlags[]; // Alias symbol type argument variance cache
}

/* @internal */
Expand Down Expand Up @@ -4131,20 +4131,24 @@ namespace ts {
}

/* @internal */
export const enum Variance {
Invariant = 0, // Neither covariant nor contravariant
Covariant = 1, // Covariant
Contravariant = 2, // Contravariant
Bivariant = 3, // Both covariant and contravariant
Independent = 4, // Unwitnessed type parameter
export const enum VarianceFlags {
Invariant = 0, // Neither covariant nor contravariant
Covariant = 1 << 0, // Covariant
Contravariant = 1 << 1, // Contravariant
Bivariant = Covariant | Contravariant, // Both covariant and contravariant
Independent = 1 << 2, // Unwitnessed type parameter
VarianceMask = Invariant | Covariant | Contravariant | Independent, // Mask containing all measured variances without the unmeasurable flag
Unmeasurable = 1 << 3, // Variance result is unusable - relationship relies on structural comparisons which are not reflected in generic relationships
Unreliable = 1 << 4, // Variance result is unreliable - relationship relies on structural comparisons which are not reflected in generic relationships
AllowsStructuralFallback = Unmeasurable | Unreliable,
}

// Generic class and interface types
export interface GenericType extends InterfaceType, TypeReference {
/* @internal */
instantiations: Map<TypeReference>; // Generic instantiation cache
/* @internal */
variances?: Variance[]; // Variance of each type parameter
variances?: VarianceFlags[]; // Variance of each type parameter
}

export interface TupleType extends GenericType {
Expand Down
@@ -0,0 +1,32 @@
tests/cases/compiler/checkInfiniteExpansionTermination.ts(16,1): error TS2322: Type 'ISubject<Bar>' is not assignable to type 'IObservable<Foo>'.
Types of property 'n' are incompatible.
Type 'IObservable<Bar[]>' is not assignable to type 'IObservable<Foo[]>'.
Type 'Bar[]' is not assignable to type 'Foo[]'.
Property 'x' is missing in type 'Bar' but required in type 'Foo'.


==== tests/cases/compiler/checkInfiniteExpansionTermination.ts (1 errors) ====
// Regression test for #1002
// Before fix this code would cause infinite loop

interface IObservable<T> {
n: IObservable<T[]>; // Needed, must be T[]
}

// Needed
interface ISubject<T> extends IObservable<T> { }

interface Foo { x }
interface Bar { y }

var values: IObservable<Foo>;
var values2: ISubject<Bar>;
values = values2;
~~~~~~
!!! error TS2322: Type 'ISubject<Bar>' is not assignable to type 'IObservable<Foo>'.
!!! error TS2322: Types of property 'n' are incompatible.
!!! error TS2322: Type 'IObservable<Bar[]>' is not assignable to type 'IObservable<Foo[]>'.
!!! error TS2322: Type 'Bar[]' is not assignable to type 'Foo[]'.
!!! error TS2322: Property 'x' is missing in type 'Bar' but required in type 'Foo'.
!!! related TS2728 tests/cases/compiler/checkInfiniteExpansionTermination.ts:11:17: 'x' is declared here.

8 changes: 0 additions & 8 deletions tests/baselines/reference/conditionalTypes1.errors.txt
Expand Up @@ -19,12 +19,8 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(104,5): error TS2
'Pick<T, { [K in keyof T]: T[K] extends Function ? never : K; }[keyof T]>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(106,5): error TS2322: Type 'Pick<T, { [K in keyof T]: T[K] extends Function ? never : K; }[keyof T]>' is not assignable to type 'Pick<T, { [K in keyof T]: T[K] extends Function ? K : never; }[keyof T]>'.
Type 'T[keyof T] extends Function ? keyof T : never' is not assignable to type 'T[keyof T] extends Function ? never : keyof T'.
Type 'keyof T' is not assignable to type 'never'.
Type 'string | number | symbol' is not assignable to type 'never'.
Type 'string' is not assignable to type 'never'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(108,5): error TS2322: Type 'Pick<T, { [K in keyof T]: T[K] extends Function ? K : never; }[keyof T]>' is not assignable to type 'Pick<T, { [K in keyof T]: T[K] extends Function ? never : K; }[keyof T]>'.
Type 'T[keyof T] extends Function ? never : keyof T' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
Type 'keyof T' is not assignable to type 'never'.
tests/cases/conformance/types/conditional/conditionalTypes1.ts(114,5): error TS2322: Type 'keyof T' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
Type 'string | number | symbol' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
Type 'string' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
Expand Down Expand Up @@ -195,15 +191,11 @@ tests/cases/conformance/types/conditional/conditionalTypes1.ts(288,43): error TS
~
!!! error TS2322: Type 'Pick<T, { [K in keyof T]: T[K] extends Function ? never : K; }[keyof T]>' is not assignable to type 'Pick<T, { [K in keyof T]: T[K] extends Function ? K : never; }[keyof T]>'.
!!! error TS2322: Type 'T[keyof T] extends Function ? keyof T : never' is not assignable to type 'T[keyof T] extends Function ? never : keyof T'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'never'.
!!! error TS2322: Type 'string | number | symbol' is not assignable to type 'never'.
!!! error TS2322: Type 'string' is not assignable to type 'never'.
z = x;
z = y; // Error
~
!!! error TS2322: Type 'Pick<T, { [K in keyof T]: T[K] extends Function ? K : never; }[keyof T]>' is not assignable to type 'Pick<T, { [K in keyof T]: T[K] extends Function ? never : K; }[keyof T]>'.
!!! error TS2322: Type 'T[keyof T] extends Function ? never : keyof T' is not assignable to type 'T[keyof T] extends Function ? keyof T : never'.
!!! error TS2322: Type 'keyof T' is not assignable to type 'never'.
}

function f8<T>(x: keyof T, y: FunctionPropertyNames<T>, z: NonFunctionPropertyNames<T>) {
Expand Down
@@ -0,0 +1,55 @@
tests/cases/compiler/consistentAliasVsNonAliasRecordBehavior.ts(18,5): error TS2741: Property 'a' is missing in type 'Record<string, string>' but required in type 'Record2<"a", string>'.
tests/cases/compiler/consistentAliasVsNonAliasRecordBehavior.ts(22,5): error TS2741: Property 'a' is missing in type 'Record2<string, string>' but required in type 'Record<"a", string>'.
tests/cases/compiler/consistentAliasVsNonAliasRecordBehavior.ts(34,5): error TS2741: Property 'a' is missing in type 'Record<string, T>' but required in type 'Record2<"a", T>'.
tests/cases/compiler/consistentAliasVsNonAliasRecordBehavior.ts(38,5): error TS2741: Property 'a' is missing in type 'Record2<string, T>' but required in type 'Record<"a", T>'.


==== tests/cases/compiler/consistentAliasVsNonAliasRecordBehavior.ts (4 errors) ====
// TODO: FIXME: All the below cases labeled `no error` _should be an error_, and are only prevented from so being
// by incorrect variance-based relationships
// Ref: https://github.com/Microsoft/TypeScript/issues/29698

type Record2<K extends keyof any, T> = {
[P in K]: T;
};

function defaultRecord(x: Record<'a', string>, y: Record<string, string>) {
x = y; // no error, but error expected.
}

function customRecord(x: Record2<'a', string>, y: Record2<string, string>) {
x = y; // no error, but error expected.
}

function mixed1(x: Record2<'a', string>, y: Record<string, string>) {
x = y; // error
~
!!! error TS2741: Property 'a' is missing in type 'Record<string, string>' but required in type 'Record2<"a", string>'.
}

function mixed2(x: Record<'a', string>, y: Record2<string, string>) {
x = y; // error
~
!!! error TS2741: Property 'a' is missing in type 'Record2<string, string>' but required in type 'Record<"a", string>'.
}

function defaultRecord2<T>(x: Record<'a', T>, y: Record<string, T>) {
x = y; // no error, but error expected.
}

function customRecord2<T>(x: Record2<'a', T>, y: Record2<string, T>) {
x = y; // no error, but error expected.
}

function mixed3<T>(x: Record2<'a', T>, y: Record<string, T>) {
x = y; // error
~
!!! error TS2741: Property 'a' is missing in type 'Record<string, T>' but required in type 'Record2<"a", T>'.
}

function mixed4<T>(x: Record<'a', T>, y: Record2<string, T>) {
x = y; // error
~
!!! error TS2741: Property 'a' is missing in type 'Record2<string, T>' but required in type 'Record<"a", T>'.
}

@@ -0,0 +1,70 @@
//// [consistentAliasVsNonAliasRecordBehavior.ts]
// TODO: FIXME: All the below cases labeled `no error` _should be an error_, and are only prevented from so being
// by incorrect variance-based relationships
// Ref: https://github.com/Microsoft/TypeScript/issues/29698

type Record2<K extends keyof any, T> = {
[P in K]: T;
};

function defaultRecord(x: Record<'a', string>, y: Record<string, string>) {
x = y; // no error, but error expected.
}

function customRecord(x: Record2<'a', string>, y: Record2<string, string>) {
x = y; // no error, but error expected.
}

function mixed1(x: Record2<'a', string>, y: Record<string, string>) {
x = y; // error
}

function mixed2(x: Record<'a', string>, y: Record2<string, string>) {
x = y; // error
}

function defaultRecord2<T>(x: Record<'a', T>, y: Record<string, T>) {
x = y; // no error, but error expected.
}

function customRecord2<T>(x: Record2<'a', T>, y: Record2<string, T>) {
x = y; // no error, but error expected.
}

function mixed3<T>(x: Record2<'a', T>, y: Record<string, T>) {
x = y; // error
}

function mixed4<T>(x: Record<'a', T>, y: Record2<string, T>) {
x = y; // error
}


//// [consistentAliasVsNonAliasRecordBehavior.js]
// TODO: FIXME: All the below cases labeled `no error` _should be an error_, and are only prevented from so being
// by incorrect variance-based relationships
// Ref: https://github.com/Microsoft/TypeScript/issues/29698
function defaultRecord(x, y) {
x = y; // no error, but error expected.
}
function customRecord(x, y) {
x = y; // no error, but error expected.
}
function mixed1(x, y) {
x = y; // error
}
function mixed2(x, y) {
x = y; // error
}
function defaultRecord2(x, y) {
x = y; // no error, but error expected.
}
function customRecord2(x, y) {
x = y; // no error, but error expected.
}
function mixed3(x, y) {
x = y; // error
}
function mixed4(x, y) {
x = y; // error
}
@@ -0,0 +1,125 @@
=== tests/cases/compiler/consistentAliasVsNonAliasRecordBehavior.ts ===
// TODO: FIXME: All the below cases labeled `no error` _should be an error_, and are only prevented from so being
// by incorrect variance-based relationships
// Ref: https://github.com/Microsoft/TypeScript/issues/29698

type Record2<K extends keyof any, T> = {
>Record2 : Symbol(Record2, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 0, 0))
>K : Symbol(K, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 4, 13))
>T : Symbol(T, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 4, 33))

[P in K]: T;
>P : Symbol(P, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 5, 5))
>K : Symbol(K, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 4, 13))
>T : Symbol(T, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 4, 33))

};

function defaultRecord(x: Record<'a', string>, y: Record<string, string>) {
>defaultRecord : Symbol(defaultRecord, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 6, 2))
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 8, 23))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 8, 46))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))

x = y; // no error, but error expected.
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 8, 23))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 8, 46))
}

function customRecord(x: Record2<'a', string>, y: Record2<string, string>) {
>customRecord : Symbol(customRecord, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 10, 1))
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 12, 22))
>Record2 : Symbol(Record2, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 0, 0))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 12, 46))
>Record2 : Symbol(Record2, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 0, 0))

x = y; // no error, but error expected.
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 12, 22))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 12, 46))
}

function mixed1(x: Record2<'a', string>, y: Record<string, string>) {
>mixed1 : Symbol(mixed1, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 14, 1))
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 16, 16))
>Record2 : Symbol(Record2, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 0, 0))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 16, 40))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))

x = y; // error
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 16, 16))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 16, 40))
}

function mixed2(x: Record<'a', string>, y: Record2<string, string>) {
>mixed2 : Symbol(mixed2, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 18, 1))
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 20, 16))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 20, 39))
>Record2 : Symbol(Record2, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 0, 0))

x = y; // error
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 20, 16))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 20, 39))
}

function defaultRecord2<T>(x: Record<'a', T>, y: Record<string, T>) {
>defaultRecord2 : Symbol(defaultRecord2, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 22, 1))
>T : Symbol(T, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 24, 24))
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 24, 27))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 24, 24))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 24, 45))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 24, 24))

x = y; // no error, but error expected.
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 24, 27))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 24, 45))
}

function customRecord2<T>(x: Record2<'a', T>, y: Record2<string, T>) {
>customRecord2 : Symbol(customRecord2, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 26, 1))
>T : Symbol(T, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 28, 23))
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 28, 26))
>Record2 : Symbol(Record2, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 0, 0))
>T : Symbol(T, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 28, 23))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 28, 45))
>Record2 : Symbol(Record2, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 0, 0))
>T : Symbol(T, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 28, 23))

x = y; // no error, but error expected.
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 28, 26))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 28, 45))
}

function mixed3<T>(x: Record2<'a', T>, y: Record<string, T>) {
>mixed3 : Symbol(mixed3, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 30, 1))
>T : Symbol(T, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 32, 16))
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 32, 19))
>Record2 : Symbol(Record2, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 0, 0))
>T : Symbol(T, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 32, 16))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 32, 38))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 32, 16))

x = y; // error
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 32, 19))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 32, 38))
}

function mixed4<T>(x: Record<'a', T>, y: Record2<string, T>) {
>mixed4 : Symbol(mixed4, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 34, 1))
>T : Symbol(T, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 36, 16))
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 36, 19))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 36, 16))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 36, 37))
>Record2 : Symbol(Record2, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 0, 0))
>T : Symbol(T, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 36, 16))

x = y; // error
>x : Symbol(x, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 36, 19))
>y : Symbol(y, Decl(consistentAliasVsNonAliasRecordBehavior.ts, 36, 37))
}

0 comments on commit b365e65

Please sign in to comment.