Avoid infinite recursion with inferReverseMappedType (#57837)

This commit is contained in:
Gabriela Araujo Britto 2024-04-16 10:43:39 -07:00 committed by GitHub
parent ce213148ec
commit fd238857b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 207 additions and 25 deletions

View File

@ -2159,7 +2159,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
/** Key is "/path/to/a.ts|/path/to/b.ts". */
var amalgamatedDuplicates: Map<string, DuplicateInfoForFiles> | undefined;
var reverseMappedCache = new Map<string, Type | undefined>();
var homomorphicMappedTypeInferenceStack: string[] = [];
var ambientModulesCache: Symbol[] | undefined;
/**
* List of every ambient module with a "*" wildcard.
@ -2277,6 +2276,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
var potentialReflectCollisions: Node[] = [];
var potentialUnusedRenamedBindingElementsInTypes: BindingElement[] = [];
var awaitedTypeStack: number[] = [];
var reverseMappedSourceStack: Type[] = [];
var reverseMappedTargetStack: Type[] = [];
var reverseExpandingFlags = ExpandingFlags.None;
var diagnostics = createDiagnosticCollection();
var suggestionDiagnostics = createDiagnosticCollection();
@ -13583,7 +13585,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const modifiers = getMappedTypeModifiers(type.mappedType);
const readonlyMask = modifiers & MappedTypeModifiers.IncludeReadonly ? false : true;
const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional;
const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly)] : emptyArray;
const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType) || unknownType, readonlyMask && indexInfo.isReadonly)] : emptyArray;
const members = createSymbolTable();
const limitedConstraint = getLimitedConstraint(type);
for (const prop of getPropertiesOfType(type.source)) {
@ -25102,13 +25104,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (reverseMappedCache.has(cacheKey)) {
return reverseMappedCache.get(cacheKey);
}
const recursionKey = source.id + "," + (target.target || target).id;
if (contains(homomorphicMappedTypeInferenceStack, recursionKey)) {
return undefined;
}
homomorphicMappedTypeInferenceStack.push(recursionKey);
const type = createReverseMappedType(source, target, constraint);
homomorphicMappedTypeInferenceStack.pop();
reverseMappedCache.set(cacheKey, type);
return type;
}
@ -25132,10 +25128,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// For arrays and tuples we infer new arrays and tuples where the reverse mapping has been
// applied to the element type(s).
if (isArrayType(source)) {
return createArrayType(inferReverseMappedType(getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source));
const elementType = inferReverseMappedType(getTypeArguments(source)[0], target, constraint);
if (!elementType) {
return undefined;
}
return createArrayType(elementType, isReadonlyArrayType(source));
}
if (isTupleType(source)) {
const elementTypes = map(getElementTypes(source), t => inferReverseMappedType(t, target, constraint));
if (!every(elementTypes, (t): t is Type => !!t)) {
return undefined;
}
const elementFlags = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ?
sameMap(source.target.elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) :
source.target.elementFlags;
@ -25150,15 +25153,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return reversed;
}
function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) {
function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol): Type {
const links = getSymbolLinks(symbol);
if (!links.type) {
links.type = inferReverseMappedType(symbol.links.propertyType, symbol.links.mappedType, symbol.links.constraintType);
links.type = inferReverseMappedType(symbol.links.propertyType, symbol.links.mappedType, symbol.links.constraintType) || unknownType;
}
return links.type;
}
function inferReverseMappedType(sourceType: Type, target: MappedType, constraint: IndexType): Type {
function inferReverseMappedTypeWorker(sourceType: Type, target: MappedType, constraint: IndexType): Type {
const typeParameter = getIndexedAccessType(constraint.type, getTypeParameterFromMappedType(target)) as TypeParameter;
const templateType = getTemplateTypeFromMappedType(target);
const inference = createInferenceInfo(typeParameter);
@ -25166,6 +25169,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return getTypeFromInference(inference) || unknownType;
}
function inferReverseMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined {
const cacheKey = source.id + "," + target.id + "," + constraint.id;
if (reverseMappedCache.has(cacheKey)) {
return reverseMappedCache.get(cacheKey) || unknownType;
}
reverseMappedSourceStack.push(source);
reverseMappedTargetStack.push(target);
const saveExpandingFlags = reverseExpandingFlags;
if (isDeeplyNestedType(source, reverseMappedSourceStack, reverseMappedSourceStack.length, 2)) reverseExpandingFlags |= ExpandingFlags.Source;
if (isDeeplyNestedType(target, reverseMappedTargetStack, reverseMappedTargetStack.length, 2)) reverseExpandingFlags |= ExpandingFlags.Target;
let type;
if (reverseExpandingFlags !== ExpandingFlags.Both) {
type = inferReverseMappedTypeWorker(source, target, constraint);
}
reverseMappedSourceStack.pop();
reverseMappedTargetStack.pop();
reverseExpandingFlags = saveExpandingFlags;
reverseMappedCache.set(cacheKey, type);
return type;
}
function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator<Symbol> {
const properties = getPropertiesOfType(target);
for (const targetProp of properties) {

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,11 @@
//// [tests/cases/compiler/mappedTypeRecursiveInference.ts] ////
=== Performance Stats ===
Strict subtype cache: 300 / 300 (nearest 100)
Assignability cache: 5,000 / 5,000 (nearest 100)
Type Count: 15,000 / 15,000 (nearest 100)
Instantiation count: 486,000 / 503,500 (nearest 500)
Symbol count: 174,500 / 177,000 (nearest 500)
Strict subtype cache: 100 / 100 (nearest 100)
Assignability cache: 4,100 / 4,100 (nearest 100)
Type Count: 12,200 / 12,200 (nearest 100)
Instantiation count: 341,000 / 358,500 (nearest 500)
Symbol count: 117,500 / 120,000 (nearest 500)
=== mappedTypeRecursiveInference.ts ===
interface A { a: A }

View File

@ -0,0 +1,28 @@
//// [tests/cases/compiler/reverseMappedTypeRecursiveInference.ts] ////
//// [reverseMappedTypeRecursiveInference.ts]
type Foo<V> = {
[K in keyof V]: Foo<V[K]>;
}
type Bar<V> = {
[K in keyof V]: V[K] extends object ? Bar<V[K]> : string;
}
function test<V>(value: Foo<V>): V {
console.log(value);
return undefined as any;
}
const bar: Bar<any> = {};
test(bar);
//// [reverseMappedTypeRecursiveInference.js]
"use strict";
function test(value) {
console.log(value);
return undefined;
}
var bar = {};
test(bar);

View File

@ -0,0 +1,55 @@
//// [tests/cases/compiler/reverseMappedTypeRecursiveInference.ts] ////
=== reverseMappedTypeRecursiveInference.ts ===
type Foo<V> = {
>Foo : Symbol(Foo, Decl(reverseMappedTypeRecursiveInference.ts, 0, 0))
>V : Symbol(V, Decl(reverseMappedTypeRecursiveInference.ts, 0, 9))
[K in keyof V]: Foo<V[K]>;
>K : Symbol(K, Decl(reverseMappedTypeRecursiveInference.ts, 1, 5))
>V : Symbol(V, Decl(reverseMappedTypeRecursiveInference.ts, 0, 9))
>Foo : Symbol(Foo, Decl(reverseMappedTypeRecursiveInference.ts, 0, 0))
>V : Symbol(V, Decl(reverseMappedTypeRecursiveInference.ts, 0, 9))
>K : Symbol(K, Decl(reverseMappedTypeRecursiveInference.ts, 1, 5))
}
type Bar<V> = {
>Bar : Symbol(Bar, Decl(reverseMappedTypeRecursiveInference.ts, 2, 1))
>V : Symbol(V, Decl(reverseMappedTypeRecursiveInference.ts, 4, 9))
[K in keyof V]: V[K] extends object ? Bar<V[K]> : string;
>K : Symbol(K, Decl(reverseMappedTypeRecursiveInference.ts, 5, 5))
>V : Symbol(V, Decl(reverseMappedTypeRecursiveInference.ts, 4, 9))
>V : Symbol(V, Decl(reverseMappedTypeRecursiveInference.ts, 4, 9))
>K : Symbol(K, Decl(reverseMappedTypeRecursiveInference.ts, 5, 5))
>Bar : Symbol(Bar, Decl(reverseMappedTypeRecursiveInference.ts, 2, 1))
>V : Symbol(V, Decl(reverseMappedTypeRecursiveInference.ts, 4, 9))
>K : Symbol(K, Decl(reverseMappedTypeRecursiveInference.ts, 5, 5))
}
function test<V>(value: Foo<V>): V {
>test : Symbol(test, Decl(reverseMappedTypeRecursiveInference.ts, 6, 1))
>V : Symbol(V, Decl(reverseMappedTypeRecursiveInference.ts, 8, 14))
>value : Symbol(value, Decl(reverseMappedTypeRecursiveInference.ts, 8, 17))
>Foo : Symbol(Foo, Decl(reverseMappedTypeRecursiveInference.ts, 0, 0))
>V : Symbol(V, Decl(reverseMappedTypeRecursiveInference.ts, 8, 14))
>V : Symbol(V, Decl(reverseMappedTypeRecursiveInference.ts, 8, 14))
console.log(value);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>value : Symbol(value, Decl(reverseMappedTypeRecursiveInference.ts, 8, 17))
return undefined as any;
>undefined : Symbol(undefined)
}
const bar: Bar<any> = {};
>bar : Symbol(bar, Decl(reverseMappedTypeRecursiveInference.ts, 13, 5))
>Bar : Symbol(Bar, Decl(reverseMappedTypeRecursiveInference.ts, 2, 1))
test(bar);
>test : Symbol(test, Decl(reverseMappedTypeRecursiveInference.ts, 6, 1))
>bar : Symbol(bar, Decl(reverseMappedTypeRecursiveInference.ts, 13, 5))

View File

@ -0,0 +1,55 @@
//// [tests/cases/compiler/reverseMappedTypeRecursiveInference.ts] ////
=== reverseMappedTypeRecursiveInference.ts ===
type Foo<V> = {
>Foo : Foo<V>
> : ^^^^^^
[K in keyof V]: Foo<V[K]>;
}
type Bar<V> = {
>Bar : Bar<V>
> : ^^^^^^
[K in keyof V]: V[K] extends object ? Bar<V[K]> : string;
}
function test<V>(value: Foo<V>): V {
>test : <V>(value: Foo<V>) => V
> : ^ ^^ ^^ ^^^^^
>value : Foo<V>
> : ^^^^^^
console.log(value);
>console.log(value) : void
> : ^^^^
>console.log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^^^^^
>console : Console
> : ^^^^^^^
>log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^^^^^
>value : Foo<V>
> : ^^^^^^
return undefined as any;
>undefined as any : any
>undefined : undefined
> : ^^^^^^^^^
}
const bar: Bar<any> = {};
>bar : Bar<any>
> : ^^^^^^^^
>{} : {}
> : ^^
test(bar);
>test(bar) : { [x: string]: any; }
> : ^^^^^^^^^^^^^^^^^^^^^
>test : <V>(value: Foo<V>) => V
> : ^ ^^ ^^ ^^^^^^
>bar : Bar<any>
> : ^^^^^^^^

View File

@ -0,0 +1,18 @@
// @strict: true
type Foo<V> = {
[K in keyof V]: Foo<V[K]>;
}
type Bar<V> = {
[K in keyof V]: V[K] extends object ? Bar<V[K]> : string;
}
function test<V>(value: Foo<V>): V {
console.log(value);
return undefined as any;
}
const bar: Bar<any> = {};
test(bar);