mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 03:09:39 -06:00
Avoid infinite recursion with inferReverseMappedType (#57837)
This commit is contained in:
parent
ce213148ec
commit
fd238857b4
@ -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
@ -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 }
|
||||
|
||||
@ -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);
|
||||
@ -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))
|
||||
|
||||
@ -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>
|
||||
> : ^^^^^^^^
|
||||
|
||||
18
tests/cases/compiler/reverseMappedTypeRecursiveInference.ts
Normal file
18
tests/cases/compiler/reverseMappedTypeRecursiveInference.ts
Normal 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);
|
||||
Loading…
x
Reference in New Issue
Block a user