Skip to content

refactor(module_graph): preserve same-name function overloads as a set#10585

Open
IxxyDev wants to merge 2 commits into
biomejs:mainfrom
IxxyDev:module-graph/preserve-function-overloads
Open

refactor(module_graph): preserve same-name function overloads as a set#10585
IxxyDev wants to merge 2 commits into
biomejs:mainfrom
IxxyDev:module-graph/preserve-function-overloads

Conversation

@IxxyDev

@IxxyDev IxxyDev commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Groundwork for #9568 (part 1 of 2). This change has no user-facing effect on its own.

Same-name function / declare function declarations in one scope collapsed via last-wins in TsBindingReference::union_with, so only one signature survived before reaching any consumer — the overload set never existed. This splits JsDeclarationKind::Function out of HoistedValue and adds TsBindingReference::FunctionValue (a single function) and Overloaded (two or more, in source order), so functions are marked distinctly and accumulate into an ordered set on merge. var / const / let redeclaration keeps its previous last-wins behaviour.

Every non-overload read path (type resolution, exports, qualifier lookup) degrades to the set's last binding, so resolution and exports are unchanged.

Part 2 selects the matching overload at the call site and is what actually fixes #9568.

How to review

  • scope.rs — the new TsBindingReference variants and the merge arms in union_with (the core), plus the read-path handling.
  • binding.rsFunction split out of HoistedValue.
  • collector.rs — the Copy -> Clone fallout at the two move sites.

Test Plan

  • Unit tests in scope.rs cover overload accumulation into an ordered set and confirm const / var redeclaration is unaffected.
  • The full analyzer spec suite passes unchanged, confirming no resolution or export regressions.

Docs

N/A — internal change, no changeset (no user-facing behaviour difference).

@changeset-bot

changeset-bot Bot commented Jun 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: df46d90

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

This PR includes changesets to release 14 packages
Name Type
@biomejs/biome Minor
@biomejs/cli-win32-x64 Minor
@biomejs/cli-win32-arm64 Minor
@biomejs/cli-darwin-x64 Minor
@biomejs/cli-darwin-arm64 Minor
@biomejs/cli-linux-x64 Minor
@biomejs/cli-linux-arm64 Minor
@biomejs/cli-linux-x64-musl Minor
@biomejs/cli-linux-arm64-musl Minor
@biomejs/wasm-web Minor
@biomejs/wasm-bundler Minor
@biomejs/wasm-nodejs Minor
@biomejs/backend-jsonrpc Patch
@biomejs/js-api Major

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

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

✅ Organic activity

No automation signals detected in the analyzed events.

View full analysis →

This is an automated analysis by AgentScan

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

Failed to post review comments

Walkthrough

This PR bundles release notes documenting numerous bug fixes for existing rules (Vue attribute handling, ARIA props detection, TypeScript imports, Svelte parsing, CSS Modules), introduces a new nursery rule for restricted dependencies, promotes 73 nursery rules to stable groups with four rule renames, adds support for a standalone biome upgrade command, and systematically updates GitHub Actions workflows across the CI/CD pipeline to newer pinned versions. It also adjusts ESLint migration logic to place TypeScript no-shadow in the stable suspicious group and updates tests to reflect rule promotions.

Possibly related PRs

  • biomejs/biome#10568: Documents the same useAriaPropsForRole Vue shorthand fix in release notes.
  • biomejs/biome#10470: Implements the noProcessEnv named import detection described in this PR's changeset.
  • biomejs/biome#10565: Implements the useVueConsistentVBindStyle v-bind fix documented here.

Suggested labels

A-CLI, A-Linter, A-Documentation

Suggested reviewers

  • ematipico
  • dyc3
🚥 Pre-merge checks | ✅ 1 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The raw_summary contains many changeset files and GitHub workflow updates unrelated to the module graph refactoring described in the PR title and objectives. Investigate whether changeset files and workflow updates are intentional or whether the PR scope has been misunderstood; the title suggests a module graph refactor, not a release or workflow maintenance.
Title check ❓ Inconclusive The PR title describes refactoring the module graph to preserve function overloads, but the raw_summary shows only changeset and workflow updates with no actual implementation changes to scope.rs, binding.rs, or collector.rs mentioned. Verify that implementation changes to core files (scope.rs, binding.rs, collector.rs) are present in the full PR; the raw_summary may be incomplete or the title may not match actual changes.
Linked Issues check ❓ Inconclusive Issue #9568 requires preserving overloads in the module graph and performing call-site overload selection. This PR (part 1) covers overload preservation via TsBindingReference changes, but part 2 (selecting overloads at call sites) is deferred to PR #10586. Verify that the core implementation changes (TsBindingReference variants, union_with merge logic) are complete in scope.rs, binding.rs, and collector.rs as described; part 2 is expected separately.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description thoroughly explains groundwork for #9568, detailing changes to TsBindingReference variants, function overload preservation, and test coverage, which aligns with the stated objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/biome_module_graph/src/js_module_info/scope.rs`:
- Around line 154-167: The current match arms in union_with collapse
FunctionValue/Overloaded into ValueType(last) and lose overloads; change them to
preserve overload lists when merging with a value/type/namespace by producing a
carrier that keeps the full overload set plus the value-binding (e.g., add a new
variant like OverloadedWithValue(binding_id) or extend Self::Overloaded to hold
Option<binding_id>) instead of converting to Self::ValueType, and update
union_with handling so (Self::FunctionValue(own_binding_id), other), (this,
Self::FunctionValue(other_binding_id)), (Self::Overloaded(set), other) and
(this, Self::Overloaded(set)) forward to create/merge into that
combined-overload carrier rather than calling
Self::ValueType(last).union_with(...), ensuring bindings_by_name retains the
overload list for later call-site resolution.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f4bb6a86-36ae-4f72-ace6-bd7bdab2efc3

📥 Commits

Reviewing files that changed from the base of the PR and between 9e3cad4 and a7a7f0c.

📒 Files selected for processing (3)
  • crates/biome_module_graph/src/js_module_info/binding.rs
  • crates/biome_module_graph/src/js_module_info/collector.rs
  • crates/biome_module_graph/src/js_module_info/scope.rs

Comment on lines +154 to +167
// Any other combination degrades to a plain value via the existing rules.
(Self::FunctionValue(own_binding_id), other) => {
Self::ValueType(own_binding_id).union_with(other)
}
(this, Self::FunctionValue(other_binding_id)) => {
this.union_with(Self::ValueType(other_binding_id))
}
(Self::Overloaded(set), other) => {
let last = *set.last().expect("overload set is never empty");
Self::ValueType(last).union_with(other)
}
(this, Self::Overloaded(set)) => {
let last = *set.last().expect("overload set is never empty");
this.union_with(Self::ValueType(last))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't throw the overload set away when another declaration space joins in.

If an overloaded function later merges with a same-name type/interface/namespace, these arms first squash Overloaded down to ValueType(last) and only then apply the existing merge rules. That means valid TS like overloaded function f(...) plus type f = ... loses the overload list in bindings_by_name, so later call-site selection can only ever see the last binding. A larger carrier is needed here for “overloads + type/namespace”, otherwise the groundwork falls over at the first declaration merge.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/biome_module_graph/src/js_module_info/scope.rs` around lines 154 -
167, The current match arms in union_with collapse FunctionValue/Overloaded into
ValueType(last) and lose overloads; change them to preserve overload lists when
merging with a value/type/namespace by producing a carrier that keeps the full
overload set plus the value-binding (e.g., add a new variant like
OverloadedWithValue(binding_id) or extend Self::Overloaded to hold
Option<binding_id>) instead of converting to Self::ValueType, and update
union_with handling so (Self::FunctionValue(own_binding_id), other), (this,
Self::FunctionValue(other_binding_id)), (Self::Overloaded(set), other) and
(this, Self::Overloaded(set)) forward to create/merge into that
combined-overload carrier rather than calling
Self::ValueType(last).union_with(...), ensuring bindings_by_name retains the
overload list for later call-site resolution.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

resolved

@ematipico

Copy link
Copy Markdown
Member

This change has no user-facing effect on its own.

If that's the case, send the PR against main

@IxxyDev IxxyDev force-pushed the module-graph/preserve-function-overloads branch from a7a7f0c to 505ac00 Compare June 8, 2026 13:58
@github-actions github-actions Bot added the L-JavaScript Language: JavaScript and super languages label Jun 8, 2026
@IxxyDev IxxyDev force-pushed the module-graph/preserve-function-overloads branch from 505ac00 to b265aef Compare June 8, 2026 14:09
@codspeed-hq

codspeed-hq Bot commented Jun 8, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 62 untouched benchmarks
⏩ 192 skipped benchmarks1


Comparing IxxyDev:module-graph/preserve-function-overloads (df46d90) with main (ac4944f)

Open in CodSpeed

Footnotes

  1. 192 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@IxxyDev IxxyDev changed the base branch from next to main June 9, 2026 14:25
@IxxyDev IxxyDev changed the base branch from main to next June 9, 2026 14:46
@IxxyDev

IxxyDev commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

This change has no user-facing effect on its own.

If that's the case, send the PR against main

@ematipico It depends on TsBindingReference, which only exists on next, so it can't target main. Groundwork for #9568 — user-facing change is #10586.

@ematipico

Copy link
Copy Markdown
Member

@IxxyDev we recently merged next to main, so it's in there.

IxxyDev added 2 commits June 10, 2026 15:51
Same-name `function` / `declare function` declarations in one scope collapsed
via last-wins in `TsBindingReference::union_with`, so only one signature
survived before reaching any consumer.

Split `JsDeclarationKind::Function` out of `HoistedValue` (in both the semantic
event extractor and `from_node`) and add `TsBindingReference::FunctionValue` and
`Overloaded`, so functions are marked distinctly and accumulate into an ordered
set on merge. `var` / `const` redeclaration keeps its previous last-wins
behaviour, and every non-overload read path degrades to the set's last binding,
so resolution and exports are unchanged.

This is the type-system groundwork for biomejs#9568; call-site overload selection
follows separately.
Record overload sets only on name collisions and resolve them through
the scope instead of storing them on every binding, restoring analyzer
performance to baseline.
@IxxyDev IxxyDev force-pushed the module-graph/preserve-function-overloads branch from b265aef to df46d90 Compare June 10, 2026 13:11
@IxxyDev IxxyDev changed the base branch from next to main June 10, 2026 13:11
@IxxyDev

IxxyDev commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

@IxxyDev we recently merged next to main, so it's in there.

@ematipico done

@dyc3 dyc3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Makes sense to me

/// an overload set.
///
/// It **does not** return overloads of parent scopes.
pub fn overload_sets(&self) -> Vec<Vec<BindingId>> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can't we flatten the result?

@dyc3 dyc3 Jun 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Its a list of sets. Each item in the list is a list of all the overloads for one function, so I don't think so.

@ematipico ematipico Jun 12, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Then why not an HashMap? Probably a dumb question but I fail to see the data structure. Considering how it's used below, the question pops up

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

overloads_by_name is already a FxHashMap<name, set> internally. overload_sets() just exposes its values (filtered to len > 1), because no consumer needs the name key — only the groups. Returning the map would leak TokenText into the public API for no benefit.

The nesting (Vec<Vec<_>>) is load-bearing: each inner vec is one overload set, and flattening the sets themselves would lose which bindings belong together and break the per-set representative selection in the collector.

// returns for the set (its last one) so a call site can select among them.
let carriers: Vec<(usize, Vec<TypeMember>)> = semantic_model
.scopes()
.flat_map(|scope| scope.overload_sets())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Considering that here we're flattening overload_sets, it makes more sense that overload_sets does the flattening.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The flattening here is across scopes, not within a set: each scope yields its own list of overload sets, and flat_map concatenates them into a single stream of sets. Each item the .map below then receives is one full overload set, which is exactly the grouping we need — it becomes one object with a call signature per overload, assigned to the set's representative (last) binding. So there's nothing to move into overload_sets(); it uplifts a different dimension.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Project Area: project L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

💅 noFloatingPromises doesn't respect TypeScript overloads

3 participants