Incremental prototype+prototype assignment work

Had to fix nested incremental prototype detection, so I'll probably
merge this branch back into the PR branch.
This commit is contained in:
Nathan Shively-Sanders 2018-02-22 11:04:29 -08:00
parent b14cf4ef9a
commit 41fba6f34b
6 changed files with 91 additions and 52 deletions

View file

@ -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

View file

@ -17895,32 +17895,33 @@ namespace ts {
function getJavaScriptClassType(symbol: Symbol): Type | undefined {
if (isDeclarationOfFunctionOrClassExpression(symbol)) {
symbol = getSymbolOfNode((<VariableDeclaration>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) {

View file

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

View file

@ -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))

View file

@ -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

View file

@ -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