mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-25 06:24:27 +08:00
Compile derived constructors whose super() call leads a comma statement (#1354)
This commit is contained in:
@@ -7712,6 +7712,24 @@ function paramPropInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrLoca
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Whether a super() call's value is discarded by its statement: the call
|
||||
* is the expression statement itself, or an operand of a comma chain that
|
||||
* is (`super(m, o), this.code = c;`, as minifiers join statements). The
|
||||
* statement lowering runs each comma operand as its own statement in
|
||||
* source order, so the chain lowers exactly like `super(m, o); this.code =
|
||||
* c;`. Parentheses are transparent to both. */
|
||||
function superCallInStatementPosition(call: ts.CallExpression): boolean {
|
||||
let node: ts.Node = call;
|
||||
for (;;) {
|
||||
const parent = node.parent;
|
||||
if (ts.isExpressionStatement(parent)) return true;
|
||||
const comma =
|
||||
ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.CommaToken;
|
||||
if (!comma && !ts.isParenthesizedExpression(parent)) return false;
|
||||
node = parent;
|
||||
}
|
||||
}
|
||||
|
||||
/** Super statements initialize the base and then the derived fields on
|
||||
* whichever control-flow path executes. Repeated calls are explicitly
|
||||
* refused until native layouts can allocate a second receiver. */
|
||||
@@ -7729,7 +7747,7 @@ export function lowerDerivedCtorBody(
|
||||
const previousInitialized = context.superInitialized;
|
||||
context.superInitialized = initialized;
|
||||
context.superCall = (superCall) => {
|
||||
if (!ts.isExpressionStatement(superCall.parent))
|
||||
if (!superCallInStatementPosition(superCall))
|
||||
lowerer.unsupported("SC1090", superCall, "super() in an expression position");
|
||||
superSeen = true;
|
||||
const loc = locOf(superCall);
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/super-comma-jose-errors.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/super-comma-sequence-js.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/super-comma-sequence.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/diagnostics/super-comma-nested.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// jose 6.2.12's error classes (dist/webapi/util/errors.js), verbatim in
|
||||
// shape: minifiers join a derived constructor's statements into one comma
|
||||
// expression that opens with super(...). Each comma operand runs as its
|
||||
// own statement, so the base constructor and the class field initializers
|
||||
// finish before the trailing assignments.
|
||||
class JOSEError extends Error {
|
||||
static code = "ERR_JOSE_GENERIC";
|
||||
code = "ERR_JOSE_GENERIC";
|
||||
constructor(message, options) {
|
||||
super(message, options), this.name = this.constructor.name, Error.captureStackTrace?.(this, this.constructor);
|
||||
}
|
||||
}
|
||||
class JWTClaimValidationFailed extends JOSEError {
|
||||
static code = "ERR_JWT_CLAIM_VALIDATION_FAILED";
|
||||
code = "ERR_JWT_CLAIM_VALIDATION_FAILED";
|
||||
claim;
|
||||
reason;
|
||||
payload;
|
||||
constructor(message, payload, claim = "unspecified", reason = "unspecified") {
|
||||
super(message, { cause: { claim, reason, payload } }), this.claim = claim, this.reason = reason, this.payload = payload;
|
||||
}
|
||||
}
|
||||
class JWTExpired extends JOSEError {
|
||||
static code = "ERR_JWT_EXPIRED";
|
||||
code = "ERR_JWT_EXPIRED";
|
||||
claim;
|
||||
reason;
|
||||
payload;
|
||||
constructor(message, payload, claim = "unspecified", reason = "unspecified") {
|
||||
super(message, { cause: { claim, reason, payload } }), this.claim = claim, this.reason = reason, this.payload = payload;
|
||||
}
|
||||
}
|
||||
class JOSEAlgNotAllowed extends JOSEError {
|
||||
static code = "ERR_JOSE_ALG_NOT_ALLOWED";
|
||||
code = "ERR_JOSE_ALG_NOT_ALLOWED";
|
||||
}
|
||||
class JWSSignatureVerificationFailed extends JOSEError {
|
||||
static code = "ERR_JWS_SIGNATURE_VERIFICATION_FAILED";
|
||||
code = "ERR_JWS_SIGNATURE_VERIFICATION_FAILED";
|
||||
constructor(message = "signature verification failed", options) {
|
||||
super(message, options);
|
||||
}
|
||||
}
|
||||
|
||||
// The stack is read where the receiver's class is known: a read through an
|
||||
// untyped parameter does not see the stack (a documented scriptc gap).
|
||||
function describe(label, error, stack) {
|
||||
console.log(label, error.name, error.code, JSON.stringify(error.message));
|
||||
console.log(" instanceof", error instanceof JOSEError, error instanceof Error);
|
||||
console.log(" stack head", String(stack).split("\n")[0]);
|
||||
}
|
||||
|
||||
const generic = new JOSEError("generic failure");
|
||||
describe("generic", generic, generic.stack);
|
||||
console.log(" static", JOSEError.code, "cause" in generic);
|
||||
|
||||
const withCause = new JOSEError("wrapped", { cause: "root" });
|
||||
describe("wrapped", withCause, withCause.stack);
|
||||
console.log(" cause", withCause.cause);
|
||||
|
||||
const claims = { sub: "user", exp: 1 };
|
||||
const failed = new JWTClaimValidationFailed('"iss" claim mismatch', claims, "iss", "check_failed");
|
||||
describe("claim", failed, failed.stack);
|
||||
console.log(" fields", failed.claim, failed.reason, failed.payload === claims);
|
||||
console.log(" cause", failed.cause.claim, failed.cause.reason, failed.cause.payload === claims);
|
||||
console.log(" chain", failed instanceof JWTClaimValidationFailed, failed instanceof JWTExpired);
|
||||
|
||||
const defaults = new JWTClaimValidationFailed("defaults", claims);
|
||||
console.log("defaults", defaults.claim, defaults.reason);
|
||||
|
||||
const expired = new JWTExpired('"exp" claim timestamp check failed', claims, "exp");
|
||||
describe("expired", expired, expired.stack);
|
||||
console.log(" static", JWTExpired.code, expired.reason);
|
||||
|
||||
const alg = new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter value not allowed');
|
||||
describe("alg", alg, alg.stack);
|
||||
const sig = new JWSSignatureVerificationFailed();
|
||||
describe("sig", sig, sig.stack);
|
||||
|
||||
try {
|
||||
throw new JWTExpired("thrown", claims, "exp", "check_failed");
|
||||
} catch (error) {
|
||||
if (error instanceof JOSEError) console.log("caught", error.name, error.code, error.claim);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// The JavaScript side of super-comma-sequence.ts: a statement-level comma
|
||||
// expression containing super(...) runs its operands in source order, with
|
||||
// the base constructor and the derived field initializers at the super()
|
||||
// call, as the separate statements a minifier joined would.
|
||||
function log(label, value) {
|
||||
console.log(" " + label);
|
||||
return value;
|
||||
}
|
||||
|
||||
class Base {
|
||||
constructor(origin) {
|
||||
console.log(" Base body", origin);
|
||||
this.origin = origin;
|
||||
}
|
||||
}
|
||||
|
||||
class Joined extends Base {
|
||||
first = log("field first", 1);
|
||||
second = this.first + 1;
|
||||
constructor(origin) {
|
||||
super(log("super argument", origin)), log("after super", 0), this.tag = `${this.origin}:${this.second}`;
|
||||
}
|
||||
}
|
||||
|
||||
class Leading extends Base {
|
||||
value = log("field value", "v");
|
||||
constructor(origin) {
|
||||
log("before super", 0), super(origin), log("after super " + this.value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
class Again extends Joined {
|
||||
extra = log("field extra", "e");
|
||||
constructor() {
|
||||
super("again"), this.tag = this.tag + "!", this.extra = this.extra + this.tag;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("joined:");
|
||||
const joined = new Joined("j");
|
||||
console.log(joined.origin, joined.first, joined.second, joined.tag);
|
||||
|
||||
console.log("leading:");
|
||||
const leading = new Leading("l");
|
||||
console.log(leading.origin, leading.value);
|
||||
|
||||
console.log("again:");
|
||||
const again = new Again();
|
||||
console.log(again.origin, again.tag, again.extra, again instanceof Joined, again instanceof Base);
|
||||
@@ -0,0 +1,101 @@
|
||||
// @transform-types
|
||||
// super(...) as an operand of a statement-level comma expression lowers
|
||||
// like the separate statements a minifier joined: operands run in source
|
||||
// order, the base constructor and then the derived field initializers run
|
||||
// at the super() call, and the trailing operands see the initialized
|
||||
// instance.
|
||||
function log<T>(label: string, value: T): T {
|
||||
console.log(" " + label);
|
||||
return value;
|
||||
}
|
||||
|
||||
class Base {
|
||||
readonly origin: string;
|
||||
constructor(origin: string) {
|
||||
console.log(" Base body", origin);
|
||||
this.origin = origin;
|
||||
}
|
||||
}
|
||||
|
||||
class Joined extends Base {
|
||||
first = log("field first", 1);
|
||||
second: number = this.first + 1;
|
||||
tag: string;
|
||||
constructor(origin: string) {
|
||||
super(log("super argument", origin)), log("after super", 0), this.tag = `${this.origin}:${this.second}`;
|
||||
}
|
||||
}
|
||||
|
||||
class Leading extends Base {
|
||||
value = log("field value", "v");
|
||||
constructor(origin: string) {
|
||||
log("before super", 0), super(origin), log("after super " + this.value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
class Parenthesized extends Base {
|
||||
count = 0;
|
||||
constructor() {
|
||||
(super("paren"), this.count++, this.count++);
|
||||
}
|
||||
}
|
||||
|
||||
class Nested extends Base {
|
||||
steps: string[] = [];
|
||||
constructor() {
|
||||
super("nested"), (this.steps.push("a"), this.steps.push("b")), this.steps.push("c");
|
||||
}
|
||||
}
|
||||
|
||||
class Branching extends Base {
|
||||
side: string;
|
||||
constructor(left: boolean) {
|
||||
if (left) super("left"), this.side = "L";
|
||||
else super("right"), this.side = "R";
|
||||
}
|
||||
}
|
||||
|
||||
class Point {
|
||||
constructor(
|
||||
public x: number,
|
||||
public y: number,
|
||||
) {}
|
||||
}
|
||||
|
||||
class Point3 extends Point {
|
||||
label = "p3";
|
||||
constructor(
|
||||
x: number,
|
||||
y: number,
|
||||
public z: number,
|
||||
) {
|
||||
super(x, y), console.log(" Point3", this.x, this.y, this.z, this.label);
|
||||
}
|
||||
}
|
||||
|
||||
class Point4 extends Point3 {
|
||||
w: number;
|
||||
constructor(x: number, y: number, z: number, w: number) {
|
||||
super(x, y, z), this.w = w, this.label = "p4";
|
||||
}
|
||||
}
|
||||
|
||||
console.log("joined:");
|
||||
const joined = new Joined("j");
|
||||
console.log(joined.origin, joined.first, joined.second, joined.tag);
|
||||
|
||||
console.log("leading:");
|
||||
const leading = new Leading("l");
|
||||
console.log(leading.origin, leading.value);
|
||||
|
||||
const paren = new Parenthesized();
|
||||
console.log("parenthesized:", paren.origin, paren.count);
|
||||
|
||||
const nested = new Nested();
|
||||
console.log("nested:", nested.origin, nested.steps.join(","));
|
||||
|
||||
console.log("branching:", new Branching(true).side, new Branching(false).side);
|
||||
|
||||
console.log("chain:");
|
||||
const p4 = new Point4(1, 2, 3, 4);
|
||||
console.log(p4.x, p4.y, p4.z, p4.w, p4.label, p4 instanceof Point3, p4 instanceof Point);
|
||||
@@ -0,0 +1,24 @@
|
||||
// super(...) as an operand of a statement-level comma expression compiles
|
||||
// (super-comma-sequence.ts). Any other expression position still refuses:
|
||||
// the field initializers that run at super() would be misordered.
|
||||
class Base {
|
||||
constructor(readonly origin: string) {}
|
||||
}
|
||||
|
||||
class Selected extends Base {
|
||||
constructor(left: boolean) {
|
||||
left ? super("left") : super("right");
|
||||
}
|
||||
}
|
||||
|
||||
function keep<T>(value: T): T {
|
||||
return value;
|
||||
}
|
||||
|
||||
class Argument extends Base {
|
||||
constructor() {
|
||||
keep((super("argument"), 1));
|
||||
}
|
||||
}
|
||||
|
||||
console.log(new Selected(true).origin, new Argument().origin);
|
||||
@@ -0,0 +1,27 @@
|
||||
super-comma-nested.ts:9:3 - error SC1090: derived constructors without a lowered super() call is not supported yet
|
||||
|
||||
8 | class Selected extends Base {
|
||||
9 | constructor(left: boolean) {
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
10 | left ? super("left") : super("right");
|
||||
|
||||
super-comma-nested.ts:10:12 - error SC1090: super() in an expression position is not supported yet
|
||||
|
||||
9 | constructor(left: boolean) {
|
||||
10 | left ? super("left") : super("right");
|
||||
| ^~~~~~~~~~~~~
|
||||
11 | }
|
||||
|
||||
super-comma-nested.ts:19:3 - error SC1090: derived constructors without a lowered super() call is not supported yet
|
||||
|
||||
18 | class Argument extends Base {
|
||||
19 | constructor() {
|
||||
| ^~~~~~~~~~~~~~~
|
||||
20 | keep((super("argument"), 1));
|
||||
|
||||
super-comma-nested.ts:20:11 - error SC1090: super() in an expression position is not supported yet
|
||||
|
||||
19 | constructor() {
|
||||
20 | keep((super("argument"), 1));
|
||||
| ^~~~~~~~~~~~~~~~~
|
||||
21 | }
|
||||
Reference in New Issue
Block a user