Skip to content

feat(es/minifier): Remove useless arguments for non inlined callee#11645

Merged
kdy1 merged 6 commits intoswc-project:mainfrom
Austaras:main
Mar 28, 2026
Merged

feat(es/minifier): Remove useless arguments for non inlined callee#11645
kdy1 merged 6 commits intoswc-project:mainfrom
Austaras:main

Conversation

@Austaras
Copy link
Copy Markdown
Member

Description:

BREAKING CHANGE:

Related issue (if exists):
Closes #11089

@Austaras Austaras requested a review from a team as a code owner March 11, 2026 13:44
Copilot AI review requested due to automatic review settings March 11, 2026 13:44
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 11, 2026

🦋 Changeset detected

Latest commit: 2a062bf

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd21aab84a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread crates/swc_ecma_minifier/src/compress/optimize/unused.rs
let data = self.data.var_or_default(id.id.to_id());

if known {
data.mark_param_count(Value::Known(n.function.params.len() as u8));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid truncating inferred arity to 8 bits

Parameter counts are recorded via len() as u8, which silently wraps for functions with more than 255 parameters; those wrapped values are later used by ignore_unused_args_of_call to delete trailing arguments. In such cases, real positional arguments can be removed incorrectly (e.g. 300 params inferred as 44), producing wrong runtime inputs. Please store arity in a wider type or mark it unknown when it exceeds u8::MAX.

Useful? React with 👍 / 👎.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 11, 2026

Binary Sizes

File Size
swc.linux-x64-gnu.node 28M (29077448 bytes)

Commit: d1360dd

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends SWC’s minifier to drop unused call arguments even when the callee is not inlined, by tracking a function’s parameter count in usage analysis and using that during compression (fixing #11089).

Changes:

  • Extend swc_ecma_usage_analyzer to record whether a function’s parameter count is “known” and store that count on variable usage info.
  • Add a new minifier optimization to remove trailing call arguments when the callee’s parameter count is known (direct fn/arrow callees and identifiers with recorded param_count).
  • Update fixture/terser/project snapshot outputs to reflect the new argument-dropping behavior (including a new regression fixture for #11089).

Reviewed changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs Extends analyzer storage traits to expose scope flags and store parameter-count metadata.
crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs Records param_count for functions/arrow expressions assigned to identifiers and for function declarations.
crates/swc_ecma_minifier/src/program_data.rs Adds param_count to VarUsageInfo and wires it through VarDataLike.
crates/swc_ecma_minifier/src/compress/optimize/unused.rs Implements ignore_unused_args_of_call to drop trailing call args based on callee parameter count.
crates/swc_ecma_minifier/src/compress/optimize/mod.rs Invokes the new call-argument dropping pass during call-expression optimization.
crates/swc_ecma_minifier/src/compress/optimize/iife.rs Refactors IIFE arg handling to rely on scope flags and removes duplicated trailing-arg removal logic.
crates/swc_ecma_minifier/tests/terser/compress/export/issue_2131/output.js Updates expected output with removed unused argument.
crates/swc_ecma_minifier/tests/terser/compress/collapse_vars/issue_2436_12/output.js Updates expected output with removed unused argument.
crates/swc_ecma_minifier/tests/projects/output/react-dom-17.0.2.js Updates expected output with removed unused arguments in wrapper calls.
crates/swc_ecma_minifier/tests/libs-size.snapshot.md Updates size snapshot to reflect small output differences.
crates/swc_ecma_minifier/tests/fixture/next/wrap-contracts/output.js Updates expected output with removed unused arguments in bundled code.
crates/swc_ecma_minifier/tests/fixture/next/regression-1/framework-798bab57daac3897/output.js Updates expected output with removed unused arguments across multiple call sites.
crates/swc_ecma_minifier/tests/fixture/next/react-pdf-renderer/output.js Updates expected output with removed unused arguments and resulting renames.
crates/swc_ecma_minifier/tests/fixture/next/47005/output.js Updates expected output with removed unused arguments in app bundle.
crates/swc_ecma_minifier/tests/fixture/next/33265/static/chunks/d6e1aeb5-38a8d7ae57119c23/output.js Updates expected output with removed unused argument(s) in helper calls.
crates/swc_ecma_minifier/tests/fixture/issues/firebase-core/1/output.js Updates expected output with removed unused arguments in generated code.
crates/swc_ecma_minifier/tests/fixture/issues/emotion/react/1/output.js Updates expected output with removed unused arguments in wrapper call.
crates/swc_ecma_minifier/tests/fixture/issues/7784/3/output.js Updates expected output with removed unused argument.
crates/swc_ecma_minifier/tests/fixture/issues/2257/full/output.js Updates expected output with removed unused arguments in bundled runtime.
crates/swc_ecma_minifier/tests/fixture/issues/11089/input.js Adds new regression input for #11089.
crates/swc_ecma_minifier/tests/fixture/issues/11089/output.js Adds expected output showing dropped unused arguments for #11089.
crates/swc_ecma_minifier/tests/benches-full/victory.js Updates expected bench output with removed unused arguments.
crates/swc_ecma_minifier/tests/benches-full/d3.js Updates expected bench output with removed unused arguments.
crates/swc/tests/tsc-references/taggedTemplateStringsWithOverloadResolution3.2.minified.js Updates expected minified output with removed unused arguments.
crates/swc/tests/tsc-references/callbackTagVariadicType.2.minified.js Updates expected minified output with removed unused arguments.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/swc_ecma_minifier/src/compress/optimize/unused.rs

fn mark_used_recursively(&mut self);

fn mark_param_count(&mut self, count: Value<u8>);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mark_param_count uses Value<u8>, which will force call sites to truncate params.len() for functions with >255 parameters. That can lead to incorrect param_count and unsafe argument dropping. Consider using a larger integer type here (e.g. u16/usize) or specifying that overflow must map to Value::Unknown and enforcing it at the API boundary.

Suggested change
fn mark_param_count(&mut self, count: Value<u8>);
fn mark_param_count(&mut self, count: Value<usize>);

Copilot uses AI. Check for mistakes.
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 11, 2026

Merging this PR will degrade performance by 2.58%

❌ 1 regressed benchmark
✅ 218 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
es/full/minify/libraries/terser 511 ms 524.5 ms -2.58%

Comparing Austaras:main (2a062bf) with main (7e1ad26)

Open in CodSpeed

Copy link
Copy Markdown
Member

@kdy1 kdy1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. I left three inline findings.

Comment thread crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs Outdated
Comment thread crates/swc_ecma_minifier/src/compress/optimize/unused.rs
Comment thread crates/swc_ecma_minifier/src/compress/optimize/unused.rs
Copilot AI review requested due to automatic review settings March 19, 2026 07:56
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

crates/swc_ecma_minifier/src/program_data.rs:132

  • param_count is added to VarUsageInfo, but ProgramData::merge does not merge this new field when combining child.vars into the parent. This can leave a stale param_count from an outer scope (e.g. variable initialized to a 1-arg fn, then reassigned in a block to a 2-arg fn) and make ignore_unused_args_of_call drop arguments incorrectly. Please merge param_count in merge (e.g. by applying the same conflict/unknown logic as mark_param_count when the child provides a value).
    pub(crate) merged_var_type: Option<Value<Type>>,

    pub(crate) callee_count: u32,
    pub(crate) param_count: Option<Value<u8>>,

    /// `infects_to`. This should be renamed, but it will be done with another
    /// PR. (because it's hard to review)
    infects_to: Vec<Access>,

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +333 to +343
let scope = self.data.scope(n.function.ctxt);
let known = !scope.used_arguments()
&& !scope.used_eval()
&& !n.function.params.iter().any(|p| p.pat.is_rest());
let data = self.data.var_or_default(id.id.to_id());

if known {
data.mark_param_count(Value::Known(n.function.params.len() as u8));
} else {
data.mark_param_count(Value::Unknown);
}
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review


P1 Badge Track arity changes for &&=, ||=, and ??= assignments

visit_assign_expr only updates param_count inside the n.op == op!("=") branch, so logical/nullish assignments leave the previous arity attached to the binding. That makes ignore_unused_args_of_call unsound for patterns like let f = a => a; if (cond) f &&= ((a, b) => b); f(1, 2): when the branch runs, f now observes the second argument, but this pass still thinks f takes one parameter and can trim the 2.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

pub(crate) merged_var_type: Option<Value<Type>>,

pub(crate) callee_count: u32,
pub(crate) param_count: Option<Value<u8>>,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Propagate param_count when merging child-scope data

This new param_count field is never merged in ProgramData::merge, so bindings keep a stale known arity when a nested block/function assigns a different callee. For example, let f = a => a; if (cond) { f = (a, b) => b } f(1, 2) records the reassignment in the child scope, but the parent VarUsageInfo still looks like arity 1, so ignore_unused_args_of_call can delete the second argument even when the branch executes.

Useful? React with 👍 / 👎.

Comment thread crates/swc_ecma_minifier/src/compress/optimize/unused.rs Outdated
Copilot AI review requested due to automatic review settings March 19, 2026 08:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +334 to +343
let scope = self.data.scope(n.function.ctxt);
let known = !scope.used_arguments()
&& !scope.used_eval()
&& !n.function.params.iter().any(|p| p.pat.is_rest());
let data = self.data.var_or_default(id.id.to_id());

if known {
data.mark_param_count(Value::Known(n.function.params.len() as u8));
} else {
data.mark_param_count(Value::Unknown);
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a85801181

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1189 to +1193
if let Some(Value::Known(count)) = self
.data
.get_var_data(i.to_id())
.and_then(|d| d.param_count)
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require definite function initialization before trimming identifier calls

This treats any recorded param_count as proof that the callee is already a function at this call site, but the analyzer records arity from later initializers and assignments too. That makes calls like function g(){ f(0, sideEffect()); var f = a => a } unsafe: at runtime f is still undefined, so sideEffect() runs before the TypeError, while this path can drop the second argument entirely. Please gate this on the binding being definitely initialized to that function before the call, rather than only on a known arity.

Useful? React with 👍 / 👎.

Comment on lines +1189 to +1193
if let Some(Value::Known(count)) = self
.data
.get_var_data(i.to_id())
.and_then(|d| d.param_count)
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invalidate stored arity on update expressions

This also trusts param_count after writes like f++, but the new arity tracking is only cleared from visit_assign_expr; visit_update_expr still just reports an assignment and leaves the old count intact. In let f = a => a; f++; f(0, sideEffect()), f becomes NaN, the original program evaluates sideEffect() before throwing, and this optimization can incorrectly remove that trailing argument because the stale arity is still recorded as 1.

Useful? React with 👍 / 👎.

@Austaras Austaras requested a review from kdy1 March 20, 2026 07:01
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: de7f217c0f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1189 to +1192
if let Some(Value::Known(count)) = self
.data
.get_var_data(i.to_id())
.and_then(|d| d.param_count)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invalidate cached arity on for…in/of loop rebindings

This path trusts param_count even after the callee variable has been rebound by a loop head. In crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs, visit_for_in_stmt / visit_for_of_stmt assign through report_assign_pat (lines 167-172, 919-960), but that helper never calls mark_param_count(Value::Unknown), and ForHead::VarDecl heads also skip any arity invalidation. A case like let f = a => a; for (f in {x:1}) break; f(0, sideEffect()) therefore keeps the old arity of f, so this optimization can drop sideEffect() even though the runtime call now throws only after evaluating it.

Useful? React with 👍 / 👎.

@kdy1
Copy link
Copy Markdown
Member

kdy1 commented Mar 20, 2026

@Austaras I added some edge cases that are related to this PR

@Austaras
Copy link
Copy Markdown
Member Author

I'm fine with most but I believe 256 is a reasonable number for the upper limit of param count in real life code.

@kdy1
Copy link
Copy Markdown
Member

kdy1 commented Mar 20, 2026

Yeah I agree. You can ignore it. Sorry for that.

Copilot AI review requested due to automatic review settings March 23, 2026 03:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 36 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

pub(crate) merged_var_type: Option<Value<Type>>,

pub(crate) callee_count: u32,
pub(crate) param_count: Option<Value<u8>>,
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

param_count is stored as Value<u8>, but the analyzer writes params.len() as u8. If a function has >255 parameters, this will truncate and can make ignore_unused_args_of_call drop arguments incorrectly. Consider using a wider integer type (e.g. u16/usize) or at minimum treat params.len() > u8::MAX as Unknown when recording the count.

Suggested change
pub(crate) param_count: Option<Value<u8>>,
pub(crate) param_count: Option<Value<u16>>,

Copilot uses AI. Check for mistakes.
Comment on lines +1202 to +1214
if e.args.len() > params_len {
for i in (params_len..e.args.len()).rev() {
if let Some(arg) = e.args.get_mut(i) {
let new = self.ignore_return_value(&mut arg.expr);

if let Some(new) = new {
arg.expr = Box::new(new);
} else {
e.args.remove(i);
}
}
}
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ignore_unused_args_of_call mutates/removes call arguments but never sets self.changed = true (and doesn’t emit a report_change!) when it actually drops/replaces args. This can prevent follow-up optimizations in later passes that depend on the changed flag. Please mark the optimizer as changed whenever an argument is removed or rewritten here.

Copilot uses AI. Check for mistakes.
Comment on lines +1182 to +1197
Expr::Ident(i) => {
if let Some(scope) = self.data.get_scope(self.ctx.scope) {
if scope.intersects(ScopeData::HAS_EVAL_CALL.union(ScopeData::HAS_WITH_STMT)) {
return;
}
}

if let Some(Value::Known(count)) = self
.data
.get_var_data(i.to_id())
.and_then(|d| d.param_count)
{
count as usize
} else {
return;
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Expr::Ident callees, the safety check only looks at self.ctx.scope for eval/with, but the callee binding may come from an outer scope that contains eval/with (which can dynamically rebind the function and invalidate the recorded param_count). This can make argument-dropping unsound for captured variables. Consider also bailing out when the program/top scope has HAS_EVAL_CALL/HAS_WITH_STMT, or using VarUsageInfoFlags (e.g. USED_IN_NON_CHILD_FN) to conservatively avoid using param_count across scope boundaries when eval/with exists.

Suggested change
Expr::Ident(i) => {
if let Some(scope) = self.data.get_scope(self.ctx.scope) {
if scope.intersects(ScopeData::HAS_EVAL_CALL.union(ScopeData::HAS_WITH_STMT)) {
return;
}
}
if let Some(Value::Known(count)) = self
.data
.get_var_data(i.to_id())
.and_then(|d| d.param_count)
{
count as usize
} else {
return;
}
Expr::Ident(_) => {
// Be conservative for identifier callees: their binding may come from
// an outer scope that uses `eval` or `with`, which can dynamically
// rebind the function and invalidate any recorded `param_count`.
// To avoid unsound argument dropping in such cases, bail out.
return;

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 954c01cfd6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1183 to +1185
if let Some(scope) = self.data.get_scope(self.ctx.scope) {
if scope.intersects(ScopeData::HAS_EVAL_CALL.union(ScopeData::HAS_WITH_STMT)) {
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor enclosing eval/with scopes before trimming identifier calls

This guard only inspects self.ctx.scope, but visit_mut_block_stmt switches ctx.scope to each nested block and ScopeData::merge only propagates HAS_EVAL_CALL/HAS_WITH_STMT upward. As a result, f(1, 2) inside an inner block can still be rewritten to f(1) even when an enclosing function or with statement has already made f dynamically rebindable, e.g. function outer(){ let f = a => a; eval("f = (_, b) => b"); { return f(1, 2); } }. Please walk enclosing scopes (or use the existing BitCtx::InWithStmt) before trusting param_count here.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 729ff09595

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1189 to +1193
if let Some(Value::Known(count)) = self
.data
.get_var_data(i.to_id())
.and_then(|d| d.param_count)
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict arity trimming to declared local callees

This branch trusts any stored param_count for identifier callees, but param_count is also populated for unresolved assignments in visit_assign_expr (e.g. f = (a)=>a). In non-strict/script code those are global-property bindings, so a later write like globalThis.f = (_, b) => b can change the runtime callee without invalidating this cached arity; rewriting f(1, sideEffect()) to f(1) then drops observable argument evaluation. Please require a definitely declared local binding (or otherwise exclude unresolved/global bindings) before using param_count here.

Useful? React with 👍 / 👎.

Copilot AI review requested due to automatic review settings March 27, 2026 03:23
@kdy1
Copy link
Copy Markdown
Member

kdy1 commented Mar 27, 2026

I added more tests as this PR is about adding more minification rules

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d19a4d8166

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +363 to +366
for id in find_pat_ids(&n.left) {
self.data
.var_or_default(id)
.mark_param_count(Value::Unknown)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invalidate arity for wrapped assignment targets

When the left-hand side is wrapped (e.g. (f as any) = ..., f! = ..., or parenthesized forms), n.left.as_ident() is None, so this falls back to find_pat_ids(&n.left). find_pat_ids intentionally ignores expressions, so no identifier is found and param_count is not invalidated. That leaves stale known arity for f, and ignore_unused_args_of_call can later drop side-effectful trailing arguments from f(...) after such reassignments.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a156dcce5c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1210 to +1215
let new = self.ignore_return_value(&mut arg.expr);

if let Some(new) = new {
arg.expr = Box::new(new);
} else {
e.args.remove(i);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve potentially throwing trailing arguments

This new call-arity trimming path removes an argument whenever ignore_return_value returns None, but that helper can drop expressions that are side-effect-free yet still throw when evaluated (for example, 1n + 1). In a case like function f(a){}; f(0, 1n + 1), this optimization now rewrites to f(0) and suppresses the runtime TypeError, which is a behavior change. The trailing argument should only be removed when evaluation is guaranteed not to throw.

Useful? React with 👍 / 👎.

@kdy1 kdy1 requested a review from a team as a code owner March 28, 2026 09:03
Copy link
Copy Markdown
Member

@kdy1 kdy1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use Value::Unknown if the parameter count overflows u8.

@claude Apply the review

Comment thread crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs Outdated
@kdy1
Copy link
Copy Markdown
Member

kdy1 commented Mar 28, 2026

I'll apply the review

@kdy1 kdy1 enabled auto-merge (squash) March 28, 2026 09:59
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2a062bff9e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +289 to +290
(Some(Value::Known(v)), None) | (None, Some(Value::Known(v))) => {
Some(Value::Known(v))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Treat one-sided child arity merges as unknown

ProgramData::merge preserves a known param_count when only one side has data ((Known, None) or (None, Known)), but this merge runs for child scopes that may not execute (e.g. if branches and nested function bodies). That makes arity look definite when it is path-dependent: in let f; if (cond) { f = a => a } f(0, sideEffect()), the branch contributes Known(1) and this arm keeps it, so ignore_unused_args_of_call can drop sideEffect() even though cond = false leaves f non-callable and the original program still evaluates that argument before throwing.

Useful? React with 👍 / 👎.

@kdy1 kdy1 merged commit bab249e into swc-project:main Mar 28, 2026
71 checks passed
@github-actions github-actions Bot modified the milestones: Planned, 1.15.24 Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

minifier doesn't remove unused arguments

3 participants