Skip to content

Commit ae6393e

Browse files
authored
Add fallback when both co- and contra-variant inference candidates exist (#54072)
1 parent cbb8dfe commit ae6393e

File tree

4 files changed

+160
-14
lines changed

4 files changed

+160
-14
lines changed

Diff for: src/compiler/checker.ts

+16-14
Original file line numberDiff line numberDiff line change
@@ -25157,22 +25157,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2515725157
const inference = context.inferences[index];
2515825158
if (!inference.inferredType) {
2515925159
let inferredType: Type | undefined;
25160-
const signature = context.signature;
25161-
if (signature) {
25162-
const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined;
25163-
if (inference.contraCandidates) {
25164-
// If we have both co- and contra-variant inferences, we use the co-variant inference if it is not 'never',
25165-
// it is a subtype of some contra-variant inference, and no other type parameter is constrained to this type
25166-
// parameter and has inferences that would conflict. Otherwise, we use the contra-variant inference.
25167-
const useCovariantType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) &&
25160+
let fallbackType: Type | undefined;
25161+
if (context.signature) {
25162+
const inferredCovariantType = inference.candidates ? getCovariantInference(inference, context.signature) : undefined;
25163+
const inferredContravariantType = inference.contraCandidates ? getContravariantInference(inference) : undefined;
25164+
if (inferredCovariantType || inferredContravariantType) {
25165+
// If we have both co- and contra-variant inferences, we prefer the co-variant inference if it is not 'never',
25166+
// all co-variant inferences are subtypes of it (i.e. it isn't one of a conflicting set of candidates), it is
25167+
// a subtype of some contra-variant inference, and no other type parameter is constrained to this type parameter
25168+
// and has inferences that would conflict. Otherwise, we prefer the contra-variant inference.
25169+
const preferCovariantType = inferredCovariantType && (!inferredContravariantType ||
25170+
!(inferredCovariantType.flags & TypeFlags.Never) &&
2516825171
some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) &&
2516925172
every(context.inferences, other =>
2517025173
other !== inference && getConstraintOfTypeParameter(other.typeParameter) !== inference.typeParameter ||
25171-
every(other.candidates, t => isTypeSubtypeOf(t, inferredCovariantType)));
25172-
inferredType = useCovariantType ? inferredCovariantType : getContravariantInference(inference);
25173-
}
25174-
else if (inferredCovariantType) {
25175-
inferredType = inferredCovariantType;
25174+
every(other.candidates, t => isTypeSubtypeOf(t, inferredCovariantType))));
25175+
inferredType = preferCovariantType ? inferredCovariantType : inferredContravariantType;
25176+
fallbackType = preferCovariantType ? inferredContravariantType : inferredCovariantType;
2517625177
}
2517725178
else if (context.flags & InferenceFlags.NoDefault) {
2517825179
// We use silentNeverType as the wildcard that signals no inferences.
@@ -25202,7 +25203,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2520225203
if (constraint) {
2520325204
const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper);
2520425205
if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
25205-
inference.inferredType = inferredType = instantiatedConstraint;
25206+
// If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint.
25207+
inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint;
2520625208
}
2520725209
}
2520825210
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
=== tests/cases/compiler/coAndContraVariantInferences5.ts ===
2+
type Thing = 'a' | 'b';
3+
>Thing : Symbol(Thing, Decl(coAndContraVariantInferences5.ts, 0, 0))
4+
5+
function f(
6+
>f : Symbol(f, Decl(coAndContraVariantInferences5.ts, 0, 23))
7+
8+
options: SelectOptions<Thing>,
9+
>options : Symbol(options, Decl(coAndContraVariantInferences5.ts, 2, 11))
10+
>SelectOptions : Symbol(SelectOptions, Decl(coAndContraVariantInferences5.ts, 17, 2))
11+
>Thing : Symbol(Thing, Decl(coAndContraVariantInferences5.ts, 0, 0))
12+
13+
onChange: (status: Thing | null) => void,
14+
>onChange : Symbol(onChange, Decl(coAndContraVariantInferences5.ts, 3, 34))
15+
>status : Symbol(status, Decl(coAndContraVariantInferences5.ts, 4, 15))
16+
>Thing : Symbol(Thing, Decl(coAndContraVariantInferences5.ts, 0, 0))
17+
18+
): void {
19+
select({
20+
>select : Symbol(select, Decl(coAndContraVariantInferences5.ts, 10, 1))
21+
22+
options,
23+
>options : Symbol(options, Decl(coAndContraVariantInferences5.ts, 6, 12))
24+
25+
onChange,
26+
>onChange : Symbol(onChange, Decl(coAndContraVariantInferences5.ts, 7, 16))
27+
28+
});
29+
}
30+
31+
declare function select<KeyT extends string>(props: SelectProps<KeyT>): void;
32+
>select : Symbol(select, Decl(coAndContraVariantInferences5.ts, 10, 1))
33+
>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 12, 24))
34+
>props : Symbol(props, Decl(coAndContraVariantInferences5.ts, 12, 45))
35+
>SelectProps : Symbol(SelectProps, Decl(coAndContraVariantInferences5.ts, 12, 77))
36+
>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 12, 24))
37+
38+
type SelectProps<KeyT extends string> = {
39+
>SelectProps : Symbol(SelectProps, Decl(coAndContraVariantInferences5.ts, 12, 77))
40+
>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 14, 17))
41+
42+
options?: SelectOptions<KeyT>;
43+
>options : Symbol(options, Decl(coAndContraVariantInferences5.ts, 14, 41))
44+
>SelectOptions : Symbol(SelectOptions, Decl(coAndContraVariantInferences5.ts, 17, 2))
45+
>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 14, 17))
46+
47+
onChange: (key: KeyT) => void;
48+
>onChange : Symbol(onChange, Decl(coAndContraVariantInferences5.ts, 15, 34))
49+
>key : Symbol(key, Decl(coAndContraVariantInferences5.ts, 16, 15))
50+
>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 14, 17))
51+
52+
};
53+
54+
type SelectOptions<KeyT extends string> =
55+
>SelectOptions : Symbol(SelectOptions, Decl(coAndContraVariantInferences5.ts, 17, 2))
56+
>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 19, 19))
57+
58+
| Array<{key: KeyT}>
59+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
60+
>key : Symbol(key, Decl(coAndContraVariantInferences5.ts, 20, 13))
61+
>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 19, 19))
62+
63+
| Array<KeyT>;
64+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
65+
>KeyT : Symbol(KeyT, Decl(coAndContraVariantInferences5.ts, 19, 19))
66+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
=== tests/cases/compiler/coAndContraVariantInferences5.ts ===
2+
type Thing = 'a' | 'b';
3+
>Thing : "a" | "b"
4+
5+
function f(
6+
>f : (options: SelectOptions<Thing>, onChange: (status: Thing | null) => void) => void
7+
8+
options: SelectOptions<Thing>,
9+
>options : SelectOptions<Thing>
10+
11+
onChange: (status: Thing | null) => void,
12+
>onChange : (status: Thing | null) => void
13+
>status : Thing | null
14+
15+
): void {
16+
select({
17+
>select({ options, onChange, }) : void
18+
>select : <KeyT extends string>(props: SelectProps<KeyT>) => void
19+
>{ options, onChange, } : { options: SelectOptions<Thing>; onChange: (status: Thing | null) => void; }
20+
21+
options,
22+
>options : SelectOptions<Thing>
23+
24+
onChange,
25+
>onChange : (status: Thing | null) => void
26+
27+
});
28+
}
29+
30+
declare function select<KeyT extends string>(props: SelectProps<KeyT>): void;
31+
>select : <KeyT extends string>(props: SelectProps<KeyT>) => void
32+
>props : SelectProps<KeyT>
33+
34+
type SelectProps<KeyT extends string> = {
35+
>SelectProps : SelectProps<KeyT>
36+
37+
options?: SelectOptions<KeyT>;
38+
>options : SelectOptions<KeyT> | undefined
39+
40+
onChange: (key: KeyT) => void;
41+
>onChange : (key: KeyT) => void
42+
>key : KeyT
43+
44+
};
45+
46+
type SelectOptions<KeyT extends string> =
47+
>SelectOptions : SelectOptions<KeyT>
48+
49+
| Array<{key: KeyT}>
50+
>key : KeyT
51+
52+
| Array<KeyT>;
53+
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
type Thing = 'a' | 'b';
5+
6+
function f(
7+
options: SelectOptions<Thing>,
8+
onChange: (status: Thing | null) => void,
9+
): void {
10+
select({
11+
options,
12+
onChange,
13+
});
14+
}
15+
16+
declare function select<KeyT extends string>(props: SelectProps<KeyT>): void;
17+
18+
type SelectProps<KeyT extends string> = {
19+
options?: SelectOptions<KeyT>;
20+
onChange: (key: KeyT) => void;
21+
};
22+
23+
type SelectOptions<KeyT extends string> =
24+
| Array<{key: KeyT}>
25+
| Array<KeyT>;

0 commit comments

Comments
 (0)