-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Limit type argument inference from binding patterns #49086
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
Changes from 6 commits
dc325fe
b00e6b5
73e58ea
65f453f
c923baf
7e8c56c
7dc1952
96f2f52
024bf7a
4537a79
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21248,11 +21248,15 @@ namespace ts { | |
type; | ||
} | ||
|
||
function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined) { | ||
if (!isLiteralOfContextualType(type, contextualType)) { | ||
type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type)); | ||
function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined, node?: Node) { | ||
if (isLiteralOfContextualType(type, contextualType)) { | ||
return type; | ||
} | ||
return type; | ||
const instantiatedContextualType = node && instantiateContextualType(contextualType, node) || contextualType; | ||
if (instantiatedContextualType !== contextualType && isLiteralOfContextualType(type, instantiatedContextualType)) { | ||
return type; | ||
} | ||
return getWidenedUniqueESSymbolType(getWidenedLiteralType(type)); | ||
} | ||
|
||
function getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, isAsync: boolean) { | ||
|
@@ -27051,22 +27055,22 @@ namespace ts { | |
const inferenceContext = getInferenceContext(node); | ||
// If no inferences have been made, nothing is gained from instantiating as type parameters | ||
// would just be replaced with their defaults similar to the apparent type. | ||
if (inferenceContext && some(inferenceContext.inferences, hasInferenceCandidates)) { | ||
if (inferenceContext && contextFlags! & ContextFlags.Signature && (inferenceContext.returnMapper || some(inferenceContext.inferences, hasInferenceCandidates))) { | ||
// For contextual signatures we incorporate all inferences made so far, e.g. from return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, because the |
||
// types as well as arguments to the left in a function call. | ||
if (contextFlags && contextFlags & ContextFlags.Signature) { | ||
return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper); | ||
} | ||
return instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper | ||
? combineTypeMappers(inferenceContext.nonFixingMapper, inferenceContext.returnMapper) | ||
: inferenceContext.nonFixingMapper); | ||
} | ||
if (inferenceContext?.returnMapper) { | ||
// For other purposes (e.g. determining whether to produce literal types) we only | ||
// incorporate inferences made from the return type in a function call. We remove | ||
// the 'boolean' type from the contextual type such that contextually typed boolean | ||
// literals actually end up widening to 'boolean' (see #48363). | ||
if (inferenceContext.returnMapper) { | ||
const type = instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper); | ||
return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ? | ||
filterType(type, t => t !== regularFalseType && t !== regularTrueType) : | ||
type; | ||
} | ||
const type = instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper); | ||
return type.flags & TypeFlags.Union && containsType((type as UnionType).types, regularFalseType) && containsType((type as UnionType).types, regularTrueType) ? | ||
filterType(type, t => t !== regularFalseType && t !== regularTrueType) : | ||
type; | ||
} | ||
} | ||
return contextualType; | ||
|
@@ -29902,29 +29906,43 @@ namespace ts { | |
// 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the | ||
// return type of 'wrap'. | ||
if (node.kind !== SyntaxKind.Decorator) { | ||
const contextualType = getContextualType(node, every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p)) ? ContextFlags.SkipBindingPatterns : ContextFlags.None); | ||
const skipBindingPatterns = every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p)); | ||
const contextualType = getContextualType(node, skipBindingPatterns ? ContextFlags.SkipBindingPatterns : ContextFlags.None); | ||
if (contextualType) { | ||
const inferenceTargetType = getReturnTypeOfSignature(signature); | ||
if (couldContainTypeVariables(inferenceTargetType)) { | ||
// We clone the inference context to avoid disturbing a resolution in progress for an | ||
// outer call expression. Effectively we just want a snapshot of whatever has been | ||
// inferred for any outer call expression so far. | ||
const outerContext = getInferenceContext(node); | ||
const outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, InferenceFlags.NoDefault)); | ||
const instantiatedType = instantiateType(contextualType, outerMapper); | ||
// If the contextual type is a generic function type with a single call signature, we | ||
// instantiate the type with its own type parameters and type arguments. This ensures that | ||
// the type parameters are not erased to type any during type inference such that they can | ||
// be inferred as actual types from the contextual type. For example: | ||
// declare function arrayMap<T, U>(f: (x: T) => U): (a: T[]) => U[]; | ||
// const boxElements: <A>(a: A[]) => { value: A }[] = arrayMap(value => ({ value })); | ||
// Above, the type of the 'value' parameter is inferred to be 'A'. | ||
const contextualSignature = getSingleCallSignature(instantiatedType); | ||
const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ? | ||
getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(contextualSignature, contextualSignature.typeParameters)) : | ||
instantiatedType; | ||
// Inferences made from return types have lower priority than all other inferences. | ||
inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType); | ||
const isFromBindingPattern = !skipBindingPatterns && getContextualType(node, ContextFlags.SkipBindingPatterns) !== contextualType; | ||
// A return type inference from a binding pattern can be used in instantiating the contextual | ||
// type of an argument later in inference, but cannot stand on its own as the final return type. | ||
// It is incorporated into `context.returnMapper` which is used in `instantiateContextualType`, | ||
// but doesn't need to go into `context.inferences`. This allows a an array binding pattern to | ||
// produce a tuple for `T` in | ||
// declare function f<T>(cb: () => T): T; | ||
// const [e1, e2, e3] = f(() => [1, "hi", true]); | ||
// but does not produce any inference for `T` in | ||
// declare function f<T>(): T; | ||
// const [e1, e2, e3] = f(); | ||
if (!isFromBindingPattern) { | ||
// We clone the inference context to avoid disturbing a resolution in progress for an | ||
// outer call expression. Effectively we just want a snapshot of whatever has been | ||
// inferred for any outer call expression so far. | ||
const outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, InferenceFlags.NoDefault)); | ||
const instantiatedType = instantiateType(contextualType, outerMapper); | ||
// If the contextual type is a generic function type with a single call signature, we | ||
// instantiate the type with its own type parameters and type arguments. This ensures that | ||
// the type parameters are not erased to type any during type inference such that they can | ||
// be inferred as actual types from the contextual type. For example: | ||
// declare function arrayMap<T, U>(f: (x: T) => U): (a: T[]) => U[]; | ||
// const boxElements: <A>(a: A[]) => { value: A }[] = arrayMap(value => ({ value })); | ||
// Above, the type of the 'value' parameter is inferred to be 'A'. | ||
const contextualSignature = getSingleCallSignature(instantiatedType); | ||
const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ? | ||
getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(contextualSignature, contextualSignature.typeParameters)) : | ||
instantiatedType; | ||
// Inferences made from return types have lower priority than all other inferences. | ||
inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType); | ||
} | ||
// Create a type mapper for instantiating generic contextual types using the inferences made | ||
// from the return type. We need a separate inference pass here because (a) instantiation of | ||
// the source type uses the outer context's return mapper (which excludes inferences made from | ||
|
@@ -34485,7 +34503,7 @@ namespace ts { | |
const type = checkExpression(node, checkMode, forceTuple); | ||
return isConstContext(node) || isCommonJsExportedExpression(node) ? getRegularTypeOfLiteralType(type) : | ||
isTypeAssertion(node) ? type : | ||
getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(arguments.length === 2 ? getContextualType(node) : contextualType, node)); | ||
getWidenedLiteralLikeTypeForContextualType(type, arguments.length === 2 ? getContextualType(node) : contextualType, node); | ||
} | ||
|
||
function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts(2,7): error TS2571: Object is of type 'unknown'. | ||
tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts(3,9): error TS2339: Property 'p1' does not exist on type 'unknown'. | ||
tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts(4,7): error TS2461: Type 'unknown' is not an array type. | ||
tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts(4,7): error TS2571: Object is of type 'unknown'. | ||
tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts(5,7): error TS2461: Type 'unknown' is not an array type. | ||
|
||
|
||
==== tests/cases/compiler/bindingPatternCannotBeOnlyInferenceSource.ts (5 errors) ==== | ||
declare function f<T>(): T; | ||
const {} = f(); // error (only in strictNullChecks) | ||
~~ | ||
!!! error TS2571: Object is of type 'unknown'. | ||
const { p1 } = f(); // error | ||
~~ | ||
!!! error TS2339: Property 'p1' does not exist on type 'unknown'. | ||
const [] = f(); // error | ||
~~ | ||
!!! error TS2461: Type 'unknown' is not an array type. | ||
~~ | ||
!!! error TS2571: Object is of type 'unknown'. | ||
const [e1, e2] = f(); // error | ||
~~~~~~~~ | ||
!!! error TS2461: Type 'unknown' is not an array type. | ||
|
||
// Repro from #43605 | ||
type Dispatch<A = { type: any; [extraProps: string]: any }> = { <T extends A>(action: T): T }; | ||
type IFuncs = { readonly [key: string]: (...p: any) => void }; | ||
type IDestructuring<T extends IFuncs> = { readonly [key in keyof T]?: (...p: Parameters<T[key]>) => void }; | ||
type Destructuring<T extends IFuncs, U extends IDestructuring<T>> = (dispatch: Dispatch<any>, funcs: T) => U; | ||
const funcs1 = { | ||
funcA: (a: boolean): void => {}, | ||
funcB: (b: string, bb: string): void => {}, | ||
funcC: (c: number, cc: number, ccc: boolean): void => {}, | ||
}; | ||
type TFuncs1 = typeof funcs1; | ||
declare function useReduxDispatch1<T extends IDestructuring<TFuncs1>>(destructuring: Destructuring<TFuncs1, T>): T; | ||
const {} = useReduxDispatch1( | ||
(d, f) => ({ | ||
funcA: (...p) => d(f.funcA(...p)), // p should be inferrable | ||
funcB: (...p) => d(f.funcB(...p)), | ||
funcC: (...p) => d(f.funcC(...p)), | ||
}) | ||
); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
//// [bindingPatternCannotBeOnlyInferenceSource.ts] | ||
declare function f<T>(): T; | ||
const {} = f(); // error (only in strictNullChecks) | ||
const { p1 } = f(); // error | ||
const [] = f(); // error | ||
const [e1, e2] = f(); // error | ||
|
||
// Repro from #43605 | ||
type Dispatch<A = { type: any; [extraProps: string]: any }> = { <T extends A>(action: T): T }; | ||
type IFuncs = { readonly [key: string]: (...p: any) => void }; | ||
type IDestructuring<T extends IFuncs> = { readonly [key in keyof T]?: (...p: Parameters<T[key]>) => void }; | ||
type Destructuring<T extends IFuncs, U extends IDestructuring<T>> = (dispatch: Dispatch<any>, funcs: T) => U; | ||
const funcs1 = { | ||
funcA: (a: boolean): void => {}, | ||
funcB: (b: string, bb: string): void => {}, | ||
funcC: (c: number, cc: number, ccc: boolean): void => {}, | ||
}; | ||
type TFuncs1 = typeof funcs1; | ||
declare function useReduxDispatch1<T extends IDestructuring<TFuncs1>>(destructuring: Destructuring<TFuncs1, T>): T; | ||
const {} = useReduxDispatch1( | ||
(d, f) => ({ | ||
funcA: (...p) => d(f.funcA(...p)), // p should be inferrable | ||
funcB: (...p) => d(f.funcB(...p)), | ||
funcC: (...p) => d(f.funcC(...p)), | ||
}) | ||
); | ||
|
||
|
||
//// [bindingPatternCannotBeOnlyInferenceSource.js] | ||
var _a = f(); // error (only in strictNullChecks) | ||
var p1 = f().p1; // error | ||
var _b = f(); // error | ||
var _c = f(), e1 = _c[0], e2 = _c[1]; // error | ||
var funcs1 = { | ||
funcA: function (a) { }, | ||
funcB: function (b, bb) { }, | ||
funcC: function (c, cc, ccc) { } | ||
}; | ||
var _d = useReduxDispatch1(function (d, f) { return ({ | ||
funcA: function () { | ||
var p = []; | ||
for (var _i = 0; _i < arguments.length; _i++) { | ||
p[_i] = arguments[_i]; | ||
} | ||
return d(f.funcA.apply(f, p)); | ||
}, | ||
funcB: function () { | ||
var p = []; | ||
for (var _i = 0; _i < arguments.length; _i++) { | ||
p[_i] = arguments[_i]; | ||
} | ||
return d(f.funcB.apply(f, p)); | ||
}, | ||
funcC: function () { | ||
var p = []; | ||
for (var _i = 0; _i < arguments.length; _i++) { | ||
p[_i] = arguments[_i]; | ||
} | ||
return d(f.funcC.apply(f, p)); | ||
} | ||
}); }); |
Uh oh!
There was an error while loading. Please reload this page.