From 3107e1142380e4bb3b2bfe3522b9b094624bef57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 31 Jan 2025 21:20:27 +0100 Subject: [PATCH] Reuse `getEffectiveTypeParameterHost` in `isTypeParameterPossiblyReferenced` --- src/compiler/checker.ts | 23 ++-- ...omReturnOfContextSensitiveFnJsDoc1.symbols | 70 +++++++++++ ...FromReturnOfContextSensitiveFnJsDoc1.types | 118 ++++++++++++++++++ ...ferFromReturnOfContextSensitiveFnJsDoc1.ts | 42 +++++++ 4 files changed, 244 insertions(+), 9 deletions(-) create mode 100644 tests/baselines/reference/inferFromReturnOfContextSensitiveFnJsDoc1.symbols create mode 100644 tests/baselines/reference/inferFromReturnOfContextSensitiveFnJsDoc1.types create mode 100644 tests/cases/compiler/inferFromReturnOfContextSensitiveFnJsDoc1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d214b4f60579f..518ebce5d73bb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16217,8 +16217,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getEffectiveTypeParameterHost(typeParameter: TypeParameter) { - const tp = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter)!; - return isJSDocTemplateTag(tp.parent) ? getEffectiveJSDocHost(tp.parent) : tp.parent; + const declarations = typeParameter.symbol.declarations; + if (!declarations || declarations.length !== 1) { + return; + } + const tpDeclaration = getDeclarationOfKind(typeParameter.symbol, SyntaxKind.TypeParameter); + if (!tpDeclaration) { + // Type parameter is the this type, and its declaration is the class declaration. + return typeParameter.isThisType ? declarations[0].parent : undefined; + } + return isJSDocTemplateTag(tpDeclaration.parent) ? getEffectiveJSDocHost(tpDeclaration.parent) : tpDeclaration.parent; } function getParentSymbolOfTypeParameter(typeParameter: TypeParameter): Symbol | undefined { @@ -20193,7 +20201,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // type parameter, or if the node contains type queries that we can't prove couldn't contain references to the type parameter, // we consider the type parameter possibly referenced. if (tp.symbol && tp.symbol.declarations && tp.symbol.declarations.length === 1) { - const container = tp.symbol.declarations[0].parent; + const container = getEffectiveTypeParameterHost(tp); for (let n = node; n !== container; n = n.parent) { if (!n || n.kind === SyntaxKind.Block || n.kind === SyntaxKind.ConditionalType && forEachChild((n as ConditionalTypeNode).extendsType, containsReference)) { return true; @@ -20214,12 +20222,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const firstIdentifier = getFirstIdentifier(entityName); if (!isThisIdentifier(firstIdentifier)) { // Don't attempt to analyze typeof this.xxx const firstIdentifierSymbol = getResolvedSymbol(firstIdentifier); - const tpDeclaration = tp.symbol.declarations![0]; // There is exactly one declaration, otherwise `containsReference` is not called - const tpScope = tpDeclaration.kind === SyntaxKind.TypeParameter ? tpDeclaration.parent : // Type parameter is a regular type parameter, e.g. foo - tp.isThisType ? tpDeclaration : // Type parameter is the this type, and its declaration is the class declaration. - undefined; // Type parameter's declaration was unrecognized, e.g. comes from JSDoc annotation. - if (firstIdentifierSymbol.declarations && tpScope) { - return some(firstIdentifierSymbol.declarations, idDecl => isNodeDescendantOf(idDecl, tpScope)) || + const tpHost = getEffectiveTypeParameterHost(tp); + if (firstIdentifierSymbol.declarations && tpHost) { + return some(firstIdentifierSymbol.declarations, idDecl => isNodeDescendantOf(idDecl, tpHost)) || some((node as TypeQueryNode).typeArguments, containsReference); } } diff --git a/tests/baselines/reference/inferFromReturnOfContextSensitiveFnJsDoc1.symbols b/tests/baselines/reference/inferFromReturnOfContextSensitiveFnJsDoc1.symbols new file mode 100644 index 0000000000000..ae52eea8f04c6 --- /dev/null +++ b/tests/baselines/reference/inferFromReturnOfContextSensitiveFnJsDoc1.symbols @@ -0,0 +1,70 @@ +//// [tests/cases/compiler/inferFromReturnOfContextSensitiveFnJsDoc1.ts] //// + +=== index.js === +/** + * @template S + * @param {(arg0: { observer: EO }) => S} callback + * @param {Options} [options] + * @returns {VC} + */ +/* + * @type { (fn: (arg0: { observer: EO; }) => S, options?: Options) => VC } + */ +function define(callback, options) { +>define : Symbol(define, Decl(index.js, 0, 0)) +>callback : Symbol(callback, Decl(index.js, 9, 16)) +>options : Symbol(options, Decl(index.js, 9, 25)) + + const { name } = options ?? {}; +>name : Symbol(name, Decl(index.js, 10, 9)) +>options : Symbol(options, Decl(index.js, 9, 25)) + + const observer = new EO(); +>observer : Symbol(observer, Decl(index.js, 11, 7)) +>EO : Symbol(EO, Decl(index.js, 28, 1)) + + const state = callback({ observer }); +>state : Symbol(state, Decl(index.js, 12, 7)) +>callback : Symbol(callback, Decl(index.js, 9, 16)) +>observer : Symbol(observer, Decl(index.js, 12, 26)) + + return new VC(state); +>VC : Symbol(VC, Decl(index.js, 14, 1)) +>state : Symbol(state, Decl(index.js, 12, 7)) +} + +/** + * @template S + */ +class VC { +>VC : Symbol(VC, Decl(index.js, 14, 1)) + + /** @type {S} */ + state; +>state : Symbol(VC.state, Decl(index.js, 19, 10)) + + /** + * @param {S} state + */ + constructor(state) { +>state : Symbol(state, Decl(index.js, 25, 14)) + + this.state = state; +>this.state : Symbol(VC.state, Decl(index.js, 19, 10)) +>this : Symbol(VC, Decl(index.js, 14, 1)) +>state : Symbol(VC.state, Decl(index.js, 19, 10)) +>state : Symbol(state, Decl(index.js, 25, 14)) + } +} + +/** @typedef {{ name?: string }} Options */ + +class EO {} +>EO : Symbol(EO, Decl(index.js, 28, 1)) + +const v1 = define((arg0) => true, { name: "default" }); +>v1 : Symbol(v1, Decl(index.js, 34, 5)) +>define : Symbol(define, Decl(index.js, 0, 0)) +>arg0 : Symbol(arg0, Decl(index.js, 34, 19)) +>name : Symbol(name, Decl(index.js, 34, 35)) + diff --git a/tests/baselines/reference/inferFromReturnOfContextSensitiveFnJsDoc1.types b/tests/baselines/reference/inferFromReturnOfContextSensitiveFnJsDoc1.types new file mode 100644 index 0000000000000..03f766224f8e7 --- /dev/null +++ b/tests/baselines/reference/inferFromReturnOfContextSensitiveFnJsDoc1.types @@ -0,0 +1,118 @@ +//// [tests/cases/compiler/inferFromReturnOfContextSensitiveFnJsDoc1.ts] //// + +=== index.js === +/** + * @template S + * @param {(arg0: { observer: EO }) => S} callback + * @param {Options} [options] + * @returns {VC} + */ +/* + * @type { (fn: (arg0: { observer: EO; }) => S, options?: Options) => VC } + */ +function define(callback, options) { +>define : (callback: (arg0: { observer: EO; }) => S, options?: Options) => VC +> : ^ ^^ ^^ ^^ ^^^ ^^^^^ +>callback : (arg0: { observer: EO; }) => S +> : ^ ^^ ^^^^^ +>options : Options | undefined +> : ^^^^^^^^^^^^^^^^^^^ + + const { name } = options ?? {}; +>name : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>options ?? {} : Options +> : ^^^^^^^ +>options : Options | undefined +> : ^^^^^^^^^^^^^^^^^^^ +>{} : {} +> : ^^ + + const observer = new EO(); +>observer : EO +> : ^^ +>new EO() : EO +> : ^^ +>EO : typeof EO +> : ^^^^^^^^^ + + const state = callback({ observer }); +>state : S +> : ^ +>callback({ observer }) : S +> : ^ +>callback : (arg0: { observer: EO; }) => S +> : ^ ^^ ^^^^^ +>{ observer } : { observer: EO; } +> : ^^^^^^^^^^^^^^^^^ +>observer : EO +> : ^^ + + return new VC(state); +>new VC(state) : VC +> : ^^^^^ +>VC : typeof VC +> : ^^^^^^^^^ +>state : S +> : ^ +} + +/** + * @template S + */ +class VC { +>VC : VC +> : ^^^^^ + + /** @type {S} */ + state; +>state : S +> : ^ + + /** + * @param {S} state + */ + constructor(state) { +>state : S +> : ^ + + this.state = state; +>this.state = state : S +> : ^ +>this.state : S +> : ^ +>this : this +> : ^^^^ +>state : S +> : ^ +>state : S +> : ^ + } +} + +/** @typedef {{ name?: string }} Options */ + +class EO {} +>EO : EO +> : ^^ + +const v1 = define((arg0) => true, { name: "default" }); +>v1 : VC +> : ^^^^^^^^^^^ +>define((arg0) => true, { name: "default" }) : VC +> : ^^^^^^^^^^^ +>define : (callback: (arg0: { observer: EO; }) => S, options?: Options) => VC +> : ^ ^^ ^^ ^^ ^^^ ^^^^^ +>(arg0) => true : (arg0: { observer: EO; }) => boolean +> : ^ ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ +>arg0 : { observer: EO; } +> : ^^^^^^^^^^^^ ^^^ +>true : true +> : ^^^^ +>{ name: "default" } : { name: string; } +> : ^^^^^^^^^^^^^^^^^ +>name : string +> : ^^^^^^ +>"default" : "default" +> : ^^^^^^^^^ + diff --git a/tests/cases/compiler/inferFromReturnOfContextSensitiveFnJsDoc1.ts b/tests/cases/compiler/inferFromReturnOfContextSensitiveFnJsDoc1.ts new file mode 100644 index 0000000000000..b82b4807c1eec --- /dev/null +++ b/tests/cases/compiler/inferFromReturnOfContextSensitiveFnJsDoc1.ts @@ -0,0 +1,42 @@ +// @strict: true +// @noEmit: true +// @checkJs: true +// @allowJs: true + +// @filename: index.js + +/** + * @template S + * @param {(arg0: { observer: EO }) => S} callback + * @param {Options} [options] + * @returns {VC} + */ +/* + * @type { (fn: (arg0: { observer: EO; }) => S, options?: Options) => VC } + */ +function define(callback, options) { + const { name } = options ?? {}; + const observer = new EO(); + const state = callback({ observer }); + return new VC(state); +} + +/** + * @template S + */ +class VC { + /** @type {S} */ + state; + /** + * @param {S} state + */ + constructor(state) { + this.state = state; + } +} + +/** @typedef {{ name?: string }} Options */ + +class EO {} + +const v1 = define((arg0) => true, { name: "default" }); \ No newline at end of file