Skip to content

TS2589 error in TSGo with recursive mapped type over DOM types but not is tsc; ~18x more instantiations than tsc #64486

Description

@promitdan

🔎 Search Terms

Bug

TSGo reports TS2589: Type instantiation is excessively deep and possibly infinite for a program that successfully type-checks with the JavaScript TypeScript compiler.

Minimal Repro done in: https://github.com/promitdan/tsgo-ts2589-issue

With TSGo, the repro reports:
TS2589: Type instantiation is excessively deep and possibly infinite.
TSGo performs approximately 10.6 million type instantiations before failing.

With TypeScript 5.9.3, the repro performs approximately 594k type instantiations and completes without TS2589.

🕗 Version & Regression Information

  • This changed between versions TypeScript 7.0.2 and 5.9.3
  • @types/lodash 4.17.24
  • Reproduced on macOS, Node 24

⏯ Playground Link

https://github.com/promitdan/tsgo-ts2589-issue

💻 Code

// Your code here
// TypeScript 7.0.2 reports TS2589 on the last line of this file.
// TypeScript 5.9.3 compiles the same file with no errors.
//
// The shape of the problem: a generic function whose parameter type is a recursive
// "deep partial" conditional type, called with a DOM constructor. Expanding the
// recursive type over the DOM type graph is expensive, and 7.0.2 runs out of
// instantiation budget where 5.9.3 does not.

// The two types below are copied from react-magnetic-di:
//   https://github.com/albertogasparin/react-magnetic-di
// React's ComponentType/ComponentProps are replaced with stubs so this file has no
// dependencies of its own.
type ComponentType<P> = (props: P) => unknown;
type ComponentProps<T> = T extends (props: infer P) => unknown ? P : never;
type Injectable<T = unknown> = T & { [marker: symbol]: 'marker' };

// Recursive deep-partial type, verbatim. Note there is no depth limit and no cycle guard.
type DeepPartial<Type> = Type extends (...args: any) => any
	? (...args: Parameters<Type>) => DeepPartial<ReturnType<Type>>
	: Type extends ReadonlyArray<infer InferredArrayMember>
		? InferredArrayMember[] extends Type
			? readonly InferredArrayMember[] extends Type
				? ReadonlyArray<DeepPartial<InferredArrayMember>>
				: Array<DeepPartial<InferredArrayMember>>
			: DeepPartialObject<Type>
		: Type extends object
			? DeepPartialObject<Type>
			: Type | undefined;

type DeepPartialObject<Type> = { [Key in keyof Type]?: DeepPartial<Type[Key]> };

type ComponentOrFunction<Type> = Type extends ComponentType<any>
	? ComponentType<ComponentProps<Type>>
	: DeepPartial<Type>;

// Takes a real thing plus a stand-in for it, as a DI or mocking helper would.
declare function injectable<T>(from: T, implementation: ComponentOrFunction<T>): Injectable<T>;

// The stand-in needs members. With an empty class both compilers finish instantly.
class MockIntersectionObserver {
	observe(): void {}
	disconnect(): void {}
	unobserve(): void {}
	takeRecords(): IntersectionObserverEntry[] {
		return [];
	}
	root = null;
	rootMargin = '';
	thresholds = [];
}

export const dependencies = [injectable(IntersectionObserver, MockIntersectionObserver)];

🙁 Actual behavior

TSGo performs approximately 10.6 million type instantiations before failing with TS2589
TS5 passes without any errors and performs around 594k instantiations

🙂 Expected behavior

TSGo should not fail with TS2589 and should not perform ~18x instantiations compared to TS5

Additional information about the issue

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions