Skip to content

Better types from jsdoc type references #16316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 43 additions & 26 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6814,21 +6814,46 @@ namespace ts {
return undefined;
}

function resolveTypeReferenceName(typeReferenceName: EntityNameExpression | EntityName) {
function resolveTypeReferenceName(typeReferenceName: EntityNameExpression | EntityName, meaning: SymbolFlags) {
if (!typeReferenceName) {
return unknownSymbol;
}

return resolveEntityName(typeReferenceName, SymbolFlags.Type) || unknownSymbol;
return resolveEntityName(typeReferenceName, meaning) || unknownSymbol;
}

function getTypeReferenceType(node: TypeReferenceType, symbol: Symbol) {
const typeArguments = typeArgumentsFromTypeReferenceNode(node); // Do unconditionally so we mark type arguments as referenced.

if (symbol === unknownSymbol) {
return unknownType;
}

const type = getTypeReferenceTypeWorker(node, symbol, typeArguments);
if (type) {
return type;
}

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;
}
}

// Resolve the type reference as a Type for the purpose of reporting errors.
resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type);
return valueType;
}

return getTypeFromNonGenericTypeReference(node, symbol);
}

function getTypeReferenceTypeWorker(node: TypeReferenceType, symbol: Symbol, typeArguments: Type[]): Type | undefined {
if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments);
}
Expand All @@ -6837,14 +6862,9 @@ namespace ts {
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). In
// that case, the type of this reference is just the type of the value we resolved
// to.
return getTypeOfSymbol(symbol);
if (symbol.flags & SymbolFlags.Function && node.kind === SyntaxKind.JSDocTypeReference && (symbol.members || getJSDocClassTag(symbol.valueDeclaration))) {
return getInferredClassType(symbol);
}

return getTypeFromNonGenericTypeReference(node, symbol);
}

function getPrimitiveTypeFromJSDocTypeReference(node: JSDocTypeReference): Type {
Expand Down Expand Up @@ -6887,22 +6907,13 @@ namespace ts {
if (!links.resolvedType) {
let symbol: Symbol;
let type: Type;
let meaning = SymbolFlags.Type;
if (node.kind === SyntaxKind.JSDocTypeReference) {
type = getPrimitiveTypeFromJSDocTypeReference(<JSDocTypeReference>node);
if (!type) {
const typeReferenceName = getTypeReferenceName(node);
symbol = resolveTypeReferenceName(typeReferenceName);
type = getTypeReferenceType(node, symbol);
}
type = getPrimitiveTypeFromJSDocTypeReference(node);
meaning |= SymbolFlags.Value;
}
else {
// We only support expressions that are simple qualified names. For other expressions this produces undefined.
const typeNameOrExpression: EntityNameOrEntityNameExpression = node.kind === SyntaxKind.TypeReference
? (<TypeReferenceNode>node).typeName
: isEntityNameExpression((<ExpressionWithTypeArguments>node).expression)
? <EntityNameExpression>(<ExpressionWithTypeArguments>node).expression
: undefined;
symbol = typeNameOrExpression && resolveEntityName(typeNameOrExpression, SymbolFlags.Type) || unknownSymbol;
if (!type) {
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
Expand Down Expand Up @@ -16145,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.
Expand Down Expand Up @@ -19367,8 +19384,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);
Expand Down
184 changes: 184 additions & 0 deletions tests/baselines/reference/typeFromParamTagForFunction.symbols
Original file line number Diff line number Diff line change
@@ -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))

Loading