From 50ef631b590898eeafa9f3a59b3c09af3b35ea49 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 5 Jul 2018 09:04:28 -0700 Subject: [PATCH] Support prototype assignment with a function declaration (#25300) Previously variable declaration+function expression worked. Note that class expression/class declaration do not work, due to the way they are specified. I added a test for future reference. --- src/compiler/checker.ts | 3 +- .../typeFromPropertyAssignment27.symbols | 26 +++++++++++++ .../typeFromPropertyAssignment27.types | 34 +++++++++++++++++ .../typeFromPropertyAssignment28.errors.txt | 23 +++++++++++ .../typeFromPropertyAssignment28.symbols | 30 +++++++++++++++ .../typeFromPropertyAssignment28.types | 38 +++++++++++++++++++ .../salsa/typeFromPropertyAssignment27.ts | 11 ++++++ .../salsa/typeFromPropertyAssignment28.ts | 15 ++++++++ 8 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/typeFromPropertyAssignment27.symbols create mode 100644 tests/baselines/reference/typeFromPropertyAssignment27.types create mode 100644 tests/baselines/reference/typeFromPropertyAssignment28.errors.txt create mode 100644 tests/baselines/reference/typeFromPropertyAssignment28.symbols create mode 100644 tests/baselines/reference/typeFromPropertyAssignment28.types create mode 100644 tests/cases/conformance/salsa/typeFromPropertyAssignment27.ts create mode 100644 tests/cases/conformance/salsa/typeFromPropertyAssignment28.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 207d7317078..db5ded1093e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19458,7 +19458,8 @@ namespace ts { function getAssignedClassType(symbol: Symbol) { const decl = symbol.valueDeclaration; const assignmentSymbol = decl && decl.parent && - (isBinaryExpression(decl.parent) && getSymbolOfNode(decl.parent.left) || + (isFunctionDeclaration(decl) && getSymbolOfNode(decl) || + isBinaryExpression(decl.parent) && getSymbolOfNode(decl.parent.left) || isVariableDeclaration(decl.parent) && getSymbolOfNode(decl.parent)); if (assignmentSymbol) { const prototype = forEach(assignmentSymbol.declarations, getAssignedJavascriptPrototype); diff --git a/tests/baselines/reference/typeFromPropertyAssignment27.symbols b/tests/baselines/reference/typeFromPropertyAssignment27.symbols new file mode 100644 index 00000000000..e76f61b2bbc --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment27.symbols @@ -0,0 +1,26 @@ +=== tests/cases/conformance/salsa/a.js === +// mixed prototype-assignment+function declaration +function C() { this.p = 1; } +>C : Symbol(C, Decl(a.js, 0, 0), Decl(a.js, 1, 28)) +>p : Symbol(C.p, Decl(a.js, 1, 14)) + +C.prototype = { q: 2 }; +>C.prototype : Symbol(C.prototype, Decl(a.js, 1, 28)) +>C : Symbol(C, Decl(a.js, 0, 0), Decl(a.js, 1, 28)) +>prototype : Symbol(C.prototype, Decl(a.js, 1, 28)) +>q : Symbol(q, Decl(a.js, 2, 15)) + +const c = new C() +>c : Symbol(c, Decl(a.js, 4, 5)) +>C : Symbol(C, Decl(a.js, 0, 0), Decl(a.js, 1, 28)) + +c.p +>c.p : Symbol(C.p, Decl(a.js, 1, 14)) +>c : Symbol(c, Decl(a.js, 4, 5)) +>p : Symbol(C.p, Decl(a.js, 1, 14)) + +c.q +>c.q : Symbol(q, Decl(a.js, 2, 15)) +>c : Symbol(c, Decl(a.js, 4, 5)) +>q : Symbol(q, Decl(a.js, 2, 15)) + diff --git a/tests/baselines/reference/typeFromPropertyAssignment27.types b/tests/baselines/reference/typeFromPropertyAssignment27.types new file mode 100644 index 00000000000..222bd04e74d --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment27.types @@ -0,0 +1,34 @@ +=== tests/cases/conformance/salsa/a.js === +// mixed prototype-assignment+function declaration +function C() { this.p = 1; } +>C : typeof C +>this.p = 1 : 1 +>this.p : any +>this : any +>p : any +>1 : 1 + +C.prototype = { q: 2 }; +>C.prototype = { q: 2 } : { [x: string]: any; q: number; } +>C.prototype : { [x: string]: any; } +>C : typeof C +>prototype : { [x: string]: any; } +>{ q: 2 } : { [x: string]: any; q: number; } +>q : number +>2 : 2 + +const c = new C() +>c : C & { [x: string]: any; q: number; } +>new C() : C & { [x: string]: any; q: number; } +>C : typeof C + +c.p +>c.p : number +>c : C & { [x: string]: any; q: number; } +>p : number + +c.q +>c.q : number +>c : C & { [x: string]: any; q: number; } +>q : number + diff --git a/tests/baselines/reference/typeFromPropertyAssignment28.errors.txt b/tests/baselines/reference/typeFromPropertyAssignment28.errors.txt new file mode 100644 index 00000000000..2f048840a1e --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment28.errors.txt @@ -0,0 +1,23 @@ +tests/cases/conformance/salsa/a.js(7,17): error TS2322: Type '{ [x: string]: any; q: number; }' is not assignable to type 'C'. + Object literal may only specify known properties, and 'q' does not exist in type 'C'. +tests/cases/conformance/salsa/a.js(11,3): error TS2339: Property 'q' does not exist on type 'C'. + + +==== tests/cases/conformance/salsa/a.js (2 errors) ==== + // mixed prototype-assignment+class declaration + class C { constructor() { this.p = 1; } } + // Property assignment does nothing. + // You have to use Object.defineProperty(C, "prototype", { q: 2 }) + // and that only works on classes with no superclass. + // (Object.defineProperty isn't recognised as a JS special assignment right now.) + C.prototype = { q: 2 }; + ~~~~ +!!! error TS2322: Type '{ [x: string]: any; q: number; }' is not assignable to type 'C'. +!!! error TS2322: Object literal may only specify known properties, and 'q' does not exist in type 'C'. + + const c = new C() + c.p + c.q + ~ +!!! error TS2339: Property 'q' does not exist on type 'C'. + \ No newline at end of file diff --git a/tests/baselines/reference/typeFromPropertyAssignment28.symbols b/tests/baselines/reference/typeFromPropertyAssignment28.symbols new file mode 100644 index 00000000000..a2997ef9180 --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment28.symbols @@ -0,0 +1,30 @@ +=== tests/cases/conformance/salsa/a.js === +// mixed prototype-assignment+class declaration +class C { constructor() { this.p = 1; } } +>C : Symbol(C, Decl(a.js, 0, 0), Decl(a.js, 1, 41)) +>this.p : Symbol(C.p, Decl(a.js, 1, 25)) +>this : Symbol(C, Decl(a.js, 0, 0), Decl(a.js, 1, 41)) +>p : Symbol(C.p, Decl(a.js, 1, 25)) + +// Property assignment does nothing. +// You have to use Object.defineProperty(C, "prototype", { q: 2 }) +// and that only works on classes with no superclass. +// (Object.defineProperty isn't recognised as a JS special assignment right now.) +C.prototype = { q: 2 }; +>C.prototype : Symbol(C.prototype, Decl(a.js, 1, 41)) +>C : Symbol(C, Decl(a.js, 0, 0), Decl(a.js, 1, 41)) +>prototype : Symbol(C.prototype, Decl(a.js, 1, 41)) +>q : Symbol(q, Decl(a.js, 6, 15)) + +const c = new C() +>c : Symbol(c, Decl(a.js, 8, 5)) +>C : Symbol(C, Decl(a.js, 0, 0), Decl(a.js, 1, 41)) + +c.p +>c.p : Symbol(C.p, Decl(a.js, 1, 25)) +>c : Symbol(c, Decl(a.js, 8, 5)) +>p : Symbol(C.p, Decl(a.js, 1, 25)) + +c.q +>c : Symbol(c, Decl(a.js, 8, 5)) + diff --git a/tests/baselines/reference/typeFromPropertyAssignment28.types b/tests/baselines/reference/typeFromPropertyAssignment28.types new file mode 100644 index 00000000000..89056ee1398 --- /dev/null +++ b/tests/baselines/reference/typeFromPropertyAssignment28.types @@ -0,0 +1,38 @@ +=== tests/cases/conformance/salsa/a.js === +// mixed prototype-assignment+class declaration +class C { constructor() { this.p = 1; } } +>C : C +>this.p = 1 : 1 +>this.p : number +>this : this +>p : number +>1 : 1 + +// Property assignment does nothing. +// You have to use Object.defineProperty(C, "prototype", { q: 2 }) +// and that only works on classes with no superclass. +// (Object.defineProperty isn't recognised as a JS special assignment right now.) +C.prototype = { q: 2 }; +>C.prototype = { q: 2 } : { [x: string]: any; q: number; } +>C.prototype : C +>C : typeof C +>prototype : C +>{ q: 2 } : { [x: string]: any; q: number; } +>q : number +>2 : 2 + +const c = new C() +>c : C +>new C() : C +>C : typeof C + +c.p +>c.p : number +>c : C +>p : number + +c.q +>c.q : any +>c : C +>q : any + diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment27.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment27.ts new file mode 100644 index 00000000000..71963749f1a --- /dev/null +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment27.ts @@ -0,0 +1,11 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: a.js +// mixed prototype-assignment+function declaration +function C() { this.p = 1; } +C.prototype = { q: 2 }; + +const c = new C() +c.p +c.q diff --git a/tests/cases/conformance/salsa/typeFromPropertyAssignment28.ts b/tests/cases/conformance/salsa/typeFromPropertyAssignment28.ts new file mode 100644 index 00000000000..5a9ffd3e415 --- /dev/null +++ b/tests/cases/conformance/salsa/typeFromPropertyAssignment28.ts @@ -0,0 +1,15 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: a.js +// mixed prototype-assignment+class declaration +class C { constructor() { this.p = 1; } } +// Property assignment does nothing. +// You have to use Object.defineProperty(C, "prototype", { q: 2 }) +// and that only works on classes with no superclass. +// (Object.defineProperty isn't recognised as a JS special assignment right now.) +C.prototype = { q: 2 }; + +const c = new C() +c.p +c.q