From 471e680ef087ce789698162e7c0ee74d585b8859 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 6 Jun 2017 18:10:00 -0700 Subject: [PATCH 1/4] Better types from jsdoc param tags --- src/compiler/checker.ts | 73 +++--- .../typeFromParamTagForFunction.symbols | 184 +++++++++++++++ .../typeFromParamTagForFunction.types | 223 ++++++++++++++++++ .../salsa/typeFromParamTagForFunction.ts | 92 ++++++++ 4 files changed, 538 insertions(+), 34 deletions(-) create mode 100644 tests/baselines/reference/typeFromParamTagForFunction.symbols create mode 100644 tests/baselines/reference/typeFromParamTagForFunction.types create mode 100644 tests/cases/conformance/salsa/typeFromParamTagForFunction.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7427d921cf200..c7e7016799703 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6814,37 +6814,53 @@ namespace ts { return undefined; } - function resolveTypeReferenceName(typeReferenceName: EntityNameExpression | EntityName) { + function resolveTypeReferenceName(node: TypeReferenceType, typeReferenceName: EntityNameExpression | EntityName) { if (!typeReferenceName) { return unknownSymbol; } - return resolveEntityName(typeReferenceName, SymbolFlags.Type) || unknownSymbol; + const meaning = node.kind === SyntaxKind.JSDocTypeReference + ? SymbolFlags.Type | SymbolFlags.Value + : SymbolFlags.Type; + + return resolveEntityName(typeReferenceName, meaning) || unknownSymbol; } function getTypeReferenceType(node: TypeReferenceType, symbol: Symbol) { const typeArguments = typeArgumentsFromTypeReferenceNode(node); // Do unconditionally so we mark type arguments as referenced. + let fallbackType: Type = unknownType; + while (true) { + if (symbol === unknownSymbol) { + return fallbackType; + } - if (symbol === unknownSymbol) { - return unknownType; - } + if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments); + } - if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments); - } + if (symbol.flags & SymbolFlags.TypeAlias) { + return getTypeFromTypeAliasReference(node, symbol, typeArguments); + } - if (symbol.flags & SymbolFlags.TypeAlias) { - return getTypeFromTypeAliasReference(node, symbol, typeArguments); - } + if (symbol.flags & SymbolFlags.Value && node.kind === SyntaxKind.JSDocTypeReference) { + // A JSDocTypeReference may have resolved to a value (as opposed to a type). If + // the value has a construct signature, we use the return type of the construct + // signature as the type; otherwise, the type of this reference is just the type + // of the value we resolved to. + if (symbol.flags & SymbolFlags.Function && (symbol.members || getJSDocClassTag(symbol.valueDeclaration))) { + return getInferredClassType(symbol); + } - if (symbol.flags & SymbolFlags.Value && node.kind === SyntaxKind.JSDocTypeReference) { - // A JSDocTypeReference may have resolved to a value (as opposed to a type). In - // that case, the type of this reference is just the type of the value we resolved - // to. - return getTypeOfSymbol(symbol); - } + fallbackType = getTypeOfSymbol(symbol); + + // Try to use the symbol of the type (if present) to get a better type on the + // next pass. + symbol = fallbackType.symbol || unknownSymbol; + continue; + } - return getTypeFromNonGenericTypeReference(node, symbol); + return getTypeFromNonGenericTypeReference(node, symbol); + } } function getPrimitiveTypeFromJSDocTypeReference(node: JSDocTypeReference): Type { @@ -6888,21 +6904,10 @@ namespace ts { let symbol: Symbol; let type: Type; if (node.kind === SyntaxKind.JSDocTypeReference) { - type = getPrimitiveTypeFromJSDocTypeReference(node); - if (!type) { - const typeReferenceName = getTypeReferenceName(node); - symbol = resolveTypeReferenceName(typeReferenceName); - type = getTypeReferenceType(node, symbol); - } + type = getPrimitiveTypeFromJSDocTypeReference(node); } - else { - // We only support expressions that are simple qualified names. For other expressions this produces undefined. - const typeNameOrExpression: EntityNameOrEntityNameExpression = node.kind === SyntaxKind.TypeReference - ? (node).typeName - : isEntityNameExpression((node).expression) - ? (node).expression - : undefined; - symbol = typeNameOrExpression && resolveEntityName(typeNameOrExpression, SymbolFlags.Type) || unknownSymbol; + if (!type) { + symbol = resolveTypeReferenceName(node, getTypeReferenceName(node)); type = getTypeReferenceType(node, symbol); } // Cache both the resolved symbol and the resolved type. The resolved symbol is needed in when we check the @@ -19367,8 +19372,8 @@ namespace ts { function checkFunctionDeclaration(node: FunctionDeclaration): void { if (produceDiagnostics) { - checkFunctionOrMethodDeclaration(node) || checkGrammarForGenerator(node); - + checkFunctionOrMethodDeclaration(node); + checkGrammarForGenerator(node); checkCollisionWithCapturedSuperVariable(node, node.name); checkCollisionWithCapturedThisVariable(node, node.name); checkCollisionWithCapturedNewTargetVariable(node, node.name); diff --git a/tests/baselines/reference/typeFromParamTagForFunction.symbols b/tests/baselines/reference/typeFromParamTagForFunction.symbols new file mode 100644 index 0000000000000..0df0dfdc20663 --- /dev/null +++ b/tests/baselines/reference/typeFromParamTagForFunction.symbols @@ -0,0 +1,184 @@ +=== tests/cases/conformance/salsa/node.d.ts === +declare function require(id: string): any; +>require : Symbol(require, Decl(node.d.ts, 0, 0)) +>id : Symbol(id, Decl(node.d.ts, 0, 25)) + +declare var module: any, exports: any; +>module : Symbol(module, Decl(node.d.ts, 1, 11)) +>exports : Symbol(exports, Decl(node.d.ts, 1, 24)) + +=== tests/cases/conformance/salsa/a-ext.js === +exports.A = function () { +>exports : Symbol(A, Decl(a-ext.js, 0, 0)) +>A : Symbol(A, Decl(a-ext.js, 0, 0)) + + this.x = 1; +>x : Symbol((Anonymous function).x, Decl(a-ext.js, 0, 25)) + +}; + +=== tests/cases/conformance/salsa/a.js === +const { A } = require("./a-ext"); +>A : Symbol(A, Decl(a.js, 0, 7)) +>require : Symbol(require, Decl(node.d.ts, 0, 0)) +>"./a-ext" : Symbol("tests/cases/conformance/salsa/a-ext", Decl(a-ext.js, 0, 0)) + +/** @param {A} p */ +function a(p) { p.x; } +>a : Symbol(a, Decl(a.js, 0, 33)) +>p : Symbol(p, Decl(a.js, 3, 11)) +>p.x : Symbol((Anonymous function).x, Decl(a-ext.js, 0, 25)) +>p : Symbol(p, Decl(a.js, 3, 11)) +>x : Symbol((Anonymous function).x, Decl(a-ext.js, 0, 25)) + +=== tests/cases/conformance/salsa/b-ext.js === +exports.B = class { +>exports : Symbol(B, Decl(b-ext.js, 0, 0)) +>B : Symbol(B, Decl(b-ext.js, 0, 0)) + + constructor() { + this.x = 1; +>this.x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19)) +>this : Symbol((Anonymous class), Decl(b-ext.js, 0, 11)) +>x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19)) + } +}; + +=== tests/cases/conformance/salsa/b.js === +const { B } = require("./b-ext"); +>B : Symbol(B, Decl(b.js, 0, 7)) +>require : Symbol(require, Decl(node.d.ts, 0, 0)) +>"./b-ext" : Symbol("tests/cases/conformance/salsa/b-ext", Decl(b-ext.js, 0, 0)) + +/** @param {B} p */ +function b(p) { p.x; } +>b : Symbol(b, Decl(b.js, 0, 33)) +>p : Symbol(p, Decl(b.js, 3, 11)) +>p.x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19)) +>p : Symbol(p, Decl(b.js, 3, 11)) +>x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19)) + +=== tests/cases/conformance/salsa/c-ext.js === +export function C() { +>C : Symbol(C, Decl(c-ext.js, 0, 0)) + + this.x = 1; +>x : Symbol(C.x, Decl(c-ext.js, 0, 21)) +} + +=== tests/cases/conformance/salsa/c.js === +const { C } = require("./c-ext"); +>C : Symbol(C, Decl(c.js, 0, 7)) +>require : Symbol(require, Decl(node.d.ts, 0, 0)) +>"./c-ext" : Symbol("tests/cases/conformance/salsa/c-ext", Decl(c-ext.js, 0, 0)) + +/** @param {C} p */ +function c(p) { p.x; } +>c : Symbol(c, Decl(c.js, 0, 33)) +>p : Symbol(p, Decl(c.js, 3, 11)) +>p.x : Symbol(C.x, Decl(c-ext.js, 0, 21)) +>p : Symbol(p, Decl(c.js, 3, 11)) +>x : Symbol(C.x, Decl(c-ext.js, 0, 21)) + +=== tests/cases/conformance/salsa/d-ext.js === +export var D = function() { +>D : Symbol(D, Decl(d-ext.js, 0, 10)) + + this.x = 1; +>x : Symbol(D.x, Decl(d-ext.js, 0, 27)) + +}; + +=== tests/cases/conformance/salsa/d.js === +const { D } = require("./d-ext"); +>D : Symbol(D, Decl(d.js, 0, 7)) +>require : Symbol(require, Decl(node.d.ts, 0, 0)) +>"./d-ext" : Symbol("tests/cases/conformance/salsa/d-ext", Decl(d-ext.js, 0, 0)) + +/** @param {D} p */ +function d(p) { p.x; } +>d : Symbol(d, Decl(d.js, 0, 33)) +>p : Symbol(p, Decl(d.js, 3, 11)) +>p.x : Symbol(D.x, Decl(d-ext.js, 0, 27)) +>p : Symbol(p, Decl(d.js, 3, 11)) +>x : Symbol(D.x, Decl(d-ext.js, 0, 27)) + +=== tests/cases/conformance/salsa/e-ext.js === +export class E { +>E : Symbol(E, Decl(e-ext.js, 0, 0)) + + constructor() { + this.x = 1; +>this.x : Symbol(E.x, Decl(e-ext.js, 1, 19)) +>this : Symbol(E, Decl(e-ext.js, 0, 0)) +>x : Symbol(E.x, Decl(e-ext.js, 1, 19)) + } +} + +=== tests/cases/conformance/salsa/e.js === +const { E } = require("./e-ext"); +>E : Symbol(E, Decl(e.js, 0, 7)) +>require : Symbol(require, Decl(node.d.ts, 0, 0)) +>"./e-ext" : Symbol("tests/cases/conformance/salsa/e-ext", Decl(e-ext.js, 0, 0)) + +/** @param {E} p */ +function e(p) { p.x; } +>e : Symbol(e, Decl(e.js, 0, 33)) +>p : Symbol(p, Decl(e.js, 3, 11)) +>p.x : Symbol(E.x, Decl(e-ext.js, 1, 19)) +>p : Symbol(p, Decl(e.js, 3, 11)) +>x : Symbol(E.x, Decl(e-ext.js, 1, 19)) + +=== tests/cases/conformance/salsa/f.js === +var F = function () { +>F : Symbol(F, Decl(f.js, 0, 3)) + + this.x = 1; +>x : Symbol(F.x, Decl(f.js, 0, 21)) + +}; + +/** @param {F} p */ +function f(p) { p.x; } +>f : Symbol(f, Decl(f.js, 2, 2)) +>p : Symbol(p, Decl(f.js, 5, 11)) +>p.x : Symbol(F.x, Decl(f.js, 0, 21)) +>p : Symbol(p, Decl(f.js, 5, 11)) +>x : Symbol(F.x, Decl(f.js, 0, 21)) + +=== tests/cases/conformance/salsa/g.js === +function G() { +>G : Symbol(G, Decl(g.js, 0, 0)) + + this.x = 1; +>x : Symbol(G.x, Decl(g.js, 0, 14)) +} + +/** @param {G} p */ +function g(p) { p.x; } +>g : Symbol(g, Decl(g.js, 2, 1)) +>p : Symbol(p, Decl(g.js, 5, 11)) +>p.x : Symbol(G.x, Decl(g.js, 0, 14)) +>p : Symbol(p, Decl(g.js, 5, 11)) +>x : Symbol(G.x, Decl(g.js, 0, 14)) + +=== tests/cases/conformance/salsa/h.js === +class H { +>H : Symbol(H, Decl(h.js, 0, 0)) + + constructor() { + this.x = 1; +>this.x : Symbol(H.x, Decl(h.js, 1, 19)) +>this : Symbol(H, Decl(h.js, 0, 0)) +>x : Symbol(H.x, Decl(h.js, 1, 19)) + } +} + +/** @param {H} p */ +function h(p) { p.x; } +>h : Symbol(h, Decl(h.js, 4, 1)) +>p : Symbol(p, Decl(h.js, 7, 11)) +>p.x : Symbol(H.x, Decl(h.js, 1, 19)) +>p : Symbol(p, Decl(h.js, 7, 11)) +>x : Symbol(H.x, Decl(h.js, 1, 19)) + diff --git a/tests/baselines/reference/typeFromParamTagForFunction.types b/tests/baselines/reference/typeFromParamTagForFunction.types new file mode 100644 index 0000000000000..c1e16ddb33d7b --- /dev/null +++ b/tests/baselines/reference/typeFromParamTagForFunction.types @@ -0,0 +1,223 @@ +=== tests/cases/conformance/salsa/node.d.ts === +declare function require(id: string): any; +>require : (id: string) => any +>id : string + +declare var module: any, exports: any; +>module : any +>exports : any + +=== tests/cases/conformance/salsa/a-ext.js === +exports.A = function () { +>exports.A = function () { this.x = 1;} : () => void +>exports.A : any +>exports : any +>A : any +>function () { this.x = 1;} : () => void + + this.x = 1; +>this.x = 1 : 1 +>this.x : any +>this : any +>x : any +>1 : 1 + +}; + +=== tests/cases/conformance/salsa/a.js === +const { A } = require("./a-ext"); +>A : () => void +>require("./a-ext") : typeof "tests/cases/conformance/salsa/a-ext" +>require : (id: string) => any +>"./a-ext" : "./a-ext" + +/** @param {A} p */ +function a(p) { p.x; } +>a : (p: { x: number; }) => void +>p : { x: number; } +>p.x : number +>p : { x: number; } +>x : number + +=== tests/cases/conformance/salsa/b-ext.js === +exports.B = class { +>exports.B = class { constructor() { this.x = 1; }} : typeof (Anonymous class) +>exports.B : any +>exports : any +>B : any +>class { constructor() { this.x = 1; }} : typeof (Anonymous class) + + constructor() { + this.x = 1; +>this.x = 1 : 1 +>this.x : number +>this : this +>x : number +>1 : 1 + } +}; + +=== tests/cases/conformance/salsa/b.js === +const { B } = require("./b-ext"); +>B : typeof (Anonymous class) +>require("./b-ext") : typeof "tests/cases/conformance/salsa/b-ext" +>require : (id: string) => any +>"./b-ext" : "./b-ext" + +/** @param {B} p */ +function b(p) { p.x; } +>b : (p: (Anonymous class)) => void +>p : (Anonymous class) +>p.x : number +>p : (Anonymous class) +>x : number + +=== tests/cases/conformance/salsa/c-ext.js === +export function C() { +>C : () => void + + this.x = 1; +>this.x = 1 : 1 +>this.x : any +>this : any +>x : any +>1 : 1 +} + +=== tests/cases/conformance/salsa/c.js === +const { C } = require("./c-ext"); +>C : () => void +>require("./c-ext") : typeof "tests/cases/conformance/salsa/c-ext" +>require : (id: string) => any +>"./c-ext" : "./c-ext" + +/** @param {C} p */ +function c(p) { p.x; } +>c : (p: { x: number; }) => void +>p : { x: number; } +>p.x : number +>p : { x: number; } +>x : number + +=== tests/cases/conformance/salsa/d-ext.js === +export var D = function() { +>D : () => void +>function() { this.x = 1;} : () => void + + this.x = 1; +>this.x = 1 : 1 +>this.x : any +>this : any +>x : any +>1 : 1 + +}; + +=== tests/cases/conformance/salsa/d.js === +const { D } = require("./d-ext"); +>D : () => void +>require("./d-ext") : typeof "tests/cases/conformance/salsa/d-ext" +>require : (id: string) => any +>"./d-ext" : "./d-ext" + +/** @param {D} p */ +function d(p) { p.x; } +>d : (p: { x: number; }) => void +>p : { x: number; } +>p.x : number +>p : { x: number; } +>x : number + +=== tests/cases/conformance/salsa/e-ext.js === +export class E { +>E : E + + constructor() { + this.x = 1; +>this.x = 1 : 1 +>this.x : number +>this : this +>x : number +>1 : 1 + } +} + +=== tests/cases/conformance/salsa/e.js === +const { E } = require("./e-ext"); +>E : typeof E +>require("./e-ext") : typeof "tests/cases/conformance/salsa/e-ext" +>require : (id: string) => any +>"./e-ext" : "./e-ext" + +/** @param {E} p */ +function e(p) { p.x; } +>e : (p: E) => void +>p : E +>p.x : number +>p : E +>x : number + +=== tests/cases/conformance/salsa/f.js === +var F = function () { +>F : () => void +>function () { this.x = 1;} : () => void + + this.x = 1; +>this.x = 1 : 1 +>this.x : any +>this : any +>x : any +>1 : 1 + +}; + +/** @param {F} p */ +function f(p) { p.x; } +>f : (p: { x: number; }) => void +>p : { x: number; } +>p.x : number +>p : { x: number; } +>x : number + +=== tests/cases/conformance/salsa/g.js === +function G() { +>G : () => void + + this.x = 1; +>this.x = 1 : 1 +>this.x : any +>this : any +>x : any +>1 : 1 +} + +/** @param {G} p */ +function g(p) { p.x; } +>g : (p: { x: number; }) => void +>p : { x: number; } +>p.x : number +>p : { x: number; } +>x : number + +=== tests/cases/conformance/salsa/h.js === +class H { +>H : H + + constructor() { + this.x = 1; +>this.x = 1 : 1 +>this.x : number +>this : this +>x : number +>1 : 1 + } +} + +/** @param {H} p */ +function h(p) { p.x; } +>h : (p: H) => void +>p : H +>p.x : number +>p : H +>x : number + diff --git a/tests/cases/conformance/salsa/typeFromParamTagForFunction.ts b/tests/cases/conformance/salsa/typeFromParamTagForFunction.ts new file mode 100644 index 0000000000000..7adf236657fdd --- /dev/null +++ b/tests/cases/conformance/salsa/typeFromParamTagForFunction.ts @@ -0,0 +1,92 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @module: commonjs +// @filename: node.d.ts +declare function require(id: string): any; +declare var module: any, exports: any; + +// @filename: a-ext.js +exports.A = function () { + this.x = 1; +}; + +// @filename: a.js +const { A } = require("./a-ext"); + +/** @param {A} p */ +function a(p) { p.x; } + +// @filename: b-ext.js +exports.B = class { + constructor() { + this.x = 1; + } +}; + +// @filename: b.js +const { B } = require("./b-ext"); + +/** @param {B} p */ +function b(p) { p.x; } + +// @filename: c-ext.js +export function C() { + this.x = 1; +} + +// @filename: c.js +const { C } = require("./c-ext"); + +/** @param {C} p */ +function c(p) { p.x; } + +// @filename: d-ext.js +export var D = function() { + this.x = 1; +}; + +// @filename: d.js +const { D } = require("./d-ext"); + +/** @param {D} p */ +function d(p) { p.x; } + +// @filename: e-ext.js +export class E { + constructor() { + this.x = 1; + } +} + +// @filename: e.js +const { E } = require("./e-ext"); + +/** @param {E} p */ +function e(p) { p.x; } + +// @filename: f.js +var F = function () { + this.x = 1; +}; + +/** @param {F} p */ +function f(p) { p.x; } + +// @filename: g.js +function G() { + this.x = 1; +} + +/** @param {G} p */ +function g(p) { p.x; } + +// @filename: h.js +class H { + constructor() { + this.x = 1; + } +} + +/** @param {H} p */ +function h(p) { p.x; } \ No newline at end of file From 9f9e20c5a354fc27e59fb5aa4f7adc4ac11a2163 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 6 Jun 2017 18:25:55 -0700 Subject: [PATCH 2/4] Limit getTypeReferenceType to two passes --- src/compiler/checker.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c7e7016799703..c160b4f97ab99 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6828,6 +6828,7 @@ namespace ts { function getTypeReferenceType(node: TypeReferenceType, symbol: Symbol) { const typeArguments = typeArgumentsFromTypeReferenceNode(node); // Do unconditionally so we mark type arguments as referenced. + let secondPass = true; let fallbackType: Type = unknownType; while (true) { if (symbol === unknownSymbol) { @@ -6844,18 +6845,22 @@ namespace ts { if (symbol.flags & SymbolFlags.Value && node.kind === SyntaxKind.JSDocTypeReference) { // A JSDocTypeReference may have resolved to a value (as opposed to a type). If - // the value has a construct signature, we use the return type of the construct - // signature as the type; otherwise, the type of this reference is just the type - // of the value we resolved to. + // the symbol is a constructor function, return the inferred class type; otherwise, + // the type of this reference is just the type of the value we resolved to. if (symbol.flags & SymbolFlags.Function && (symbol.members || getJSDocClassTag(symbol.valueDeclaration))) { return getInferredClassType(symbol); } - fallbackType = getTypeOfSymbol(symbol); + // Stop if this is the second pass + if (secondPass) { + return fallbackType; + } // Try to use the symbol of the type (if present) to get a better type on the - // next pass. + // second pass. + fallbackType = getTypeOfSymbol(symbol); symbol = fallbackType.symbol || unknownSymbol; + secondPass = true; continue; } From 7304a738a0564b6589888d8acec3b99941d447ec Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 7 Jun 2017 10:58:51 -0700 Subject: [PATCH 3/4] Fix typo in getTypeReferenceType --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c160b4f97ab99..3b3551c1af81b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6828,7 +6828,7 @@ namespace ts { function getTypeReferenceType(node: TypeReferenceType, symbol: Symbol) { const typeArguments = typeArgumentsFromTypeReferenceNode(node); // Do unconditionally so we mark type arguments as referenced. - let secondPass = true; + let secondPass = false; let fallbackType: Type = unknownType; while (true) { if (symbol === unknownSymbol) { From d3d917584162d9afa0e33397c8bd403e636f95ed Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 7 Jun 2017 14:13:30 -0700 Subject: [PATCH 4/4] PR Feedback --- src/compiler/checker.ts | 79 ++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3b3551c1af81b..5868cd6dc3a78 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6814,57 +6814,56 @@ namespace ts { return undefined; } - function resolveTypeReferenceName(node: TypeReferenceType, typeReferenceName: EntityNameExpression | EntityName) { + function resolveTypeReferenceName(typeReferenceName: EntityNameExpression | EntityName, meaning: SymbolFlags) { if (!typeReferenceName) { return unknownSymbol; } - const meaning = node.kind === SyntaxKind.JSDocTypeReference - ? SymbolFlags.Type | SymbolFlags.Value - : SymbolFlags.Type; - return resolveEntityName(typeReferenceName, meaning) || unknownSymbol; } function getTypeReferenceType(node: TypeReferenceType, symbol: Symbol) { const typeArguments = typeArgumentsFromTypeReferenceNode(node); // Do unconditionally so we mark type arguments as referenced. - let secondPass = false; - let fallbackType: Type = unknownType; - while (true) { - if (symbol === unknownSymbol) { - return fallbackType; - } + if (symbol === unknownSymbol) { + return unknownType; + } - if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments); - } + const type = getTypeReferenceTypeWorker(node, symbol, typeArguments); + if (type) { + return type; + } - if (symbol.flags & SymbolFlags.TypeAlias) { - return getTypeFromTypeAliasReference(node, symbol, typeArguments); + if (symbol.flags & SymbolFlags.Value && node.kind === SyntaxKind.JSDocTypeReference) { + // A JSDocTypeReference may have resolved to a value (as opposed to a type). If + // the symbol is a constructor function, return the inferred class type; otherwise, + // the type of this reference is just the type of the value we resolved to. + const valueType = getTypeOfSymbol(symbol); + if (valueType.symbol && !isInferredClassType(valueType)) { + const referenceType = getTypeReferenceTypeWorker(node, valueType.symbol, typeArguments); + if (referenceType) { + return referenceType; + } } - if (symbol.flags & SymbolFlags.Value && node.kind === SyntaxKind.JSDocTypeReference) { - // A JSDocTypeReference may have resolved to a value (as opposed to a type). If - // the symbol is a constructor function, return the inferred class type; otherwise, - // the type of this reference is just the type of the value we resolved to. - if (symbol.flags & SymbolFlags.Function && (symbol.members || getJSDocClassTag(symbol.valueDeclaration))) { - return getInferredClassType(symbol); - } + // Resolve the type reference as a Type for the purpose of reporting errors. + resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type); + return valueType; + } - // Stop if this is the second pass - if (secondPass) { - return fallbackType; - } + return getTypeFromNonGenericTypeReference(node, symbol); + } - // Try to use the symbol of the type (if present) to get a better type on the - // second pass. - fallbackType = getTypeOfSymbol(symbol); - symbol = fallbackType.symbol || unknownSymbol; - secondPass = true; - continue; - } + function getTypeReferenceTypeWorker(node: TypeReferenceType, symbol: Symbol, typeArguments: Type[]): Type | undefined { + if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments); + } + + if (symbol.flags & SymbolFlags.TypeAlias) { + return getTypeFromTypeAliasReference(node, symbol, typeArguments); + } - return getTypeFromNonGenericTypeReference(node, symbol); + if (symbol.flags & SymbolFlags.Function && node.kind === SyntaxKind.JSDocTypeReference && (symbol.members || getJSDocClassTag(symbol.valueDeclaration))) { + return getInferredClassType(symbol); } } @@ -6908,11 +6907,13 @@ namespace ts { if (!links.resolvedType) { let symbol: Symbol; let type: Type; + let meaning = SymbolFlags.Type; if (node.kind === SyntaxKind.JSDocTypeReference) { type = getPrimitiveTypeFromJSDocTypeReference(node); + meaning |= SymbolFlags.Value; } if (!type) { - symbol = resolveTypeReferenceName(node, getTypeReferenceName(node)); + symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning); type = getTypeReferenceType(node, symbol); } // Cache both the resolved symbol and the resolved type. The resolved symbol is needed in when we check the @@ -16155,6 +16156,12 @@ namespace ts { return links.inferredClassType; } + function isInferredClassType(type: Type) { + return type.symbol + && getObjectFlags(type) & ObjectFlags.Anonymous + && getSymbolLinks(type.symbol).inferredClassType === type; + } + /** * Syntactically and semantically checks a call or new expression. * @param node The call/new expression to be checked.