diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9b2835524c6..1708639758f 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2367,6 +2367,7 @@ namespace ts { /** For `x.prototype = { p, ... }`, declare members p,... if `x` is function/class/{}, or not declared. */ function bindPrototypeAssignment(node: BinaryExpression) { node.left.parent = node; + node.right.parent = node; bindPropertyAssignment(node.left as PropertyAccessEntityNameExpression, node.left as PropertyAccessEntityNameExpression, /*isPrototypeProperty*/ false); } @@ -2416,7 +2417,7 @@ namespace ts { function bindPropertyAssignment(name: EntityNameExpression, propertyAccess: PropertyAccessEntityNameExpression, isPrototypeProperty: boolean) { let symbol = getJSInitializerSymbol(lookupSymbolForPropertyAccess(name)); const isToplevelNamespaceableInitializer = isBinaryExpression(propertyAccess.parent) ? - propertyAccess.parent.parent.parent.kind === SyntaxKind.SourceFile && (getJavascriptInitializer(propertyAccess.parent.right) || isJavascriptPrototypeAssignment(propertyAccess.parent)) : + propertyAccess.parent.parent.parent.kind === SyntaxKind.SourceFile && getJavascriptInitializer(propertyAccess.parent.right) : propertyAccess.parent.parent.kind === SyntaxKind.SourceFile; if (!isPrototypeProperty && (!symbol || !(symbol.flags & SymbolFlags.Namespace)) && isToplevelNamespaceableInitializer) { // make symbols or add declarations for intermediate containers diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1024a9843fe..941aa0eadc1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17895,32 +17895,33 @@ namespace ts { function getJavaScriptClassType(symbol: Symbol): Type | undefined { if (isDeclarationOfFunctionOrClassExpression(symbol)) { - symbol = getSymbolOfNode((symbol.valueDeclaration).initializer); + symbol = getSymbolOfNode((symbol.valueDeclaration as VariableDeclaration).initializer); } - // TODO: Could stick members on this somehow + let assigned = getAssignedClassType(symbol); + let inferred: Type | undefined; if (isJavaScriptConstructor(symbol.valueDeclaration)) { - return getInferredClassType(symbol); - } - // // OR: Get them another way - const otherSymbol: Symbol = getOtherSymbol(symbol.valueDeclaration); - if (otherSymbol) { - const prototype = forEach(otherSymbol.declarations, d => getAssignedJavascriptPrototype(d.parent)); - if (prototype) { - // NOTE: Should be able to check the original symbol for other prototype declarations, right? - // Not sure why not. Maybe intersecting them would work then. - return checkExpression(prototype); - } + inferred = getInferredClassType(symbol); } if (symbol.flags & SymbolFlags.Variable) { const valueType = getTypeOfSymbol(symbol); if (valueType.symbol && !isInferredClassType(valueType) && isJavaScriptConstructor(valueType.symbol.valueDeclaration)) { - return getInferredClassType(valueType.symbol); + inferred = getInferredClassType(valueType.symbol); } } + return !inferred ? assigned : + !assigned ? inferred : + getIntersectionType([inferred, assigned]); } - function getOtherSymbol(node: Node) { - return node && node.parent && isBinaryExpression(node.parent) && getSymbolOfNode(node.parent.left); + function getAssignedClassType(symbol: Symbol) { + const decl = symbol.valueDeclaration; + const assignmentSymbol = decl && decl.parent && isBinaryExpression(decl.parent) && getSymbolOfNode(decl.parent.left); + if (assignmentSymbol) { + const prototype = forEach(assignmentSymbol.declarations, d => getAssignedJavascriptPrototype(d.parent)); + if (prototype) { + return checkExpression(prototype); + } + } } function getInferredClassType(symbol: Symbol) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 537a00daeea..4058b9ac6b8 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1483,12 +1483,6 @@ namespace ts { return false; } - export function isJavascriptPrototypeAssignment(e: BinaryExpression) { - return isObjectLiteralExpression(e.right) && - isPropertyAccessExpression(e.left) && - e.left.name.escapedText === "prototype"; - } - export function getJSInitializerSymbol(symbol: Symbol) { if (!symbol || !symbol.valueDeclaration) { return symbol; @@ -1525,9 +1519,12 @@ namespace ts { const e = skipParentheses(initializer.expression); return e.kind === SyntaxKind.FunctionExpression || e.kind === SyntaxKind.ArrowFunction ? initializer : undefined; } - if (initializer.kind === SyntaxKind.FunctionExpression || - initializer.kind === SyntaxKind.ClassExpression || - isObjectLiteralExpression(initializer) && initializer.properties.length === 0) { + if (initializer.kind === SyntaxKind.FunctionExpression || initializer.kind === SyntaxKind.ClassExpression) { + return initializer; + } + if (isObjectLiteralExpression(initializer) && + (initializer.properties.length === 0 || + isBinaryExpression(initializer.parent) && isPropertyAccessExpression(initializer.parent.left) && initializer.parent.left.name.escapedText === "prototype")) { return initializer; } } @@ -1599,32 +1596,38 @@ namespace ts { // module.exports = expr return SpecialPropertyAssignmentKind.ModuleExports; } + // TODO: Can probably unify these checks with those in the second half + else if (lhs.name.escapedText === "prototype") { + return SpecialPropertyAssignmentKind.Prototype; + } else { // F.x = expr return SpecialPropertyAssignmentKind.Property; } } - else if (lhs.name.escapedText === "prototype" && expr.right.kind === SyntaxKind.ObjectLiteralExpression) { - // F.prototype = { ... } - return SpecialPropertyAssignmentKind.Prototype; - } else if (lhs.expression.kind === SyntaxKind.ThisKeyword) { return SpecialPropertyAssignmentKind.ThisProperty; } - else if (isPropertyAccessExpression(lhs.expression)) { - // chained dot, e.g. x.y.z = expr; this var is the 'x.y' part - if (isIdentifier(lhs.expression.expression)) { - // module.exports.name = expr - if (lhs.expression.expression.escapedText === "module" && lhs.expression.name.escapedText === "exports") { + else if (isEntityNameExpression(lhs.expression)) { + if (lhs.name.escapedText === "prototype" && isObjectLiteralExpression(expr.right)) { + // F.prototype = { ... } + return SpecialPropertyAssignmentKind.Prototype; + } + else if (isPropertyAccessExpression(lhs.expression)) { + // chained dot, e.g. x.y.z = expr; this var is the 'x.y' part + if (isIdentifier(lhs.expression.expression) && + lhs.expression.expression.escapedText === "module" && + lhs.expression.name.escapedText === "exports") { + // module.exports.name = expr return SpecialPropertyAssignmentKind.ExportsProperty; } if (lhs.expression.name.escapedText === "prototype") { + // F.G....prototype.x = expr return SpecialPropertyAssignmentKind.PrototypeProperty; } } - if (isEntityNameExpression(lhs.expression)) { - return SpecialPropertyAssignmentKind.Property; - } + // F.G...x = expr + return SpecialPropertyAssignmentKind.Property; } return SpecialPropertyAssignmentKind.None; diff --git a/tests/baselines/reference/typeFromPropertyAssignment13.symbols b/tests/baselines/reference/typeFromPropertyAssignment13.symbols index 96de275c1c6..f6d0b4bd9d3 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment13.symbols +++ b/tests/baselines/reference/typeFromPropertyAssignment13.symbols @@ -20,8 +20,17 @@ Outer.Inner.prototype = { i: 1 >i : Symbol(i, Decl(module.js, 3, 12)) } -// NOTE: incremental assignments don't work (but don't need to for chrome at least) +// incremental assignments still work Outer.Inner.prototype.j = 2 +>Outer.Inner.prototype : Symbol((Anonymous function).j, Decl(module.js, 5, 1)) +>Outer.Inner : Symbol(Inner, Decl(module.js, 0, 14), Decl(module.js, 2, 6)) +>Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(module.js, 0, 14), Decl(module.js, 1, 27)) +>Inner : Symbol(Inner, Decl(module.js, 0, 14), Decl(module.js, 2, 6)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>j : Symbol((Anonymous function).j, Decl(module.js, 5, 1)) + +/** @type {string} */ +Outer.Inner.prototype.k; >Outer.Inner.prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) >Outer.Inner : Symbol(Inner, Decl(module.js, 0, 14), Decl(module.js, 2, 6)) >Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(module.js, 0, 14), Decl(module.js, 1, 27)) @@ -29,21 +38,28 @@ Outer.Inner.prototype.j = 2 >prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) var inner = new Outer.Inner() ->inner : Symbol(inner, Decl(module.js, 8, 3)) +>inner : Symbol(inner, Decl(module.js, 10, 3)) >Outer.Inner : Symbol(Inner, Decl(module.js, 0, 14), Decl(module.js, 2, 6)) >Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(module.js, 0, 14), Decl(module.js, 1, 27)) >Inner : Symbol(Inner, Decl(module.js, 0, 14), Decl(module.js, 2, 6)) inner.m() >inner.m : Symbol(m, Decl(module.js, 2, 25)) ->inner : Symbol(inner, Decl(module.js, 8, 3)) +>inner : Symbol(inner, Decl(module.js, 10, 3)) >m : Symbol(m, Decl(module.js, 2, 25)) inner.i >inner.i : Symbol(i, Decl(module.js, 3, 12)) ->inner : Symbol(inner, Decl(module.js, 8, 3)) +>inner : Symbol(inner, Decl(module.js, 10, 3)) >i : Symbol(i, Decl(module.js, 3, 12)) inner.j ->inner : Symbol(inner, Decl(module.js, 8, 3)) +>inner.j : Symbol((Anonymous function).j, Decl(module.js, 5, 1)) +>inner : Symbol(inner, Decl(module.js, 10, 3)) +>j : Symbol((Anonymous function).j, Decl(module.js, 5, 1)) + +inner.k +>inner.k : Symbol((Anonymous function).k, Decl(module.js, 7, 27)) +>inner : Symbol(inner, Decl(module.js, 10, 3)) +>k : Symbol((Anonymous function).k, Decl(module.js, 7, 27)) diff --git a/tests/baselines/reference/typeFromPropertyAssignment13.types b/tests/baselines/reference/typeFromPropertyAssignment13.types index 155c671de71..ef0caf5c849 100644 --- a/tests/baselines/reference/typeFromPropertyAssignment13.types +++ b/tests/baselines/reference/typeFromPropertyAssignment13.types @@ -26,7 +26,7 @@ Outer.Inner.prototype = { >i : number >1 : 1 } -// NOTE: incremental assignments don't work (but don't need to for chrome at least) +// incremental assignments still work Outer.Inner.prototype.j = 2 >Outer.Inner.prototype.j = 2 : 2 >Outer.Inner.prototype.j : any @@ -38,9 +38,19 @@ Outer.Inner.prototype.j = 2 >j : any >2 : 2 +/** @type {string} */ +Outer.Inner.prototype.k; +>Outer.Inner.prototype.k : any +>Outer.Inner.prototype : any +>Outer.Inner : () => void +>Outer : { [x: string]: any; Inner: () => void; } +>Inner : () => void +>prototype : any +>k : any + var inner = new Outer.Inner() ->inner : { [x: string]: any; m(): void; i: number; } ->new Outer.Inner() : { [x: string]: any; m(): void; i: number; } +>inner : { j: number; k: string; } & { [x: string]: any; m(): void; i: number; } +>new Outer.Inner() : { j: number; k: string; } & { [x: string]: any; m(): void; i: number; } >Outer.Inner : () => void >Outer : { [x: string]: any; Inner: () => void; } >Inner : () => void @@ -48,16 +58,21 @@ var inner = new Outer.Inner() inner.m() >inner.m() : void >inner.m : () => void ->inner : { [x: string]: any; m(): void; i: number; } +>inner : { j: number; k: string; } & { [x: string]: any; m(): void; i: number; } >m : () => void inner.i >inner.i : number ->inner : { [x: string]: any; m(): void; i: number; } +>inner : { j: number; k: string; } & { [x: string]: any; m(): void; i: number; } >i : number inner.j ->inner.j : any ->inner : { [x: string]: any; m(): void; i: number; } ->j : any +>inner.j : number +>inner : { j: number; k: string; } & { [x: string]: any; m(): void; i: number; } +>j : number + +inner.k +>inner.k : string +>inner : { j: number; k: string; } & { [x: string]: any; m(): void; i: number; } +>k : string diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment13.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment13.ts index cd19a50bd0f..201560d5055 100644 --- a/tests/cases/conformance/salsa/typeFromPropertyAssignment13.ts +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment13.ts @@ -9,9 +9,12 @@ Outer.Inner.prototype = { m() { }, i: 1 } -// NOTE: incremental assignments don't work (but don't need to for chrome at least) +// incremental assignments still work Outer.Inner.prototype.j = 2 +/** @type {string} */ +Outer.Inner.prototype.k; var inner = new Outer.Inner() inner.m() inner.i inner.j +inner.k