From 934f477d07eaf00f55971e4d800984d19d7dfb09 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 9 Jul 2020 12:11:20 -0700 Subject: [PATCH] Fix @param type parameter lookup Previously, getObjectTypeInstantiation had special-case code to look up type parameters for `@param` as if they were in the parameter location. This should occur in the main lookup loop of `getOuterTypeParameters`, however. The current code only runs once, which is not sufficient, and it also jumps to the parameter for any type contained in a `@param`, which skips type parameters that occur in the tag itself. --- src/compiler/checker.ts | 20 ++++++-------- .../reference/paramTagTypeResolution2.symbols | 21 +++++++++++++++ .../reference/paramTagTypeResolution2.types | 26 +++++++++++++++++++ .../jsdoc/paramTagTypeResolution2.ts | 14 ++++++++++ 4 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 tests/baselines/reference/paramTagTypeResolution2.symbols create mode 100644 tests/baselines/reference/paramTagTypeResolution2.types create mode 100644 tests/cases/conformance/jsdoc/paramTagTypeResolution2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 312bbff48466e..606ea1f7bf38d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8755,6 +8755,12 @@ namespace ts { (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration || isJSConstructor(node)) && getDeclaredTypeOfClassOrInterface(getSymbolOfNode(node as ClassLikeDeclaration | InterfaceDeclaration)).thisType; return thisType ? append(outerAndOwnTypeParameters, thisType) : outerAndOwnTypeParameters; + case SyntaxKind.JSDocParameterTag: + const paramSymbol = getParameterSymbolFromJSDoc(node as JSDocParameterTag); + if (paramSymbol) { + node = paramSymbol.valueDeclaration; + } + break; } } } @@ -14581,24 +14587,14 @@ namespace ts { function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper) { const target = type.objectFlags & ObjectFlags.Instantiated ? type.target! : type; - const node = type.objectFlags & ObjectFlags.Reference ? (type).node! : type.symbol.declarations[0]; - const links = getNodeLinks(node); + const declaration = type.objectFlags & ObjectFlags.Reference ? (type).node! : type.symbol.declarations[0]; + const links = getNodeLinks(declaration); let typeParameters = links.outerTypeParameters; if (!typeParameters) { // The first time an anonymous type is instantiated we compute and store a list of the type // parameters that are in scope (and therefore potentially referenced). For type literals that // aren't the right hand side of a generic type alias declaration we optimize by reducing the // set of type parameters to those that are possibly referenced in the literal. - let declaration = node; - if (isInJSFile(declaration)) { - const paramTag = findAncestor(declaration, isJSDocParameterTag); - if (paramTag) { - const paramSymbol = getParameterSymbolFromJSDoc(paramTag); - if (paramSymbol) { - declaration = paramSymbol.valueDeclaration; - } - } - } let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true); if (isJSConstructor(declaration)) { const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters); diff --git a/tests/baselines/reference/paramTagTypeResolution2.symbols b/tests/baselines/reference/paramTagTypeResolution2.symbols new file mode 100644 index 0000000000000..dd1632606cf8e --- /dev/null +++ b/tests/baselines/reference/paramTagTypeResolution2.symbols @@ -0,0 +1,21 @@ +=== tests/cases/conformance/jsdoc/38572.js === +/** + * @template T + * @param {T} a + * @param {{[K in keyof T]: (value: T[K]) => void }} b + */ +function f(a, b) { +>f : Symbol(f, Decl(38572.js, 0, 0)) +>a : Symbol(a, Decl(38572.js, 5, 11)) +>b : Symbol(b, Decl(38572.js, 5, 13)) +} + +f({ x: 42 }, { x(param) { param.toFixed() } }); +>f : Symbol(f, Decl(38572.js, 0, 0)) +>x : Symbol(x, Decl(38572.js, 8, 3)) +>x : Symbol(x, Decl(38572.js, 8, 14)) +>param : Symbol(param, Decl(38572.js, 8, 17)) +>param.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>param : Symbol(param, Decl(38572.js, 8, 17)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + diff --git a/tests/baselines/reference/paramTagTypeResolution2.types b/tests/baselines/reference/paramTagTypeResolution2.types new file mode 100644 index 0000000000000..e3cb0358ed6fa --- /dev/null +++ b/tests/baselines/reference/paramTagTypeResolution2.types @@ -0,0 +1,26 @@ +=== tests/cases/conformance/jsdoc/38572.js === +/** + * @template T + * @param {T} a + * @param {{[K in keyof T]: (value: T[K]) => void }} b + */ +function f(a, b) { +>f : (a: T, b: { [K in keyof T]: (value: T[K]) => void; }) => void +>a : T +>b : { [K in keyof T]: (value: T[K]) => void; } +} + +f({ x: 42 }, { x(param) { param.toFixed() } }); +>f({ x: 42 }, { x(param) { param.toFixed() } }) : void +>f : (a: T, b: { [K in keyof T]: (value: T[K]) => void; }) => void +>{ x: 42 } : { x: number; } +>x : number +>42 : 42 +>{ x(param) { param.toFixed() } } : { x(param: number): void; } +>x : (param: number) => void +>param : number +>param.toFixed() : string +>param.toFixed : (fractionDigits?: number) => string +>param : number +>toFixed : (fractionDigits?: number) => string + diff --git a/tests/cases/conformance/jsdoc/paramTagTypeResolution2.ts b/tests/cases/conformance/jsdoc/paramTagTypeResolution2.ts new file mode 100644 index 0000000000000..a1cd30426b7a4 --- /dev/null +++ b/tests/cases/conformance/jsdoc/paramTagTypeResolution2.ts @@ -0,0 +1,14 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: 38572.js + +/** + * @template T + * @param {T} a + * @param {{[K in keyof T]: (value: T[K]) => void }} b + */ +function f(a, b) { +} + +f({ x: 42 }, { x(param) { param.toFixed() } });