diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6bc966a11cb..582687530fc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2294,10 +2294,17 @@ namespace ts { return type && (type.flags & TypeFlags.Any) !== 0; } + // Return the type of a binding element parent. We check SymbolLinks first to see if a type has been + // assigned by contextual typing. + function getTypeForBindingElementParent(node: VariableLikeDeclaration) { + let symbol = getSymbolOfNode(node); + return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node); + } + // Return the inferred type for a binding element function getTypeForBindingElement(declaration: BindingElement): Type { let pattern = declaration.parent; - let parentType = getTypeForVariableLikeDeclaration(pattern.parent); + let parentType = getTypeForBindingElementParent(pattern.parent); // If parent has the unknown (error) type, then so does this binding element if (parentType === unknownType) { return unknownType; @@ -9163,10 +9170,24 @@ namespace ts { } } + // When contextual typing assigns a type to a parameter that contains a binding pattern, we also need to push + // the destructured type into the contained binding elements. + function assignBindingElementTypes(node: VariableLikeDeclaration) { + if (isBindingPattern(node.name)) { + for (let element of (node.name).elements) { + if (element.kind !== SyntaxKind.OmittedExpression) { + getSymbolLinks(getSymbolOfNode(element)).type = getTypeForBindingElement(element); + assignBindingElementTypes(element); + } + } + } + } + function assignTypeToParameterAndFixTypeParameters(parameter: Symbol, contextualType: Type, mapper: TypeMapper) { let links = getSymbolLinks(parameter); if (!links.type) { links.type = instantiateType(contextualType, mapper); + assignBindingElementTypes(parameter.valueDeclaration); } else if (isInferentialContext(mapper)) { // Even if the parameter already has a type, it might be because it was given a type while diff --git a/tests/baselines/reference/destructuringWithGenericParameter.js b/tests/baselines/reference/destructuringWithGenericParameter.js new file mode 100644 index 00000000000..61d523f81fa --- /dev/null +++ b/tests/baselines/reference/destructuringWithGenericParameter.js @@ -0,0 +1,30 @@ +//// [destructuringWithGenericParameter.ts] +class GenericClass { + payload: T; +} + +var genericObject = new GenericClass<{ greeting: string }>(); + +function genericFunction(object: GenericClass, callback: (payload: T) => void) { + callback(object.payload); +} + +genericFunction(genericObject, ({greeting}) => { + var s = greeting.toLocaleLowerCase(); // Greeting should be of type string +}); + + +//// [destructuringWithGenericParameter.js] +var GenericClass = (function () { + function GenericClass() { + } + return GenericClass; +})(); +var genericObject = new GenericClass(); +function genericFunction(object, callback) { + callback(object.payload); +} +genericFunction(genericObject, function (_a) { + var greeting = _a.greeting; + var s = greeting.toLocaleLowerCase(); // Greeting should be of type string +}); diff --git a/tests/baselines/reference/destructuringWithGenericParameter.symbols b/tests/baselines/reference/destructuringWithGenericParameter.symbols new file mode 100644 index 00000000000..941cb0bf237 --- /dev/null +++ b/tests/baselines/reference/destructuringWithGenericParameter.symbols @@ -0,0 +1,45 @@ +=== tests/cases/compiler/destructuringWithGenericParameter.ts === +class GenericClass { +>GenericClass : Symbol(GenericClass, Decl(destructuringWithGenericParameter.ts, 0, 0)) +>T : Symbol(T, Decl(destructuringWithGenericParameter.ts, 0, 19)) + + payload: T; +>payload : Symbol(payload, Decl(destructuringWithGenericParameter.ts, 0, 23)) +>T : Symbol(T, Decl(destructuringWithGenericParameter.ts, 0, 19)) +} + +var genericObject = new GenericClass<{ greeting: string }>(); +>genericObject : Symbol(genericObject, Decl(destructuringWithGenericParameter.ts, 4, 3)) +>GenericClass : Symbol(GenericClass, Decl(destructuringWithGenericParameter.ts, 0, 0)) +>greeting : Symbol(greeting, Decl(destructuringWithGenericParameter.ts, 4, 38)) + +function genericFunction(object: GenericClass, callback: (payload: T) => void) { +>genericFunction : Symbol(genericFunction, Decl(destructuringWithGenericParameter.ts, 4, 61)) +>T : Symbol(T, Decl(destructuringWithGenericParameter.ts, 6, 25)) +>object : Symbol(object, Decl(destructuringWithGenericParameter.ts, 6, 28)) +>GenericClass : Symbol(GenericClass, Decl(destructuringWithGenericParameter.ts, 0, 0)) +>T : Symbol(T, Decl(destructuringWithGenericParameter.ts, 6, 25)) +>callback : Symbol(callback, Decl(destructuringWithGenericParameter.ts, 6, 52)) +>payload : Symbol(payload, Decl(destructuringWithGenericParameter.ts, 6, 64)) +>T : Symbol(T, Decl(destructuringWithGenericParameter.ts, 6, 25)) + + callback(object.payload); +>callback : Symbol(callback, Decl(destructuringWithGenericParameter.ts, 6, 52)) +>object.payload : Symbol(GenericClass.payload, Decl(destructuringWithGenericParameter.ts, 0, 23)) +>object : Symbol(object, Decl(destructuringWithGenericParameter.ts, 6, 28)) +>payload : Symbol(GenericClass.payload, Decl(destructuringWithGenericParameter.ts, 0, 23)) +} + +genericFunction(genericObject, ({greeting}) => { +>genericFunction : Symbol(genericFunction, Decl(destructuringWithGenericParameter.ts, 4, 61)) +>genericObject : Symbol(genericObject, Decl(destructuringWithGenericParameter.ts, 4, 3)) +>greeting : Symbol(greeting, Decl(destructuringWithGenericParameter.ts, 10, 33)) + + var s = greeting.toLocaleLowerCase(); // Greeting should be of type string +>s : Symbol(s, Decl(destructuringWithGenericParameter.ts, 11, 7)) +>greeting.toLocaleLowerCase : Symbol(String.toLocaleLowerCase, Decl(lib.d.ts, 402, 26)) +>greeting : Symbol(greeting, Decl(destructuringWithGenericParameter.ts, 10, 33)) +>toLocaleLowerCase : Symbol(String.toLocaleLowerCase, Decl(lib.d.ts, 402, 26)) + +}); + diff --git a/tests/baselines/reference/destructuringWithGenericParameter.types b/tests/baselines/reference/destructuringWithGenericParameter.types new file mode 100644 index 00000000000..07a86e679a9 --- /dev/null +++ b/tests/baselines/reference/destructuringWithGenericParameter.types @@ -0,0 +1,50 @@ +=== tests/cases/compiler/destructuringWithGenericParameter.ts === +class GenericClass { +>GenericClass : GenericClass +>T : T + + payload: T; +>payload : T +>T : T +} + +var genericObject = new GenericClass<{ greeting: string }>(); +>genericObject : GenericClass<{ greeting: string; }> +>new GenericClass<{ greeting: string }>() : GenericClass<{ greeting: string; }> +>GenericClass : typeof GenericClass +>greeting : string + +function genericFunction(object: GenericClass, callback: (payload: T) => void) { +>genericFunction : (object: GenericClass, callback: (payload: T) => void) => void +>T : T +>object : GenericClass +>GenericClass : GenericClass +>T : T +>callback : (payload: T) => void +>payload : T +>T : T + + callback(object.payload); +>callback(object.payload) : void +>callback : (payload: T) => void +>object.payload : T +>object : GenericClass +>payload : T +} + +genericFunction(genericObject, ({greeting}) => { +>genericFunction(genericObject, ({greeting}) => { var s = greeting.toLocaleLowerCase(); // Greeting should be of type string}) : void +>genericFunction : (object: GenericClass, callback: (payload: T) => void) => void +>genericObject : GenericClass<{ greeting: string; }> +>({greeting}) => { var s = greeting.toLocaleLowerCase(); // Greeting should be of type string} : ({greeting}: { greeting: string; }) => void +>greeting : string + + var s = greeting.toLocaleLowerCase(); // Greeting should be of type string +>s : string +>greeting.toLocaleLowerCase() : string +>greeting.toLocaleLowerCase : () => string +>greeting : string +>toLocaleLowerCase : () => string + +}); + diff --git a/tests/cases/compiler/destructuringWithGenericParameter.ts b/tests/cases/compiler/destructuringWithGenericParameter.ts new file mode 100644 index 00000000000..99b5bfade36 --- /dev/null +++ b/tests/cases/compiler/destructuringWithGenericParameter.ts @@ -0,0 +1,13 @@ +class GenericClass { + payload: T; +} + +var genericObject = new GenericClass<{ greeting: string }>(); + +function genericFunction(object: GenericClass, callback: (payload: T) => void) { + callback(object.payload); +} + +genericFunction(genericObject, ({greeting}) => { + var s = greeting.toLocaleLowerCase(); // Greeting should be of type string +}); diff --git a/tests/cases/fourslash/parameterWithDestructuring.ts b/tests/cases/fourslash/parameterWithDestructuring.ts new file mode 100644 index 00000000000..72dfefd553a --- /dev/null +++ b/tests/cases/fourslash/parameterWithDestructuring.ts @@ -0,0 +1,22 @@ +/// + +// Repros from issues #4949 and #4818 + +////const result = [{ foo: 'hello' }] +//// .map(({ /*1*/foo }) => /*2*/foo) +//// .map(foo => foo); +//// +////const f = (foo: (bar: string[]) => void) => { }; +//// +////f(([a, b]) => { +//// /*3*/a.charAt(0); // Not okay: inferred as `any` +////}); + +goTo.marker('1'); +verify.quickInfoIs('var foo: string'); + +goTo.marker('2'); +verify.quickInfoIs('var foo: string'); + +goTo.marker('3'); +verify.quickInfoIs('var a: string');