From 17d57fdfb54b26af4077a9f07f9bace808847628 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 6 Jul 2020 11:33:21 -0700 Subject: [PATCH] In JS, assignment to `void 0` isn't a declaration Previously, property assignments with `void 0` initialisers were treated like any other values. But this causes us to choke when checking our own commonjs emit. This is something that happens by mistake a fair amount, so this PR goes back to treating these assignments as normal assignments. This should allow us to check our own emit in loose cases without harming other code bases, since `void 0` is rarely written by hand. Note that other errors still happen: noImplicitAny forbids accessing undeclared properties on object literals, and strictNullChecks forbids assigning `undefined` to properties with a different type. However, this change is enough to unblock compilation with `strictNullChecks: false`. --- src/compiler/utilities.ts | 6 +- .../reference/assignmentToVoidZero1.js | 17 ++++ .../reference/assignmentToVoidZero1.symbols | 20 +++++ .../reference/assignmentToVoidZero1.types | 28 ++++++ .../assignmentToVoidZero2.errors.txt | 36 ++++++++ .../reference/assignmentToVoidZero2.js | 46 ++++++++++ .../reference/assignmentToVoidZero2.symbols | 53 ++++++++++++ .../reference/assignmentToVoidZero2.types | 86 +++++++++++++++++++ .../salsa/assignmentToVoidZero1.ts | 11 +++ .../salsa/assignmentToVoidZero2.ts | 24 ++++++ 10 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/assignmentToVoidZero1.js create mode 100644 tests/baselines/reference/assignmentToVoidZero1.symbols create mode 100644 tests/baselines/reference/assignmentToVoidZero1.types create mode 100644 tests/baselines/reference/assignmentToVoidZero2.errors.txt create mode 100644 tests/baselines/reference/assignmentToVoidZero2.js create mode 100644 tests/baselines/reference/assignmentToVoidZero2.symbols create mode 100644 tests/baselines/reference/assignmentToVoidZero2.types create mode 100644 tests/cases/conformance/salsa/assignmentToVoidZero1.ts create mode 100644 tests/cases/conformance/salsa/assignmentToVoidZero2.ts diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 74ebe61b454ef..732630d7d9f5a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2220,7 +2220,7 @@ namespace ts { } return AssignmentDeclarationKind.ObjectDefinePropertyValue; } - if (expr.operatorToken.kind !== SyntaxKind.EqualsToken || !isAccessExpression(expr.left)) { + if (expr.operatorToken.kind !== SyntaxKind.EqualsToken || !isAccessExpression(expr.left) || isVoidZero(getRightMostAssignedExpression(expr))) { return AssignmentDeclarationKind.None; } if (isBindableStaticNameExpression(expr.left.expression, /*excludeThisKeyword*/ true) && getElementOrPropertyAccessName(expr.left) === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) { @@ -2230,6 +2230,10 @@ namespace ts { return getAssignmentDeclarationPropertyAccessKind(expr.left); } + function isVoidZero(node: Node) { + return isVoidExpression(node) && isNumericLiteral(node.expression) && node.expression.text === "0"; + } + /** * Does not handle signed numeric names like `a[+0]` - handling those would require handling prefix unary expressions * throughout late binding handling as well, which is awkward (but ultimately probably doable if there is demand) diff --git a/tests/baselines/reference/assignmentToVoidZero1.js b/tests/baselines/reference/assignmentToVoidZero1.js new file mode 100644 index 0000000000000..542c1fdcf1818 --- /dev/null +++ b/tests/baselines/reference/assignmentToVoidZero1.js @@ -0,0 +1,17 @@ +//// [assignmentToVoidZero1.js] +// #38552 +exports.y = exports.x = void 0; +exports.x = 1; +exports.y = 2; + + +//// [assignmentToVoidZero1.js] +// #38552 +exports.y = exports.x = void 0; +exports.x = 1; +exports.y = 2; + + +//// [assignmentToVoidZero1.d.ts] +export var x: number; +export var y: number; diff --git a/tests/baselines/reference/assignmentToVoidZero1.symbols b/tests/baselines/reference/assignmentToVoidZero1.symbols new file mode 100644 index 0000000000000..814c82b35e21c --- /dev/null +++ b/tests/baselines/reference/assignmentToVoidZero1.symbols @@ -0,0 +1,20 @@ +=== tests/cases/conformance/salsa/assignmentToVoidZero1.js === +// #38552 +exports.y = exports.x = void 0; +>exports.y : Symbol(y, Decl(assignmentToVoidZero1.js, 2, 14)) +>exports : Symbol("tests/cases/conformance/salsa/assignmentToVoidZero1", Decl(assignmentToVoidZero1.js, 0, 0)) +>y : Symbol(y, Decl(assignmentToVoidZero1.js, 2, 14)) +>exports.x : Symbol(x, Decl(assignmentToVoidZero1.js, 1, 31)) +>exports : Symbol("tests/cases/conformance/salsa/assignmentToVoidZero1", Decl(assignmentToVoidZero1.js, 0, 0)) +>x : Symbol(x, Decl(assignmentToVoidZero1.js, 1, 31)) + +exports.x = 1; +>exports.x : Symbol(x, Decl(assignmentToVoidZero1.js, 1, 31)) +>exports : Symbol(x, Decl(assignmentToVoidZero1.js, 1, 31)) +>x : Symbol(x, Decl(assignmentToVoidZero1.js, 1, 31)) + +exports.y = 2; +>exports.y : Symbol(y, Decl(assignmentToVoidZero1.js, 2, 14)) +>exports : Symbol(y, Decl(assignmentToVoidZero1.js, 2, 14)) +>y : Symbol(y, Decl(assignmentToVoidZero1.js, 2, 14)) + diff --git a/tests/baselines/reference/assignmentToVoidZero1.types b/tests/baselines/reference/assignmentToVoidZero1.types new file mode 100644 index 0000000000000..810cdff57aa0c --- /dev/null +++ b/tests/baselines/reference/assignmentToVoidZero1.types @@ -0,0 +1,28 @@ +=== tests/cases/conformance/salsa/assignmentToVoidZero1.js === +// #38552 +exports.y = exports.x = void 0; +>exports.y = exports.x = void 0 : undefined +>exports.y : number +>exports : typeof import("tests/cases/conformance/salsa/assignmentToVoidZero1") +>y : number +>exports.x = void 0 : undefined +>exports.x : number +>exports : typeof import("tests/cases/conformance/salsa/assignmentToVoidZero1") +>x : number +>void 0 : undefined +>0 : 0 + +exports.x = 1; +>exports.x = 1 : 1 +>exports.x : number +>exports : typeof import("tests/cases/conformance/salsa/assignmentToVoidZero1") +>x : number +>1 : 1 + +exports.y = 2; +>exports.y = 2 : 2 +>exports.y : number +>exports : typeof import("tests/cases/conformance/salsa/assignmentToVoidZero1") +>y : number +>2 : 2 + diff --git a/tests/baselines/reference/assignmentToVoidZero2.errors.txt b/tests/baselines/reference/assignmentToVoidZero2.errors.txt new file mode 100644 index 0000000000000..b9da759c092cc --- /dev/null +++ b/tests/baselines/reference/assignmentToVoidZero2.errors.txt @@ -0,0 +1,36 @@ +tests/cases/conformance/salsa/assignmentToVoidZero2.js(2,9): error TS2339: Property 'k' does not exist on type 'typeof import("tests/cases/conformance/salsa/assignmentToVoidZero2")'. +tests/cases/conformance/salsa/assignmentToVoidZero2.js(5,3): error TS2339: Property 'y' does not exist on type 'typeof o'. +tests/cases/conformance/salsa/assignmentToVoidZero2.js(6,9): error TS2339: Property 'y' does not exist on type 'typeof o'. +tests/cases/conformance/salsa/assignmentToVoidZero2.js(13,9): error TS2339: Property 'q' does not exist on type 'C'. +tests/cases/conformance/salsa/importer.js(1,13): error TS2305: Module '"./assignmentToVoidZero2"' has no exported member 'k'. + + +==== tests/cases/conformance/salsa/assignmentToVoidZero2.js (4 errors) ==== + exports.j = 1; + exports.k = void 0; + ~ +!!! error TS2339: Property 'k' does not exist on type 'typeof import("tests/cases/conformance/salsa/assignmentToVoidZero2")'. + var o = {} + o.x = 1 + o.y = void 0 + ~ +!!! error TS2339: Property 'y' does not exist on type 'typeof o'. + o.x + o.y + ~ +!!! error TS2339: Property 'y' does not exist on type 'typeof o'. + + function C() { + this.p = 1 + this.q = void 0 + } + var c = new C() + c.p + c.q + ~ +!!! error TS2339: Property 'q' does not exist on type 'C'. + +==== tests/cases/conformance/salsa/importer.js (1 errors) ==== + import { j, k } from './assignmentToVoidZero2' + ~ +!!! error TS2305: Module '"./assignmentToVoidZero2"' has no exported member 'k'. + j + k + \ No newline at end of file diff --git a/tests/baselines/reference/assignmentToVoidZero2.js b/tests/baselines/reference/assignmentToVoidZero2.js new file mode 100644 index 0000000000000..c8b658474d2b8 --- /dev/null +++ b/tests/baselines/reference/assignmentToVoidZero2.js @@ -0,0 +1,46 @@ +//// [tests/cases/conformance/salsa/assignmentToVoidZero2.ts] //// + +//// [assignmentToVoidZero2.js] +exports.j = 1; +exports.k = void 0; +var o = {} +o.x = 1 +o.y = void 0 +o.x + o.y + +function C() { + this.p = 1 + this.q = void 0 +} +var c = new C() +c.p + c.q + +//// [importer.js] +import { j, k } from './assignmentToVoidZero2' +j + k + + +//// [assignmentToVoidZero2.js] +exports.j = 1; +exports.k = void 0; +var o = {}; +o.x = 1; +o.y = void 0; +o.x + o.y; +function C() { + this.p = 1; + this.q = void 0; +} +var c = new C(); +c.p + c.q; +//// [importer.js] +"use strict"; +exports.__esModule = true; +var assignmentToVoidZero2_1 = require("./assignmentToVoidZero2"); +assignmentToVoidZero2_1.j + assignmentToVoidZero2_1.k; + + +//// [assignmentToVoidZero2.d.ts] +export var j: number; +//// [importer.d.ts] +export {}; diff --git a/tests/baselines/reference/assignmentToVoidZero2.symbols b/tests/baselines/reference/assignmentToVoidZero2.symbols new file mode 100644 index 0000000000000..b5a93872fa8e3 --- /dev/null +++ b/tests/baselines/reference/assignmentToVoidZero2.symbols @@ -0,0 +1,53 @@ +=== tests/cases/conformance/salsa/assignmentToVoidZero2.js === +exports.j = 1; +>exports.j : Symbol(j, Decl(assignmentToVoidZero2.js, 0, 0)) +>exports : Symbol(j, Decl(assignmentToVoidZero2.js, 0, 0)) +>j : Symbol(j, Decl(assignmentToVoidZero2.js, 0, 0)) + +exports.k = void 0; +>exports : Symbol("tests/cases/conformance/salsa/assignmentToVoidZero2", Decl(assignmentToVoidZero2.js, 0, 0)) + +var o = {} +>o : Symbol(o, Decl(assignmentToVoidZero2.js, 2, 3), Decl(assignmentToVoidZero2.js, 2, 10)) + +o.x = 1 +>o.x : Symbol(o.x, Decl(assignmentToVoidZero2.js, 2, 10)) +>o : Symbol(o, Decl(assignmentToVoidZero2.js, 2, 3), Decl(assignmentToVoidZero2.js, 2, 10)) +>x : Symbol(o.x, Decl(assignmentToVoidZero2.js, 2, 10)) + +o.y = void 0 +>o : Symbol(o, Decl(assignmentToVoidZero2.js, 2, 3), Decl(assignmentToVoidZero2.js, 2, 10)) + +o.x + o.y +>o.x : Symbol(o.x, Decl(assignmentToVoidZero2.js, 2, 10)) +>o : Symbol(o, Decl(assignmentToVoidZero2.js, 2, 3), Decl(assignmentToVoidZero2.js, 2, 10)) +>x : Symbol(o.x, Decl(assignmentToVoidZero2.js, 2, 10)) +>o : Symbol(o, Decl(assignmentToVoidZero2.js, 2, 3), Decl(assignmentToVoidZero2.js, 2, 10)) + +function C() { +>C : Symbol(C, Decl(assignmentToVoidZero2.js, 5, 9)) + + this.p = 1 +>p : Symbol(C.p, Decl(assignmentToVoidZero2.js, 7, 14)) + + this.q = void 0 +} +var c = new C() +>c : Symbol(c, Decl(assignmentToVoidZero2.js, 11, 3)) +>C : Symbol(C, Decl(assignmentToVoidZero2.js, 5, 9)) + +c.p + c.q +>c.p : Symbol(C.p, Decl(assignmentToVoidZero2.js, 7, 14)) +>c : Symbol(c, Decl(assignmentToVoidZero2.js, 11, 3)) +>p : Symbol(C.p, Decl(assignmentToVoidZero2.js, 7, 14)) +>c : Symbol(c, Decl(assignmentToVoidZero2.js, 11, 3)) + +=== tests/cases/conformance/salsa/importer.js === +import { j, k } from './assignmentToVoidZero2' +>j : Symbol(j, Decl(importer.js, 0, 8)) +>k : Symbol(k, Decl(importer.js, 0, 11)) + +j + k +>j : Symbol(j, Decl(importer.js, 0, 8)) +>k : Symbol(k, Decl(importer.js, 0, 11)) + diff --git a/tests/baselines/reference/assignmentToVoidZero2.types b/tests/baselines/reference/assignmentToVoidZero2.types new file mode 100644 index 0000000000000..0b24811f9579d --- /dev/null +++ b/tests/baselines/reference/assignmentToVoidZero2.types @@ -0,0 +1,86 @@ +=== tests/cases/conformance/salsa/assignmentToVoidZero2.js === +exports.j = 1; +>exports.j = 1 : 1 +>exports.j : number +>exports : typeof import("tests/cases/conformance/salsa/assignmentToVoidZero2") +>j : number +>1 : 1 + +exports.k = void 0; +>exports.k = void 0 : undefined +>exports.k : any +>exports : typeof import("tests/cases/conformance/salsa/assignmentToVoidZero2") +>k : any +>void 0 : undefined +>0 : 0 + +var o = {} +>o : typeof o +>{} : {} + +o.x = 1 +>o.x = 1 : 1 +>o.x : number +>o : typeof o +>x : number +>1 : 1 + +o.y = void 0 +>o.y = void 0 : undefined +>o.y : any +>o : typeof o +>y : any +>void 0 : undefined +>0 : 0 + +o.x + o.y +>o.x + o.y : any +>o.x : number +>o : typeof o +>x : number +>o.y : any +>o : typeof o +>y : any + +function C() { +>C : typeof C + + this.p = 1 +>this.p = 1 : 1 +>this.p : any +>this : any +>p : any +>1 : 1 + + this.q = void 0 +>this.q = void 0 : undefined +>this.q : any +>this : any +>q : any +>void 0 : undefined +>0 : 0 +} +var c = new C() +>c : C +>new C() : C +>C : typeof C + +c.p + c.q +>c.p + c.q : any +>c.p : number +>c : C +>p : number +>c.q : any +>c : C +>q : any + +=== tests/cases/conformance/salsa/importer.js === +import { j, k } from './assignmentToVoidZero2' +>j : number +>k : any + +j + k +>j + k : any +>j : number +>k : any + diff --git a/tests/cases/conformance/salsa/assignmentToVoidZero1.ts b/tests/cases/conformance/salsa/assignmentToVoidZero1.ts new file mode 100644 index 0000000000000..b4911c98d2bfc --- /dev/null +++ b/tests/cases/conformance/salsa/assignmentToVoidZero1.ts @@ -0,0 +1,11 @@ +// @filename: assignmentToVoidZero1.js +// @declaration: true +// @module: commonjs +// @outdir: auss +// @checkJs: true +// @allowJs: true + +// #38552 +exports.y = exports.x = void 0; +exports.x = 1; +exports.y = 2; diff --git a/tests/cases/conformance/salsa/assignmentToVoidZero2.ts b/tests/cases/conformance/salsa/assignmentToVoidZero2.ts new file mode 100644 index 0000000000000..6820076ee5f3b --- /dev/null +++ b/tests/cases/conformance/salsa/assignmentToVoidZero2.ts @@ -0,0 +1,24 @@ +// @filename: assignmentToVoidZero2.js +// @declaration: true +// @module: commonjs +// @outdir: auss +// @checkJs: true +// @allowJs: true +// @noImplicitAny: true +exports.j = 1; +exports.k = void 0; +var o = {} +o.x = 1 +o.y = void 0 +o.x + o.y + +function C() { + this.p = 1 + this.q = void 0 +} +var c = new C() +c.p + c.q + +// @filename: importer.js +import { j, k } from './assignmentToVoidZero2' +j + k