Skip to content
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

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

Merged
merged 11 commits into from
Jun 17, 2024
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 {};