Skip to content

Commit a2b785b

Browse files
authored
Limit type argument inference from binding patterns (microsoft#49086)
* WIP * Don’t widen literals based on bogus contextual type instantiation * Split tests * Skip unnecessary inference pass * Accept test baselines * Fix stray edit * Fix type mapper combination * Revert src/ of 7dc1952 * Make empty binding pattern provide no contextual type * Add missed baseline
1 parent 194a2ae commit a2b785b

29 files changed

+604
-97
lines changed

src/compiler/checker.ts

+42-30
Original file line numberDiff line numberDiff line change
@@ -26519,7 +26519,7 @@ namespace ts {
2651926519
if (result) {
2652026520
return result;
2652126521
}
26522-
if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable
26522+
if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name) && declaration.name.elements.length > 0) {
2652326523
return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false);
2652426524
}
2652526525
}
@@ -27053,22 +27053,20 @@ namespace ts {
2705327053
const inferenceContext = getInferenceContext(node);
2705427054
// If no inferences have been made, nothing is gained from instantiating as type parameters
2705527055
// would just be replaced with their defaults similar to the apparent type.
27056-
if (inferenceContext && some(inferenceContext.inferences, hasInferenceCandidates)) {
27056+
if (inferenceContext && contextFlags! & ContextFlags.Signature && some(inferenceContext.inferences, hasInferenceCandidates)) {
2705727057
// For contextual signatures we incorporate all inferences made so far, e.g. from return
2705827058
// types as well as arguments to the left in a function call.
27059-
if (contextFlags && contextFlags & ContextFlags.Signature) {
27060-
return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper);
27061-
}
27059+
return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper);
27060+
}
27061+
if (inferenceContext?.returnMapper) {
2706227062
// For other purposes (e.g. determining whether to produce literal types) we only
2706327063
// incorporate inferences made from the return type in a function call. We remove
2706427064
// the 'boolean' type from the contextual type such that contextually typed boolean
2706527065
// literals actually end up widening to 'boolean' (see #48363).
27066-
if (inferenceContext.returnMapper) {
27067-
const type = instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper);
27068-
return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ?
27069-
filterType(type, t => t !== regularFalseType && t !== regularTrueType) :
27070-
type;
27071-
}
27066+
const type = instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper);
27067+
return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ?
27068+
filterType(type, t => t !== regularFalseType && t !== regularTrueType) :
27069+
type;
2707227070
}
2707327071
}
2707427072
return contextualType;
@@ -29904,29 +29902,43 @@ namespace ts {
2990429902
// 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the
2990529903
// return type of 'wrap'.
2990629904
if (node.kind !== SyntaxKind.Decorator) {
29907-
const contextualType = getContextualType(node, every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p)) ? ContextFlags.SkipBindingPatterns : ContextFlags.None);
29905+
const skipBindingPatterns = every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p));
29906+
const contextualType = getContextualType(node, skipBindingPatterns ? ContextFlags.SkipBindingPatterns : ContextFlags.None);
2990829907
if (contextualType) {
2990929908
const inferenceTargetType = getReturnTypeOfSignature(signature);
2991029909
if (couldContainTypeVariables(inferenceTargetType)) {
29911-
// We clone the inference context to avoid disturbing a resolution in progress for an
29912-
// outer call expression. Effectively we just want a snapshot of whatever has been
29913-
// inferred for any outer call expression so far.
2991429910
const outerContext = getInferenceContext(node);
29915-
const outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, InferenceFlags.NoDefault));
29916-
const instantiatedType = instantiateType(contextualType, outerMapper);
29917-
// If the contextual type is a generic function type with a single call signature, we
29918-
// instantiate the type with its own type parameters and type arguments. This ensures that
29919-
// the type parameters are not erased to type any during type inference such that they can
29920-
// be inferred as actual types from the contextual type. For example:
29921-
// declare function arrayMap<T, U>(f: (x: T) => U): (a: T[]) => U[];
29922-
// const boxElements: <A>(a: A[]) => { value: A }[] = arrayMap(value => ({ value }));
29923-
// Above, the type of the 'value' parameter is inferred to be 'A'.
29924-
const contextualSignature = getSingleCallSignature(instantiatedType);
29925-
const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ?
29926-
getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(contextualSignature, contextualSignature.typeParameters)) :
29927-
instantiatedType;
29928-
// Inferences made from return types have lower priority than all other inferences.
29929-
inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType);
29911+
const isFromBindingPattern = !skipBindingPatterns && getContextualType(node, ContextFlags.SkipBindingPatterns) !== contextualType;
29912+
// A return type inference from a binding pattern can be used in instantiating the contextual
29913+
// type of an argument later in inference, but cannot stand on its own as the final return type.
29914+
// It is incorporated into `context.returnMapper` which is used in `instantiateContextualType`,
29915+
// but doesn't need to go into `context.inferences`. This allows a an array binding pattern to
29916+
// produce a tuple for `T` in
29917+
// declare function f<T>(cb: () => T): T;
29918+
// const [e1, e2, e3] = f(() => [1, "hi", true]);
29919+
// but does not produce any inference for `T` in
29920+
// declare function f<T>(): T;
29921+
// const [e1, e2, e3] = f();
29922+
if (!isFromBindingPattern) {
29923+
// We clone the inference context to avoid disturbing a resolution in progress for an
29924+
// outer call expression. Effectively we just want a snapshot of whatever has been
29925+
// inferred for any outer call expression so far.
29926+
const outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, InferenceFlags.NoDefault));
29927+
const instantiatedType = instantiateType(contextualType, outerMapper);
29928+
// If the contextual type is a generic function type with a single call signature, we
29929+
// instantiate the type with its own type parameters and type arguments. This ensures that
29930+
// the type parameters are not erased to type any during type inference such that they can
29931+
// be inferred as actual types from the contextual type. For example:
29932+
// declare function arrayMap<T, U>(f: (x: T) => U): (a: T[]) => U[];
29933+
// const boxElements: <A>(a: A[]) => { value: A }[] = arrayMap(value => ({ value }));
29934+
// Above, the type of the 'value' parameter is inferred to be 'A'.
29935+
const contextualSignature = getSingleCallSignature(instantiatedType);
29936+
const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ?
29937+
getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(contextualSignature, contextualSignature.typeParameters)) :
29938+
instantiatedType;
29939+
// Inferences made from return types have lower priority than all other inferences.
29940+
inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType);
29941+
}
2993029942
// Create a type mapper for instantiating generic contextual types using the inferences made
2993129943
// from the return type. We need a separate inference pass here because (a) instantiation of
2993229944
// the source type uses the outer context's return mapper (which excludes inferences made from

src/compiler/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5874,7 +5874,7 @@ namespace ts {
58745874
ReturnType = 1 << 7, // Inference made from return type of generic function
58755875
LiteralKeyof = 1 << 8, // Inference made from a string literal to a keyof T
58765876
NoConstraints = 1 << 9, // Don't infer from constraints of instantiable types
5877-
AlwaysStrict = 1 << 10, // Always use strict rules for contravariant inferences
5877+
AlwaysStrict = 1 << 10, // Always use strict rules for contravariant inferences
58785878
MaxValue = 1 << 11, // Seed for inference priority tracking
58795879

58805880
PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof, // These priorities imply that the resulting type should be a combination of all candidates
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts(2,7): error TS2571: Object is of type 'unknown'.
2+
tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts(3,9): error TS2339: Property 'p1' does not exist on type 'unknown'.
3+
tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts(4,7): error TS2461: Type 'unknown' is not an array type.
4+
tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts(4,7): error TS2571: Object is of type 'unknown'.
5+
tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts(5,7): error TS2461: Type 'unknown' is not an array type.
6+
7+
8+
==== tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts (5 errors) ====
9+
declare function f<T>(): T;
10+
const {} = f(); // error (only in strictNullChecks)
11+
~~
12+
!!! error TS2571: Object is of type 'unknown'.
13+
const { p1 } = f(); // error
14+
~~
15+
!!! error TS2339: Property 'p1' does not exist on type 'unknown'.
16+
const [] = f(); // error
17+
~~
18+
!!! error TS2461: Type 'unknown' is not an array type.
19+
~~
20+
!!! error TS2571: Object is of type 'unknown'.
21+
const [e1, e2] = f(); // error
22+
~~~~~~~~
23+
!!! error TS2461: Type 'unknown' is not an array type.
24+
25+
// Repro from #43605
26+
type Dispatch<A = { type: any; [extraProps: string]: any }> = { <T extends A>(action: T): T };
27+
type IFuncs = { readonly [key: string]: (...p: any) => void };
28+
type IDestructuring<T extends IFuncs> = { readonly [key in keyof T]?: (...p: Parameters<T[key]>) => void };
29+
type Destructuring<T extends IFuncs, U extends IDestructuring<T>> = (dispatch: Dispatch<any>, funcs: T) => U;
30+
const funcs1 = {
31+
funcA: (a: boolean): void => {},
32+
funcB: (b: string, bb: string): void => {},
33+
funcC: (c: number, cc: number, ccc: boolean): void => {},
34+
};
35+
type TFuncs1 = typeof funcs1;
36+
declare function useReduxDispatch1<T extends IDestructuring<TFuncs1>>(destructuring: Destructuring<TFuncs1, T>): T;
37+
const {} = useReduxDispatch1(
38+
(d, f) => ({
39+
funcA: (...p) => d(f.funcA(...p)), // p should be inferrable
40+
funcB: (...p) => d(f.funcB(...p)),
41+
funcC: (...p) => d(f.funcC(...p)),
42+
})
43+
);
44+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//// [bindingPatternCannotBeOnlyInferenceSource.ts]
2+
declare function f<T>(): T;
3+
const {} = f(); // error (only in strictNullChecks)
4+
const { p1 } = f(); // error
5+
const [] = f(); // error
6+
const [e1, e2] = f(); // error
7+
8+
// Repro from #43605
9+
type Dispatch<A = { type: any; [extraProps: string]: any }> = { <T extends A>(action: T): T };
10+
type IFuncs = { readonly [key: string]: (...p: any) => void };
11+
type IDestructuring<T extends IFuncs> = { readonly [key in keyof T]?: (...p: Parameters<T[key]>) => void };
12+
type Destructuring<T extends IFuncs, U extends IDestructuring<T>> = (dispatch: Dispatch<any>, funcs: T) => U;
13+
const funcs1 = {
14+
funcA: (a: boolean): void => {},
15+
funcB: (b: string, bb: string): void => {},
16+
funcC: (c: number, cc: number, ccc: boolean): void => {},
17+
};
18+
type TFuncs1 = typeof funcs1;
19+
declare function useReduxDispatch1<T extends IDestructuring<TFuncs1>>(destructuring: Destructuring<TFuncs1, T>): T;
20+
const {} = useReduxDispatch1(
21+
(d, f) => ({
22+
funcA: (...p) => d(f.funcA(...p)), // p should be inferrable
23+
funcB: (...p) => d(f.funcB(...p)),
24+
funcC: (...p) => d(f.funcC(...p)),
25+
})
26+
);
27+
28+
29+
//// [bindingPatternCannotBeOnlyInferenceSource.js]
30+
var _a = f(); // error (only in strictNullChecks)
31+
var p1 = f().p1; // error
32+
var _b = f(); // error
33+
var _c = f(), e1 = _c[0], e2 = _c[1]; // error
34+
var funcs1 = {
35+
funcA: function (a) { },
36+
funcB: function (b, bb) { },
37+
funcC: function (c, cc, ccc) { }
38+
};
39+
var _d = useReduxDispatch1(function (d, f) { return ({
40+
funcA: function () {
41+
var p = [];
42+
for (var _i = 0; _i < arguments.length; _i++) {
43+
p[_i] = arguments[_i];
44+
}
45+
return d(f.funcA.apply(f, p));
46+
},
47+
funcB: function () {
48+
var p = [];
49+
for (var _i = 0; _i < arguments.length; _i++) {
50+
p[_i] = arguments[_i];
51+
}
52+
return d(f.funcB.apply(f, p));
53+
},
54+
funcC: function () {
55+
var p = [];
56+
for (var _i = 0; _i < arguments.length; _i++) {
57+
p[_i] = arguments[_i];
58+
}
59+
return d(f.funcC.apply(f, p));
60+
}
61+
}); });

0 commit comments

Comments
 (0)