diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 406ff8715d124..da695c1f391a0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25154,22 +25154,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const inference = context.inferences[index]; if (!inference.inferredType) { let inferredType: Type | undefined; - const signature = context.signature; - if (signature) { - const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined; - if (inference.contraCandidates) { - // If we have both co- and contra-variant inferences, we use the co-variant inference if it is not 'never', - // it is a subtype of some contra-variant inference, and no other type parameter is constrained to this type - // parameter and has inferences that would conflict. Otherwise, we use the contra-variant inference. - const useCovariantType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) && + let fallbackType: Type | undefined; + if (context.signature) { + const inferredCovariantType = inference.candidates ? getCovariantInference(inference, context.signature) : undefined; + 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 + // and has inferences that would conflict. Otherwise, we prefer the contra-variant inference. + const preferCovariantType = inferredCovariantType && (!inferredContravariantType || + !(inferredCovariantType.flags & TypeFlags.Never) && some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) && every(context.inferences, other => other !== inference && getConstraintOfTypeParameter(other.typeParameter) !== inference.typeParameter || - every(other.candidates, t => isTypeSubtypeOf(t, inferredCovariantType))); - inferredType = useCovariantType ? inferredCovariantType : getContravariantInference(inference); - } - else if (inferredCovariantType) { - inferredType = inferredCovariantType; + every(other.candidates, t => isTypeSubtypeOf(t, inferredCovariantType)))); + inferredType = preferCovariantType ? inferredCovariantType : inferredContravariantType; + fallbackType = preferCovariantType ? inferredContravariantType : inferredCovariantType; } else if (context.flags & InferenceFlags.NoDefault) { // We use silentNeverType as the wildcard that signals no inferences. @@ -25199,7 +25200,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (constraint) { const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper); if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { - inference.inferredType = inferredType = instantiatedConstraint; + // If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint. + inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint; } } } diff --git a/tests/baselines/reference/coAndContraVariantInferences5.symbols b/tests/baselines/reference/coAndContraVariantInferences5.symbols new file mode 100644 index 0000000000000..b9f6b6fc3081d --- /dev/null +++ b/tests/baselines/reference/coAndContraVariantInferences5.symbols @@ -0,0 +1,66 @@ +=== tests/cases/compiler/coAndContraVariantInferences5.ts === +type Thing = 'a' | 'b'; +>Thing : Symbol(Thing, Decl(coAndContraVariantInferences5.ts, 0, 0)) + +function f( +>f : Symbol(f, Decl(coAndContraVariantInferences5.ts, 0, 23)) + + options: SelectOptions<Thing>, +>options : Symbol(options, Decl(coAndContraVariantInferences5.ts, 2, 11)) +>SelectOptions : Symbol(SelectOptions, Decl(coAndContraVariantInferences5.ts, 17, 2)) +>Thing : Symbol(Thing, Decl(coAndContraVariantInferences5.ts, 0, 0)) + + onChange: (status: Thing | null) => void, +>onChange : Symbol(onChange, Decl(coAndContraVariantInferences5.ts, 3, 34)) +>status : Symbol(status, Decl(coAndContraVariantInferences5.ts, 4, 15)) +>Thing : Symbol(Thing, Decl(coAndContraVariantInferences5.ts, 0, 0)) + +): void { + select({ +>select : Symbol(select, Decl(coAndContraVariantInferences5.ts, 10, 1)) + + options, +>options : Symbol(options, Decl(coAndContraVariantInferences5.ts, 6, 12)) + + onChange, +>onChange : Symbol(onChange, Decl(coAndContraVariantInferences5.ts, 7, 16)) + + }); +} + +declare function select<KeyT extends string>(props: SelectProps<KeyT>): void; +>select : Symbol(select, Decl(coAndContraVariantInferences5.ts, 10, 1)) +>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 12, 24)) +>props : Symbol(props, Decl(coAndContraVariantInferences5.ts, 12, 45)) +>SelectProps : Symbol(SelectProps, Decl(coAndContraVariantInferences5.ts, 12, 77)) +>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 12, 24)) + +type SelectProps<KeyT extends string> = { +>SelectProps : Symbol(SelectProps, Decl(coAndContraVariantInferences5.ts, 12, 77)) +>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 14, 17)) + + options?: SelectOptions<KeyT>; +>options : Symbol(options, Decl(coAndContraVariantInferences5.ts, 14, 41)) +>SelectOptions : Symbol(SelectOptions, Decl(coAndContraVariantInferences5.ts, 17, 2)) +>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 14, 17)) + + onChange: (key: KeyT) => void; +>onChange : Symbol(onChange, Decl(coAndContraVariantInferences5.ts, 15, 34)) +>key : Symbol(key, Decl(coAndContraVariantInferences5.ts, 16, 15)) +>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 14, 17)) + +}; + +type SelectOptions<KeyT extends string> = +>SelectOptions : Symbol(SelectOptions, Decl(coAndContraVariantInferences5.ts, 17, 2)) +>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 19, 19)) + + | Array<{key: KeyT}> +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>key : Symbol(key, Decl(coAndContraVariantInferences5.ts, 20, 13)) +>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 19, 19)) + + | Array<KeyT>; +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 19, 19)) + diff --git a/tests/baselines/reference/coAndContraVariantInferences5.types b/tests/baselines/reference/coAndContraVariantInferences5.types new file mode 100644 index 0000000000000..4bcacf5535e32 --- /dev/null +++ b/tests/baselines/reference/coAndContraVariantInferences5.types @@ -0,0 +1,53 @@ +=== tests/cases/compiler/coAndContraVariantInferences5.ts === +type Thing = 'a' | 'b'; +>Thing : "a" | "b" + +function f( +>f : (options: SelectOptions<Thing>, onChange: (status: Thing | null) => void) => void + + options: SelectOptions<Thing>, +>options : SelectOptions<Thing> + + onChange: (status: Thing | null) => void, +>onChange : (status: Thing | null) => void +>status : Thing | null + +): void { + select({ +>select({ options, onChange, }) : void +>select : <KeyT extends string>(props: SelectProps<KeyT>) => void +>{ options, onChange, } : { options: SelectOptions<Thing>; onChange: (status: Thing | null) => void; } + + options, +>options : SelectOptions<Thing> + + onChange, +>onChange : (status: Thing | null) => void + + }); +} + +declare function select<KeyT extends string>(props: SelectProps<KeyT>): void; +>select : <KeyT extends string>(props: SelectProps<KeyT>) => void +>props : SelectProps<KeyT> + +type SelectProps<KeyT extends string> = { +>SelectProps : SelectProps<KeyT> + + options?: SelectOptions<KeyT>; +>options : SelectOptions<KeyT> | undefined + + onChange: (key: KeyT) => void; +>onChange : (key: KeyT) => void +>key : KeyT + +}; + +type SelectOptions<KeyT extends string> = +>SelectOptions : SelectOptions<KeyT> + + | Array<{key: KeyT}> +>key : KeyT + + | Array<KeyT>; + diff --git a/tests/cases/compiler/coAndContraVariantInferences5.ts b/tests/cases/compiler/coAndContraVariantInferences5.ts new file mode 100644 index 0000000000000..a569065ea146e --- /dev/null +++ b/tests/cases/compiler/coAndContraVariantInferences5.ts @@ -0,0 +1,25 @@ +// @strict: true +// @noEmit: true + +type Thing = 'a' | 'b'; + +function f( + options: SelectOptions<Thing>, + onChange: (status: Thing | null) => void, +): void { + select({ + options, + onChange, + }); +} + +declare function select<KeyT extends string>(props: SelectProps<KeyT>): void; + +type SelectProps<KeyT extends string> = { + options?: SelectOptions<KeyT>; + onChange: (key: KeyT) => void; +}; + +type SelectOptions<KeyT extends string> = + | Array<{key: KeyT}> + | Array<KeyT>;