Skip to content

Report unmeasurable variance when measurements are circular #45578

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

Closed
wants to merge 3 commits into from
Closed
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
5 changes: 3 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18791,10 +18791,11 @@ 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((source as TypeReference).target);
// We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This
// We return Ternary.Unknown 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) {
outofbandVarianceMarkerHandler!(/*onlyUnreliable*/ false);
Copy link
Member

@DanielRosenwasser DanielRosenwasser Aug 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we know that outofbandVarianceMarkerHandler will always be defined when variances is emptyArray? Kinda sketchy, but I guess so is the current mechanism.

return Ternary.Unknown;
}
const varianceResult = relateVariances(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), variances, intersectionState);
Expand Down Expand Up @@ -19756,7 +19757,7 @@ namespace ts {
outofbandVarianceMarkerHandler = oldHandler;
if (unmeasurable || unreliable) {
if (unmeasurable) {
variance |= VarianceFlags.Unmeasurable;
variance = VarianceFlags.Unmeasurable;
}
if (unreliable) {
variance |= VarianceFlags.Unreliable;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
tests/cases/compiler/checkOrderDependenceGenericAssignability.ts(24,7): error TS2322: Type 'Parent<unknown>' is not assignable to type 'Parent<string>'.
The types of 'child.a' are incompatible between these types.
Type 'unknown' is not assignable to type 'string'.


==== tests/cases/compiler/checkOrderDependenceGenericAssignability.ts (1 errors) ====
// #44572

interface Parent<A> {
child: Child<A> | null;
parent: Parent<A> | null;
}

interface Child<A, B = unknown> extends Parent<A> {
readonly a: A;
// This field isn't necessary to the repro, but the
// type parameter is, so including it
readonly b: B;
}

function fn<A>(inp: Child<A>) {
// This assignability check defeats the later one
const a: Child<unknown> = inp;
}

// Allowed initialization of pu
const pu: Parent<unknown> = { child: { a: 0, b: 0, child: null, parent: null }, parent: null };

// Should error
const notString: Parent<string> = pu;
~~~~~~~~~
!!! error TS2322: Type 'Parent<unknown>' is not assignable to type 'Parent<string>'.
!!! error TS2322: The types of 'child.a' are incompatible between these types.
!!! error TS2322: Type 'unknown' is not assignable to type 'string'.

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//// [checkOrderDependenceGenericAssignability.ts]
// #44572

interface Parent<A> {
child: Child<A> | null;
parent: Parent<A> | null;
}

interface Child<A, B = unknown> extends Parent<A> {
readonly a: A;
// This field isn't necessary to the repro, but the
// type parameter is, so including it
readonly b: B;
}

function fn<A>(inp: Child<A>) {
// This assignability check defeats the later one
const a: Child<unknown> = inp;
}

// Allowed initialization of pu
const pu: Parent<unknown> = { child: { a: 0, b: 0, child: null, parent: null }, parent: null };

// Should error
const notString: Parent<string> = pu;


//// [checkOrderDependenceGenericAssignability.js]
// #44572
function fn(inp) {
// This assignability check defeats the later one
var a = inp;
}
// Allowed initialization of pu
var pu = { child: { a: 0, b: 0, child: null, parent: null }, parent: null };
// Should error
var notString = pu;
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
=== tests/cases/compiler/checkOrderDependenceGenericAssignability.ts ===
// #44572

interface Parent<A> {
>Parent : Symbol(Parent, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0))
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 2, 17))

child: Child<A> | null;
>child : Symbol(Parent.child, Decl(checkOrderDependenceGenericAssignability.ts, 2, 21))
>Child : Symbol(Child, Decl(checkOrderDependenceGenericAssignability.ts, 5, 1))
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 2, 17))

parent: Parent<A> | null;
>parent : Symbol(Parent.parent, Decl(checkOrderDependenceGenericAssignability.ts, 3, 25))
>Parent : Symbol(Parent, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0))
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 2, 17))
}

interface Child<A, B = unknown> extends Parent<A> {
>Child : Symbol(Child, Decl(checkOrderDependenceGenericAssignability.ts, 5, 1))
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 7, 16))
>B : Symbol(B, Decl(checkOrderDependenceGenericAssignability.ts, 7, 18))
>Parent : Symbol(Parent, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0))
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 7, 16))

readonly a: A;
>a : Symbol(Child.a, Decl(checkOrderDependenceGenericAssignability.ts, 7, 51))
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 7, 16))

// This field isn't necessary to the repro, but the
// type parameter is, so including it
readonly b: B;
>b : Symbol(Child.b, Decl(checkOrderDependenceGenericAssignability.ts, 8, 16))
>B : Symbol(B, Decl(checkOrderDependenceGenericAssignability.ts, 7, 18))
}

function fn<A>(inp: Child<A>) {
>fn : Symbol(fn, Decl(checkOrderDependenceGenericAssignability.ts, 12, 1))
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 14, 12))
>inp : Symbol(inp, Decl(checkOrderDependenceGenericAssignability.ts, 14, 15))
>Child : Symbol(Child, Decl(checkOrderDependenceGenericAssignability.ts, 5, 1))
>A : Symbol(A, Decl(checkOrderDependenceGenericAssignability.ts, 14, 12))

// This assignability check defeats the later one
const a: Child<unknown> = inp;
>a : Symbol(a, Decl(checkOrderDependenceGenericAssignability.ts, 16, 7))
>Child : Symbol(Child, Decl(checkOrderDependenceGenericAssignability.ts, 5, 1))
>inp : Symbol(inp, Decl(checkOrderDependenceGenericAssignability.ts, 14, 15))
}

// Allowed initialization of pu
const pu: Parent<unknown> = { child: { a: 0, b: 0, child: null, parent: null }, parent: null };
>pu : Symbol(pu, Decl(checkOrderDependenceGenericAssignability.ts, 20, 5))
>Parent : Symbol(Parent, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0))
>child : Symbol(child, Decl(checkOrderDependenceGenericAssignability.ts, 20, 29))
>a : Symbol(a, Decl(checkOrderDependenceGenericAssignability.ts, 20, 38))
>b : Symbol(b, Decl(checkOrderDependenceGenericAssignability.ts, 20, 44))
>child : Symbol(child, Decl(checkOrderDependenceGenericAssignability.ts, 20, 50))
>parent : Symbol(parent, Decl(checkOrderDependenceGenericAssignability.ts, 20, 63))
>parent : Symbol(parent, Decl(checkOrderDependenceGenericAssignability.ts, 20, 79))

// Should error
const notString: Parent<string> = pu;
>notString : Symbol(notString, Decl(checkOrderDependenceGenericAssignability.ts, 23, 5))
>Parent : Symbol(Parent, Decl(checkOrderDependenceGenericAssignability.ts, 0, 0))
>pu : Symbol(pu, Decl(checkOrderDependenceGenericAssignability.ts, 20, 5))

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
=== tests/cases/compiler/checkOrderDependenceGenericAssignability.ts ===
// #44572

interface Parent<A> {
child: Child<A> | null;
>child : Child<A, unknown>
>null : null

parent: Parent<A> | null;
>parent : Parent<A>
>null : null
}

interface Child<A, B = unknown> extends Parent<A> {
readonly a: A;
>a : A

// This field isn't necessary to the repro, but the
// type parameter is, so including it
readonly b: B;
>b : B
}

function fn<A>(inp: Child<A>) {
>fn : <A>(inp: Child<A>) => void
>inp : Child<A, unknown>

// This assignability check defeats the later one
const a: Child<unknown> = inp;
>a : Child<unknown, unknown>
>inp : Child<A, unknown>
}

// Allowed initialization of pu
const pu: Parent<unknown> = { child: { a: 0, b: 0, child: null, parent: null }, parent: null };
>pu : Parent<unknown>
>{ child: { a: 0, b: 0, child: null, parent: null }, parent: null } : { child: { a: number; b: number; child: null; parent: null; }; parent: null; }
>child : { a: number; b: number; child: null; parent: null; }
>{ a: 0, b: 0, child: null, parent: null } : { a: number; b: number; child: null; parent: null; }
>a : number
>0 : 0
>b : number
>0 : 0
>child : null
>null : null
>parent : null
>null : null
>parent : null
>null : null

// Should error
const notString: Parent<string> = pu;
>notString : Parent<string>
>pu : Parent<unknown>

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
tests/cases/conformance/types/typeRelationships/recursiveTypes/objectTypeWithRecursiveWrappedProperty.ts(13,1): error TS2322: Type 'List<string>' is not assignable to type 'List<number>'.
Type 'string' is not assignable to type 'number'.
Types of property 'data' are incompatible.
Type 'string' is not assignable to type 'number'.


==== tests/cases/conformance/types/typeRelationships/recursiveTypes/objectTypeWithRecursiveWrappedProperty.ts (1 errors) ====
Expand All @@ -18,4 +19,5 @@ tests/cases/conformance/types/typeRelationships/recursiveTypes/objectTypeWithRec
list1 = list3; // error
~~~~~
!!! error TS2322: Type 'List<string>' is not assignable to type 'List<number>'.
!!! error TS2322: Type 'string' is not assignable to type 'number'.
!!! error TS2322: Types of property 'data' are incompatible.
!!! error TS2322: Type 'string' is not assignable to type 'number'.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
tests/cases/conformance/types/typeRelationships/recursiveTypes/objectTypeWithRecursiveWrappedProperty2.ts(13,1): error TS2322: Type 'List<string>' is not assignable to type 'List<number>'.
Type 'string' is not assignable to type 'number'.
Types of property 'data' are incompatible.
Type 'string' is not assignable to type 'number'.


==== tests/cases/conformance/types/typeRelationships/recursiveTypes/objectTypeWithRecursiveWrappedProperty2.ts (1 errors) ====
Expand All @@ -18,4 +19,5 @@ tests/cases/conformance/types/typeRelationships/recursiveTypes/objectTypeWithRec
list1 = list3; // error
~~~~~
!!! error TS2322: Type 'List<string>' is not assignable to type 'List<number>'.
!!! error TS2322: Type 'string' is not assignable to type 'number'.
!!! error TS2322: Types of property 'data' are incompatible.
!!! error TS2322: Type 'string' is not assignable to type 'number'.
29 changes: 24 additions & 5 deletions tests/baselines/reference/varianceMeasurement.errors.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
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"'.
Types of property 'x' are incompatible.
Type 'string' is not assignable to type '"a"'.
tests/cases/compiler/varianceMeasurement.ts(11,7): error TS2322: Type 'Foo1<string>' is not assignable to type 'Foo1<unknown>'.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new error comes after a comment that says

// 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 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(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"'.
Expand All @@ -19,14 +25,17 @@ tests/cases/compiler/varianceMeasurement.ts(45,7): error TS2322: Type 'Foo4<stri
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'.
The types returned by 'then(...)' are incompatible between these types.
Type 'Fn<string, any>' is not assignable to type 'Fn<unknown, any>'.
Types of parameters 'a' and 'a' are incompatible.
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(75,7): error TS2322: Type 'C<unknown, number>' is not assignable to type 'C<unknown, string>'.
Type 'number' is not assignable to type 'string'.


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

Expand All @@ -39,8 +48,15 @@ tests/cases/compiler/varianceMeasurement.ts(75,7): error TS2322: Type 'C<unknown
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"'.
!!! error TS2322: Types of property 'x' are incompatible.
!!! error TS2322: Type 'string' is not assignable to type '"a"'.
const f12: Foo1<unknown> = f10;
~~~
!!! error TS2322: Type 'Foo1<string>' is not assignable to type 'Foo1<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 is invariant in T and is measured as such.

Expand Down Expand Up @@ -112,7 +128,10 @@ tests/cases/compiler/varianceMeasurement.ts(75,7): error TS2322: Type 'C<unknown
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'.
!!! error TS2322: The types returned by 'then(...)' are incompatible between these types.
!!! error TS2322: Type 'Fn<string, any>' is not assignable to type 'Fn<unknown, any>'.
!!! error TS2322: Types of parameters 'a' and 'a' are incompatible.
!!! error TS2322: Type 'unknown' is not assignable to type 'string'.
const fn2: Fn<'a', number> = fn;

// Covariant in B
Expand Down
24 changes: 24 additions & 0 deletions tests/cases/compiler/checkOrderDependenceGenericAssignability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// #44572

interface Parent<A> {
child: Child<A> | null;
parent: Parent<A> | null;
}

interface Child<A, B = unknown> extends Parent<A> {
readonly a: A;
// This field isn't necessary to the repro, but the
// type parameter is, so including it
readonly b: B;
}

function fn<A>(inp: Child<A>) {
// This assignability check defeats the later one
const a: Child<unknown> = inp;
}

// Allowed initialization of pu
const pu: Parent<unknown> = { child: { a: 0, b: 0, child: null, parent: null }, parent: null };

// Should error
const notString: Parent<string> = pu;