Skip to content

Track source and target relationship stack depth seperately, only increase on change in value #41821

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 16 commits into from
Sep 30, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
36 changes: 25 additions & 11 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16507,7 +16507,8 @@ namespace ts {
let sourceStack: Type[];
let targetStack: Type[];
let maybeCount = 0;
let depth = 0;
let sourceDepth = 0;
let targetDepth = 0;
let expandingFlags = ExpandingFlags.None;
let overflow = false;
let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid
Expand All @@ -16522,7 +16523,7 @@ namespace ts {
reportIncompatibleStack();
}
if (overflow) {
tracing.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth });
tracing.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth: sourceDepth, targetDepth });
const diag = error(errorNode || currentNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target));
if (errorOutputContainer) {
(errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
Expand Down Expand Up @@ -17320,20 +17321,27 @@ namespace ts {
return Ternary.Maybe;
}
}
if (depth === 100) {
if (sourceDepth === 100 || targetDepth === 100) {
overflow = true;
return Ternary.False;
}
}
const constantSource = sourceStack[sourceDepth] === source;
const constantTarget = targetStack[targetDepth] === target;
const maybeStart = maybeCount;
maybeKeys[maybeCount] = id;
maybeCount++;
sourceStack[depth] = source;
targetStack[depth] = target;
depth++;
if (!constantSource) {
sourceStack[sourceDepth] = source;
sourceDepth++;
}
if (!constantTarget) {
targetStack[targetDepth] = target;
targetDepth++;
}
const saveExpandingFlags = expandingFlags;
if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, depth)) expandingFlags |= ExpandingFlags.Source;
if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, depth)) expandingFlags |= ExpandingFlags.Target;
if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, sourceDepth)) expandingFlags |= ExpandingFlags.Source;
if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, targetDepth)) expandingFlags |= ExpandingFlags.Target;
let originalHandler: typeof outofbandVarianceMarkerHandler;
let propagatingVarianceFlags: RelationComparisonResult = 0;
if (outofbandVarianceMarkerHandler) {
Expand All @@ -17350,7 +17358,8 @@ namespace ts {
sourceIdStack: sourceStack.map(t => t.id),
targetId: target.id,
targetIdStack: targetStack.map(t => t.id),
depth,
depth: sourceDepth,
targetDepth
});
}

Expand All @@ -17359,9 +17368,14 @@ namespace ts {
outofbandVarianceMarkerHandler = originalHandler;
}
expandingFlags = saveExpandingFlags;
depth--;
if (!constantSource) {
sourceDepth--;
}
if (!constantTarget) {
targetDepth--;
}
if (result) {
if (result === Ternary.True || depth === 0) {
if (result === Ternary.True || (sourceDepth === 0 && targetDepth === 0)) {
if (result === Ternary.True || result === Ternary.Maybe) {
// If result is definitely true, record all maybe keys as having succeeded. Also, record Ternary.Maybe
// results as having succeeded once we reach depth 0, but never record Ternary.Unknown results.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts(13,5): error TS2322: Type 'ReturnType<T[M]>' is not assignable to type 'A'.
Type 'unknown' is not assignable to type 'A'.
Type 'ReturnType<T[keyof T]>' is not assignable to type 'A'.
Type 'unknown' is not assignable to type 'A'.
Type 'ReturnType<T[string | number | symbol]>' is not assignable to type 'A'.
Type 'unknown' is not assignable to type 'A'.
Type 'ReturnType<T[string]> | ReturnType<T[number]> | ReturnType<T[symbol]>' is not assignable to type 'A'.
Type 'ReturnType<T[number]>' is not assignable to type 'A'.
Type 'unknown' is not assignable to type 'A'.
Type 'ReturnType<FunctionsObj<T>[number]>' is not assignable to type 'A'.
Type 'unknown' is not assignable to type 'A'.
Property 'x' is missing in type '{}' but required in type 'A'.


==== tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts (1 errors) ====
interface A { x: number }

declare function isA(a: unknown): a is A;

type FunctionsObj<T> = {
[K in keyof T]: () => unknown
}

function g<
T extends FunctionsObj<T>,
M extends keyof T
>(a2: ReturnType<T[M]>, x: A) {
x = a2;
~
!!! error TS2322: Type 'ReturnType<T[M]>' is not assignable to type 'A'.
!!! error TS2322: Type 'unknown' is not assignable to type 'A'.
!!! error TS2322: Type 'ReturnType<T[keyof T]>' is not assignable to type 'A'.
!!! error TS2322: Type 'unknown' is not assignable to type 'A'.
!!! error TS2322: Type 'ReturnType<T[string | number | symbol]>' is not assignable to type 'A'.
!!! error TS2322: Type 'unknown' is not assignable to type 'A'.
!!! error TS2322: Type 'ReturnType<T[string]> | ReturnType<T[number]> | ReturnType<T[symbol]>' is not assignable to type 'A'.
!!! error TS2322: Type 'ReturnType<T[number]>' is not assignable to type 'A'.
!!! error TS2322: Type 'unknown' is not assignable to type 'A'.
!!! error TS2322: Type 'ReturnType<FunctionsObj<T>[number]>' is not assignable to type 'A'.
!!! error TS2322: Type 'unknown' is not assignable to type 'A'.
!!! error TS2322: Property 'x' is missing in type '{}' but required in type 'A'.
!!! related TS2728 tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts:1:15: 'x' is declared here.
}

// Original CFA report of the above issue

function g2<
T extends FunctionsObj<T>,
M extends keyof T
>(a2: ReturnType<T[M]>) {
if (isA(a2)) {
// a2 is not narrowed
a2.x // error, but should be ok
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//// [genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts]
interface A { x: number }

declare function isA(a: unknown): a is A;

type FunctionsObj<T> = {
[K in keyof T]: () => unknown
}

function g<
T extends FunctionsObj<T>,
M extends keyof T
>(a2: ReturnType<T[M]>, x: A) {
x = a2;
}

// Original CFA report of the above issue

function g2<
T extends FunctionsObj<T>,
M extends keyof T
>(a2: ReturnType<T[M]>) {
if (isA(a2)) {
// a2 is not narrowed
a2.x // error, but should be ok
}
}


//// [genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.js]
function g(a2, x) {
x = a2;
}
// Original CFA report of the above issue
function g2(a2) {
if (isA(a2)) {
// a2 is not narrowed
a2.x; // error, but should be ok
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
=== tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts ===
interface A { x: number }
>A : Symbol(A, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 0))
>x : Symbol(A.x, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 13))

declare function isA(a: unknown): a is A;
>isA : Symbol(isA, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 25))
>a : Symbol(a, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 2, 21))
>a : Symbol(a, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 2, 21))
>A : Symbol(A, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 0))

type FunctionsObj<T> = {
>FunctionsObj : Symbol(FunctionsObj, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 2, 41))
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 4, 18))

[K in keyof T]: () => unknown
>K : Symbol(K, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 5, 5))
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 4, 18))
}

function g<
>g : Symbol(g, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 6, 1))

T extends FunctionsObj<T>,
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 8, 11))
>FunctionsObj : Symbol(FunctionsObj, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 2, 41))
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 8, 11))

M extends keyof T
>M : Symbol(M, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 9, 30))
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 8, 11))

>(a2: ReturnType<T[M]>, x: A) {
>a2 : Symbol(a2, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 11, 2))
>ReturnType : Symbol(ReturnType, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 8, 11))
>M : Symbol(M, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 9, 30))
>x : Symbol(x, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 11, 23))
>A : Symbol(A, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 0))

x = a2;
>x : Symbol(x, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 11, 23))
>a2 : Symbol(a2, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 11, 2))
}

// Original CFA report of the above issue

function g2<
>g2 : Symbol(g2, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 13, 1))

T extends FunctionsObj<T>,
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 17, 12))
>FunctionsObj : Symbol(FunctionsObj, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 2, 41))
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 17, 12))

M extends keyof T
>M : Symbol(M, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 18, 30))
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 17, 12))

>(a2: ReturnType<T[M]>) {
>a2 : Symbol(a2, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 20, 2))
>ReturnType : Symbol(ReturnType, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 17, 12))
>M : Symbol(M, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 18, 30))

if (isA(a2)) {
>isA : Symbol(isA, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 25))
>a2 : Symbol(a2, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 20, 2))

// a2 is not narrowed
a2.x // error, but should be ok
>a2.x : Symbol(A.x, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 13))
>a2 : Symbol(a2, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 20, 2))
>x : Symbol(A.x, Decl(genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts, 0, 13))
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
=== tests/cases/compiler/genericConditionalConstrainedToUnknownNotAssignableToConcreteObject.ts ===
interface A { x: number }
>x : number

declare function isA(a: unknown): a is A;
>isA : (a: unknown) => a is A
>a : unknown

type FunctionsObj<T> = {
>FunctionsObj : FunctionsObj<T>

[K in keyof T]: () => unknown
}

function g<
>g : <T extends FunctionsObj<T>, M extends keyof T>(a2: ReturnType<T[M]>, x: A) => void

T extends FunctionsObj<T>,
M extends keyof T
>(a2: ReturnType<T[M]>, x: A) {
>a2 : ReturnType<T[M]>
>x : A

x = a2;
>x = a2 : ReturnType<T[M]>
>x : A
>a2 : ReturnType<T[M]>
}

// Original CFA report of the above issue

function g2<
>g2 : <T extends FunctionsObj<T>, M extends keyof T>(a2: ReturnType<T[M]>) => void

T extends FunctionsObj<T>,
M extends keyof T
>(a2: ReturnType<T[M]>) {
>a2 : ReturnType<T[M]>

if (isA(a2)) {
>isA(a2) : boolean
>isA : (a: unknown) => a is A
>a2 : ReturnType<T[M]>

// a2 is not narrowed
a2.x // error, but should be ok
>a2.x : number
>a2 : ReturnType<T[M]> & A
>x : number
}
}

5 changes: 4 additions & 1 deletion tests/baselines/reference/infiniteConstraints.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' ca
tests/cases/compiler/infiniteConstraints.ts(31,43): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
tests/cases/compiler/infiniteConstraints.ts(31,63): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.
tests/cases/compiler/infiniteConstraints.ts(48,27): error TS2321: Excessive stack depth comparing types 'Conv<ExactExtract<U, T>, ExactExtract<U, T>>' and 'unknown[]'.


==== tests/cases/compiler/infiniteConstraints.ts (4 errors) ====
==== tests/cases/compiler/infiniteConstraints.ts (5 errors) ====
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint

type T1<B extends { [K in keyof B]: Extract<B[Exclude<keyof B, K>], { val: string }>["val"] }> = B;
Expand Down Expand Up @@ -63,4 +64,6 @@ tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' c

type Conv<T, U = T> =
{ 0: [T]; 1: Prepend<T, Conv<ExactExtract<U, T>>>;}[U extends T ? 0 : 1];
~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2321: Excessive stack depth comparing types 'Conv<ExactExtract<U, T>, ExactExtract<U, T>>' and 'unknown[]'.
Copy link
Member Author

@weswigham weswigham Dec 4, 2020

Choose a reason for hiding this comment

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

This part of this test was originally added because it had bad perf, and the test had a stack out error once the perf was fixed (circa #32079), however the error disappeared in #40971 and we just kidna didn't care (the test is for perf, after all). Turns out, the error should still be here, since the relationship does still infinitely expand (on only one side - we were erroneously flagging both sides are infinitely expanding - unknown[] definitely doesn't infinitely expand!).


Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
interface A { x: number }

declare function isA(a: unknown): a is A;

type FunctionsObj<T> = {
[K in keyof T]: () => unknown
}

function g<
T extends FunctionsObj<T>,
M extends keyof T
>(a2: ReturnType<T[M]>, x: A) {
x = a2;
}

// Original CFA report of the above issue

function g2<
T extends FunctionsObj<T>,
M extends keyof T
>(a2: ReturnType<T[M]>) {
if (isA(a2)) {
// a2 is not narrowed
a2.x // error, but should be ok
}
}