Skip to content
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
24 changes: 23 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2241,6 +2241,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();
Expand Down Expand Up @@ -25299,14 +25302,33 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
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);
inferTypes([inference], sourceType, templateType);
return getTypeFromInference(inference) || unknownType;
}

function inferReverseMappedType(source: Type, target: MappedType, constraint: IndexType): Type {
Copy link
Member

Choose a reason for hiding this comment

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

Should we cache the results inside this, so we guarantee deeply nested results get cached? Of the callers of this function, only getTypeOfReverseMappedSymbol currently caches - the other uses don't (directly) cache the result, so could, in theory, witness different results for the same set of input types at different points. You can probably only easily trigger such a behavior with a reverse mapped type over an array...? Maybe?

Copy link
Member Author

Choose a reason for hiding this comment

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

I was adding the cache to inferReverseMappedType and looking at inferTypeForHomomorphicMappedType. I'm now wondering: could I do away with the recursion detection in inferTypeForHomomorphicMappedType (that uses homomorphicMappedTypeInferenceStack)?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm pretty sure that we can, however this once again begs the question of what to return when we find a circularity. inferTypeForHomomorphicMappedType returns undefined in this case. I'll have to think some more about this, but I'm planning on removing that circularity detection code in inferTypeForHomomorphicMappedType.

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);
}
else {
type = unknownType;
}
reverseMappedSourceStack.pop();
reverseMappedTargetStack.pop();
reverseExpandingFlags = saveExpandingFlags;
return type;
}

function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator<Symbol> {
const properties = getPropertiesOfType(target);
for (const targetProp of properties) {
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/mappedTypeRecursiveInference.types
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
=== 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)
Type Count: 14,700 / 14,800 (nearest 100)
Instantiation count: 483,000 / 501,000 (nearest 500)
Symbol count: 174,000 / 176,500 (nearest 500)

=== mappedTypeRecursiveInference.ts ===
interface A { a: A }
Expand Down
28 changes: 28 additions & 0 deletions tests/baselines/reference/reverseMappedTypeRecursiveInference.js
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -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))

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//// [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 changes: 18 additions & 0 deletions tests/cases/compiler/reverseMappedTypeRecursiveInference.ts
Original file line number Diff line number Diff line change
@@ -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);