Skip to content

Commit 5d08b68

Browse files
authored
Fix react-redux break on DT (#30375)
* Forbid reentrancy in conditional type creation (force deferal on occurance) * Add repro from react-redux, accept updated baselines
1 parent e42f37f commit 5d08b68

8 files changed

+549
-27
lines changed

src/compiler/checker.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ namespace ts {
388388
const intersectionTypes = createMap<IntersectionType>();
389389
const literalTypes = createMap<LiteralType>();
390390
const indexedAccessTypes = createMap<IndexedAccessType>();
391-
const conditionalTypes = createMap<Type>();
391+
const conditionalTypes = createMap<Type | undefined>();
392392
const evolvingArrayTypes: EvolvingArrayType[] = [];
393393
const undefinedProperties = createMap<Symbol>() as UnderscoreEscapedMap<Symbol>;
394394

@@ -10138,11 +10138,24 @@ namespace ts {
1013810138
const trueType = instantiateType(root.trueType, mapper);
1013910139
const falseType = instantiateType(root.falseType, mapper);
1014010140
const instantiationId = `${root.isDistributive ? "d" : ""}${getTypeId(checkType)}>${getTypeId(extendsType)}?${getTypeId(trueType)}:${getTypeId(falseType)}`;
10141-
const result = conditionalTypes.get(instantiationId);
10142-
if (result) {
10143-
return result;
10141+
if (conditionalTypes.has(instantiationId)) {
10142+
const result = conditionalTypes.get(instantiationId);
10143+
if (result !== undefined) {
10144+
return result;
10145+
}
10146+
// Somehow the conditional type depends on itself - usually via `infer` types in the `extends` clause
10147+
// paired with a (potentially deferred) circularly constrained type.
10148+
// The conditional _must_ be deferred.
10149+
const deferred = getDeferredConditionalType(root, mapper, /*combinedMapper*/ undefined, checkType, extendsType, trueType, falseType);
10150+
conditionalTypes.set(instantiationId, deferred);
10151+
return deferred;
1014410152
}
10153+
conditionalTypes.set(instantiationId, undefined);
1014510154
const newResult = getConditionalTypeWorker(root, mapper, checkType, extendsType, trueType, falseType);
10155+
const cachedRecursiveResult = conditionalTypes.get(instantiationId);
10156+
if (cachedRecursiveResult) {
10157+
return cachedRecursiveResult;
10158+
}
1014610159
conditionalTypes.set(instantiationId, newResult);
1014710160
return newResult;
1014810161
}
@@ -10206,6 +10219,10 @@ namespace ts {
1020610219
}
1020710220
}
1020810221
// Return a deferred type for a check that is neither definitely true nor definitely false
10222+
return getDeferredConditionalType(root, mapper, combinedMapper, checkType, extendsType, trueType, falseType);
10223+
}
10224+
10225+
function getDeferredConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, combinedMapper: TypeMapper | undefined, checkType: Type, extendsType: Type, trueType: Type, falseType: Type) {
1020910226
const erasedCheckType = getActualTypeVariable(checkType);
1021010227
const result = <ConditionalType>createType(TypeFlags.Conditional);
1021110228
result.root = root;

tests/baselines/reference/circularTypeofWithVarOrFunc.errors.txt

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarO
44
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(5,6): error TS2456: Type alias 'typeAlias2' circularly references itself.
55
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(7,18): error TS2577: Return type annotation circularly references itself.
66
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(9,6): error TS2456: Type alias 'typeAlias3' circularly references itself.
7-
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(18,6): error TS2456: Type alias 'R' circularly references itself.
8-
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(19,29): error TS2577: Return type annotation circularly references itself.
9-
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(25,6): error TS2456: Type alias 'R2' circularly references itself.
10-
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(26,15): error TS2577: Return type annotation circularly references itself.
7+
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(20,3): error TS2322: Type 'number' is not assignable to type 'ReturnType<(input: Input) => ReturnType<typeof mul>>'.
8+
tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts(26,20): error TS2322: Type '0' is not assignable to type 'ReturnType<() => ReturnType<typeof f>>'.
119

1210

13-
==== tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts (10 errors) ====
11+
==== tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarOrFunc.ts (8 errors) ====
1412
type typeAlias1 = typeof varOfAliasedType1;
1513
~~~~~~~~~~
1614
!!! error TS2456: Type alias 'typeAlias1' circularly references itself.
@@ -41,20 +39,16 @@ tests/cases/conformance/types/specifyingTypes/typeQueries/circularTypeofWithVarO
4139
}
4240

4341
type R = ReturnType<typeof mul>;
44-
~
45-
!!! error TS2456: Type alias 'R' circularly references itself.
4642
function mul(input: Input): R {
47-
~
48-
!!! error TS2577: Return type annotation circularly references itself.
4943
return input.a * input.b;
44+
~~~~~~~~~~~~~~~~~~~~~~~~~
45+
!!! error TS2322: Type 'number' is not assignable to type 'ReturnType<(input: Input) => ReturnType<typeof mul>>'.
5046
}
5147

5248
// Repro from #26104
5349

5450
type R2 = ReturnType<typeof f>;
55-
~~
56-
!!! error TS2456: Type alias 'R2' circularly references itself.
5751
function f(): R2 { return 0; }
58-
~~
59-
!!! error TS2577: Return type annotation circularly references itself.
52+
~~~~~~~~~
53+
!!! error TS2322: Type '0' is not assignable to type 'ReturnType<() => ReturnType<typeof f>>'.
6054

tests/baselines/reference/circularTypeofWithVarOrFunc.types

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ interface Input {
3737
}
3838

3939
type R = ReturnType<typeof mul>;
40-
>R : any
41-
>mul : (input: Input) => any
40+
>R : ReturnType<(input: Input) => ReturnType<typeof mul>>
41+
>mul : (input: Input) => ReturnType<typeof mul>
4242

4343
function mul(input: Input): R {
44-
>mul : (input: Input) => any
44+
>mul : (input: Input) => ReturnType<typeof mul>
4545
>input : Input
4646

4747
return input.a * input.b;
@@ -57,10 +57,10 @@ function mul(input: Input): R {
5757
// Repro from #26104
5858

5959
type R2 = ReturnType<typeof f>;
60-
>R2 : any
61-
>f : () => any
60+
>R2 : ReturnType<() => ReturnType<typeof f>>
61+
>f : () => ReturnType<typeof f>
6262

6363
function f(): R2 { return 0; }
64-
>f : () => any
64+
>f : () => ReturnType<typeof f>
6565
>0 : 0
6666

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//// [circularlyConstrainedMappedTypeContainingConditionalNoInfiniteInstantiationDepth.ts]
2+
declare class Component<P> {
3+
constructor(props: Readonly<P>);
4+
constructor(props: P, context?: any);
5+
readonly props: Readonly<P> & Readonly<{ children?: {} }>;
6+
}
7+
interface ComponentClass<P = {}> {
8+
new (props: P, context?: any): Component<P>;
9+
propTypes?: WeakValidationMap<P>;
10+
defaultProps?: Partial<P>;
11+
displayName?: string;
12+
}
13+
interface FunctionComponent<P = {}> {
14+
(props: P & { children?: {} }, context?: any): {} | null;
15+
propTypes?: WeakValidationMap<P>;
16+
defaultProps?: Partial<P>;
17+
displayName?: string;
18+
}
19+
20+
export declare const nominalTypeHack: unique symbol;
21+
export interface Validator<T> {
22+
(props: object, propName: string, componentName: string, location: string, propFullName: string): Error | null;
23+
[nominalTypeHack]?: T;
24+
}
25+
type WeakValidationMap<T> = {
26+
[K in keyof T]?: null extends T[K]
27+
? Validator<T[K] | null | undefined>
28+
: undefined extends T[K]
29+
? Validator<T[K] | null | undefined>
30+
: Validator<T[K]>
31+
};
32+
type ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P>;
33+
34+
export type Shared<
35+
InjectedProps,
36+
DecorationTargetProps extends Shared<InjectedProps, DecorationTargetProps>
37+
> = {
38+
[P in Extract<keyof InjectedProps, keyof DecorationTargetProps>]?: InjectedProps[P] extends DecorationTargetProps[P] ? DecorationTargetProps[P] : never;
39+
};
40+
41+
// Infers prop type from component C
42+
export type GetProps<C> = C extends ComponentType<infer P> ? P : never;
43+
44+
export type ConnectedComponentClass<
45+
C extends ComponentType<any>,
46+
P
47+
> = ComponentClass<P> & {
48+
WrappedComponent: C;
49+
};
50+
51+
export type Matching<InjectedProps, DecorationTargetProps> = {
52+
[P in keyof DecorationTargetProps]: P extends keyof InjectedProps
53+
? InjectedProps[P] extends DecorationTargetProps[P]
54+
? DecorationTargetProps[P]
55+
: InjectedProps[P]
56+
: DecorationTargetProps[P];
57+
};
58+
59+
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
60+
61+
export type InferableComponentEnhancerWithProps<TInjectedProps, TNeedsProps> =
62+
<C extends ComponentType<Matching<TInjectedProps, GetProps<C>>>>(
63+
component: C
64+
) => ConnectedComponentClass<C, Omit<GetProps<C>, keyof Shared<TInjectedProps, GetProps<C>>> & TNeedsProps>;
65+
66+
67+
//// [circularlyConstrainedMappedTypeContainingConditionalNoInfiniteInstantiationDepth.js]
68+
"use strict";
69+
exports.__esModule = true;

0 commit comments

Comments
 (0)