Add fallback when both co- and contra-variant inference candidates exist (#54072)

This commit is contained in:
Anders Hejlsberg 2023-05-02 06:39:57 -07:00 committed by GitHub
parent cbb8dfe784
commit ae6393e5eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 160 additions and 14 deletions

View File

@ -25157,22 +25157,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.
@ -25202,7 +25203,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;
}
}
}

View File

@ -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))

View File

@ -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>;

View File

@ -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>;