Skip to content

Commit

Permalink
Reuse getEffectiveTypeParameterHost in `isTypeParameterPossiblyRefe…
Browse files Browse the repository at this point in the history
…renced`
  • Loading branch information
Andarist committed Jan 31, 2025
1 parent 230925a commit 3107e11
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 9 deletions.
23 changes: 14 additions & 9 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16217,8 +16217,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function getEffectiveTypeParameterHost(typeParameter: TypeParameter) {
const tp = getDeclarationOfKind<TypeParameterDeclaration>(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<TypeParameterDeclaration>(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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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<T>
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);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//// [tests/cases/compiler/inferFromReturnOfContextSensitiveFnJsDoc1.ts] ////

=== index.js ===
/**
* @template S
* @param {(arg0: { observer: EO }) => S} callback
* @param {Options} [options]
* @returns {VC<S>}
*/
/*
* @type { <S>(fn: (arg0: { observer: EO; }) => S, options?: Options) => VC<S> }
*/
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))

Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//// [tests/cases/compiler/inferFromReturnOfContextSensitiveFnJsDoc1.ts] ////

=== index.js ===
/**
* @template S
* @param {(arg0: { observer: EO }) => S} callback
* @param {Options} [options]
* @returns {VC<S>}
*/
/*
* @type { <S>(fn: (arg0: { observer: EO; }) => S, options?: Options) => VC<S> }
*/
function define(callback, options) {
>define : <S>(callback: (arg0: { observer: EO; }) => S, options?: Options) => VC<S>
> : ^ ^^ ^^ ^^ ^^^ ^^^^^
>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<S>
> : ^^^^^
>VC : typeof VC
> : ^^^^^^^^^
>state : S
> : ^
}

/**
* @template S
*/
class VC {
>VC : VC<S>
> : ^^^^^

/** @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<boolean>
> : ^^^^^^^^^^^
>define((arg0) => true, { name: "default" }) : VC<boolean>
> : ^^^^^^^^^^^
>define : <S>(callback: (arg0: { observer: EO; }) => S, options?: Options) => VC<S>
> : ^ ^^ ^^ ^^ ^^^ ^^^^^
>(arg0) => true : (arg0: { observer: EO; }) => boolean
> : ^ ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
>arg0 : { observer: EO; }
> : ^^^^^^^^^^^^ ^^^
>true : true
> : ^^^^
>{ name: "default" } : { name: string; }
> : ^^^^^^^^^^^^^^^^^
>name : string
> : ^^^^^^
>"default" : "default"
> : ^^^^^^^^^

42 changes: 42 additions & 0 deletions tests/cases/compiler/inferFromReturnOfContextSensitiveFnJsDoc1.ts
Original file line number Diff line number Diff line change
@@ -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<S>}
*/
/*
* @type { <S>(fn: (arg0: { observer: EO; }) => S, options?: Options) => VC<S> }
*/
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" });

0 comments on commit 3107e11

Please sign in to comment.