Skip to content

Improve logic that chooses co- vs. contra-variant inferences #57909

New issue

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

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

Already on GitHub? # to your account

Merged
merged 11 commits into from
Jun 17, 2024
Merged
12 changes: 7 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26809,15 +26809,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const inferredContravariantType = inference.contraCandidates ? getContravariantInference(inference) : undefined;
if (inferredCovariantType || inferredContravariantType) {
// If we have both co- and contra-variant inferences, we prefer the co-variant inference if it is not 'never',
// all co-variant inferences are subtypes of it (i.e. it isn't one of a conflicting set of candidates), it is
// a subtype of some contra-variant inference, and no other type parameter is constrained to this type parameter
// all co-variant inferences are assignable to it (i.e. it isn't one of a conflicting set of candidates), it is
// assignable to some contra-variant inference, and no other type parameter is constrained to this type parameter
// and has inferences that would conflict. Otherwise, we prefer the contra-variant inference.
// Similarly ignore co-variant `any` inference when both are available as almost everything is assignable to it
// and it would spoil the overall inference.
const preferCovariantType = inferredCovariantType && (!inferredContravariantType ||
!(inferredCovariantType.flags & TypeFlags.Never) &&
some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) &&
!(inferredCovariantType.flags & (TypeFlags.Never | TypeFlags.Any)) &&
some(inference.contraCandidates, t => isTypeAssignableTo(inferredCovariantType, t)) &&
every(context.inferences, other =>
other !== inference && getConstraintOfTypeParameter(other.typeParameter) !== inference.typeParameter ||
every(other.candidates, t => isTypeSubtypeOf(t, inferredCovariantType))));
every(other.candidates, t => isTypeAssignableTo(t, inferredCovariantType))));
inferredType = preferCovariantType ? inferredCovariantType : inferredContravariantType;
fallbackType = preferCovariantType ? inferredContravariantType : inferredCovariantType;
}
Expand Down
77 changes: 77 additions & 0 deletions tests/baselines/reference/coAndContraVariantInferences7.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//// [tests/cases/compiler/coAndContraVariantInferences7.ts] ////

=== coAndContraVariantInferences7.ts ===
type Request<TSchema extends Schema> = {
>Request : Symbol(Request, Decl(coAndContraVariantInferences7.ts, 0, 0))
>TSchema : Symbol(TSchema, Decl(coAndContraVariantInferences7.ts, 0, 13))
>Schema : Symbol(Schema, Decl(coAndContraVariantInferences7.ts, 2, 2))

query: TSchema["query"];
>query : Symbol(query, Decl(coAndContraVariantInferences7.ts, 0, 40))
>TSchema : Symbol(TSchema, Decl(coAndContraVariantInferences7.ts, 0, 13))

};

type Schema = { query?: unknown; body?: unknown };
>Schema : Symbol(Schema, Decl(coAndContraVariantInferences7.ts, 2, 2))
>query : Symbol(query, Decl(coAndContraVariantInferences7.ts, 4, 15))
>body : Symbol(body, Decl(coAndContraVariantInferences7.ts, 4, 32))

declare function route<TSchema extends Schema>(obj: {
>route : Symbol(route, Decl(coAndContraVariantInferences7.ts, 4, 50))
>TSchema : Symbol(TSchema, Decl(coAndContraVariantInferences7.ts, 6, 23))
>Schema : Symbol(Schema, Decl(coAndContraVariantInferences7.ts, 2, 2))
>obj : Symbol(obj, Decl(coAndContraVariantInferences7.ts, 6, 47))

pre: (a: TSchema) => void;
>pre : Symbol(pre, Decl(coAndContraVariantInferences7.ts, 6, 53))
>a : Symbol(a, Decl(coAndContraVariantInferences7.ts, 7, 8))
>TSchema : Symbol(TSchema, Decl(coAndContraVariantInferences7.ts, 6, 23))

schema: TSchema;
>schema : Symbol(schema, Decl(coAndContraVariantInferences7.ts, 7, 28))
>TSchema : Symbol(TSchema, Decl(coAndContraVariantInferences7.ts, 6, 23))

handle: (req: Request<TSchema>) => void;
>handle : Symbol(handle, Decl(coAndContraVariantInferences7.ts, 8, 18))
>req : Symbol(req, Decl(coAndContraVariantInferences7.ts, 9, 11))
>Request : Symbol(Request, Decl(coAndContraVariantInferences7.ts, 0, 0))
>TSchema : Symbol(TSchema, Decl(coAndContraVariantInferences7.ts, 6, 23))

}): void;

const validate = (_: { query?: unknown; body?: unknown }) => {};
>validate : Symbol(validate, Decl(coAndContraVariantInferences7.ts, 12, 5))
>_ : Symbol(_, Decl(coAndContraVariantInferences7.ts, 12, 18))
>query : Symbol(query, Decl(coAndContraVariantInferences7.ts, 12, 22))
>body : Symbol(body, Decl(coAndContraVariantInferences7.ts, 12, 39))

route({
>route : Symbol(route, Decl(coAndContraVariantInferences7.ts, 4, 50))

pre: validate,
>pre : Symbol(pre, Decl(coAndContraVariantInferences7.ts, 14, 7))
>validate : Symbol(validate, Decl(coAndContraVariantInferences7.ts, 12, 5))

schema: {
>schema : Symbol(schema, Decl(coAndContraVariantInferences7.ts, 15, 16))

query: "",
>query : Symbol(query, Decl(coAndContraVariantInferences7.ts, 16, 11))

},
handle: (req) => {
>handle : Symbol(handle, Decl(coAndContraVariantInferences7.ts, 18, 4))
>req : Symbol(req, Decl(coAndContraVariantInferences7.ts, 19, 11))

const test: string = req.query;
>test : Symbol(test, Decl(coAndContraVariantInferences7.ts, 20, 9))
>req.query : Symbol(query, Decl(coAndContraVariantInferences7.ts, 0, 40))
>req : Symbol(req, Decl(coAndContraVariantInferences7.ts, 19, 11))
>query : Symbol(query, Decl(coAndContraVariantInferences7.ts, 0, 40))

},
});

export {};

107 changes: 107 additions & 0 deletions tests/baselines/reference/coAndContraVariantInferences7.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//// [tests/cases/compiler/coAndContraVariantInferences7.ts] ////

=== coAndContraVariantInferences7.ts ===
type Request<TSchema extends Schema> = {
>Request : Request<TSchema>
> : ^^^^^^^^^^^^^^^^

query: TSchema["query"];
>query : TSchema["query"]
> : ^^^^^^^^^^^^^^^^

};

type Schema = { query?: unknown; body?: unknown };
>Schema : Schema
> : ^^^^^^
>query : unknown
> : ^^^^^^^
>body : unknown
> : ^^^^^^^

declare function route<TSchema extends Schema>(obj: {
>route : <TSchema extends Schema>(obj: { pre: (a: TSchema) => void; schema: TSchema; handle: (req: Request<TSchema>) => void; }) => void
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>obj : { pre: (a: TSchema) => void; schema: TSchema; handle: (req: Request<TSchema>) => void; }
> : ^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^^

pre: (a: TSchema) => void;
>pre : (a: TSchema) => void
> : ^ ^^ ^^^^^
>a : TSchema
> : ^^^^^^^

schema: TSchema;
>schema : TSchema
> : ^^^^^^^

handle: (req: Request<TSchema>) => void;
>handle : (req: Request<TSchema>) => void
> : ^ ^^ ^^^^^
>req : Request<TSchema>
> : ^^^^^^^^^^^^^^^^

}): void;

const validate = (_: { query?: unknown; body?: unknown }) => {};
>validate : (_: { query?: unknown; body?: unknown; }) => void
> : ^ ^^ ^^^^^^^^^
>(_: { query?: unknown; body?: unknown }) => {} : (_: { query?: unknown; body?: unknown; }) => void
> : ^ ^^ ^^^^^^^^^
>_ : { query?: unknown; body?: unknown; }
> : ^^^^^^^^^^ ^^^^^^^^^ ^^^
>query : unknown
> : ^^^^^^^
>body : unknown
> : ^^^^^^^

route({
>route({ pre: validate, schema: { query: "", }, handle: (req) => { const test: string = req.query; },}) : void
> : ^^^^
>route : <TSchema extends Schema>(obj: { pre: (a: TSchema) => void; schema: TSchema; handle: (req: Request<TSchema>) => void; }) => void
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^
>{ pre: validate, schema: { query: "", }, handle: (req) => { const test: string = req.query; },} : { pre: (_: { query?: unknown; body?: unknown; }) => void; schema: { query: string; }; handle: (req: Request<{ query: string; }>) => void; }
> : ^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

pre: validate,
>pre : (_: { query?: unknown; body?: unknown; }) => void
> : ^ ^^ ^^^^^^^^^
>validate : (_: { query?: unknown; body?: unknown; }) => void
> : ^ ^^ ^^^^^^^^^

schema: {
>schema : { query: string; }
> : ^^^^^^^^^^^^^^^^^^
>{ query: "", } : { query: string; }
> : ^^^^^^^^^^^^^^^^^^

query: "",
>query : string
> : ^^^^^^
>"" : ""
> : ^^

},
handle: (req) => {
>handle : (req: Request<{ query: string; }>) => void
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>(req) => { const test: string = req.query; } : (req: Request<{ query: string; }>) => void
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>req : Request<{ query: string; }>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^

const test: string = req.query;
>test : string
> : ^^^^^^
>req.query : string
> : ^^^^^^
>req : Request<{ query: string; }>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^
>query : string
> : ^^^^^^

},
});

export {};

29 changes: 29 additions & 0 deletions tests/baselines/reference/coAndContraVariantInferences8.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//// [tests/cases/compiler/coAndContraVariantInferences8.ts] ////

=== coAndContraVariantInferences8.ts ===
// https://github.com/microsoft/TypeScript/issues/58468

declare const fn: (() => void) | ((a: number) => void);
>fn : Symbol(fn, Decl(coAndContraVariantInferences8.ts, 2, 13))
>a : Symbol(a, Decl(coAndContraVariantInferences8.ts, 2, 35))

declare const x: number;
>x : Symbol(x, Decl(coAndContraVariantInferences8.ts, 4, 13))

declare const y: any;
>y : Symbol(y, Decl(coAndContraVariantInferences8.ts, 5, 13))

fn.call(null, x);
>fn.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>fn : Symbol(fn, Decl(coAndContraVariantInferences8.ts, 2, 13))
>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(coAndContraVariantInferences8.ts, 4, 13))

fn.call(null, y);
>fn.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>fn : Symbol(fn, Decl(coAndContraVariantInferences8.ts, 2, 13))
>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>y : Symbol(y, Decl(coAndContraVariantInferences8.ts, 5, 13))

export {};

43 changes: 43 additions & 0 deletions tests/baselines/reference/coAndContraVariantInferences8.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//// [tests/cases/compiler/coAndContraVariantInferences8.ts] ////

=== coAndContraVariantInferences8.ts ===
// https://github.com/microsoft/TypeScript/issues/58468

declare const fn: (() => void) | ((a: number) => void);
>fn : (() => void) | ((a: number) => void)
> : ^^^^^^^ ^^^^^^ ^^ ^^^^^ ^
>a : number
> : ^^^^^^

declare const x: number;
>x : number
> : ^^^^^^

declare const y: any;
>y : any

fn.call(null, x);
>fn.call(null, x) : void
> : ^^^^
>fn.call : <T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^
>fn : (() => void) | ((a: number) => void)
> : ^^^^^^^ ^^^^^^ ^^ ^^^^^ ^
>call : <T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^
>x : number
> : ^^^^^^

fn.call(null, y);
>fn.call(null, y) : void
> : ^^^^
>fn.call : <T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^
>fn : (() => void) | ((a: number) => void)
> : ^^^^^^^ ^^^^^^ ^^ ^^^^^ ^
>call : <T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ ^^ ^^^^^
>y : any

export {};

28 changes: 28 additions & 0 deletions tests/cases/compiler/coAndContraVariantInferences7.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @strict: true
// @noEmit: true

type Request<TSchema extends Schema> = {
query: TSchema["query"];
};

type Schema = { query?: unknown; body?: unknown };

declare function route<TSchema extends Schema>(obj: {
pre: (a: TSchema) => void;
schema: TSchema;
handle: (req: Request<TSchema>) => void;
}): void;

const validate = (_: { query?: unknown; body?: unknown }) => {};

route({
pre: validate,
schema: {
query: "",
},
handle: (req) => {
const test: string = req.query;
},
});

export {};
14 changes: 14 additions & 0 deletions tests/cases/compiler/coAndContraVariantInferences8.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// @strict: true
// @noEmit: true

// https://github.com/microsoft/TypeScript/issues/58468

declare const fn: (() => void) | ((a: number) => void);

declare const x: number;
declare const y: any;

fn.call(null, x);
fn.call(null, y);

export {};