Skip to content

Commit 7d1d0f5

Browse files
feat(javascript): inline functions a conditional callee chooses and flatten calls passed their parameters' own names in the minifier
Claude-Session: https://claude.ai/code/session_01ViD41i2KdwwGxrDk7yrjeD
1 parent 3019b4d commit 7d1d0f5

2 files changed

Lines changed: 216 additions & 15 deletions

File tree

‎lib/javascript/syntax-printer.js‎

Lines changed: 173 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19399,6 +19399,137 @@ const installImprove = (modules) => {
1939919399
isUndeclaredRef(node) &&
1940019400
!globalMayBeRebound(compressor);
1940119401

19402+
/** @type {InlineHooks} */
19403+
const inlineHooks = modules.webpackInline;
19404+
19405+
// `(a ? f : g)(x)` reads `f` only to call it, as `f(x)` does. A function
19406+
// calling itself stays declared: its expression, nested deeper, mangles worse.
19407+
inlineHooks.inlinesIntoCallee = (reference, definition, compressor) => {
19408+
if (!improvements.enabled || definition.recursive_refs > 0) return false;
19409+
let node = reference;
19410+
let parent = compressor.parent();
19411+
for (
19412+
let i = 1;
19413+
parent && A.isConditionalNode(parent) && parent.condition !== node;
19414+
i++
19415+
) {
19416+
node = parent;
19417+
parent = compressor.parent(i);
19418+
}
19419+
return (
19420+
node !== reference &&
19421+
A.isCallNode(parent) &&
19422+
!A.isNewNode(parent) &&
19423+
parent.callee === node
19424+
);
19425+
};
19426+
19427+
/**
19428+
* Whether a node writes a variable anywhere in it: `reduce_vars` counts
19429+
* neither a pattern's writes nor any in a function copied in since.
19430+
* @param {Node} node where to look
19431+
* @param {SymbolDefinition} definition the variable
19432+
* @returns {boolean} true when one does
19433+
*/
19434+
const writesVariable = (node, definition) =>
19435+
A.walk(node, (/** @type {Node} */ child) => {
19436+
const kind = child[walkedKinds];
19437+
const target =
19438+
kind === WRITES_LEFT
19439+
? child.left
19440+
: kind === WRITES_ARGUMENT
19441+
? child.argument
19442+
: undefined;
19443+
if (
19444+
target !== undefined &&
19445+
A.walk(target, (/** @type {Node} */ symbol) => {
19446+
if (A.isPropAccessNode(symbol)) return true;
19447+
if (A.isSymbolNode(symbol) && symbol.definition === definition) {
19448+
return A.walk_abort;
19449+
}
19450+
})
19451+
) {
19452+
return A.walk_abort;
19453+
}
19454+
});
19455+
19456+
/**
19457+
* Whether a `var`'s initializer runs before the call the compressor is at,
19458+
* in a statement before it; inlining refuses a loop around the call itself.
19459+
* @param {SymbolDefinition} definition the `var`
19460+
* @param {CompressorShape} compressor the compressor at the call
19461+
* @returns {boolean} true when it is
19462+
*/
19463+
const initializedBefore = (definition, compressor) => {
19464+
let child = compressor.self();
19465+
for (let i = 0; ; i++) {
19466+
const parent = compressor.parent(i);
19467+
const list = A.bodyOf(parent);
19468+
if (Array.isArray(list)) {
19469+
for (let j = list.indexOf(child); --j >= 0;) {
19470+
const statement = list[j];
19471+
if (
19472+
A.isVarNode(statement) &&
19473+
statement.declarations.some(
19474+
(/** @type {Node} */ declaration) =>
19475+
declaration.init && declaration.id.definition === definition
19476+
)
19477+
) {
19478+
return true;
19479+
}
19480+
}
19481+
}
19482+
if (A.isScopeNode(parent)) return false;
19483+
child = parent;
19484+
}
19485+
};
19486+
19487+
// `function(a){…}(a)` flattens where `a` is taken, its body reading the `a`
19488+
// around it, which nothing writes, so it holds the argument's value.
19489+
inlineHooks.aliasesParameter = (call, lambda, index, compressor) => {
19490+
if (!improvements.enabled) return false;
19491+
const parameter = lambda.params[index];
19492+
const value = call.arguments[index];
19493+
// Measured: a body's `var`s hoisted beside the call gzip worse than the call.
19494+
if (
19495+
lambda.body.body.some(A.isVarNode) ||
19496+
!A.isSymbolFunargNode(parameter) ||
19497+
!A.isSymbolRefNode(value) ||
19498+
value.name !== parameter.name
19499+
) {
19500+
return false;
19501+
}
19502+
const parameterDefinition = parameter.definition;
19503+
const variable = value.definition;
19504+
if (
19505+
!variable ||
19506+
parameterDefinition.identifiers.length !== 1 ||
19507+
variable.undeclared ||
19508+
variable.global ||
19509+
variable.assignments !== 0 ||
19510+
variable.identifiers.length !== 1 ||
19511+
variable.scope.isPinned() ||
19512+
variable.scope.usesArguments ||
19513+
/** @type {Scope} */ (compressor.find_scope()).findVariable(
19514+
value.name
19515+
) !== variable
19516+
) {
19517+
return false;
19518+
}
19519+
// A closure reads the `var` after the call, when it may hold another value.
19520+
if (
19521+
A.isSymbolVarNode(variable.identifiers[0]) &&
19522+
modules.common.is_reachable(lambda, [parameterDefinition]) &&
19523+
!initializedBefore(variable, compressor)
19524+
) {
19525+
return false;
19526+
}
19527+
return (
19528+
!writesVariable(lambda.body, parameterDefinition) &&
19529+
!writesVariable(variable.scope.node, variable)
19530+
);
19531+
};
19532+
1940219533
/** @type {CollapseHooks} */
1940319534
const collapseHooks = modules.webpackCollapse;
1940419535
collapseHooks.readsBuiltIn = (node, compressor, movesEffects) => {
@@ -29093,13 +29224,23 @@ const createTightenBody = (modules) => {
2909329224
return { tightenBody, extractFromUnreachableCode };
2909429225
};
2909529226

29227+
/**
29228+
* @typedef {object} InlineHooks what later phases add to inlining a variable's value
29229+
* @property {((reference: Node, definition: SymbolDefinition, compressor: CompressorShape) => boolean) | null} inlinesIntoCallee whether a function read once, in another scope, is inlined where its value is only called
29230+
* @property {((call: Node, lambda: Node, index: number, compressor: CompressorShape) => boolean) | null} aliasesParameter whether a parameter of a function flattened into its call, its name taken around the call, reads the argument's variable instead
29231+
*/
29232+
2909629233
/**
2909729234
* Builds webpack's `compress/inline.js`: a variable's value inlined into its
2909829235
* reference, and a function inlined into its call.
2909929236
* @param {MinifierModules} modules terser's modules
2910029237
* @returns {{ inlineIntoSymbolRef: OptimizerHelpers["inlineIntoSymbolRef"], inlineIntoCall: OptimizerHelpers["inlineIntoCall"] }} terser's `inline_into_symbolref` and `inline_into_call`
2910129238
*/
29102-
const createInline = ({ ast, common, flags, utils }) => {
29239+
const createInline = (modules) => {
29240+
const { ast, common, flags, utils } = modules;
29241+
/** @type {InlineHooks} */
29242+
const inlineHooks = { inlinesIntoCallee: null, aliasesParameter: null };
29243+
modules.webpackInline = inlineHooks;
2910329244
const {
2910429245
isArrayNode,
2910529246
AssignNode,
@@ -29317,12 +29458,13 @@ const createInline = ({ ast, common, flags, utils }) => {
2931729458
if (singleUse && (isLambdaNode(fixed) || isClassNode(fixed))) {
2931829459
nearestScope = /** @type {Scope} */ (compressor.find_scope());
2931929460
singleUse =
29320-
(definition.scope === self.scope &&
29321-
!scopeEnclosesVariablesInThisScope(nearestScope, fixed)) ||
29322-
(isCallNode(parent) &&
29323-
parent.callee === self &&
29324-
!scopeEnclosesVariablesInThisScope(nearestScope, fixed) &&
29325-
!(fixed.id && fixed.id.definition.recursive_refs > 0));
29461+
!scopeEnclosesVariablesInThisScope(nearestScope, fixed) &&
29462+
(definition.scope === self.scope ||
29463+
(isCallNode(parent) &&
29464+
parent.callee === self &&
29465+
!(fixed.id && fixed.id.definition.recursive_refs > 0)) ||
29466+
(inlineHooks.inlinesIntoCallee !== null &&
29467+
inlineHooks.inlinesIntoCallee(self, definition, compressor)));
2932629468
}
2932729469

2932829470
if (singleUse && fixed) {
@@ -29430,6 +29572,9 @@ const createInline = ({ ast, common, flags, utils }) => {
2943029572
this.scope = NO_NODE;
2943129573
/** @type {SymbolDefinition[] | undefined} */
2943229574
this.inLoop = undefined;
29575+
// The parameters read as the variable their argument names.
29576+
/** @type {Node[] | undefined} */
29577+
this.aliases = undefined;
2943329578
this.level = -1;
2943429579
}
2943529580
}
@@ -29447,6 +29592,7 @@ const createInline = ({ ast, common, flags, utils }) => {
2944729592
state.lambda = NO_NODE;
2944829593
state.scope = NO_NODE;
2944929594
state.inLoop = undefined;
29595+
state.aliases = undefined;
2945029596
state.level = -1;
2945129597
};
2945229598

@@ -29537,13 +29683,18 @@ const createInline = ({ ast, common, flags, utils }) => {
2953729683
return false;
2953829684
}
2953929685
if (hasFlag(arg, UNUSED)) continue;
29686+
if (!safeToInject || identifierAtom.has(arg.name)) return false;
2954029687
if (
29541-
!safeToInject ||
2954229688
blockScoped.has(arg.name) ||
29543-
identifierAtom.has(arg.name) ||
2954429689
scope.block_scope.conflictingDefinition(arg.name)
2954529690
) {
29546-
return false;
29691+
if (
29692+
inlineHooks.aliasesParameter === null ||
29693+
!inlineHooks.aliasesParameter(state.self, lambda, i, state.compressor)
29694+
) {
29695+
return false;
29696+
}
29697+
(state.aliases || (state.aliases = [])).push(arg);
2954729698
}
2954829699
if (inLoop) inLoop.push(arg.definition);
2954929700
}
@@ -29676,10 +29827,21 @@ const createInline = ({ ast, common, flags, utils }) => {
2967629827
for (let i = self.arguments.length; --i >= length;) {
2967729828
expressions.push(self.arguments[i]);
2967829829
}
29830+
const { aliases } = state;
2967929831
for (let i = length; --i >= 0;) {
2968029832
const name = lambda.params[i];
2968129833
let value = self.arguments[i];
29682-
if (
29834+
if (aliases !== undefined && aliases.includes(name)) {
29835+
const parameter = name.definition;
29836+
const variable = value.definition;
29837+
walk(lambda.body, (/** @type {Node} */ node) => {
29838+
if (isSymbolRefNode(node) && node.definition === parameter) {
29839+
node.definition = variable;
29840+
variable.references.push(node);
29841+
}
29842+
});
29843+
expressions.push(value);
29844+
} else if (
2968329845
hasFlag(name, UNUSED) ||
2968429846
!name.name ||
2968529847
scope.block_scope.conflictingDefinition(name.name)

‎test/unitCases/syntax-printer.unittest.js‎

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,7 @@ const IMPROVED_CASES = [
827827
"a function written in place passed the names of its parameters, which it reads instead, its value called, tagged, typed or read past a chain",
828828
'function f(o, k) { var a = (function (o, k) { return o.k ? o.m : o[k]; })(o, k)(), c = typeof (function (o, k) { return o[k] || o.p; })(o, k), d = (function (o, k) { return o?.[o.k]?.[k]; })(o, k).length, e = (function (o, k) { return o[k] ? o.u : o.t; })(o, k)`x`; return [a === o, c, d, e]; } console.log(f({ k: "p", p: { q: "ab" }, m() { return this; }, t(s) { return s[0]; } }, "q"));',
829829
{ compress: {}, mangle: false },
830-
'function f(o,k){var a=function(){return o.k?o.m:o[k]}()(),c=typeof function(){return o[k]||o.p}(),d=function(){return o?.[o.k]?.[k]}().length,e=function(){return o[k]?o.u:o.t}()`x`;return[a===o,c,d,e]}console.log(f({k:"p",p:{q:"ab"},m(){return this},t:s=>s[0]},"q"));'
830+
'function f(o,k){var a=(o.k?o.m:o[k])(),c=typeof(o[k]||o.p),d=(o?.[o.k]?.[k]).length,e=(o[k]?o.u:o.t)`x`;return[a===o,c,d,e]}console.log(f({k:"p",p:{q:"ab"},m(){return this},t:s=>s[0]},"q"));'
831831
],
832832
[
833833
"an arrow written in place passed the names of its parameters, naming `yield` inside a generator",
@@ -969,6 +969,38 @@ const IMPROVED_CASES = [
969969
[
970970
"an arrow written in place passed the name of its parameter inside a generator, as the value it returns",
971971
"function* g(o) { yield ((o) => [o.a, o.b, o])(o); } console.log([...g({ a: 1, b: 2 })][0].length);"
972+
],
973+
[
974+
"a function written in place passed the name of its parameter, a `let` read before it is set, or a global",
975+
"function f() { try { return (function (a) { return [a, a]; })(a); } catch (e) { return e.name; } let a = 1; } function g() { return (function (b) { return [b, b]; })(b); } var b = 1; console.log(f(), g());"
976+
],
977+
[
978+
"a function written in place passed the name of its parameter, a `var` read before it is set",
979+
"function f() { var g = (function (a) { return [a, a]; })(a); var a = 1; return g; } console.log(f());"
980+
],
981+
[
982+
"a function declared once, inlined where a conditional choosing it is called",
983+
"!function () { function center(s) { return s + 1; } function number(s) { return s * 2; } function make(b) { return function (x) { return (b ? center : number)(x); }; } console.log(make(1)(3), make(0)(3)); }();"
984+
],
985+
[
986+
"functions declared once, inlined where nested conditionals choosing them are called",
987+
"!function () { function f1(s) { return s + 1; } function f2(s) { return s * 2; } function f3(s) { return s - 1; } function make(a, b) { return function (x) { return (a ? f1 : b ? f2 : f3)(x); }; } console.log(make(1, 0)(3), make(0, 1)(3), make(0, 0)(3)); }();"
988+
],
989+
[
990+
"a function read in a conditional's test stays, those it chooses to call are inlined",
991+
"!function () { function f1(s) { return s + 1; } function f2(s) { return s * 2; } function f3(s) { return s - 1; } function make(b) { return function (x) { return (f1(b) ? f2 : f3)(x); }; } console.log(make(1)(3), make(-1)(3)); }();"
992+
],
993+
[
994+
"a function passed the variables of its parameters' names, set before the call, flattened into it",
995+
"!function () { var log = []; function show(props, name) { props.get = function () { return name; }; log.push(props.get()); } function create(type, flag) { var props = {}, name = type.name; flag && show(props, name); return props; } console.log(create({ name: \"a\" }, 1).get(), create({ name: \"b\" }, 0).get, log.join()); }();"
996+
],
997+
[
998+
"a function passed the variables of its parameters' names, flattened into it, no closure reading them",
999+
"!function () { function show(props, name) { props.seen = name; } function create(type, flag) { if (flag) var props = {}; var name = type.name; flag && show(props, name); return props; } console.log(create({ name: \"a\" }, 1).seen, create({ name: \"b\" }, 0)); }();"
1000+
],
1001+
[
1002+
"a function passed the parameters of its own parameters' names, flattened into it",
1003+
"!function () { function show(props, name) { var n = name + 1; props.seen = n; } function create(props, name, flag) { flag && show(props, name); return props; } console.log(create({}, \"a\", 1).seen, create({}, \"b\", 0).seen); }();"
9721004
]
9731005
].map(
9741006
([name, input]) =>
@@ -1153,12 +1185,19 @@ const KEPT_CASES = [
11531185
["a function written in place calling `eval`", 'function f(a) { return (function (a) { return [a, a, eval("a")]; })(a); } console.log(f(1));'],
11541186
["a function written in place with a default value", "function f(a, b) { return (function (a, b = 2) { return [a, a, b]; })(a, b); } console.log(f(1));"],
11551187
["a function written in place passed a spread", "function f(a, c) { return (function (a, b) { return [a, a, b]; })(a, ...c); } console.log(f(1, [2]));"],
1156-
["a function written in place passed a `let` before it is set, or a global", "function f() { try { return (function (a) { return [a, a]; })(a); } catch (e) { return e.name; } let a = 1; } function g() { return (function (b) { return [b, b]; })(b); } var b = 1; console.log(f(), g());"],
1157-
["a function written in place passed a `var` before it is set", "function f() { var g = (function (a) { return [a, a]; })(a); var a = 1; return g; } console.log(f());"],
11581188
["a function written in place passed a name declared twice, or declaring its parameter again", "function f(a, b) { var a; return [(function (a) { return [a, a]; })(a), (function (b) { var b; return [b, b]; })(b)]; } console.log(f(1, 2));"],
11591189
["a function written in place passed a name a `with` may read", "function f(a) { with ({}) return (function (a) { return [a, a]; })(a); } console.log(f(1));"],
11601190
["a function written in place passed an undeclared name", "function f() { return (function (a) { return [a, a]; })(a); } try { console.log(f()); } catch (e) { console.log(e.name); }"],
1161-
["a function written in place passed the name of its one parameter, which would stay called", "function f(o) { return (function (o) { var t = o.a; return [t, t, o.b]; })(o); } console.log(f({ a: 1, b: 2 }));"]
1191+
["a function written in place passed the name of its one parameter, which would stay called", "function f(o) { return (function (o) { var t = o.a; return [t, t, o.b]; })(o); } console.log(f({ a: 1, b: 2 }));"],
1192+
["functions calling themselves, chosen by a conditional that is called", "!function () { function fact(n) { return n < 2 ? 1 : n * fact(n - 1); } function sum(n) { return n < 1 ? 0 : n + sum(n - 1); } function make(b) { return function (x) { return (b ? fact : sum)(x); }; } console.log(make(1)(4), make(0)(4)); }();"],
1193+
["functions chosen by a conditional that is constructed or passed on", "!function () { function P(s) { this.s = s; } function Q(s) { this.s = -s; } function f1(s) { return s + 1; } function f2(s) { return s * 2; } function make(b) { return function (x) { return [new (b ? P : Q)(x).s, [x].map(b ? f1 : f2)]; }; } console.log(make(1)(3), make(0)(3)); }();"],
1194+
["a function passed variables of its parameters' names that a closure in it reads, set in a loop or after the call", "!function () { var fs = []; function show(props, name) { props.get = function () { return name; }; } function create(list) { for (var i = 0; i < list.length; i++) { var props = {}, name = list[i]; list[i] && show(props, name); fs.push(props); } } function late(props, flag) { flag && show(props, name); var name = \"late\"; return props; } create([\"a\", \"b\"]); console.log(fs.map(function (p) { return p.get(); }).join(), late({}, 1).get()); }();"],
1195+
["a function passed a variable of another name, or of its name but written", "!function () { function show(props, name) { props.seen = name; } function other(props, value, name, flag) { flag && show(props, value); return [props, name]; } function written(props, name, flag) { flag && show(props, name); name = 2; return [props, name]; } console.log(other({}, \"a\", \"n\", 1)[0].seen, written({}, \"a\", 1)[0].seen); }();"],
1196+
["a function passed variables of its parameters' names, which a pattern or a `for` head writes", "!function () { function show(props, name) { props.get = function () { return name; }; } function pattern(props, name, flag) { flag && show(props, name); [name] = [\"later\"]; return props; } function head(props, name, flag) { flag && show(props, name); for (name in { later: 1 }); return props; } console.log(pattern({}, \"a\", 1).get(), head({}, \"a\", 1).get()); }();"],
1197+
["a function incrementing a parameter, passed the variable of its name", "!function () { function show(props, name) { props.seen = name++; props.next = name; } function create(props, name, flag) { flag && show(props, name); return [props, name]; } console.log(create({}, \"a\", 1)[0].next, create({}, 1, 1)[0].next); }();"],
1198+
["a function writing a parameter by a pattern, passed the variable of its name", "!function () { function show(props, name) { [name] = [name + \"!\"]; props.seen = name; } function create(props, name, flag) { flag && show(props, name); return [props, name]; } console.log(create({}, \"a\", 1)[0].seen, create({}, \"b\", 0)[0].seen); }();"],
1199+
["a function passed variables of its parameters' names, of a function reading `arguments` or `eval`", "!function () { function show(props, name) { props.seen = name; } function args(props, name, flag) { flag && show(props, name); return [props, name, arguments.length]; } function evaluates(props, name, flag) { flag && show(props, name); return [props, eval(\"name\")]; } console.log(args({}, \"a\", 1)[0].seen, evaluates({}, \"b\", 1)[0].seen); }();"],
1200+
["a function passed a global of its parameter's name", "var name = \"g\"; !function () { function show(props, name) { props.seen = name; } function create(props, flag) { flag && show(props, name); return props; } console.log(create({}, 1).seen, create({}, 0).seen); }();"]
11621201
];
11631202

11641203
// Each prints one thing and terser's output another, under the options named.

0 commit comments

Comments
 (0)