From 09e5824267a72c21bfa9469504f45800506bddd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 20 Jul 2025 08:51:12 +0200 Subject: [PATCH 1/4] Combine inferences from distributive conditional types --- src/compiler/checker.ts | 15 +- src/compiler/types.ts | 10 +- tests/baselines/reference/api/typescript.d.ts | 9 +- ...FunctionParametersConditionalType1.symbols | 145 ++++++++++++++++++ ...icFunctionParametersConditionalType1.types | 131 ++++++++++++++++ .../typePredicateFreshLiteralWidening.symbols | 4 +- .../typePredicateFreshLiteralWidening.types | 100 ++++++------ ...nericFunctionParametersConditionalType1.ts | 51 ++++++ 8 files changed, 402 insertions(+), 63 deletions(-) create mode 100644 tests/baselines/reference/genericFunctionParametersConditionalType1.symbols create mode 100644 tests/baselines/reference/genericFunctionParametersConditionalType1.types create mode 100644 tests/cases/compiler/genericFunctionParametersConditionalType1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c5701087ebfd4..30ec6c749bca1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26784,13 +26784,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (candidate === blockedStringType) { return; } - if (inference.priority === undefined || priority < inference.priority) { + const combinedPriority = priority | (inference.individualPriority || InferencePriority.None) + if (inference.priority === undefined || combinedPriority < inference.priority) { inference.candidates = undefined; inference.contraCandidates = undefined; inference.topLevel = true; - inference.priority = priority; + inference.priority = combinedPriority; } - if (priority === inference.priority) { + if (combinedPriority === inference.priority) { // We make contravariant inferences only if we are in a pure contravariant position, // i.e. only if we have not descended into a bivariant position. if (contravariant && !bivariant) { @@ -27156,6 +27157,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function inferToConditionalType(source: Type, target: ConditionalType) { + const info = target.root.isDistributive ? getInferenceInfoForType(getActualTypeVariable(target.checkType)) : undefined; + const saveIndividualPriority = info?.individualPriority + if (info) { + info.individualPriority = (info.individualPriority || InferencePriority.None) | InferencePriority.DistributiveConditional; + } if (source.flags & TypeFlags.Conditional) { inferFromTypes((source as ConditionalType).checkType, target.checkType); inferFromTypes((source as ConditionalType).extendsType, target.extendsType); @@ -27166,6 +27172,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)]; inferToMultipleTypesWithPriority(source, targetTypes, target.flags, contravariant ? InferencePriority.ContravariantConditional : 0); } + if (info) { + info.individualPriority = saveIndividualPriority; + } } function inferToTemplateLiteralType(source: Type, target: TemplateLiteralType) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1cfe3e04ba68d..8bbfa50a90c84 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -7102,11 +7102,12 @@ export const enum InferencePriority { ContravariantConditional = 1 << 6, // Conditional type in contravariant position ReturnType = 1 << 7, // Inference made from return type of generic function LiteralKeyof = 1 << 8, // Inference made from a string literal to a keyof T - NoConstraints = 1 << 9, // Don't infer from constraints of instantiable types - AlwaysStrict = 1 << 10, // Always use strict rules for contravariant inferences - MaxValue = 1 << 11, // Seed for inference priority tracking + DistributiveConditional = 1 << 9, + NoConstraints = 1 << 10, // Don't infer from constraints of instantiable types + AlwaysStrict = 1 << 11, // Always use strict rules for contravariant inferences + MaxValue = 1 << 12, // Seed for inference priority tracking - PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof, // These priorities imply that the resulting type should be a combination of all candidates + PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof | DistributiveConditional, // These priorities imply that the resulting type should be a combination of all candidates Circularity = -1, // Inference circularity (value less than all other priorities) } @@ -7118,6 +7119,7 @@ export interface InferenceInfo { contraCandidates: Type[] | undefined; // Candidates in contravariant positions (or undefined) inferredType?: Type; // Cache for resolved inferred type priority?: InferencePriority; // Priority of current inference set + individualPriority?: InferencePriority; topLevel: boolean; // True if all inferences are to top level occurrences isFixed: boolean; // True if inferences are fixed impliedArity?: number; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 87942520f2ab0..752b0f6725da3 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -6893,10 +6893,11 @@ declare namespace ts { ContravariantConditional = 64, ReturnType = 128, LiteralKeyof = 256, - NoConstraints = 512, - AlwaysStrict = 1024, - MaxValue = 2048, - PriorityImpliesCombination = 416, + DistributiveConditional = 512, + NoConstraints = 1024, + AlwaysStrict = 2048, + MaxValue = 4096, + PriorityImpliesCombination = 928, Circularity = -1, } interface FileExtensionInfo { diff --git a/tests/baselines/reference/genericFunctionParametersConditionalType1.symbols b/tests/baselines/reference/genericFunctionParametersConditionalType1.symbols new file mode 100644 index 0000000000000..f89e37c677e74 --- /dev/null +++ b/tests/baselines/reference/genericFunctionParametersConditionalType1.symbols @@ -0,0 +1,145 @@ +//// [tests/cases/compiler/genericFunctionParametersConditionalType1.ts] //// + +=== genericFunctionParametersConditionalType1.ts === +// https://github.com/microsoft/TypeScript/issues/62079 + +export {}; + +interface _Map { +>_Map : Symbol(_Map, Decl(genericFunctionParametersConditionalType1.ts, 2, 10)) + + foo: { a: 123 } +>foo : Symbol(_Map.foo, Decl(genericFunctionParametersConditionalType1.ts, 4, 16)) +>a : Symbol(a, Decl(genericFunctionParametersConditionalType1.ts, 5, 8)) +} + +type ModuleSubType = "bar" & { brand: true }; +>ModuleSubType : Symbol(ModuleSubType, Decl(genericFunctionParametersConditionalType1.ts, 6, 1)) +>brand : Symbol(brand, Decl(genericFunctionParametersConditionalType1.ts, 8, 30)) + +type Map = _Map & Record +>Map : Symbol(Map, Decl(genericFunctionParametersConditionalType1.ts, 8, 45)) +>_Map : Symbol(_Map, Decl(genericFunctionParametersConditionalType1.ts, 2, 10)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>ModuleSubType : Symbol(ModuleSubType, Decl(genericFunctionParametersConditionalType1.ts, 6, 1)) +>blah : Symbol(blah, Decl(genericFunctionParametersConditionalType1.ts, 10, 41)) + +type SubTypeGet< +>SubTypeGet : Symbol(SubTypeGet, Decl(genericFunctionParametersConditionalType1.ts, 10, 57)) + + SubType extends string, +>SubType : Symbol(SubType, Decl(genericFunctionParametersConditionalType1.ts, 12, 16)) + + Map extends Record, +>Map : Symbol(Map, Decl(genericFunctionParametersConditionalType1.ts, 13, 25)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>SubType : Symbol(SubType, Decl(genericFunctionParametersConditionalType1.ts, 12, 16)) + +> = SubType extends unknown +>SubType : Symbol(SubType, Decl(genericFunctionParametersConditionalType1.ts, 12, 16)) + +? { type?: SubType } & Map[SubType] +>type : Symbol(type, Decl(genericFunctionParametersConditionalType1.ts, 16, 3)) +>SubType : Symbol(SubType, Decl(genericFunctionParametersConditionalType1.ts, 12, 16)) +>Map : Symbol(Map, Decl(genericFunctionParametersConditionalType1.ts, 13, 25)) +>SubType : Symbol(SubType, Decl(genericFunctionParametersConditionalType1.ts, 12, 16)) + +: never; + +type TestParameters = Parameters<(arg: SubTypeGet) => void> +>TestParameters : Symbol(TestParameters, Decl(genericFunctionParametersConditionalType1.ts, 17, 8)) +>Parameters : Symbol(Parameters, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(genericFunctionParametersConditionalType1.ts, 19, 34)) +>ModuleSubType : Symbol(ModuleSubType, Decl(genericFunctionParametersConditionalType1.ts, 6, 1)) +>arg : Symbol(arg, Decl(genericFunctionParametersConditionalType1.ts, 19, 67)) +>SubTypeGet : Symbol(SubTypeGet, Decl(genericFunctionParametersConditionalType1.ts, 10, 57)) +>T : Symbol(T, Decl(genericFunctionParametersConditionalType1.ts, 19, 34)) +>Map : Symbol(Map, Decl(genericFunctionParametersConditionalType1.ts, 8, 45)) + +declare class Test { +>Test : Symbol(Test, Decl(genericFunctionParametersConditionalType1.ts, 19, 100)) +>T : Symbol(T, Decl(genericFunctionParametersConditionalType1.ts, 21, 19)) +>ModuleSubType : Symbol(ModuleSubType, Decl(genericFunctionParametersConditionalType1.ts, 6, 1)) + + constructor(arg: SubTypeGet); +>arg : Symbol(arg, Decl(genericFunctionParametersConditionalType1.ts, 22, 14)) +>SubTypeGet : Symbol(SubTypeGet, Decl(genericFunctionParametersConditionalType1.ts, 10, 57)) +>T : Symbol(T, Decl(genericFunctionParametersConditionalType1.ts, 21, 19)) +>Map : Symbol(Map, Decl(genericFunctionParametersConditionalType1.ts, 8, 45)) +} + +type TestConstructorParameters = ConstructorParameters; +>TestConstructorParameters : Symbol(TestConstructorParameters, Decl(genericFunctionParametersConditionalType1.ts, 23, 1)) +>ConstructorParameters : Symbol(ConstructorParameters, Decl(lib.es5.d.ts, --, --)) +>Test : Symbol(Test, Decl(genericFunctionParametersConditionalType1.ts, 19, 100)) + +declare class Animal { eat(): void; } +>Animal : Symbol(Animal, Decl(genericFunctionParametersConditionalType1.ts, 25, 68)) +>eat : Symbol(Animal.eat, Decl(genericFunctionParametersConditionalType1.ts, 27, 22)) + +declare class Cat extends Animal { meow(): void; } +>Cat : Symbol(Cat, Decl(genericFunctionParametersConditionalType1.ts, 27, 37)) +>Animal : Symbol(Animal, Decl(genericFunctionParametersConditionalType1.ts, 25, 68)) +>meow : Symbol(Cat.meow, Decl(genericFunctionParametersConditionalType1.ts, 28, 34)) + +declare class Dog extends Animal { bark(): void; } +>Dog : Symbol(Dog, Decl(genericFunctionParametersConditionalType1.ts, 28, 50)) +>Animal : Symbol(Animal, Decl(genericFunctionParametersConditionalType1.ts, 25, 68)) +>bark : Symbol(Dog.bark, Decl(genericFunctionParametersConditionalType1.ts, 29, 34)) + +type WithDistributiveConditionalDirectlyInParam = ( +>WithDistributiveConditionalDirectlyInParam : Symbol(WithDistributiveConditionalDirectlyInParam, Decl(genericFunctionParametersConditionalType1.ts, 29, 50)) +>T : Symbol(T, Decl(genericFunctionParametersConditionalType1.ts, 31, 51)) +>Cat : Symbol(Cat, Decl(genericFunctionParametersConditionalType1.ts, 27, 37)) +>Dog : Symbol(Dog, Decl(genericFunctionParametersConditionalType1.ts, 28, 50)) + + arg: T extends unknown ? T : never, +>arg : Symbol(arg, Decl(genericFunctionParametersConditionalType1.ts, 31, 72)) +>T : Symbol(T, Decl(genericFunctionParametersConditionalType1.ts, 31, 51)) +>T : Symbol(T, Decl(genericFunctionParametersConditionalType1.ts, 31, 51)) + +) => void; + +type Result1 = Parameters; +>Result1 : Symbol(Result1, Decl(genericFunctionParametersConditionalType1.ts, 33, 10)) +>Parameters : Symbol(Parameters, Decl(lib.es5.d.ts, --, --)) +>WithDistributiveConditionalDirectlyInParam : Symbol(WithDistributiveConditionalDirectlyInParam, Decl(genericFunctionParametersConditionalType1.ts, 29, 50)) + +type WithDistributiveConditionalNested = ( +>WithDistributiveConditionalNested : Symbol(WithDistributiveConditionalNested, Decl(genericFunctionParametersConditionalType1.ts, 35, 70)) +>T : Symbol(T, Decl(genericFunctionParametersConditionalType1.ts, 37, 42)) +>Cat : Symbol(Cat, Decl(genericFunctionParametersConditionalType1.ts, 27, 37)) +>Dog : Symbol(Dog, Decl(genericFunctionParametersConditionalType1.ts, 28, 50)) + + arg: T extends unknown ? { animal: T } : never, +>arg : Symbol(arg, Decl(genericFunctionParametersConditionalType1.ts, 37, 63)) +>T : Symbol(T, Decl(genericFunctionParametersConditionalType1.ts, 37, 42)) +>animal : Symbol(animal, Decl(genericFunctionParametersConditionalType1.ts, 38, 28)) +>T : Symbol(T, Decl(genericFunctionParametersConditionalType1.ts, 37, 42)) + +) => void; + +type Result2 = Parameters; +>Result2 : Symbol(Result2, Decl(genericFunctionParametersConditionalType1.ts, 39, 10)) +>Parameters : Symbol(Parameters, Decl(lib.es5.d.ts, --, --)) +>WithDistributiveConditionalNested : Symbol(WithDistributiveConditionalNested, Decl(genericFunctionParametersConditionalType1.ts, 35, 70)) + +type WithNonDistributiveConditionalNested = ( +>WithNonDistributiveConditionalNested : Symbol(WithNonDistributiveConditionalNested, Decl(genericFunctionParametersConditionalType1.ts, 41, 61)) +>T : Symbol(T, Decl(genericFunctionParametersConditionalType1.ts, 43, 45)) +>Cat : Symbol(Cat, Decl(genericFunctionParametersConditionalType1.ts, 27, 37)) +>Dog : Symbol(Dog, Decl(genericFunctionParametersConditionalType1.ts, 28, 50)) + + arg: [T] extends [unknown] ? { animal: T } : never, +>arg : Symbol(arg, Decl(genericFunctionParametersConditionalType1.ts, 43, 66)) +>T : Symbol(T, Decl(genericFunctionParametersConditionalType1.ts, 43, 45)) +>animal : Symbol(animal, Decl(genericFunctionParametersConditionalType1.ts, 44, 32)) +>T : Symbol(T, Decl(genericFunctionParametersConditionalType1.ts, 43, 45)) + +) => void; + +type Result3 = Parameters; +>Result3 : Symbol(Result3, Decl(genericFunctionParametersConditionalType1.ts, 45, 10)) +>Parameters : Symbol(Parameters, Decl(lib.es5.d.ts, --, --)) +>WithNonDistributiveConditionalNested : Symbol(WithNonDistributiveConditionalNested, Decl(genericFunctionParametersConditionalType1.ts, 41, 61)) + diff --git a/tests/baselines/reference/genericFunctionParametersConditionalType1.types b/tests/baselines/reference/genericFunctionParametersConditionalType1.types new file mode 100644 index 0000000000000..35467c015c815 --- /dev/null +++ b/tests/baselines/reference/genericFunctionParametersConditionalType1.types @@ -0,0 +1,131 @@ +//// [tests/cases/compiler/genericFunctionParametersConditionalType1.ts] //// + +=== genericFunctionParametersConditionalType1.ts === +// https://github.com/microsoft/TypeScript/issues/62079 + +export {}; + +interface _Map { + foo: { a: 123 } +>foo : { a: 123; } +> : ^^^^^ ^^^ +>a : 123 +> : ^^^ +} + +type ModuleSubType = "bar" & { brand: true }; +>ModuleSubType : ModuleSubType +> : ^^^^^^^^^^^^^ +>brand : true +> : ^^^^ +>true : true +> : ^^^^ + +type Map = _Map & Record +>Map : Map +> : ^^^ +>blah : string +> : ^^^^^^ + +type SubTypeGet< +>SubTypeGet : SubTypeGet +> : ^^^^^^^^^^^^^^^^^^^^^^^^ + + SubType extends string, + Map extends Record, +> = SubType extends unknown +? { type?: SubType } & Map[SubType] +>type : SubType | undefined +> : ^^^^^^^^^^^^^^^^^^^ + +: never; + +type TestParameters = Parameters<(arg: SubTypeGet) => void> +>TestParameters : [arg: { type?: ModuleSubType | undefined; } | ({ type?: "foo" | undefined; } & { a: 123; })] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^ +>arg : SubTypeGet +> : ^^^^^^^^^^^^^^^^^^ + +declare class Test { +>Test : Test +> : ^^^^^^^ + + constructor(arg: SubTypeGet); +>arg : SubTypeGet +> : ^^^^^^^^^^^^^^^^^^ +} + +type TestConstructorParameters = ConstructorParameters; +>TestConstructorParameters : [arg: { type?: ModuleSubType | undefined; } | ({ type?: "foo" | undefined; } & { a: 123; })] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^ +>Test : typeof Test +> : ^^^^^^^^^^^ + +declare class Animal { eat(): void; } +>Animal : Animal +> : ^^^^^^ +>eat : () => void +> : ^^^^^^ + +declare class Cat extends Animal { meow(): void; } +>Cat : Cat +> : ^^^ +>Animal : Animal +> : ^^^^^^ +>meow : () => void +> : ^^^^^^ + +declare class Dog extends Animal { bark(): void; } +>Dog : Dog +> : ^^^ +>Animal : Animal +> : ^^^^^^ +>bark : () => void +> : ^^^^^^ + +type WithDistributiveConditionalDirectlyInParam = ( +>WithDistributiveConditionalDirectlyInParam : WithDistributiveConditionalDirectlyInParam +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + arg: T extends unknown ? T : never, +>arg : T extends unknown ? T : never +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +) => void; + +type Result1 = Parameters; +>Result1 : [arg: Cat | Dog] +> : ^^^^^^^^^^^^^^^^ + +type WithDistributiveConditionalNested = ( +>WithDistributiveConditionalNested : WithDistributiveConditionalNested +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + arg: T extends unknown ? { animal: T } : never, +>arg : T extends unknown ? { animal: T; } : never +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^ +>animal : T +> : ^ + +) => void; + +type Result2 = Parameters; +>Result2 : [arg: { animal: Cat; } | { animal: Dog; }] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +type WithNonDistributiveConditionalNested = ( +>WithNonDistributiveConditionalNested : WithNonDistributiveConditionalNested +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + arg: [T] extends [unknown] ? { animal: T } : never, +>arg : [T] extends [unknown] ? { animal: T; } : never +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^ +>animal : T +> : ^ + +) => void; + +type Result3 = Parameters; +>Result3 : [arg: { animal: Cat | Dog; }] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + diff --git a/tests/baselines/reference/typePredicateFreshLiteralWidening.symbols b/tests/baselines/reference/typePredicateFreshLiteralWidening.symbols index c4464e5d4bd39..e399cf4eb6388 100644 --- a/tests/baselines/reference/typePredicateFreshLiteralWidening.symbols +++ b/tests/baselines/reference/typePredicateFreshLiteralWidening.symbols @@ -88,9 +88,9 @@ const values1 = [item1, item2, item3].map(item => item.value); >item3 : Symbol(item3, Decl(typePredicateFreshLiteralWidening.ts, 19, 5)) >map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) >item : Symbol(item, Decl(typePredicateFreshLiteralWidening.ts, 24, 42)) ->item.value : Symbol(value, Decl(typePredicateFreshLiteralWidening.ts, 17, 33), Decl(typePredicateFreshLiteralWidening.ts, 18, 33), Decl(typePredicateFreshLiteralWidening.ts, 19, 33)) +>item.value : Symbol(value, Decl(typePredicateFreshLiteralWidening.ts, 15, 13)) >item : Symbol(item, Decl(typePredicateFreshLiteralWidening.ts, 24, 42)) ->value : Symbol(value, Decl(typePredicateFreshLiteralWidening.ts, 17, 33), Decl(typePredicateFreshLiteralWidening.ts, 18, 33), Decl(typePredicateFreshLiteralWidening.ts, 19, 33)) +>value : Symbol(value, Decl(typePredicateFreshLiteralWidening.ts, 15, 13)) const filteredValues1 = values1.filter(isNotNull); >filteredValues1 : Symbol(filteredValues1, Decl(typePredicateFreshLiteralWidening.ts, 25, 5)) diff --git a/tests/baselines/reference/typePredicateFreshLiteralWidening.types b/tests/baselines/reference/typePredicateFreshLiteralWidening.types index 83f8935c32f38..785365ac329b5 100644 --- a/tests/baselines/reference/typePredicateFreshLiteralWidening.types +++ b/tests/baselines/reference/typePredicateFreshLiteralWidening.types @@ -51,34 +51,34 @@ type Item = { value: string | null }; > : ^^^^^^^^^^^^^ const item1 = satisfies()({ value: "1" }); ->item1 : { value: "1"; } -> : ^^^^^^^^^^^^^^^ ->satisfies()({ value: "1" }) : { value: "1"; } -> : ^^^^^^^^^^^^^^^ +>item1 : { value: Narrow; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>satisfies()({ value: "1" }) : { value: Narrow; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >satisfies() : (narrow: Narrow) => Narrow > : ^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >satisfies : () => (narrow: Narrow) => Narrow > : ^ ^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^ ->{ value: "1" } : { value: "1"; } -> : ^^^^^^^^^^^^^^^ ->value : "1" -> : ^^^ +>{ value: "1" } : { value: string; } +> : ^^^^^^^^^^^^^^^^^^ +>value : string +> : ^^^^^^ >"1" : "1" > : ^^^ const item2 = satisfies()({ value: "2" }); ->item2 : { value: "2"; } -> : ^^^^^^^^^^^^^^^ ->satisfies()({ value: "2" }) : { value: "2"; } -> : ^^^^^^^^^^^^^^^ +>item2 : { value: Narrow; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>satisfies()({ value: "2" }) : { value: Narrow; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >satisfies() : (narrow: Narrow) => Narrow > : ^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >satisfies : () => (narrow: Narrow) => Narrow > : ^ ^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^ ->{ value: "2" } : { value: "2"; } -> : ^^^^^^^^^^^^^^^ ->value : "2" -> : ^^^ +>{ value: "2" } : { value: string; } +> : ^^^^^^^^^^^^^^^^^^ +>value : string +> : ^^^^^^ >"2" : "2" > : ^^^ @@ -121,44 +121,44 @@ const filteredValues2 = values2.filter(isNotNull); > : ^ ^^ ^^ ^^^^^ const values1 = [item1, item2, item3].map(item => item.value); ->values1 : ("1" | "2" | null)[] -> : ^^^^^^^^^^^^^^^^^^^^ ->[item1, item2, item3].map(item => item.value) : ("1" | "2" | null)[] -> : ^^^^^^^^^^^^^^^^^^^^ ->[item1, item2, item3].map : (callbackfn: (value: { value: "1"; } | { value: "2"; } | { value: null; }, index: number, array: ({ value: "1"; } | { value: "2"; } | { value: null; })[]) => U, thisArg?: any) => U[] -> : ^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^ ->[item1, item2, item3] : ({ value: "1"; } | { value: "2"; } | { value: null; })[] -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->item1 : { value: "1"; } -> : ^^^^^^^^^^^^^^^ ->item2 : { value: "2"; } -> : ^^^^^^^^^^^^^^^ +>values1 : Narrow[] +> : ^^^^^^^^^^^^^^^^^^^^^^^ +>[item1, item2, item3].map(item => item.value) : Narrow[] +> : ^^^^^^^^^^^^^^^^^^^^^^^ +>[item1, item2, item3].map : (callbackfn: (value: { value: Narrow; }, index: number, array: { value: Narrow; }[]) => U, thisArg?: any) => U[] +> : ^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^ +>[item1, item2, item3] : { value: Narrow; }[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>item1 : { value: Narrow; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>item2 : { value: Narrow; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >item3 : { value: null; } > : ^^^^^^^^^^^^^^^^ ->map : (callbackfn: (value: { value: "1"; } | { value: "2"; } | { value: null; }, index: number, array: ({ value: "1"; } | { value: "2"; } | { value: null; })[]) => U, thisArg?: any) => U[] -> : ^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^ ->item => item.value : (item: { value: "1"; } | { value: "2"; } | { value: null; }) => "1" | "2" | null -> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->item : { value: "1"; } | { value: "2"; } | { value: null; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->item.value : "1" | "2" | null -> : ^^^^^^^^^^^^^^^^ ->item : { value: "1"; } | { value: "2"; } | { value: null; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->value : "1" | "2" | null -> : ^^^^^^^^^^^^^^^^ +>map : (callbackfn: (value: { value: Narrow; }, index: number, array: { value: Narrow; }[]) => U, thisArg?: any) => U[] +> : ^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^ +>item => item.value : (item: { value: Narrow; }) => Narrow +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>item : { value: Narrow; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>item.value : Narrow +> : ^^^^^^^^^^^^^^^^^^^^^ +>item : { value: Narrow; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>value : Narrow +> : ^^^^^^^^^^^^^^^^^^^^^ const filteredValues1 = values1.filter(isNotNull); ->filteredValues1 : ("1" | "2")[] -> : ^^^^^^^^^^^^^ ->values1.filter(isNotNull) : ("1" | "2")[] -> : ^^^^^^^^^^^^^ ->values1.filter : { (predicate: (value: "1" | "2" | null, index: number, array: ("1" | "2" | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: "1" | "2" | null, index: number, array: ("1" | "2" | null)[]) => unknown, thisArg?: any): ("1" | "2" | null)[]; } -> : ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ ->values1 : ("1" | "2" | null)[] -> : ^^^^^^^^^^^^^^^^^^^^ ->filter : { (predicate: (value: "1" | "2" | null, index: number, array: ("1" | "2" | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: "1" | "2" | null, index: number, array: ("1" | "2" | null)[]) => unknown, thisArg?: any): ("1" | "2" | null)[]; } -> : ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +>filteredValues1 : string[] +> : ^^^^^^^^ +>values1.filter(isNotNull) : string[] +> : ^^^^^^^^ +>values1.filter : { >(predicate: (value: Narrow, index: number, array: Narrow[]) => value is S, thisArg?: any): S[]; (predicate: (value: Narrow, index: number, array: Narrow[]) => unknown, thisArg?: any): Narrow[]; } +> : ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>values1 : Narrow[] +> : ^^^^^^^^^^^^^^^^^^^^^^^ +>filter : { >(predicate: (value: Narrow, index: number, array: Narrow[]) => value is S, thisArg?: any): S[]; (predicate: (value: Narrow, index: number, array: Narrow[]) => unknown, thisArg?: any): Narrow[]; } +> : ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >isNotNull : (value: T | null) => value is T > : ^ ^^ ^^ ^^^^^ diff --git a/tests/cases/compiler/genericFunctionParametersConditionalType1.ts b/tests/cases/compiler/genericFunctionParametersConditionalType1.ts new file mode 100644 index 0000000000000..7bcce5c36b83a --- /dev/null +++ b/tests/cases/compiler/genericFunctionParametersConditionalType1.ts @@ -0,0 +1,51 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/62079 + +export {}; + +interface _Map { + foo: { a: 123 } +} + +type ModuleSubType = "bar" & { brand: true }; + +type Map = _Map & Record + +type SubTypeGet< + SubType extends string, + Map extends Record, +> = SubType extends unknown +? { type?: SubType } & Map[SubType] +: never; + +type TestParameters = Parameters<(arg: SubTypeGet) => void> + +declare class Test { + constructor(arg: SubTypeGet); +} + +type TestConstructorParameters = ConstructorParameters; + +declare class Animal { eat(): void; } +declare class Cat extends Animal { meow(): void; } +declare class Dog extends Animal { bark(): void; } + +type WithDistributiveConditionalDirectlyInParam = ( + arg: T extends unknown ? T : never, +) => void; + +type Result1 = Parameters; + +type WithDistributiveConditionalNested = ( + arg: T extends unknown ? { animal: T } : never, +) => void; + +type Result2 = Parameters; + +type WithNonDistributiveConditionalNested = ( + arg: [T] extends [unknown] ? { animal: T } : never, +) => void; + +type Result3 = Parameters; From cf7ea8af25a768158c1f68856c6cd8c865cf7b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 20 Jul 2025 12:04:50 +0200 Subject: [PATCH 2/4] keep those inferences when "external" candidates are found --- src/compiler/checker.ts | 10 +- .../typePredicateFreshLiteralWidening.symbols | 4 +- .../typePredicateFreshLiteralWidening.types | 100 +++++++++--------- 3 files changed, 59 insertions(+), 55 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 30ec6c749bca1..2ada5f6eed018 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26784,14 +26784,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (candidate === blockedStringType) { return; } - const combinedPriority = priority | (inference.individualPriority || InferencePriority.None) - if (inference.priority === undefined || combinedPriority < inference.priority) { + const combinedPriority = priority | (inference.individualPriority || InferencePriority.None); + if (inference.priority === undefined || priority < (inference.priority & ~InferencePriority.DistributiveConditional)) { inference.candidates = undefined; inference.contraCandidates = undefined; inference.topLevel = true; inference.priority = combinedPriority; } - if (combinedPriority === inference.priority) { + if (priority === (inference.priority & ~InferencePriority.DistributiveConditional)) { + if (inference.priority !== combinedPriority) { + inference.priority = combinedPriority; + clearCachedInferences(inferences); + } // We make contravariant inferences only if we are in a pure contravariant position, // i.e. only if we have not descended into a bivariant position. if (contravariant && !bivariant) { diff --git a/tests/baselines/reference/typePredicateFreshLiteralWidening.symbols b/tests/baselines/reference/typePredicateFreshLiteralWidening.symbols index e399cf4eb6388..c4464e5d4bd39 100644 --- a/tests/baselines/reference/typePredicateFreshLiteralWidening.symbols +++ b/tests/baselines/reference/typePredicateFreshLiteralWidening.symbols @@ -88,9 +88,9 @@ const values1 = [item1, item2, item3].map(item => item.value); >item3 : Symbol(item3, Decl(typePredicateFreshLiteralWidening.ts, 19, 5)) >map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) >item : Symbol(item, Decl(typePredicateFreshLiteralWidening.ts, 24, 42)) ->item.value : Symbol(value, Decl(typePredicateFreshLiteralWidening.ts, 15, 13)) +>item.value : Symbol(value, Decl(typePredicateFreshLiteralWidening.ts, 17, 33), Decl(typePredicateFreshLiteralWidening.ts, 18, 33), Decl(typePredicateFreshLiteralWidening.ts, 19, 33)) >item : Symbol(item, Decl(typePredicateFreshLiteralWidening.ts, 24, 42)) ->value : Symbol(value, Decl(typePredicateFreshLiteralWidening.ts, 15, 13)) +>value : Symbol(value, Decl(typePredicateFreshLiteralWidening.ts, 17, 33), Decl(typePredicateFreshLiteralWidening.ts, 18, 33), Decl(typePredicateFreshLiteralWidening.ts, 19, 33)) const filteredValues1 = values1.filter(isNotNull); >filteredValues1 : Symbol(filteredValues1, Decl(typePredicateFreshLiteralWidening.ts, 25, 5)) diff --git a/tests/baselines/reference/typePredicateFreshLiteralWidening.types b/tests/baselines/reference/typePredicateFreshLiteralWidening.types index 785365ac329b5..83f8935c32f38 100644 --- a/tests/baselines/reference/typePredicateFreshLiteralWidening.types +++ b/tests/baselines/reference/typePredicateFreshLiteralWidening.types @@ -51,34 +51,34 @@ type Item = { value: string | null }; > : ^^^^^^^^^^^^^ const item1 = satisfies()({ value: "1" }); ->item1 : { value: Narrow; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->satisfies()({ value: "1" }) : { value: Narrow; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>item1 : { value: "1"; } +> : ^^^^^^^^^^^^^^^ +>satisfies()({ value: "1" }) : { value: "1"; } +> : ^^^^^^^^^^^^^^^ >satisfies() : (narrow: Narrow) => Narrow > : ^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >satisfies : () => (narrow: Narrow) => Narrow > : ^ ^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^ ->{ value: "1" } : { value: string; } -> : ^^^^^^^^^^^^^^^^^^ ->value : string -> : ^^^^^^ +>{ value: "1" } : { value: "1"; } +> : ^^^^^^^^^^^^^^^ +>value : "1" +> : ^^^ >"1" : "1" > : ^^^ const item2 = satisfies()({ value: "2" }); ->item2 : { value: Narrow; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->satisfies()({ value: "2" }) : { value: Narrow; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>item2 : { value: "2"; } +> : ^^^^^^^^^^^^^^^ +>satisfies()({ value: "2" }) : { value: "2"; } +> : ^^^^^^^^^^^^^^^ >satisfies() : (narrow: Narrow) => Narrow > : ^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ >satisfies : () => (narrow: Narrow) => Narrow > : ^ ^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^ ->{ value: "2" } : { value: string; } -> : ^^^^^^^^^^^^^^^^^^ ->value : string -> : ^^^^^^ +>{ value: "2" } : { value: "2"; } +> : ^^^^^^^^^^^^^^^ +>value : "2" +> : ^^^ >"2" : "2" > : ^^^ @@ -121,44 +121,44 @@ const filteredValues2 = values2.filter(isNotNull); > : ^ ^^ ^^ ^^^^^ const values1 = [item1, item2, item3].map(item => item.value); ->values1 : Narrow[] -> : ^^^^^^^^^^^^^^^^^^^^^^^ ->[item1, item2, item3].map(item => item.value) : Narrow[] -> : ^^^^^^^^^^^^^^^^^^^^^^^ ->[item1, item2, item3].map : (callbackfn: (value: { value: Narrow; }, index: number, array: { value: Narrow; }[]) => U, thisArg?: any) => U[] -> : ^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^ ->[item1, item2, item3] : { value: Narrow; }[] -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->item1 : { value: Narrow; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->item2 : { value: Narrow; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>values1 : ("1" | "2" | null)[] +> : ^^^^^^^^^^^^^^^^^^^^ +>[item1, item2, item3].map(item => item.value) : ("1" | "2" | null)[] +> : ^^^^^^^^^^^^^^^^^^^^ +>[item1, item2, item3].map : (callbackfn: (value: { value: "1"; } | { value: "2"; } | { value: null; }, index: number, array: ({ value: "1"; } | { value: "2"; } | { value: null; })[]) => U, thisArg?: any) => U[] +> : ^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^ +>[item1, item2, item3] : ({ value: "1"; } | { value: "2"; } | { value: null; })[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>item1 : { value: "1"; } +> : ^^^^^^^^^^^^^^^ +>item2 : { value: "2"; } +> : ^^^^^^^^^^^^^^^ >item3 : { value: null; } > : ^^^^^^^^^^^^^^^^ ->map : (callbackfn: (value: { value: Narrow; }, index: number, array: { value: Narrow; }[]) => U, thisArg?: any) => U[] -> : ^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^ ->item => item.value : (item: { value: Narrow; }) => Narrow -> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->item : { value: Narrow; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->item.value : Narrow -> : ^^^^^^^^^^^^^^^^^^^^^ ->item : { value: Narrow; } -> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->value : Narrow -> : ^^^^^^^^^^^^^^^^^^^^^ +>map : (callbackfn: (value: { value: "1"; } | { value: "2"; } | { value: null; }, index: number, array: ({ value: "1"; } | { value: "2"; } | { value: null; })[]) => U, thisArg?: any) => U[] +> : ^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^ +>item => item.value : (item: { value: "1"; } | { value: "2"; } | { value: null; }) => "1" | "2" | null +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>item : { value: "1"; } | { value: "2"; } | { value: null; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>item.value : "1" | "2" | null +> : ^^^^^^^^^^^^^^^^ +>item : { value: "1"; } | { value: "2"; } | { value: null; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>value : "1" | "2" | null +> : ^^^^^^^^^^^^^^^^ const filteredValues1 = values1.filter(isNotNull); ->filteredValues1 : string[] -> : ^^^^^^^^ ->values1.filter(isNotNull) : string[] -> : ^^^^^^^^ ->values1.filter : { >(predicate: (value: Narrow, index: number, array: Narrow[]) => value is S, thisArg?: any): S[]; (predicate: (value: Narrow, index: number, array: Narrow[]) => unknown, thisArg?: any): Narrow[]; } -> : ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ->values1 : Narrow[] -> : ^^^^^^^^^^^^^^^^^^^^^^^ ->filter : { >(predicate: (value: Narrow, index: number, array: Narrow[]) => value is S, thisArg?: any): S[]; (predicate: (value: Narrow, index: number, array: Narrow[]) => unknown, thisArg?: any): Narrow[]; } -> : ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>filteredValues1 : ("1" | "2")[] +> : ^^^^^^^^^^^^^ +>values1.filter(isNotNull) : ("1" | "2")[] +> : ^^^^^^^^^^^^^ +>values1.filter : { (predicate: (value: "1" | "2" | null, index: number, array: ("1" | "2" | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: "1" | "2" | null, index: number, array: ("1" | "2" | null)[]) => unknown, thisArg?: any): ("1" | "2" | null)[]; } +> : ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +>values1 : ("1" | "2" | null)[] +> : ^^^^^^^^^^^^^^^^^^^^ +>filter : { (predicate: (value: "1" | "2" | null, index: number, array: ("1" | "2" | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: "1" | "2" | null, index: number, array: ("1" | "2" | null)[]) => unknown, thisArg?: any): ("1" | "2" | null)[]; } +> : ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^ >isNotNull : (value: T | null) => value is T > : ^ ^^ ^^ ^^^^^ From ed80c21e47b6366db914c88f1e99c429837a4a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 20 Jul 2025 12:51:37 +0200 Subject: [PATCH 3/4] fixup whitespace --- src/compiler/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8bbfa50a90c84..5b0607f38b7be 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -7103,7 +7103,7 @@ export const enum InferencePriority { ReturnType = 1 << 7, // Inference made from return type of generic function LiteralKeyof = 1 << 8, // Inference made from a string literal to a keyof T DistributiveConditional = 1 << 9, - NoConstraints = 1 << 10, // Don't infer from constraints of instantiable types + NoConstraints = 1 << 10, // Don't infer from constraints of instantiable types AlwaysStrict = 1 << 11, // Always use strict rules for contravariant inferences MaxValue = 1 << 12, // Seed for inference priority tracking From 3a10dd21a43b2da8fda69d03b3c25eb90f17380d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 20 Jul 2025 14:35:30 +0200 Subject: [PATCH 4/4] add extra test case --- src/compiler/checker.ts | 2 +- ...nferToDistributiveConditionalType1.symbols | 38 ++++++++++++++ .../inferToDistributiveConditionalType1.types | 51 +++++++++++++++++++ .../inferToDistributiveConditionalType1.ts | 10 ++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/inferToDistributiveConditionalType1.symbols create mode 100644 tests/baselines/reference/inferToDistributiveConditionalType1.types create mode 100644 tests/cases/compiler/inferToDistributiveConditionalType1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2ada5f6eed018..f8a6a752a6f1e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27162,7 +27162,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function inferToConditionalType(source: Type, target: ConditionalType) { const info = target.root.isDistributive ? getInferenceInfoForType(getActualTypeVariable(target.checkType)) : undefined; - const saveIndividualPriority = info?.individualPriority + const saveIndividualPriority = info?.individualPriority; if (info) { info.individualPriority = (info.individualPriority || InferencePriority.None) | InferencePriority.DistributiveConditional; } diff --git a/tests/baselines/reference/inferToDistributiveConditionalType1.symbols b/tests/baselines/reference/inferToDistributiveConditionalType1.symbols new file mode 100644 index 0000000000000..4f000ccc00536 --- /dev/null +++ b/tests/baselines/reference/inferToDistributiveConditionalType1.symbols @@ -0,0 +1,38 @@ +//// [tests/cases/compiler/inferToDistributiveConditionalType1.ts] //// + +=== inferToDistributiveConditionalType1.ts === +declare class Animal { eat(): void; } +>Animal : Symbol(Animal, Decl(inferToDistributiveConditionalType1.ts, 0, 0)) +>eat : Symbol(Animal.eat, Decl(inferToDistributiveConditionalType1.ts, 0, 22)) + +declare class Cat extends Animal { meow(): void; } +>Cat : Symbol(Cat, Decl(inferToDistributiveConditionalType1.ts, 0, 37)) +>Animal : Symbol(Animal, Decl(inferToDistributiveConditionalType1.ts, 0, 0)) +>meow : Symbol(Cat.meow, Decl(inferToDistributiveConditionalType1.ts, 1, 34)) + +declare class Dog extends Animal { bark(): void; } +>Dog : Symbol(Dog, Decl(inferToDistributiveConditionalType1.ts, 1, 50)) +>Animal : Symbol(Animal, Decl(inferToDistributiveConditionalType1.ts, 0, 0)) +>bark : Symbol(Dog.bark, Decl(inferToDistributiveConditionalType1.ts, 2, 34)) + +declare function test1(a: T extends unknown ? { prop: T } : never): T; +>test1 : Symbol(test1, Decl(inferToDistributiveConditionalType1.ts, 2, 50)) +>T : Symbol(T, Decl(inferToDistributiveConditionalType1.ts, 4, 23)) +>a : Symbol(a, Decl(inferToDistributiveConditionalType1.ts, 4, 26)) +>T : Symbol(T, Decl(inferToDistributiveConditionalType1.ts, 4, 23)) +>prop : Symbol(prop, Decl(inferToDistributiveConditionalType1.ts, 4, 50)) +>T : Symbol(T, Decl(inferToDistributiveConditionalType1.ts, 4, 23)) +>T : Symbol(T, Decl(inferToDistributiveConditionalType1.ts, 4, 23)) + +declare const arg1: { prop: Dog } | { prop: Cat }; +>arg1 : Symbol(arg1, Decl(inferToDistributiveConditionalType1.ts, 5, 13)) +>prop : Symbol(prop, Decl(inferToDistributiveConditionalType1.ts, 5, 21)) +>Dog : Symbol(Dog, Decl(inferToDistributiveConditionalType1.ts, 1, 50)) +>prop : Symbol(prop, Decl(inferToDistributiveConditionalType1.ts, 5, 37)) +>Cat : Symbol(Cat, Decl(inferToDistributiveConditionalType1.ts, 0, 37)) + +const result1 = test1(arg1); +>result1 : Symbol(result1, Decl(inferToDistributiveConditionalType1.ts, 6, 5)) +>test1 : Symbol(test1, Decl(inferToDistributiveConditionalType1.ts, 2, 50)) +>arg1 : Symbol(arg1, Decl(inferToDistributiveConditionalType1.ts, 5, 13)) + diff --git a/tests/baselines/reference/inferToDistributiveConditionalType1.types b/tests/baselines/reference/inferToDistributiveConditionalType1.types new file mode 100644 index 0000000000000..83309450eb075 --- /dev/null +++ b/tests/baselines/reference/inferToDistributiveConditionalType1.types @@ -0,0 +1,51 @@ +//// [tests/cases/compiler/inferToDistributiveConditionalType1.ts] //// + +=== inferToDistributiveConditionalType1.ts === +declare class Animal { eat(): void; } +>Animal : Animal +> : ^^^^^^ +>eat : () => void +> : ^^^^^^ + +declare class Cat extends Animal { meow(): void; } +>Cat : Cat +> : ^^^ +>Animal : Animal +> : ^^^^^^ +>meow : () => void +> : ^^^^^^ + +declare class Dog extends Animal { bark(): void; } +>Dog : Dog +> : ^^^ +>Animal : Animal +> : ^^^^^^ +>bark : () => void +> : ^^^^^^ + +declare function test1(a: T extends unknown ? { prop: T } : never): T; +>test1 : (a: T extends unknown ? { prop: T; } : never) => T +> : ^ ^^ ^^ ^^^^^ +>a : T extends unknown ? { prop: T; } : never +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^ +>prop : T +> : ^ + +declare const arg1: { prop: Dog } | { prop: Cat }; +>arg1 : { prop: Dog; } | { prop: Cat; } +> : ^^^^^^^^ ^^^^^^^^^^^^^^ ^^^ +>prop : Dog +> : ^^^ +>prop : Cat +> : ^^^ + +const result1 = test1(arg1); +>result1 : Cat | Dog +> : ^^^^^^^^^ +>test1(arg1) : Cat | Dog +> : ^^^^^^^^^ +>test1 : (a: T extends unknown ? { prop: T; } : never) => T +> : ^ ^^ ^^ ^^^^^ +>arg1 : { prop: Dog; } | { prop: Cat; } +> : ^^^^^^^^ ^^^^^^^^^^^^^^ ^^^ + diff --git a/tests/cases/compiler/inferToDistributiveConditionalType1.ts b/tests/cases/compiler/inferToDistributiveConditionalType1.ts new file mode 100644 index 0000000000000..fb93b9a0252fe --- /dev/null +++ b/tests/cases/compiler/inferToDistributiveConditionalType1.ts @@ -0,0 +1,10 @@ +// @strict: true +// @noEmit: true + +declare class Animal { eat(): void; } +declare class Cat extends Animal { meow(): void; } +declare class Dog extends Animal { bark(): void; } + +declare function test1(a: T extends unknown ? { prop: T } : never): T; +declare const arg1: { prop: Dog } | { prop: Cat }; +const result1 = test1(arg1);