diff --git a/packages/compiler/src/frontend/shape-inference/analyze.ts b/packages/compiler/src/frontend/shape-inference/analyze.ts index 44d0f06a1..6722a532a 100644 --- a/packages/compiler/src/frontend/shape-inference/analyze.ts +++ b/packages/compiler/src/frontend/shape-inference/analyze.ts @@ -1441,6 +1441,10 @@ class Analyzer { // clone is its own context root, so it would resolve them in the // default context instead, and their writes would be lost. if (info.root !== null && info.root !== info.node) return null; + // A class's constructor and methods run in receiver contexts of their + // own, rooted at each member: inside a clone they too would resolve the + // function's bindings in the default context. + if (this.declaresClass(info.node)) return null; const sc = this.argsFrom.get(args); if (sc === undefined || sc.inLoop || this.forceLoop || this.repeatingSites.has(site)) return null; @@ -1468,6 +1472,19 @@ class Analyzer { return clone; } + private readonly classDeclarers = new Map(); + /** Whether a class declaration or expression is anywhere inside `node`. */ + private declaresClass(node: Node): boolean { + let r = this.classDeclarers.get(node); + if (r === undefined) { + const visit = (n: Node): true | undefined => + ts.isClassLikeDeclaration(n) ? true : ts.forEachChild(n, visit); + r = ts.forEachChild(node, visit) === true; + this.classDeclarers.set(node, r); + } + return r; + } + private readonly factories = new Map(); isFactory(node: Node): boolean { let r = this.factories.get(node); diff --git a/tests/fixtures/npm/cases/static-tables/node_modules/static-tables/index.js b/tests/fixtures/npm/cases/static-tables/node_modules/static-tables/index.js index 6bae9a947..1150aff5f 100644 --- a/tests/fixtures/npm/cases/static-tables/node_modules/static-tables/index.js +++ b/tests/fixtures/npm/cases/static-tables/node_modules/static-tables/index.js @@ -87,8 +87,21 @@ var setBound = function () { this.v = 1; }.bind(lib.bound); setBound(); +// A factory that declares a class whose methods write the factory's +// bindings. +function makeTally() { + var tally = { n: 0 }; + class Tally { + bump() { + tally.n = 1; + } + } + new Tally().bump(); + return tally; +} +lib.tallied = makeTally(); lib.inits = function () { - return lib.made.v + "," + lib.bound.v; + return lib.made.v + "," + lib.bound.v + "," + lib.tallied.n; }; // A constructor function with prototype methods.