From f0f98892173899f780eced3cd877db82f3f24fd8 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 22 Aug 2019 09:25:51 -0700 Subject: [PATCH 1/2] Fix constructor function type reference lookup I knew I missed some code in the constructor-functions-as-classes PR. This simplifies the type reference resolution code as well. --- src/compiler/checker.ts | 37 +++++++++-------- ...ocConstructorFunctionTypeReference.symbols | 31 ++++++++++++++ ...sdocConstructorFunctionTypeReference.types | 40 +++++++++++++++++++ .../jsdocConstructorFunctionTypeReference.ts | 19 +++++++++ 4 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 tests/baselines/reference/jsdocConstructorFunctionTypeReference.symbols create mode 100644 tests/baselines/reference/jsdocConstructorFunctionTypeReference.types create mode 100644 tests/cases/conformance/salsa/jsdocConstructorFunctionTypeReference.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3490ca961c562..611a2104f7a50 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2560,6 +2560,21 @@ namespace ts { return initializer || decl; } + /** + * Get the real symbol of a declaration with an expando initializer. + * + * Normally, declarations have an associated symbol, but when a declaration has an expando + * initializer, the expando's symbol is the one that has all the members merged into it. + */ + function getExpandoSymbol(node: Node | undefined): Symbol | undefined { + if (!node || !isInJSFile(node)) { + return undefined; + } + const init = isVariableDeclaration(node) ? getDeclaredExpandoInitializer(node) : getAssignedExpandoInitializer(node); + return init && getSymbolOfNode(init) || undefined; + } + + function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression, ignoreErrors?: boolean): Symbol | undefined { return resolveExternalModuleNameWorker(location, moduleReferenceExpression, ignoreErrors ? undefined : Diagnostics.Cannot_find_module_0); } @@ -9195,7 +9210,7 @@ namespace ts { if (symbol === unknownSymbol) { return errorType; } - + symbol = !(symbol.flags & SymbolFlags.TypeAlias) && getExpandoSymbol(symbol.valueDeclaration) || symbol; const type = getTypeReferenceTypeWorker(node, symbol, typeArguments); if (type) { return type; @@ -9213,7 +9228,7 @@ namespace ts { return errorType; } - const jsdocType = getJSDocTypeReference(node, symbol, typeArguments); + const jsdocType = getTypeFromJSTypeAliasReference(node, symbol, typeArguments); if (jsdocType) { return jsdocType; } @@ -9228,7 +9243,7 @@ namespace ts { * 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. */ - function getJSDocTypeReference(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined): Type | undefined { + function getTypeFromJSTypeAliasReference(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined): Type | undefined { // In the case of an assignment of a function expression (binary expressions, variable declarations, etc.), we will get the // correct instance type for the symbol on the LHS by finding the type for RHS. For example if we want to get the type of the symbol `foo`: // var foo = function() {} @@ -9245,27 +9260,11 @@ namespace ts { function getTypeReferenceTypeWorker(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined): Type | undefined { if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - if (symbol.valueDeclaration && symbol.valueDeclaration.parent && isBinaryExpression(symbol.valueDeclaration.parent)) { - const jsdocType = getJSDocTypeReference(node, symbol, typeArguments); - if (jsdocType) { - return jsdocType; - } - } return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments); } - if (symbol.flags & SymbolFlags.TypeAlias) { return getTypeFromTypeAliasReference(node, symbol, typeArguments); } - - if (symbol.flags & SymbolFlags.Function && - isJSDocTypeReference(node) && - isJSConstructor(symbol.valueDeclaration)) { - const resolved = resolveStructuredTypeMembers(getTypeOfSymbol(symbol)); - if (resolved.callSignatures.length === 1) { - return getReturnTypeOfSignature(resolved.callSignatures[0]); - } - } } function getSubstitutionType(typeVariable: TypeVariable, substitute: Type) { diff --git a/tests/baselines/reference/jsdocConstructorFunctionTypeReference.symbols b/tests/baselines/reference/jsdocConstructorFunctionTypeReference.symbols new file mode 100644 index 0000000000000..f4e58505c2e23 --- /dev/null +++ b/tests/baselines/reference/jsdocConstructorFunctionTypeReference.symbols @@ -0,0 +1,31 @@ +=== tests/cases/conformance/salsa/jsdocConstructorFunctionTypeReference.js === +var Validator = function VFunc() { +>Validator : Symbol(Validator, Decl(jsdocConstructorFunctionTypeReference.js, 0, 3)) +>VFunc : Symbol(VFunc, Decl(jsdocConstructorFunctionTypeReference.js, 0, 15)) + + this.flags = "gim" +>flags : Symbol(VFunc.flags, Decl(jsdocConstructorFunctionTypeReference.js, 0, 34)) + +}; + +Validator.prototype.num = 12 +>Validator.prototype : Symbol(Validator.num, Decl(jsdocConstructorFunctionTypeReference.js, 2, 2)) +>Validator : Symbol(Validator, Decl(jsdocConstructorFunctionTypeReference.js, 0, 3)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>num : Symbol(Validator.num, Decl(jsdocConstructorFunctionTypeReference.js, 2, 2)) + +/** + * @param {Validator} state + */ +var validateRegExpFlags = function(state) { +>validateRegExpFlags : Symbol(validateRegExpFlags, Decl(jsdocConstructorFunctionTypeReference.js, 9, 3)) +>state : Symbol(state, Decl(jsdocConstructorFunctionTypeReference.js, 9, 35)) + + return state.flags +>state.flags : Symbol(VFunc.flags, Decl(jsdocConstructorFunctionTypeReference.js, 0, 34)) +>state : Symbol(state, Decl(jsdocConstructorFunctionTypeReference.js, 9, 35)) +>flags : Symbol(VFunc.flags, Decl(jsdocConstructorFunctionTypeReference.js, 0, 34)) + +}; + + diff --git a/tests/baselines/reference/jsdocConstructorFunctionTypeReference.types b/tests/baselines/reference/jsdocConstructorFunctionTypeReference.types new file mode 100644 index 0000000000000..b536ad26ded12 --- /dev/null +++ b/tests/baselines/reference/jsdocConstructorFunctionTypeReference.types @@ -0,0 +1,40 @@ +=== tests/cases/conformance/salsa/jsdocConstructorFunctionTypeReference.js === +var Validator = function VFunc() { +>Validator : typeof VFunc +>function VFunc() { this.flags = "gim"} : typeof VFunc +>VFunc : typeof VFunc + + this.flags = "gim" +>this.flags = "gim" : "gim" +>this.flags : any +>this : any +>flags : any +>"gim" : "gim" + +}; + +Validator.prototype.num = 12 +>Validator.prototype.num = 12 : 12 +>Validator.prototype.num : any +>Validator.prototype : any +>Validator : typeof VFunc +>prototype : any +>num : any +>12 : 12 + +/** + * @param {Validator} state + */ +var validateRegExpFlags = function(state) { +>validateRegExpFlags : (state: VFunc) => string +>function(state) { return state.flags} : (state: VFunc) => string +>state : VFunc + + return state.flags +>state.flags : string +>state : VFunc +>flags : string + +}; + + diff --git a/tests/cases/conformance/salsa/jsdocConstructorFunctionTypeReference.ts b/tests/cases/conformance/salsa/jsdocConstructorFunctionTypeReference.ts new file mode 100644 index 0000000000000..46f2667c6e11a --- /dev/null +++ b/tests/cases/conformance/salsa/jsdocConstructorFunctionTypeReference.ts @@ -0,0 +1,19 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @noImplicitAny: true +// @Filename: jsdocConstructorFunctionTypeReference.js + +var Validator = function VFunc() { + this.flags = "gim" +}; + +Validator.prototype.num = 12 + +/** + * @param {Validator} state + */ +var validateRegExpFlags = function(state) { + return state.flags +}; + From 54b3c3c02dec4bbe0de5b8a933c112be68304895 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 22 Aug 2019 09:54:26 -0700 Subject: [PATCH 2/2] Simplify and document js alias type resolution --- src/compiler/checker.ts | 74 ++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 611a2104f7a50..cfd83efeb31aa 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2566,11 +2566,12 @@ namespace ts { * Normally, declarations have an associated symbol, but when a declaration has an expando * initializer, the expando's symbol is the one that has all the members merged into it. */ - function getExpandoSymbol(node: Node | undefined): Symbol | undefined { - if (!node || !isInJSFile(node)) { + function getExpandoSymbol(symbol: Symbol): Symbol | undefined { + const decl = symbol.valueDeclaration; + if (!decl || !isInJSFile(decl) || symbol.flags & SymbolFlags.TypeAlias) { return undefined; } - const init = isVariableDeclaration(node) ? getDeclaredExpandoInitializer(node) : getAssignedExpandoInitializer(node); + const init = isVariableDeclaration(decl) ? getDeclaredExpandoInitializer(decl) : getAssignedExpandoInitializer(decl); return init && getSymbolOfNode(init) || undefined; } @@ -9210,10 +9211,13 @@ namespace ts { if (symbol === unknownSymbol) { return errorType; } - symbol = !(symbol.flags & SymbolFlags.TypeAlias) && getExpandoSymbol(symbol.valueDeclaration) || symbol; - const type = getTypeReferenceTypeWorker(node, symbol, typeArguments); - if (type) { - return type; + symbol = getExpandoSymbol(symbol) || symbol; + + if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments); + } + if (symbol.flags & SymbolFlags.TypeAlias) { + return getTypeFromTypeAliasReference(node, symbol, typeArguments); } // Get type from reference to named type that cannot be generic (enum or type parameter) @@ -9224,46 +9228,34 @@ namespace ts { errorType; } - if (!(symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node))) { - return errorType; - } - - const jsdocType = getTypeFromJSTypeAliasReference(node, symbol, typeArguments); - if (jsdocType) { - return jsdocType; + if (symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node)) { + const jsdocType = getTypeFromJSAlias(node, symbol); + if (jsdocType) { + return jsdocType; + } + else { + // Resolve the type reference as a Type for the purpose of reporting errors. + resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type); + return getTypeOfSymbol(symbol); + } } - // Resolve the type reference as a Type for the purpose of reporting errors. - resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type); - return getTypeOfSymbol(symbol); + return errorType; } /** - * A jsdoc TypeReference 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. + * A JSdoc TypeReference may be to a value imported from commonjs. + * These should really be aliases, but this special-case code fakes alias resolution + * by producing a type from a value. */ - function getTypeFromJSTypeAliasReference(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined): Type | undefined { - // In the case of an assignment of a function expression (binary expressions, variable declarations, etc.), we will get the - // correct instance type for the symbol on the LHS by finding the type for RHS. For example if we want to get the type of the symbol `foo`: - // var foo = function() {} - // We will find the static type of the assigned anonymous function. - const staticType = getTypeOfSymbol(symbol); - const instanceType = - staticType.symbol && - staticType.symbol !== symbol && // Make sure this is an assignment like expression by checking that symbol -> type -> symbol doesn't roundtrips. - getTypeReferenceTypeWorker(node, staticType.symbol, typeArguments); // Get the instance type of the RHS symbol. - if (instanceType) { - return getSymbolLinks(symbol).resolvedJSDocType = instanceType; - } - } - - function getTypeReferenceTypeWorker(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined): Type | undefined { - if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - return getTypeFromClassOrInterfaceReference(node, symbol, typeArguments); - } - if (symbol.flags & SymbolFlags.TypeAlias) { - return getTypeFromTypeAliasReference(node, symbol, typeArguments); + function getTypeFromJSAlias(node: NodeWithTypeArguments, symbol: Symbol): Type | undefined { + const valueType = getTypeOfSymbol(symbol); + const typeType = + valueType.symbol && + valueType.symbol !== symbol && // Make sure this is a commonjs export by checking that symbol -> type -> symbol doesn't roundtrip. + getTypeReferenceType(node, valueType.symbol); + if (typeType) { + return getSymbolLinks(symbol).resolvedJSDocType = typeType; } }