First draft of prototype assignment
* Still misses incremental additions to the prototype. * Not tested with {} or class initalizers. * Code needs a cleanup pass.
This commit is contained in:
parent
01f2ee3d1f
commit
b14cf4ef9a
|
@ -2044,6 +2044,7 @@ namespace ts {
|
|||
break;
|
||||
case SpecialPropertyAssignmentKind.Prototype:
|
||||
bindPrototypeAssignment(node as BinaryExpression);
|
||||
break;
|
||||
case SpecialPropertyAssignmentKind.ThisProperty:
|
||||
bindThisPropertyAssignment(node as BinaryExpression);
|
||||
break;
|
||||
|
@ -2365,7 +2366,8 @@ namespace ts {
|
|||
|
||||
/** For `x.prototype = { p, ... }`, declare members p,... if `x` is function/class/{}, or not declared. */
|
||||
function bindPrototypeAssignment(node: BinaryExpression) {
|
||||
return node;
|
||||
node.left.parent = node;
|
||||
bindPropertyAssignment(node.left as PropertyAccessEntityNameExpression, node.left as PropertyAccessEntityNameExpression, /*isPrototypeProperty*/ false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2375,9 +2377,8 @@ namespace ts {
|
|||
function bindPrototypePropertyAssignment(lhs: PropertyAccessEntityNameExpression, parent: Node) {
|
||||
// Look up the function in the local scope, since prototype assignments should
|
||||
// follow the function declaration
|
||||
// TODO: This cast is now insufficient for original case+nested case
|
||||
const classPrototype = lhs.expression as PropertyAccessEntityNameExpression;
|
||||
const constructorFunction = classPrototype.expression as Identifier;
|
||||
const constructorFunction = classPrototype.expression;
|
||||
|
||||
// Fix up parent pointers since we're going to use these nodes before we bind into them
|
||||
lhs.parent = parent;
|
||||
|
@ -2415,7 +2416,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) :
|
||||
propertyAccess.parent.parent.parent.kind === SyntaxKind.SourceFile && (getJavascriptInitializer(propertyAccess.parent.right) || isJavascriptPrototypeAssignment(propertyAccess.parent)) :
|
||||
propertyAccess.parent.parent.kind === SyntaxKind.SourceFile;
|
||||
if (!isPrototypeProperty && (!symbol || !(symbol.flags & SymbolFlags.Namespace)) && isToplevelNamespaceableInitializer) {
|
||||
// make symbols or add declarations for intermediate containers
|
||||
|
|
|
@ -4186,7 +4186,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
function getWidenedTypeFromJSSpecialPropertyDeclarations(symbol: Symbol) {
|
||||
// function/class/{} assignments are fresh declarations, not property assignments, so return immediately
|
||||
// function/class/{} assignments are fresh declarations, not property assignments, so only add prototype assignments
|
||||
const specialDeclaration = getAssignedJavascriptInitializer(symbol.valueDeclaration);
|
||||
if (specialDeclaration) {
|
||||
return getWidenedLiteralType(checkExpressionCached(specialDeclaration));
|
||||
|
@ -17897,9 +17897,20 @@ namespace ts {
|
|||
if (isDeclarationOfFunctionOrClassExpression(symbol)) {
|
||||
symbol = getSymbolOfNode((<VariableDeclaration>symbol.valueDeclaration).initializer);
|
||||
}
|
||||
// TODO: Could stick members on this somehow
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (symbol.flags & SymbolFlags.Variable) {
|
||||
const valueType = getTypeOfSymbol(symbol);
|
||||
if (valueType.symbol && !isInferredClassType(valueType) && isJavaScriptConstructor(valueType.symbol.valueDeclaration)) {
|
||||
|
@ -17908,6 +17919,10 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
function getOtherSymbol(node: Node) {
|
||||
return node && node.parent && isBinaryExpression(node.parent) && getSymbolOfNode(node.parent.left);
|
||||
}
|
||||
|
||||
function getInferredClassType(symbol: Symbol) {
|
||||
const links = getSymbolLinks(symbol);
|
||||
if (!links.inferredClassType) {
|
||||
|
|
|
@ -1483,15 +1483,29 @@ 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;
|
||||
}
|
||||
const declaration = symbol.valueDeclaration;
|
||||
const e = getDeclaredJavascriptInitializer(declaration) || getAssignedJavascriptInitializer(declaration);
|
||||
const e = getDeclaredJavascriptInitializer(declaration) || getAssignedJavascriptInitializer(declaration) || getAssignedJavascriptPrototype(declaration);
|
||||
return e ? e.symbol : symbol;
|
||||
}
|
||||
|
||||
export function getAssignedJavascriptPrototype(node: Node) {
|
||||
return isPropertyAccessExpression(node) &&
|
||||
node.parent && isPropertyAccessExpression(node.parent) && node.parent.name.escapedText === "prototype" &&
|
||||
node.parent.parent && isBinaryExpression(node.parent.parent) && node.parent.parent.operatorToken.kind === SyntaxKind.EqualsToken &&
|
||||
node.parent.parent.right.kind === SyntaxKind.ObjectLiteralExpression &&
|
||||
node.parent.parent.right;
|
||||
}
|
||||
|
||||
export function getDeclaredJavascriptInitializer(node: Node) {
|
||||
if (node && isVariableDeclaration(node) && node.initializer) {
|
||||
return getJavascriptInitializer(node.initializer) ||
|
||||
|
|
|
@ -1,34 +1,35 @@
|
|||
=== tests/cases/conformance/salsa/module.js ===
|
||||
var Outer = function(element, config) {};
|
||||
>Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(module.js, 0, 41))
|
||||
>Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(usage.js, 0, 0))
|
||||
>element : Symbol(element, Decl(module.js, 0, 21))
|
||||
>config : Symbol(config, Decl(module.js, 0, 29))
|
||||
|
||||
=== tests/cases/conformance/salsa/usage.js ===
|
||||
/** @constructor */
|
||||
Outer.Pos = function (line, ch) {};
|
||||
>Outer.Pos : Symbol(Outer.Pos, Decl(module.js, 0, 41))
|
||||
>Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(module.js, 0, 41))
|
||||
>Pos : Symbol(Outer.Pos, Decl(module.js, 0, 41))
|
||||
>line : Symbol(line, Decl(module.js, 2, 22))
|
||||
>ch : Symbol(ch, Decl(module.js, 2, 27))
|
||||
>Outer.Pos : Symbol(Outer.Pos, Decl(usage.js, 0, 0))
|
||||
>Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(usage.js, 0, 0))
|
||||
>Pos : Symbol(Outer.Pos, Decl(usage.js, 0, 0))
|
||||
>line : Symbol(line, Decl(usage.js, 1, 22))
|
||||
>ch : Symbol(ch, Decl(usage.js, 1, 27))
|
||||
|
||||
/** @type {number} */
|
||||
Outer.Pos.prototype.line;
|
||||
>Outer.Pos.prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
|
||||
>Outer.Pos : Symbol(Outer.Pos, Decl(module.js, 0, 41))
|
||||
>Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(module.js, 0, 41))
|
||||
>Pos : Symbol(Outer.Pos, Decl(module.js, 0, 41))
|
||||
>Outer.Pos : Symbol(Outer.Pos, Decl(usage.js, 0, 0))
|
||||
>Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(usage.js, 0, 0))
|
||||
>Pos : Symbol(Outer.Pos, Decl(usage.js, 0, 0))
|
||||
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
|
||||
|
||||
var pos = new Outer.Pos(1, 'x');
|
||||
>pos : Symbol(pos, Decl(module.js, 5, 3))
|
||||
>Outer.Pos : Symbol(Outer.Pos, Decl(module.js, 0, 41))
|
||||
>Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(module.js, 0, 41))
|
||||
>Pos : Symbol(Outer.Pos, Decl(module.js, 0, 41))
|
||||
>pos : Symbol(pos, Decl(usage.js, 4, 3))
|
||||
>Outer.Pos : Symbol(Outer.Pos, Decl(usage.js, 0, 0))
|
||||
>Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(usage.js, 0, 0))
|
||||
>Pos : Symbol(Outer.Pos, Decl(usage.js, 0, 0))
|
||||
|
||||
pos.line;
|
||||
>pos.line : Symbol((Anonymous function).line, Decl(module.js, 2, 35))
|
||||
>pos : Symbol(pos, Decl(module.js, 5, 3))
|
||||
>line : Symbol((Anonymous function).line, Decl(module.js, 2, 35))
|
||||
>pos.line : Symbol((Anonymous function).line, Decl(usage.js, 1, 35))
|
||||
>pos : Symbol(pos, Decl(usage.js, 4, 3))
|
||||
>line : Symbol((Anonymous function).line, Decl(usage.js, 1, 35))
|
||||
|
||||
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
=== tests/cases/conformance/salsa/module.js ===
|
||||
var Outer = function(element, config) {};
|
||||
>Outer : { (element: any, config: any): void; Pos: (line: any, ch: any) => void; }
|
||||
>function(element, config) {} : { (element: any, config: any): void; Pos: (line: any, ch: any) => void; }
|
||||
>Outer : typeof Outer
|
||||
>function(element, config) {} : typeof Outer
|
||||
>element : any
|
||||
>config : any
|
||||
|
||||
=== tests/cases/conformance/salsa/usage.js ===
|
||||
/** @constructor */
|
||||
Outer.Pos = function (line, ch) {};
|
||||
>Outer.Pos = function (line, ch) {} : (line: any, ch: any) => void
|
||||
>Outer.Pos : (line: any, ch: any) => void
|
||||
>Outer : { (element: any, config: any): void; Pos: (line: any, ch: any) => void; }
|
||||
>Outer : typeof Outer
|
||||
>Pos : (line: any, ch: any) => void
|
||||
>function (line, ch) {} : (line: any, ch: any) => void
|
||||
>line : any
|
||||
|
@ -20,7 +21,7 @@ Outer.Pos.prototype.line;
|
|||
>Outer.Pos.prototype.line : any
|
||||
>Outer.Pos.prototype : any
|
||||
>Outer.Pos : (line: any, ch: any) => void
|
||||
>Outer : { (element: any, config: any): void; Pos: (line: any, ch: any) => void; }
|
||||
>Outer : typeof Outer
|
||||
>Pos : (line: any, ch: any) => void
|
||||
>prototype : any
|
||||
>line : any
|
||||
|
@ -29,7 +30,7 @@ var pos = new Outer.Pos(1, 'x');
|
|||
>pos : { line: number; }
|
||||
>new Outer.Pos(1, 'x') : { line: number; }
|
||||
>Outer.Pos : (line: any, ch: any) => void
|
||||
>Outer : { (element: any, config: any): void; Pos: (line: any, ch: any) => void; }
|
||||
>Outer : typeof Outer
|
||||
>Pos : (line: any, ch: any) => void
|
||||
>1 : 1
|
||||
>'x' : "x"
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
=== tests/cases/conformance/salsa/module.js ===
|
||||
var Outer = {}
|
||||
>Outer : Symbol(Outer, Decl(module.js, 0, 3), Decl(module.js, 0, 14), Decl(module.js, 1, 27))
|
||||
|
||||
Outer.Inner = function() {}
|
||||
>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))
|
||||
|
||||
Outer.Inner.prototype = {
|
||||
>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))
|
||||
>Inner : Symbol(Inner, Decl(module.js, 0, 14), Decl(module.js, 2, 6))
|
||||
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
|
||||
|
||||
m() { },
|
||||
>m : Symbol(m, Decl(module.js, 2, 25))
|
||||
|
||||
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)
|
||||
Outer.Inner.prototype.j = 2
|
||||
>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))
|
||||
>Inner : Symbol(Inner, Decl(module.js, 0, 14), Decl(module.js, 2, 6))
|
||||
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
|
||||
|
||||
var inner = new Outer.Inner()
|
||||
>inner : Symbol(inner, Decl(module.js, 8, 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))
|
||||
>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))
|
||||
>i : Symbol(i, Decl(module.js, 3, 12))
|
||||
|
||||
inner.j
|
||||
>inner : Symbol(inner, Decl(module.js, 8, 3))
|
||||
|
63
tests/baselines/reference/typeFromPropertyAssignment13.types
Normal file
63
tests/baselines/reference/typeFromPropertyAssignment13.types
Normal file
|
@ -0,0 +1,63 @@
|
|||
=== tests/cases/conformance/salsa/module.js ===
|
||||
var Outer = {}
|
||||
>Outer : { [x: string]: any; Inner: () => void; }
|
||||
>{} : { [x: string]: any; Inner: () => void; }
|
||||
|
||||
Outer.Inner = function() {}
|
||||
>Outer.Inner = function() {} : () => void
|
||||
>Outer.Inner : () => void
|
||||
>Outer : { [x: string]: any; Inner: () => void; }
|
||||
>Inner : () => void
|
||||
>function() {} : () => void
|
||||
|
||||
Outer.Inner.prototype = {
|
||||
>Outer.Inner.prototype = { m() { }, i: 1} : { [x: string]: any; m(): void; i: number; }
|
||||
>Outer.Inner.prototype : any
|
||||
>Outer.Inner : () => void
|
||||
>Outer : { [x: string]: any; Inner: () => void; }
|
||||
>Inner : () => void
|
||||
>prototype : any
|
||||
>{ m() { }, i: 1} : { [x: string]: any; m(): void; i: number; }
|
||||
|
||||
m() { },
|
||||
>m : () => void
|
||||
|
||||
i: 1
|
||||
>i : number
|
||||
>1 : 1
|
||||
}
|
||||
// NOTE: incremental assignments don't work (but don't need to for chrome at least)
|
||||
Outer.Inner.prototype.j = 2
|
||||
>Outer.Inner.prototype.j = 2 : 2
|
||||
>Outer.Inner.prototype.j : any
|
||||
>Outer.Inner.prototype : any
|
||||
>Outer.Inner : () => void
|
||||
>Outer : { [x: string]: any; Inner: () => void; }
|
||||
>Inner : () => void
|
||||
>prototype : any
|
||||
>j : any
|
||||
>2 : 2
|
||||
|
||||
var inner = new Outer.Inner()
|
||||
>inner : { [x: string]: any; m(): void; i: number; }
|
||||
>new Outer.Inner() : { [x: string]: any; m(): void; i: number; }
|
||||
>Outer.Inner : () => void
|
||||
>Outer : { [x: string]: any; Inner: () => void; }
|
||||
>Inner : () => void
|
||||
|
||||
inner.m()
|
||||
>inner.m() : void
|
||||
>inner.m : () => void
|
||||
>inner : { [x: string]: any; m(): void; i: number; }
|
||||
>m : () => void
|
||||
|
||||
inner.i
|
||||
>inner.i : number
|
||||
>inner : { [x: string]: any; m(): void; i: number; }
|
||||
>i : number
|
||||
|
||||
inner.j
|
||||
>inner.j : any
|
||||
>inner : { [x: string]: any; m(): void; i: number; }
|
||||
>j : any
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
// @target: es6
|
||||
// @Filename: module.js
|
||||
var Outer = function(element, config) {};
|
||||
// @Filename: usage.js
|
||||
/** @constructor */
|
||||
Outer.Pos = function (line, ch) {};
|
||||
/** @type {number} */
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
// @noEmit: true
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
// @target: es6
|
||||
// @Filename: module.js
|
||||
var Outer = {}
|
||||
Outer.Inner = function() {}
|
||||
Outer.Inner.prototype = {
|
||||
m() { },
|
||||
i: 1
|
||||
}
|
||||
// NOTE: incremental assignments don't work (but don't need to for chrome at least)
|
||||
Outer.Inner.prototype.j = 2
|
||||
var inner = new Outer.Inner()
|
||||
inner.m()
|
||||
inner.i
|
||||
inner.j
|
Loading…
Reference in a new issue