From 6edfef8c0d4535eb8fdb83405f57dabec9b8166f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 30 Nov 2023 23:57:52 +0100 Subject: [PATCH] Fixed an issue with reverse mapped types inference when single type variable is left after inferring from matching types (#55941) Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> --- src/compiler/checker.ts | 2 +- .../reverseMappedUnionInference.symbols | 172 ++++++++++++++++++ .../reverseMappedUnionInference.types | 148 +++++++++++++++ .../compiler/reverseMappedUnionInference.ts | 59 ++++++ 4 files changed, 380 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/reverseMappedUnionInference.symbols create mode 100644 tests/baselines/reference/reverseMappedUnionInference.types create mode 100644 tests/cases/compiler/reverseMappedUnionInference.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8f1b6918320..289a98a4295 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25354,7 +25354,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { target = getIntersectionType(targets); } } - else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { + if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { target = getActualTypeVariable(target); } if (target.flags & TypeFlags.TypeVariable) { diff --git a/tests/baselines/reference/reverseMappedUnionInference.symbols b/tests/baselines/reference/reverseMappedUnionInference.symbols new file mode 100644 index 00000000000..ec09965565d --- /dev/null +++ b/tests/baselines/reference/reverseMappedUnionInference.symbols @@ -0,0 +1,172 @@ +//// [tests/cases/compiler/reverseMappedUnionInference.ts] //// + +=== reverseMappedUnionInference.ts === +interface AnyExtractor { +>AnyExtractor : Symbol(AnyExtractor, Decl(reverseMappedUnionInference.ts, 0, 0)) +>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 0, 23)) + + matches: (node: any) => boolean; +>matches : Symbol(AnyExtractor.matches, Decl(reverseMappedUnionInference.ts, 0, 32)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 1, 12)) + + extract: (node: any) => Result | undefined; +>extract : Symbol(AnyExtractor.extract, Decl(reverseMappedUnionInference.ts, 1, 34)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 2, 12)) +>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 0, 23)) +} + +interface Extractor { +>Extractor : Symbol(Extractor, Decl(reverseMappedUnionInference.ts, 3, 1)) +>T : Symbol(T, Decl(reverseMappedUnionInference.ts, 5, 20)) +>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 5, 22)) + + matches: (node: unknown) => node is T; +>matches : Symbol(Extractor.matches, Decl(reverseMappedUnionInference.ts, 5, 32)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 6, 12)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 6, 12)) +>T : Symbol(T, Decl(reverseMappedUnionInference.ts, 5, 20)) + + extract: (node: T) => Result | undefined; +>extract : Symbol(Extractor.extract, Decl(reverseMappedUnionInference.ts, 6, 40)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 7, 12)) +>T : Symbol(T, Decl(reverseMappedUnionInference.ts, 5, 20)) +>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 5, 22)) +} + +declare function createExtractor(params: { +>createExtractor : Symbol(createExtractor, Decl(reverseMappedUnionInference.ts, 8, 1)) +>T : Symbol(T, Decl(reverseMappedUnionInference.ts, 10, 33)) +>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 10, 35)) +>params : Symbol(params, Decl(reverseMappedUnionInference.ts, 10, 44)) + + matcher: (node: unknown) => node is T; +>matcher : Symbol(matcher, Decl(reverseMappedUnionInference.ts, 10, 53)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 11, 12)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 11, 12)) +>T : Symbol(T, Decl(reverseMappedUnionInference.ts, 10, 33)) + + extract: (node: T) => Result; +>extract : Symbol(extract, Decl(reverseMappedUnionInference.ts, 11, 40)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 12, 12)) +>T : Symbol(T, Decl(reverseMappedUnionInference.ts, 10, 33)) +>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 10, 35)) + +}): Extractor; +>Extractor : Symbol(Extractor, Decl(reverseMappedUnionInference.ts, 3, 1)) +>T : Symbol(T, Decl(reverseMappedUnionInference.ts, 10, 33)) +>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 10, 35)) + +interface Identifier { +>Identifier : Symbol(Identifier, Decl(reverseMappedUnionInference.ts, 13, 25)) + + kind: "identifier"; +>kind : Symbol(Identifier.kind, Decl(reverseMappedUnionInference.ts, 15, 22)) + + name: string; +>name : Symbol(Identifier.name, Decl(reverseMappedUnionInference.ts, 16, 21)) +} + +declare function isIdentifier(node: unknown): node is Identifier; +>isIdentifier : Symbol(isIdentifier, Decl(reverseMappedUnionInference.ts, 18, 1)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 20, 30)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 20, 30)) +>Identifier : Symbol(Identifier, Decl(reverseMappedUnionInference.ts, 13, 25)) + +const identifierExtractor = createExtractor({ +>identifierExtractor : Symbol(identifierExtractor, Decl(reverseMappedUnionInference.ts, 22, 5)) +>createExtractor : Symbol(createExtractor, Decl(reverseMappedUnionInference.ts, 8, 1)) + + matcher: isIdentifier, +>matcher : Symbol(matcher, Decl(reverseMappedUnionInference.ts, 22, 45)) +>isIdentifier : Symbol(isIdentifier, Decl(reverseMappedUnionInference.ts, 18, 1)) + + extract: (node) => { +>extract : Symbol(extract, Decl(reverseMappedUnionInference.ts, 23, 24)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 24, 12)) + + return { + node, +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 25, 12)) + + kind: "identifier" as const, +>kind : Symbol(kind, Decl(reverseMappedUnionInference.ts, 26, 11)) +>const : Symbol(const) + + value: node.name, +>value : Symbol(value, Decl(reverseMappedUnionInference.ts, 27, 34)) +>node.name : Symbol(Identifier.name, Decl(reverseMappedUnionInference.ts, 16, 21)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 24, 12)) +>name : Symbol(Identifier.name, Decl(reverseMappedUnionInference.ts, 16, 21)) + + }; + }, +}); + +interface StringLiteral { +>StringLiteral : Symbol(StringLiteral, Decl(reverseMappedUnionInference.ts, 31, 3)) + + kind: "stringLiteral"; +>kind : Symbol(StringLiteral.kind, Decl(reverseMappedUnionInference.ts, 33, 25)) + + value: string; +>value : Symbol(StringLiteral.value, Decl(reverseMappedUnionInference.ts, 34, 24)) +} + +declare function isStringLiteral(node: unknown): node is StringLiteral; +>isStringLiteral : Symbol(isStringLiteral, Decl(reverseMappedUnionInference.ts, 36, 1)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 38, 33)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 38, 33)) +>StringLiteral : Symbol(StringLiteral, Decl(reverseMappedUnionInference.ts, 31, 3)) + +const stringExtractor = createExtractor({ +>stringExtractor : Symbol(stringExtractor, Decl(reverseMappedUnionInference.ts, 40, 5)) +>createExtractor : Symbol(createExtractor, Decl(reverseMappedUnionInference.ts, 8, 1)) + + matcher: isStringLiteral, +>matcher : Symbol(matcher, Decl(reverseMappedUnionInference.ts, 40, 41)) +>isStringLiteral : Symbol(isStringLiteral, Decl(reverseMappedUnionInference.ts, 36, 1)) + + extract: (node) => { +>extract : Symbol(extract, Decl(reverseMappedUnionInference.ts, 41, 27)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 42, 12)) + + return { + node, +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 43, 12)) + + kind: "string" as const, +>kind : Symbol(kind, Decl(reverseMappedUnionInference.ts, 44, 11)) +>const : Symbol(const) + + value: node.value, +>value : Symbol(value, Decl(reverseMappedUnionInference.ts, 45, 30)) +>node.value : Symbol(StringLiteral.value, Decl(reverseMappedUnionInference.ts, 34, 24)) +>node : Symbol(node, Decl(reverseMappedUnionInference.ts, 42, 12)) +>value : Symbol(StringLiteral.value, Decl(reverseMappedUnionInference.ts, 34, 24)) + + }; + }, +}); + +declare function unionType(parsers: { +>unionType : Symbol(unionType, Decl(reverseMappedUnionInference.ts, 49, 3)) +>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 51, 27)) +>parsers : Symbol(parsers, Decl(reverseMappedUnionInference.ts, 51, 62)) + + [K in keyof Result]: AnyExtractor; +>K : Symbol(K, Decl(reverseMappedUnionInference.ts, 52, 3)) +>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 51, 27)) +>AnyExtractor : Symbol(AnyExtractor, Decl(reverseMappedUnionInference.ts, 0, 0)) +>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 51, 27)) +>K : Symbol(K, Decl(reverseMappedUnionInference.ts, 52, 3)) + +}): AnyExtractor; +>AnyExtractor : Symbol(AnyExtractor, Decl(reverseMappedUnionInference.ts, 0, 0)) +>Result : Symbol(Result, Decl(reverseMappedUnionInference.ts, 51, 27)) + +const myUnion = unionType([identifierExtractor, stringExtractor]); +>myUnion : Symbol(myUnion, Decl(reverseMappedUnionInference.ts, 55, 5)) +>unionType : Symbol(unionType, Decl(reverseMappedUnionInference.ts, 49, 3)) +>identifierExtractor : Symbol(identifierExtractor, Decl(reverseMappedUnionInference.ts, 22, 5)) +>stringExtractor : Symbol(stringExtractor, Decl(reverseMappedUnionInference.ts, 40, 5)) + diff --git a/tests/baselines/reference/reverseMappedUnionInference.types b/tests/baselines/reference/reverseMappedUnionInference.types new file mode 100644 index 00000000000..0ce2b95184f --- /dev/null +++ b/tests/baselines/reference/reverseMappedUnionInference.types @@ -0,0 +1,148 @@ +//// [tests/cases/compiler/reverseMappedUnionInference.ts] //// + +=== reverseMappedUnionInference.ts === +interface AnyExtractor { + matches: (node: any) => boolean; +>matches : (node: any) => boolean +>node : any + + extract: (node: any) => Result | undefined; +>extract : (node: any) => Result | undefined +>node : any +} + +interface Extractor { + matches: (node: unknown) => node is T; +>matches : (node: unknown) => node is T +>node : unknown + + extract: (node: T) => Result | undefined; +>extract : (node: T) => Result | undefined +>node : T +} + +declare function createExtractor(params: { +>createExtractor : (params: { matcher: (node: unknown) => node is T; extract: (node: T) => Result; }) => Extractor +>params : { matcher: (node: unknown) => node is T; extract: (node: T) => Result; } + + matcher: (node: unknown) => node is T; +>matcher : (node: unknown) => node is T +>node : unknown + + extract: (node: T) => Result; +>extract : (node: T) => Result +>node : T + +}): Extractor; + +interface Identifier { + kind: "identifier"; +>kind : "identifier" + + name: string; +>name : string +} + +declare function isIdentifier(node: unknown): node is Identifier; +>isIdentifier : (node: unknown) => node is Identifier +>node : unknown + +const identifierExtractor = createExtractor({ +>identifierExtractor : Extractor +>createExtractor({ matcher: isIdentifier, extract: (node) => { return { node, kind: "identifier" as const, value: node.name, }; },}) : Extractor +>createExtractor : (params: { matcher: (node: unknown) => node is T; extract: (node: T) => Result; }) => Extractor +>{ matcher: isIdentifier, extract: (node) => { return { node, kind: "identifier" as const, value: node.name, }; },} : { matcher: (node: unknown) => node is Identifier; extract: (node: Identifier) => { node: Identifier; kind: "identifier"; value: string; }; } + + matcher: isIdentifier, +>matcher : (node: unknown) => node is Identifier +>isIdentifier : (node: unknown) => node is Identifier + + extract: (node) => { +>extract : (node: Identifier) => { node: Identifier; kind: "identifier"; value: string; } +>(node) => { return { node, kind: "identifier" as const, value: node.name, }; } : (node: Identifier) => { node: Identifier; kind: "identifier"; value: string; } +>node : Identifier + + return { +>{ node, kind: "identifier" as const, value: node.name, } : { node: Identifier; kind: "identifier"; value: string; } + + node, +>node : Identifier + + kind: "identifier" as const, +>kind : "identifier" +>"identifier" as const : "identifier" +>"identifier" : "identifier" + + value: node.name, +>value : string +>node.name : string +>node : Identifier +>name : string + + }; + }, +}); + +interface StringLiteral { + kind: "stringLiteral"; +>kind : "stringLiteral" + + value: string; +>value : string +} + +declare function isStringLiteral(node: unknown): node is StringLiteral; +>isStringLiteral : (node: unknown) => node is StringLiteral +>node : unknown + +const stringExtractor = createExtractor({ +>stringExtractor : Extractor +>createExtractor({ matcher: isStringLiteral, extract: (node) => { return { node, kind: "string" as const, value: node.value, }; },}) : Extractor +>createExtractor : (params: { matcher: (node: unknown) => node is T; extract: (node: T) => Result; }) => Extractor +>{ matcher: isStringLiteral, extract: (node) => { return { node, kind: "string" as const, value: node.value, }; },} : { matcher: (node: unknown) => node is StringLiteral; extract: (node: StringLiteral) => { node: StringLiteral; kind: "string"; value: string; }; } + + matcher: isStringLiteral, +>matcher : (node: unknown) => node is StringLiteral +>isStringLiteral : (node: unknown) => node is StringLiteral + + extract: (node) => { +>extract : (node: StringLiteral) => { node: StringLiteral; kind: "string"; value: string; } +>(node) => { return { node, kind: "string" as const, value: node.value, }; } : (node: StringLiteral) => { node: StringLiteral; kind: "string"; value: string; } +>node : StringLiteral + + return { +>{ node, kind: "string" as const, value: node.value, } : { node: StringLiteral; kind: "string"; value: string; } + + node, +>node : StringLiteral + + kind: "string" as const, +>kind : "string" +>"string" as const : "string" +>"string" : "string" + + value: node.value, +>value : string +>node.value : string +>node : StringLiteral +>value : string + + }; + }, +}); + +declare function unionType(parsers: { +>unionType : (parsers: { [K in keyof Result]: AnyExtractor; }) => AnyExtractor +>parsers : { [K in keyof Result]: AnyExtractor; } + + [K in keyof Result]: AnyExtractor; +}): AnyExtractor; + +const myUnion = unionType([identifierExtractor, stringExtractor]); +>myUnion : AnyExtractor<{ node: Identifier; kind: "identifier"; value: string; } | { node: StringLiteral; kind: "string"; value: string; }> +>unionType([identifierExtractor, stringExtractor]) : AnyExtractor<{ node: Identifier; kind: "identifier"; value: string; } | { node: StringLiteral; kind: "string"; value: string; }> +>unionType : (parsers: { [K in keyof Result]: AnyExtractor; }) => AnyExtractor +>[identifierExtractor, stringExtractor] : (Extractor | Extractor)[] +>identifierExtractor : Extractor +>stringExtractor : Extractor + diff --git a/tests/cases/compiler/reverseMappedUnionInference.ts b/tests/cases/compiler/reverseMappedUnionInference.ts new file mode 100644 index 00000000000..0f1ffc1c347 --- /dev/null +++ b/tests/cases/compiler/reverseMappedUnionInference.ts @@ -0,0 +1,59 @@ +// @strict: true +// @noEmit: true + +interface AnyExtractor { + matches: (node: any) => boolean; + extract: (node: any) => Result | undefined; +} + +interface Extractor { + matches: (node: unknown) => node is T; + extract: (node: T) => Result | undefined; +} + +declare function createExtractor(params: { + matcher: (node: unknown) => node is T; + extract: (node: T) => Result; +}): Extractor; + +interface Identifier { + kind: "identifier"; + name: string; +} + +declare function isIdentifier(node: unknown): node is Identifier; + +const identifierExtractor = createExtractor({ + matcher: isIdentifier, + extract: (node) => { + return { + node, + kind: "identifier" as const, + value: node.name, + }; + }, +}); + +interface StringLiteral { + kind: "stringLiteral"; + value: string; +} + +declare function isStringLiteral(node: unknown): node is StringLiteral; + +const stringExtractor = createExtractor({ + matcher: isStringLiteral, + extract: (node) => { + return { + node, + kind: "string" as const, + value: node.value, + }; + }, +}); + +declare function unionType(parsers: { + [K in keyof Result]: AnyExtractor; +}): AnyExtractor; + +const myUnion = unionType([identifierExtractor, stringExtractor]);