Skip to content

Exported class EXPRESSION reads .prototype as undefined from an importing module (class declarations are fine) #9366

Description

@proggeramlug

Repro (two files)

lib.ts

export class Decl { m() { return 1; } }
export let Expr = class { m() { return 2; } };
export const Anon = class Named { m() { return 3; } };
export function localProbe() {
  return [typeof Decl.prototype, typeof Expr.prototype, typeof Anon.prototype].join(",");
}

main.ts

import { Decl, Expr, Anon, localProbe } from "./lib";
console.log("in-defining-module=" + localProbe());
console.log("importer-Decl=" + typeof Decl.prototype);
console.log("importer-Expr=" + typeof Expr.prototype);
console.log("importer-Anon=" + typeof Anon.prototype);
node : in-defining-module=object,object,object
       importer-Decl=object   importer-Expr=object       importer-Anon=object

perry: in-defining-module=object,object,object
       importer-Decl=object   importer-Expr=undefined    importer-Anon=undefined

Scope

shape read in defining module read in importing module
export class Decl {} (declaration) ok ok
export let Expr = class {} (expression) ok undefined
export const Anon = class Named {} (named expression) ok undefined

So it is specifically a class expression, read across a module boundary. The same read inside the defining module is correct, and the class is otherwise fully functional through the importer — new Decl().m is a function, Object.getPrototypeOf(instance) is an object, instances work. It is the .prototype property access on the imported binding that answers undefined.

Not a regression: identical on 83754818e (#9242) and a03be729c (#9336).

Why this one matters

This is the shape at the heart of #9341. The diagnostic there shows cc --help dying on Object.defineProperty(undefined, "getContentType", …), and the undefined is z in axios:

kU6 = class {  static accessor(q){  let z = this.prototype;  xp5(z, A)  }  };
kU6.accessor(["Content-Type", ]);
sH = kU6;

kU6 is a class expression, which is exactly the shape above. A reduction of that accessor across two modules reproduces prototype=undefined on both the green and the broken build — so this bug is the machinery #9341's regression is landing on, even though the trigger for when the bundle takes this path is a separate change inside 83754818e..main.

Three other .prototype defects turned up in the same sweep, all pre-existing: #9365 (a statically-named o.prototype = v store silently dropped after a computed-key write), #9362 (util.inherits with a class ctor), #9364 (SIGSEGV on a chained class D extends <param> factory). Taken together, .prototype handling has several ordinary shapes that silently answer undefined, and silence is what makes them expensive: #9341 cost three wrong attributions and two unnecessary reverts before anyone saw the receiver.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions