Skip to content

Combine inferences from distributive conditional types #62093

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
19 changes: 16 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26784,13 +26784,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (candidate === blockedStringType) {
return;
}
if (inference.priority === undefined || priority < inference.priority) {
const combinedPriority = priority | (inference.individualPriority || InferencePriority.None);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't quite like how this turned out - I probably should move this thing to a separate InferenceInfo property (similar to topLevel) instead of shoving it into .priority.

That said, those changes would be cosmetic and wouldn't really change the idea behind this fix. In the meantime, I'd love to see what user/top tests have to say about this (cc @jakebailey )

if (inference.priority === undefined || priority < (inference.priority & ~InferencePriority.DistributiveConditional)) {
inference.candidates = undefined;
inference.contraCandidates = undefined;
inference.topLevel = true;
inference.priority = priority;
inference.priority = combinedPriority;
}
if (priority === 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) {
Expand Down Expand Up @@ -27156,6 +27161,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);
Expand All @@ -27166,6 +27176,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) {
Expand Down
10 changes: 6 additions & 4 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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;
Expand Down
9 changes: 5 additions & 4 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ModuleSubType, { blah: string }>
>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<SubType, unknown>,
>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<<T extends "foo" | ModuleSubType>(arg: SubTypeGet<T, Map>) => 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<T extends "foo" | ModuleSubType> {
>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<T, Map>);
>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<typeof Test>;
>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 = <T extends Cat | Dog>(
>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<WithDistributiveConditionalDirectlyInParam>;
>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 = <T extends Cat | Dog>(
>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<WithDistributiveConditionalNested>;
>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 = <T extends Cat | Dog>(
>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<WithNonDistributiveConditionalNested>;
>Result3 : Symbol(Result3, Decl(genericFunctionParametersConditionalType1.ts, 45, 10))
>Parameters : Symbol(Parameters, Decl(lib.es5.d.ts, --, --))
>WithNonDistributiveConditionalNested : Symbol(WithNonDistributiveConditionalNested, Decl(genericFunctionParametersConditionalType1.ts, 41, 61))

Original file line number Diff line number Diff line change
@@ -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<ModuleSubType, { blah: string }>
>Map : Map
> : ^^^
>blah : string
> : ^^^^^^

type SubTypeGet<
>SubTypeGet : SubTypeGet<SubType, Map>
> : ^^^^^^^^^^^^^^^^^^^^^^^^

SubType extends string,
Map extends Record<SubType, unknown>,
> = SubType extends unknown
? { type?: SubType } & Map[SubType]
>type : SubType | undefined
> : ^^^^^^^^^^^^^^^^^^^

: never;

type TestParameters = Parameters<<T extends "foo" | ModuleSubType>(arg: SubTypeGet<T, Map>) => void>
>TestParameters : [arg: { type?: ModuleSubType | undefined; } | ({ type?: "foo" | undefined; } & { a: 123; })]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^
>arg : SubTypeGet<T, Map>
> : ^^^^^^^^^^^^^^^^^^

declare class Test<T extends "foo" | ModuleSubType> {
>Test : Test<T>
> : ^^^^^^^

constructor(arg: SubTypeGet<T, Map>);
>arg : SubTypeGet<T, Map>
> : ^^^^^^^^^^^^^^^^^^
}

type TestConstructorParameters = ConstructorParameters<typeof Test>;
>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 = <T extends Cat | Dog>(
>WithDistributiveConditionalDirectlyInParam : WithDistributiveConditionalDirectlyInParam
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

arg: T extends unknown ? T : never,
>arg : T extends unknown ? T : never
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

) => void;

type Result1 = Parameters<WithDistributiveConditionalDirectlyInParam>;
>Result1 : [arg: Cat | Dog]
> : ^^^^^^^^^^^^^^^^

type WithDistributiveConditionalNested = <T extends Cat | Dog>(
>WithDistributiveConditionalNested : WithDistributiveConditionalNested
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

arg: T extends unknown ? { animal: T } : never,
>arg : T extends unknown ? { animal: T; } : never
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^
>animal : T
> : ^

) => void;

type Result2 = Parameters<WithDistributiveConditionalNested>;
>Result2 : [arg: { animal: Cat; } | { animal: Dog; }]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

type WithNonDistributiveConditionalNested = <T extends Cat | Dog>(
>WithNonDistributiveConditionalNested : WithNonDistributiveConditionalNested
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

arg: [T] extends [unknown] ? { animal: T } : never,
>arg : [T] extends [unknown] ? { animal: T; } : never
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^
>animal : T
> : ^

) => void;

type Result3 = Parameters<WithNonDistributiveConditionalNested>;
>Result3 : [arg: { animal: Cat | Dog; }]
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Loading
Loading