Skip to content

Commit 0760439

Browse files
authored
Merge pull request #16316 from Microsoft/fix14056
Better types from jsdoc type references
2 parents 4d5175b + d3d9175 commit 0760439

File tree

4 files changed

+542
-26
lines changed

4 files changed

+542
-26
lines changed

src/compiler/checker.ts

+43-26
Original file line numberDiff line numberDiff line change
@@ -6841,21 +6841,46 @@ namespace ts {
68416841
return undefined;
68426842
}
68436843

6844-
function resolveTypeReferenceName(typeReferenceName: EntityNameExpression | EntityName) {
6844+
function resolveTypeReferenceName(typeReferenceName: EntityNameExpression | EntityName, meaning: SymbolFlags) {
68456845
if (!typeReferenceName) {
68466846
return unknownSymbol;
68476847
}
68486848

6849-
return resolveEntityName(typeReferenceName, SymbolFlags.Type) || unknownSymbol;
6849+
return resolveEntityName(typeReferenceName, meaning) || unknownSymbol;
68506850
}
68516851

68526852
function getTypeReferenceType(node: TypeReferenceType, symbol: Symbol) {
68536853
const typeArguments = typeArgumentsFromTypeReferenceNode(node); // Do unconditionally so we mark type arguments as referenced.
6854-
68556854
if (symbol === unknownSymbol) {
68566855
return unknownType;
68576856
}
68586857

6858+
const type = getTypeReferenceTypeWorker(node, symbol, typeArguments);
6859+
if (type) {
6860+
return type;
6861+
}
6862+
6863+
if (symbol.flags & SymbolFlags.Value && node.kind === SyntaxKind.JSDocTypeReference) {
6864+
// A JSDocTypeReference may have resolved to a value (as opposed to a type). If
6865+
// the symbol is a constructor function, return the inferred class type; otherwise,
6866+
// the type of this reference is just the type of the value we resolved to.
6867+
const valueType = getTypeOfSymbol(symbol);
6868+
if (valueType.symbol && !isInferredClassType(valueType)) {
6869+
const referenceType = getTypeReferenceTypeWorker(node, valueType.symbol, typeArguments);
6870+
if (referenceType) {
6871+
return referenceType;
6872+
}
6873+
}
6874+
6875+
// Resolve the type reference as a Type for the purpose of reporting errors.
6876+
resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type);
6877+
return valueType;
6878+
}
6879+
6880+
return getTypeFromNonGenericTypeReference(node, symbol);
6881+
}
6882+
6883+
function getTypeReferenceTypeWorker(node: TypeReferenceType, symbol: Symbol, typeArguments: Type[]): Type | undefined {
68596884
if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
68606885
return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments);
68616886
}
@@ -6864,14 +6889,9 @@ namespace ts {
68646889
return getTypeFromTypeAliasReference(node, symbol, typeArguments);
68656890
}
68666891

6867-
if (symbol.flags & SymbolFlags.Value && node.kind === SyntaxKind.JSDocTypeReference) {
6868-
// A JSDocTypeReference may have resolved to a value (as opposed to a type). In
6869-
// that case, the type of this reference is just the type of the value we resolved
6870-
// to.
6871-
return getTypeOfSymbol(symbol);
6892+
if (symbol.flags & SymbolFlags.Function && node.kind === SyntaxKind.JSDocTypeReference && (symbol.members || getJSDocClassTag(symbol.valueDeclaration))) {
6893+
return getInferredClassType(symbol);
68726894
}
6873-
6874-
return getTypeFromNonGenericTypeReference(node, symbol);
68756895
}
68766896

68776897
function getPrimitiveTypeFromJSDocTypeReference(node: JSDocTypeReference): Type {
@@ -6914,22 +6934,13 @@ namespace ts {
69146934
if (!links.resolvedType) {
69156935
let symbol: Symbol;
69166936
let type: Type;
6937+
let meaning = SymbolFlags.Type;
69176938
if (node.kind === SyntaxKind.JSDocTypeReference) {
6918-
type = getPrimitiveTypeFromJSDocTypeReference(<JSDocTypeReference>node);
6919-
if (!type) {
6920-
const typeReferenceName = getTypeReferenceName(node);
6921-
symbol = resolveTypeReferenceName(typeReferenceName);
6922-
type = getTypeReferenceType(node, symbol);
6923-
}
6939+
type = getPrimitiveTypeFromJSDocTypeReference(node);
6940+
meaning |= SymbolFlags.Value;
69246941
}
6925-
else {
6926-
// We only support expressions that are simple qualified names. For other expressions this produces undefined.
6927-
const typeNameOrExpression: EntityNameOrEntityNameExpression = node.kind === SyntaxKind.TypeReference
6928-
? (<TypeReferenceNode>node).typeName
6929-
: isEntityNameExpression((<ExpressionWithTypeArguments>node).expression)
6930-
? <EntityNameExpression>(<ExpressionWithTypeArguments>node).expression
6931-
: undefined;
6932-
symbol = typeNameOrExpression && resolveEntityName(typeNameOrExpression, SymbolFlags.Type) || unknownSymbol;
6942+
if (!type) {
6943+
symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning);
69336944
type = getTypeReferenceType(node, symbol);
69346945
}
69356946
// Cache both the resolved symbol and the resolved type. The resolved symbol is needed in when we check the
@@ -16169,6 +16180,12 @@ namespace ts {
1616916180
return links.inferredClassType;
1617016181
}
1617116182

16183+
function isInferredClassType(type: Type) {
16184+
return type.symbol
16185+
&& getObjectFlags(type) & ObjectFlags.Anonymous
16186+
&& getSymbolLinks(type.symbol).inferredClassType === type;
16187+
}
16188+
1617216189
/**
1617316190
* Syntactically and semantically checks a call or new expression.
1617416191
* @param node The call/new expression to be checked.
@@ -19392,8 +19409,8 @@ namespace ts {
1939219409

1939319410
function checkFunctionDeclaration(node: FunctionDeclaration): void {
1939419411
if (produceDiagnostics) {
19395-
checkFunctionOrMethodDeclaration(node) || checkGrammarForGenerator(node);
19396-
19412+
checkFunctionOrMethodDeclaration(node);
19413+
checkGrammarForGenerator(node);
1939719414
checkCollisionWithCapturedSuperVariable(node, node.name);
1939819415
checkCollisionWithCapturedThisVariable(node, node.name);
1939919416
checkCollisionWithCapturedNewTargetVariable(node, node.name);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
=== tests/cases/conformance/salsa/node.d.ts ===
2+
declare function require(id: string): any;
3+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
4+
>id : Symbol(id, Decl(node.d.ts, 0, 25))
5+
6+
declare var module: any, exports: any;
7+
>module : Symbol(module, Decl(node.d.ts, 1, 11))
8+
>exports : Symbol(exports, Decl(node.d.ts, 1, 24))
9+
10+
=== tests/cases/conformance/salsa/a-ext.js ===
11+
exports.A = function () {
12+
>exports : Symbol(A, Decl(a-ext.js, 0, 0))
13+
>A : Symbol(A, Decl(a-ext.js, 0, 0))
14+
15+
this.x = 1;
16+
>x : Symbol((Anonymous function).x, Decl(a-ext.js, 0, 25))
17+
18+
};
19+
20+
=== tests/cases/conformance/salsa/a.js ===
21+
const { A } = require("./a-ext");
22+
>A : Symbol(A, Decl(a.js, 0, 7))
23+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
24+
>"./a-ext" : Symbol("tests/cases/conformance/salsa/a-ext", Decl(a-ext.js, 0, 0))
25+
26+
/** @param {A} p */
27+
function a(p) { p.x; }
28+
>a : Symbol(a, Decl(a.js, 0, 33))
29+
>p : Symbol(p, Decl(a.js, 3, 11))
30+
>p.x : Symbol((Anonymous function).x, Decl(a-ext.js, 0, 25))
31+
>p : Symbol(p, Decl(a.js, 3, 11))
32+
>x : Symbol((Anonymous function).x, Decl(a-ext.js, 0, 25))
33+
34+
=== tests/cases/conformance/salsa/b-ext.js ===
35+
exports.B = class {
36+
>exports : Symbol(B, Decl(b-ext.js, 0, 0))
37+
>B : Symbol(B, Decl(b-ext.js, 0, 0))
38+
39+
constructor() {
40+
this.x = 1;
41+
>this.x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19))
42+
>this : Symbol((Anonymous class), Decl(b-ext.js, 0, 11))
43+
>x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19))
44+
}
45+
};
46+
47+
=== tests/cases/conformance/salsa/b.js ===
48+
const { B } = require("./b-ext");
49+
>B : Symbol(B, Decl(b.js, 0, 7))
50+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
51+
>"./b-ext" : Symbol("tests/cases/conformance/salsa/b-ext", Decl(b-ext.js, 0, 0))
52+
53+
/** @param {B} p */
54+
function b(p) { p.x; }
55+
>b : Symbol(b, Decl(b.js, 0, 33))
56+
>p : Symbol(p, Decl(b.js, 3, 11))
57+
>p.x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19))
58+
>p : Symbol(p, Decl(b.js, 3, 11))
59+
>x : Symbol((Anonymous class).x, Decl(b-ext.js, 1, 19))
60+
61+
=== tests/cases/conformance/salsa/c-ext.js ===
62+
export function C() {
63+
>C : Symbol(C, Decl(c-ext.js, 0, 0))
64+
65+
this.x = 1;
66+
>x : Symbol(C.x, Decl(c-ext.js, 0, 21))
67+
}
68+
69+
=== tests/cases/conformance/salsa/c.js ===
70+
const { C } = require("./c-ext");
71+
>C : Symbol(C, Decl(c.js, 0, 7))
72+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
73+
>"./c-ext" : Symbol("tests/cases/conformance/salsa/c-ext", Decl(c-ext.js, 0, 0))
74+
75+
/** @param {C} p */
76+
function c(p) { p.x; }
77+
>c : Symbol(c, Decl(c.js, 0, 33))
78+
>p : Symbol(p, Decl(c.js, 3, 11))
79+
>p.x : Symbol(C.x, Decl(c-ext.js, 0, 21))
80+
>p : Symbol(p, Decl(c.js, 3, 11))
81+
>x : Symbol(C.x, Decl(c-ext.js, 0, 21))
82+
83+
=== tests/cases/conformance/salsa/d-ext.js ===
84+
export var D = function() {
85+
>D : Symbol(D, Decl(d-ext.js, 0, 10))
86+
87+
this.x = 1;
88+
>x : Symbol(D.x, Decl(d-ext.js, 0, 27))
89+
90+
};
91+
92+
=== tests/cases/conformance/salsa/d.js ===
93+
const { D } = require("./d-ext");
94+
>D : Symbol(D, Decl(d.js, 0, 7))
95+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
96+
>"./d-ext" : Symbol("tests/cases/conformance/salsa/d-ext", Decl(d-ext.js, 0, 0))
97+
98+
/** @param {D} p */
99+
function d(p) { p.x; }
100+
>d : Symbol(d, Decl(d.js, 0, 33))
101+
>p : Symbol(p, Decl(d.js, 3, 11))
102+
>p.x : Symbol(D.x, Decl(d-ext.js, 0, 27))
103+
>p : Symbol(p, Decl(d.js, 3, 11))
104+
>x : Symbol(D.x, Decl(d-ext.js, 0, 27))
105+
106+
=== tests/cases/conformance/salsa/e-ext.js ===
107+
export class E {
108+
>E : Symbol(E, Decl(e-ext.js, 0, 0))
109+
110+
constructor() {
111+
this.x = 1;
112+
>this.x : Symbol(E.x, Decl(e-ext.js, 1, 19))
113+
>this : Symbol(E, Decl(e-ext.js, 0, 0))
114+
>x : Symbol(E.x, Decl(e-ext.js, 1, 19))
115+
}
116+
}
117+
118+
=== tests/cases/conformance/salsa/e.js ===
119+
const { E } = require("./e-ext");
120+
>E : Symbol(E, Decl(e.js, 0, 7))
121+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
122+
>"./e-ext" : Symbol("tests/cases/conformance/salsa/e-ext", Decl(e-ext.js, 0, 0))
123+
124+
/** @param {E} p */
125+
function e(p) { p.x; }
126+
>e : Symbol(e, Decl(e.js, 0, 33))
127+
>p : Symbol(p, Decl(e.js, 3, 11))
128+
>p.x : Symbol(E.x, Decl(e-ext.js, 1, 19))
129+
>p : Symbol(p, Decl(e.js, 3, 11))
130+
>x : Symbol(E.x, Decl(e-ext.js, 1, 19))
131+
132+
=== tests/cases/conformance/salsa/f.js ===
133+
var F = function () {
134+
>F : Symbol(F, Decl(f.js, 0, 3))
135+
136+
this.x = 1;
137+
>x : Symbol(F.x, Decl(f.js, 0, 21))
138+
139+
};
140+
141+
/** @param {F} p */
142+
function f(p) { p.x; }
143+
>f : Symbol(f, Decl(f.js, 2, 2))
144+
>p : Symbol(p, Decl(f.js, 5, 11))
145+
>p.x : Symbol(F.x, Decl(f.js, 0, 21))
146+
>p : Symbol(p, Decl(f.js, 5, 11))
147+
>x : Symbol(F.x, Decl(f.js, 0, 21))
148+
149+
=== tests/cases/conformance/salsa/g.js ===
150+
function G() {
151+
>G : Symbol(G, Decl(g.js, 0, 0))
152+
153+
this.x = 1;
154+
>x : Symbol(G.x, Decl(g.js, 0, 14))
155+
}
156+
157+
/** @param {G} p */
158+
function g(p) { p.x; }
159+
>g : Symbol(g, Decl(g.js, 2, 1))
160+
>p : Symbol(p, Decl(g.js, 5, 11))
161+
>p.x : Symbol(G.x, Decl(g.js, 0, 14))
162+
>p : Symbol(p, Decl(g.js, 5, 11))
163+
>x : Symbol(G.x, Decl(g.js, 0, 14))
164+
165+
=== tests/cases/conformance/salsa/h.js ===
166+
class H {
167+
>H : Symbol(H, Decl(h.js, 0, 0))
168+
169+
constructor() {
170+
this.x = 1;
171+
>this.x : Symbol(H.x, Decl(h.js, 1, 19))
172+
>this : Symbol(H, Decl(h.js, 0, 0))
173+
>x : Symbol(H.x, Decl(h.js, 1, 19))
174+
}
175+
}
176+
177+
/** @param {H} p */
178+
function h(p) { p.x; }
179+
>h : Symbol(h, Decl(h.js, 4, 1))
180+
>p : Symbol(p, Decl(h.js, 7, 11))
181+
>p.x : Symbol(H.x, Decl(h.js, 1, 19))
182+
>p : Symbol(p, Decl(h.js, 7, 11))
183+
>x : Symbol(H.x, Decl(h.js, 1, 19))
184+

0 commit comments

Comments
 (0)