support class static initialization block (#5488)

This commit is contained in:
Alex Lam S.L 2022-06-06 05:01:15 +01:00 committed by GitHub
parent d2bd0d1c1c
commit 88b4283200
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 304 additions and 31 deletions

View File

@ -534,7 +534,7 @@ var AST_With = DEFNODE("With", "expression", {
/* -----[ scope and functions ]----- */
var AST_Scope = DEFNODE("Scope", "fn_defs may_call_this uses_eval uses_with", {
$documentation: "Base class for all statements introducing a lexical scope",
$documentation: "Base class for all statements introducing a lambda scope",
$propdoc: {
uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`",
uses_with: "[boolean/S] tells whether this scope uses the `with` statement",
@ -592,6 +592,10 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
}
}, AST_Scope);
var AST_ClassInitBlock = DEFNODE("ClassInitBlock", null, {
$documentation: "Value for `class` static initialization blocks",
}, AST_Scope);
var AST_Lambda = DEFNODE("Lambda", "argnames length_read rest safe_ids uses_arguments", {
$documentation: "Base class for functions",
$propdoc: {
@ -874,7 +878,7 @@ var AST_ClassExpression = DEFNODE("ClassExpression", null, {
var AST_ClassProperty = DEFNODE("ClassProperty", "key private static value", {
$documentation: "Base class for `class` properties",
$propdoc: {
key: "[string|AST_Node] property name (AST_Node for computed property)",
key: "[string|AST_Node?] property name (AST_Node for computed property, null for initialization block)",
private: "[boolean] whether this is a private property",
static: "[boolean] whether this is a static property",
value: "[AST_Node?] property value (AST_Accessor for getters/setters, AST_LambdaExpression for methods, null if not specified for fields)",
@ -888,7 +892,9 @@ var AST_ClassProperty = DEFNODE("ClassProperty", "key private static value", {
},
_validate: function() {
if (this.TYPE == "ClassProperty") throw new Error("should not instantiate AST_ClassProperty");
if (typeof this.key != "string") {
if (this instanceof AST_ClassInit) {
if (this.key != null) throw new Error("key must be null");
} else if (typeof this.key != "string") {
if (!(this.key instanceof AST_Node)) throw new Error("key must be string or AST_Node");
must_be_expression(this, "key");
}
@ -928,6 +934,17 @@ var AST_ClassMethod = DEFNODE("ClassMethod", null, {
},
}, AST_ClassProperty);
var AST_ClassInit = DEFNODE("ClassInit", null, {
$documentation: "A `class` static initialization block",
_validate: function() {
if (!this.static) throw new Error("static must be true");
if (!(this.value instanceof AST_ClassInitBlock)) throw new Error("value must be AST_ClassInitBlock");
},
initialize: function() {
this.static = true;
},
}, AST_ClassProperty);
/* -----[ JUMPS ]----- */
var AST_Jump = DEFNODE("Jump", null, {

View File

@ -1146,7 +1146,7 @@ Compressor.prototype.compress = function(node) {
}
}
props.forEach(function(prop) {
if (!prop.static || prop instanceof AST_ClassField && prop.value.contains_this()) {
if (!prop.static || is_static_field_or_init(prop) && prop.value.contains_this()) {
push(tw);
prop.value.walk(tw);
pop(tw);
@ -1156,6 +1156,14 @@ Compressor.prototype.compress = function(node) {
});
return true;
});
def(AST_ClassInitBlock, function(tw, descend, compressor) {
var node = this;
push(tw);
reset_variables(tw, compressor, node);
descend();
pop_scope(tw, node);
return true;
});
def(AST_Conditional, function(tw) {
this.condition.walk(tw);
push(tw);
@ -1843,6 +1851,10 @@ Compressor.prototype.compress = function(node) {
|| compressor.option("unsafe") && global_names[this.name];
});
function is_static_field_or_init(prop) {
return prop.static && prop.value && (prop instanceof AST_ClassField || prop instanceof AST_ClassInit);
}
function declarations_only(node) {
return all(node.definitions, function(var_def) {
return !var_def.value;
@ -1852,8 +1864,7 @@ Compressor.prototype.compress = function(node) {
function is_declaration(stat, lexical) {
if (stat instanceof AST_DefClass) return lexical && !stat.extends && all(stat.properties, function(prop) {
if (prop.key instanceof AST_Node) return false;
if (prop instanceof AST_ClassField && prop.static && prop.value) return false;
return true;
return !is_static_field_or_init(prop);
});
if (stat instanceof AST_Definitions) return (lexical || stat instanceof AST_Var) && declarations_only(stat);
if (stat instanceof AST_ExportDeclaration) return is_declaration(stat.body, lexical);
@ -6651,7 +6662,7 @@ Compressor.prototype.compress = function(node) {
if (prop.key instanceof AST_Node) prop.key.walk(tw);
var value = prop.value;
if (!value) return;
if (prop instanceof AST_ClassField && prop.static) {
if (is_static_field_or_init(prop)) {
if (!used && value.contains_this()) used = true;
walk_class_prop(value);
} else {
@ -8595,16 +8606,20 @@ Compressor.prototype.compress = function(node) {
});
def(AST_ClassExpression, function(compressor, first_in_statement) {
var self = this;
var exprs = [], values = [];
var exprs = [], values = [], init = 0;
var props = self.properties;
for (var i = 0; i < props.length; i++) {
var prop = props[i];
if (prop.key instanceof AST_Node) exprs.push(prop.key);
if (prop.static && prop.value
&& prop instanceof AST_ClassField
&& prop.value.has_side_effects(compressor)) {
if (prop.value.contains_this()) return self;
values.push(prop.value);
if (!is_static_field_or_init(prop)) continue;
var value = prop.value;
if (!value.has_side_effects(compressor)) continue;
if (value.contains_this()) return self;
if (prop instanceof AST_ClassInit) {
init++;
values.push(prop);
} else {
values.push(value);
}
}
var base = self.extends;
@ -8623,33 +8638,40 @@ Compressor.prototype.compress = function(node) {
if (base || self.name || !compressor.has_directive("use strict")) {
var node = to_class_expr(self);
if (!base) node.extends = null;
node.properties = [];
if (values) {
node.properties.push(make_node(AST_ClassField, self, {
node.properties = values ? values.length == init ? values : [ make_node(AST_ClassField, self, {
static: true,
key: exprs.length ? make_sequence(self, exprs) : "c",
value: make_sequence(self, values),
}));
} else if (exprs.length) {
node.properties.push(make_node(AST_ClassMethod, self, {
value: make_value(),
}) ] : exprs.length ? [ make_node(AST_ClassMethod, self, {
key: make_sequence(self, exprs),
value: make_node(AST_Function, self, {
argnames: [],
body: [],
}).init_vars(node),
}));
}
}) ] : [];
return node;
}
if (values) exprs.push(make_node(AST_Call, self, {
expression: make_node(AST_Arrow, self, {
argnames: [],
body: [],
value: make_sequence(self, values),
value: make_value(),
}).init_vars(self.parent_scope),
args: [],
}));
return make_sequence(self, exprs);
function make_value() {
return make_sequence(self, values.map(function(node) {
if (!(node instanceof AST_ClassInit)) return node;
var fn = make_node(AST_Arrow, node, node.value);
fn.argnames = [];
return make_node(AST_Call, node, {
expression: fn,
args: [],
});
}));
}
});
def(AST_Conditional, function(compressor) {
var consequent = this.consequent.drop_side_effect_free(compressor);

View File

@ -1257,6 +1257,11 @@ function OutputStream(options) {
}
print_method(self, output);
});
DEFPRINT(AST_ClassInit, function(output) {
output.print("static");
output.space();
print_braced(this.value, output);
});
/* -----[ jumps ]----- */
function print_jump(kind, prop) {

View File

@ -1119,6 +1119,18 @@ function parse($TEXT, options) {
}));
continue;
}
if (fixed && is("punc", "{")) {
props.push(new AST_ClassInit({
start: start,
value: new AST_ClassInitBlock({
start: start,
body: block_(),
end: prev(),
}),
end: prev(),
}));
continue;
}
var internal = is("name") && /^#/.test(S.token.value);
var key = as_property_key();
if (is("punc", "(")) {

View File

@ -241,6 +241,208 @@ class_super: {
node_version: ">=4"
}
static_init: {
input: {
var a = "foo";
var b = null;
class A {
static {
var a = "bar";
b = true;
var c = 42;
console.log(a, b, c);
}
}
console.log(a, b, typeof c);
}
expect_exact: 'var a="foo";var b=null;class A{static{var a="bar";b=true;var c=42;console.log(a,b,c)}}console.log(a,b,typeof c);'
expect_stdout: [
"bar true 42",
"foo true undefined",
]
node_version: ">=16"
}
static_field_init: {
options = {
side_effects: true,
}
input: {
(class {
static [console.log("foo")] = console.log("bar");
static {
console.log("baz");
}
static [console.log("moo")] = console.log("moz");
});
}
expect: {
(class {
static [(console.log("foo"), console.log("moo"))] = (
console.log("bar"),
(() => {
console.log("baz");
})(),
console.log("moz")
);
});
}
expect_stdout: [
"foo",
"moo",
"bar",
"baz",
"moz",
]
node_version: ">=16"
}
static_field_init_strict: {
options = {
side_effects: true,
}
input: {
"use strict";
(class {
static [console.log("foo")] = console.log("bar");
static {
console.log("baz");
}
static [console.log("moo")] = console.log("moz");
});
}
expect: {
"use strict";
console.log("foo"),
console.log("moo"),
(() => (
console.log("bar"),
(() => {
console.log("baz");
})(),
console.log("moz")
))();
}
expect_stdout: [
"foo",
"moo",
"bar",
"baz",
"moz",
]
node_version: ">=16"
}
static_init_side_effects_1: {
options = {
merge_vars: true,
side_effects: true,
}
input: {
var a = "FAIL";
(class {
static {
a = "PASS";
}
});
console.log(a);
}
expect: {
var a = "FAIL";
(class {
static {
a = "PASS";
}
});
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=16"
}
static_init_side_effects_1_strict: {
options = {
merge_vars: true,
side_effects: true,
}
input: {
"use strict";
var a = "FAIL";
(class {
static {
a = "PASS";
}
});
console.log(a);
}
expect: {
"use strict";
var a = "FAIL";
(() => (() => {
a = "PASS";
})())();
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=16"
}
static_init_side_effects_2: {
options = {
hoist_props: true,
reduce_vars: true,
side_effects: true,
}
input: {
var a = "FAIL";
(class {
static {
a = "PASS";
}
});
console.log(a);
}
expect: {
var a = "FAIL";
(class {
static {
a = "PASS";
}
});
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=16"
}
static_init_side_effects_2_strict: {
options = {
hoist_props: true,
reduce_vars: true,
side_effects: true,
}
input: {
"use strict";
var a = "FAIL";
(class {
static {
a = "PASS";
}
});
console.log(a);
}
expect: {
"use strict";
var a = "FAIL";
(() => (() => {
a = "PASS";
})())();
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=16"
}
block_scoped: {
options = {
evaluate: true,

View File

@ -140,6 +140,7 @@ var SUPPORT = function(matrix) {
class: "class C { f() {} }",
class_field: "class C { p = 0; }",
class_private: "class C { #f() {} }",
class_static_init: "class C { static {} }",
computed_key: "({[0]: 0});",
const_block: "var a; { const a = 0; }",
default_value: "[ a = 0 ] = [];",
@ -1181,7 +1182,11 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
unique_vars.length = unique_len;
});
}
if (n !== 0) s += " finally { " + createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + " }";
if (n !== 0) s += [
" finally { ",
createStatements(rng(5) + 1, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
" }",
].join("");
return s;
case STMT_C:
return "c = c + 1;";
@ -1839,6 +1844,16 @@ function createClassLiteral(recurmax, stmtDepth, canThrow, name) {
async = save_async;
}
s += ";\n";
} else if (SUPPORT.class_static_init && fixed && !internal && rng(10) == 0) {
async = false;
generator = false;
s += [
"{ ",
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CANNOT_RETURN, stmtDepth),
" }\n",
].join("");
generator = save_generator;
async = save_async;
} else {
if (!fixed && !internal && constructor && rng(10) == 0) {
internal = constructor;