Skip to content

Commit

Permalink
Fix prototype property type lookup (microsoft#33034)
Browse files Browse the repository at this point in the history
* 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.

* Simplify and document js alias type resolution
  • Loading branch information
sandersn committed Aug 22, 2019
1 parent 3c42760 commit 4bddf55
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 53 deletions.
97 changes: 44 additions & 53 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2560,6 +2560,22 @@ 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(symbol: Symbol): Symbol | undefined {
const decl = symbol.valueDeclaration;
if (!decl || !isInJSFile(decl) || symbol.flags & SymbolFlags.TypeAlias) {
return undefined;
}
const init = isVariableDeclaration(decl) ? getDeclaredExpandoInitializer(decl) : getAssignedExpandoInitializer(decl);
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);
}
Expand Down Expand Up @@ -9195,10 +9211,13 @@ namespace ts {
if (symbol === unknownSymbol) {
return errorType;
}
symbol = getExpandoSymbol(symbol) || symbol;

const type = getTypeReferenceTypeWorker(node, symbol, typeArguments);
if (type) {
return type;
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)
Expand All @@ -9209,62 +9228,34 @@ namespace ts {
errorType;
}

if (!(symbol.flags & SymbolFlags.Value && isJSDocTypeReference(node))) {
return errorType;
}

const jsdocType = getJSDocTypeReference(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 getJSDocTypeReference(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)) {
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(<ObjectType>getTypeOfSymbol(symbol));
if (resolved.callSignatures.length === 1) {
return getReturnTypeOfSignature(resolved.callSignatures[0]);
}
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;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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))

};


Original file line number Diff line number Diff line change
@@ -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

};


Original file line number Diff line number Diff line change
@@ -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
};

0 comments on commit 4bddf55

Please sign in to comment.