Skip to content

Conversation

@ematipico
Copy link
Member

Summary

It should partially address #8204

We widen types only for assignments that are inside a different scope from where the variable was declared.

Test Plan

Green CI. It should improve the performance of some benchmarks

Docs

@changeset-bot
Copy link

changeset-bot bot commented Dec 5, 2025

⚠️ No Changeset found

Latest commit: 9937a9c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@github-actions github-actions bot added the A-Project Area: project label Dec 5, 2025
@codspeed-hq
Copy link

codspeed-hq bot commented Dec 5, 2025

CodSpeed Performance Report

Merging #8374 will not alter performance

Comparing perf/reduce-type-widening (9937a9c) with main (d407efb)

Summary

✅ 58 untouched
⏩ 95 skipped1

Footnotes

  1. 95 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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

Walkthrough

Adds a public UnionCollector and uses it in the JS module info collector to accumulate union types from writable references instead of merging per reference. Introduces get_scope_by_range(range) to locate the innermost scope for a reference and skips widening when the reference scope equals the binding scope. Refactors assignment extraction to obtain the right‑hand expression under the new scope checks, finalises the union via UnionCollector, registers it, and updates the binding type to the resulting ResolvedTypeId.

Possibly related PRs

Suggested reviewers

  • arendjr
  • dyc3

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: a performance optimisation targeting type-widening logic in the inference system.
Description check ✅ Passed The description explains the motivation (addressing issue #8204), the scope change (widening only for different-scope assignments), and test plan (green CI with performance improvements).
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch perf/reduce-type-widening

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d407efb and 622c663.

📒 Files selected for processing (1)
  • crates/biome_module_graph/src/js_module_info/collector.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use the dbg!() macro for debugging output during testing, and pass the --show-output flag to cargo to view debug output
Use cargo t or cargo test to run tests; for a single test, pass the test name after the test command
Use snapshot testing with the insta crate; run cargo insta accept, cargo insta reject, or cargo insta review to manage snapshot changes
Write doctests as doc comments with code blocks; the code inside code blocks will be run during the testing phase
Use just f (alias for just format) to format Rust and TOML files before committing

Files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/js_module_info/collector.rs : Implement module-level (thin) inference to resolve `TypeReference::Qualifier` variants by looking up declarations in module scopes and handling import statements
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/js_module_info/collector.rs : Implement module-level (thin) inference to resolve `TypeReference::Qualifier` variants by looking up declarations in module scopes and handling import statements

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/scoped_resolver.rs : Implement full inference to resolve `TypeReference::Import` variants across the entire module graph

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Use `TypeReference` instead of `Arc` for types that reference other types to avoid stale cache issues when modules are replaced

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/local_inference.rs : Implement local inference in dedicated modules to derive type definitions from expressions without context of surrounding scopes

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Check if a variable is global before banning it to avoid false positives when the variable is redeclared in local scope; use the semantic model to verify global scope

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
📚 Learning: 2025-08-20T16:24:59.781Z
Learnt from: arendjr
Repo: biomejs/biome PR: 7266
File: crates/biome_js_type_info/src/type.rs:94-102
Timestamp: 2025-08-20T16:24:59.781Z
Learning: In crates/biome_js_type_info/src/type.rs, the flattened_union_variants() method returns TypeReference instances that already have the correct module IDs applied to them. These references should be used directly with resolver.resolve_reference() without applying additional module ID transformations, as variant references may originate from nested unions in different modules.

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Use `TypeReference` variants (`Qualifier`, `Resolved`, `Import`, `Unknown`) to represent different phases of type resolution

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Store type data in linear vectors instead of using recursive data structures with `Arc` for improved data locality and performance

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : No module may copy or clone data from another module in the module graph, not even behind an `Arc`

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Distinguish between `TypeData::Unknown` and `TypeData::UnknownKeyword` to measure inference effectiveness versus explicit user-provided unknown types

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
🧬 Code graph analysis (1)
crates/biome_module_graph/src/js_module_info/collector.rs (1)
crates/biome_module_graph/src/js_module_info.rs (1)
  • binding (264-266)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Test Node.js API
  • GitHub Check: autofix
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: End-to-end tests
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Check Dependencies
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Bench (biome_module_graph)

@github-actions github-actions bot added L-JavaScript Language: JavaScript and super languages A-Type-Inference Area: type inference labels Dec 5, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/biome_module_graph/src/js_module_info/collector.rs (1)

685-716: Include the original type in the union and remove the unused assignment.

The initial ty.clone() on line 692 is never read before being overwritten on line 719 (hence the compiler warning). More importantly, the UnionCollector only accumulates assigned types from cross-scope writes but never includes the original declared type. This means:

  1. If there are no cross-scope writes, an empty union is created (uninhabitable).
  2. The declared type is lost entirely, which contradicts the intent of "widening."

Apply this diff to fix both issues:

     let references = self.get_writable_references(binding);
-    let mut ty = ty.clone();
     let mut union_collector = UnionCollector::new();
+    union_collector.add(ty.clone());
     for reference in references {

This ensures the union starts with the declared type and grows with assigned types.

🧹 Nitpick comments (1)
crates/biome_module_graph/src/js_module_info/collector.rs (1)

668-682: Consider refactoring get_scope_by_range to use the Lapper-based approach for better performance.

The assumption that higher ScopeId values represent more deeply nested scopes is correct—scopes are created sequentially as ScopeId::new(self.scopes.len()), so later creation means deeper nesting. However, get_scope_by_range performs a linear scan through all scope intervals for each writable reference during type widening, which is inefficient.

The codebase already uses a Lapper-based approach via scope_id_for_range() in infer_all_types(). To improve performance here, pass scope_by_range as a parameter through the method chain (infer_typewiden_binding_from_writable_references) and use the same scope_id_for_range() function instead of manual iteration.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 622c663 and 9937a9c.

📒 Files selected for processing (2)
  • crates/biome_js_type_info/src/resolver.rs (1 hunks)
  • crates/biome_module_graph/src/js_module_info/collector.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use the dbg!() macro for debugging output during testing, and pass the --show-output flag to cargo to view debug output
Use cargo t or cargo test to run tests; for a single test, pass the test name after the test command
Use snapshot testing with the insta crate; run cargo insta accept, cargo insta reject, or cargo insta review to manage snapshot changes
Write doctests as doc comments with code blocks; the code inside code blocks will be run during the testing phase
Use just f (alias for just format) to format Rust and TOML files before committing

Files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
  • crates/biome_js_type_info/src/resolver.rs
crates/biome_js_type_info/**/*.rs

📄 CodeRabbit inference engine (crates/biome_js_type_info/CONTRIBUTING.md)

crates/biome_js_type_info/**/*.rs: No module may copy or clone data from another module in the module graph, not even behind an Arc
Use TypeReference instead of Arc for types that reference other types to avoid stale cache issues when modules are replaced
Store type data in linear vectors instead of using recursive data structures with Arc for improved data locality and performance
Use TypeReference variants (Qualifier, Resolved, Import, Unknown) to represent different phases of type resolution
Use TypeData::Unknown to indicate when type inference falls short or is not implemented
Distinguish between TypeData::Unknown and TypeData::UnknownKeyword to measure inference effectiveness versus explicit user-provided unknown types
When using ResolvedTypeData, track the ResolverId to ensure subsequent resolver calls use the correct context
Always apply the correct ResolverId when retrieving raw type data from ResolvedTypeData.as_raw_data() to prevent panics during subsequent resolution calls

Files:

  • crates/biome_js_type_info/src/resolver.rs
🧠 Learnings (13)
📓 Common learnings
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/js_module_info/collector.rs : Implement module-level (thin) inference to resolve `TypeReference::Qualifier` variants by looking up declarations in module scopes and handling import statements
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/js_module_info/collector.rs : Implement module-level (thin) inference to resolve `TypeReference::Qualifier` variants by looking up declarations in module scopes and handling import statements

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
  • crates/biome_js_type_info/src/resolver.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/scoped_resolver.rs : Implement full inference to resolve `TypeReference::Import` variants across the entire module graph

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
  • crates/biome_js_type_info/src/resolver.rs
📚 Learning: 2025-08-20T16:24:59.781Z
Learnt from: arendjr
Repo: biomejs/biome PR: 7266
File: crates/biome_js_type_info/src/type.rs:94-102
Timestamp: 2025-08-20T16:24:59.781Z
Learning: In crates/biome_js_type_info/src/type.rs, the flattened_union_variants() method returns TypeReference instances that already have the correct module IDs applied to them. These references should be used directly with resolver.resolve_reference() without applying additional module ID transformations, as variant references may originate from nested unions in different modules.

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
  • crates/biome_js_type_info/src/resolver.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Use `TypeReference` instead of `Arc` for types that reference other types to avoid stale cache issues when modules are replaced

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
  • crates/biome_js_type_info/src/resolver.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Use `TypeReference` variants (`Qualifier`, `Resolved`, `Import`, `Unknown`) to represent different phases of type resolution

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
  • crates/biome_js_type_info/src/resolver.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/local_inference.rs : Implement local inference in dedicated modules to derive type definitions from expressions without context of surrounding scopes

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Distinguish between `TypeData::Unknown` and `TypeData::UnknownKeyword` to measure inference effectiveness versus explicit user-provided unknown types

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
  • crates/biome_js_type_info/src/resolver.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : When using `ResolvedTypeData`, track the `ResolverId` to ensure subsequent resolver calls use the correct context

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
  • crates/biome_js_type_info/src/resolver.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Use `TypeData::Unknown` to indicate when type inference falls short or is not implemented

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
  • crates/biome_js_type_info/src/resolver.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Store type data in linear vectors instead of using recursive data structures with `Arc` for improved data locality and performance

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Always apply the correct `ResolverId` when retrieving raw type data from `ResolvedTypeData.as_raw_data()` to prevent panics during subsequent resolution calls

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
  • crates/biome_js_type_info/src/resolver.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/flattening.rs : Implement type flattening to simplify `TypeofExpression` variants once all component types are resolved

Applied to files:

  • crates/biome_module_graph/src/js_module_info/collector.rs
  • crates/biome_js_type_info/src/resolver.rs
🪛 GitHub Actions: Pull request
crates/biome_module_graph/src/js_module_info/collector.rs

[warning] 692-692: value assigned to ty is never read. maybe it is overwritten before being read? (note: #[warn(unused_assignments)])

crates/biome_js_type_info/src/resolver.rs

[error] 745-747: Clippy: you should consider adding a Default implementation for UnionCollector. This lint was treated as an error due to -D warnings, causing the build to fail while running 'cargo lint'.

Comment on lines +740 to +758
pub struct UnionCollector {
types: Vec<TypeReference>,
}

impl UnionCollector {
pub fn new() -> Self {
Self { types: Vec::new() }
}

pub fn add(&mut self, ty: TypeReference) {
self.types.push(ty);
}

pub fn finish(self) -> Cow<'static, TypeData> {
Cow::Owned(TypeData::Union(Box::new(Union(
self.types.into_boxed_slice(),
))))
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Implement Default trait and handle the empty-union edge case.

The Clippy lint correctly identifies that new() should be replaced with a Default implementation—this is causing the build to fail. Additionally, finish() will create an empty union if no types were added, which is likely incorrect (an empty union is uninhabitable).

Apply this diff to fix the Clippy error:

+impl Default for UnionCollector {
+    fn default() -> Self {
+        Self { types: Vec::new() }
+    }
+}
+
 impl UnionCollector {
-    pub fn new() -> Self {
-        Self { types: Vec::new() }
-    }
-
     pub fn add(&mut self, ty: TypeReference) {
         self.types.push(ty);
     }
 
     pub fn finish(self) -> Cow<'static, TypeData> {
+        if self.types.is_empty() {
+            return Cow::Owned(TypeData::unknown());
+        }
         Cow::Owned(TypeData::Union(Box::new(Union(
             self.types.into_boxed_slice(),
         ))))
     }
 }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Actions: Pull request

[error] 745-747: Clippy: you should consider adding a Default implementation for UnionCollector. This lint was treated as an error due to -D warnings, causing the build to fail while running 'cargo lint'.

🤖 Prompt for AI Agents
In crates/biome_js_type_info/src/resolver.rs around lines 740 to 758, replace
the manual new() with a Default impl for UnionCollector to satisfy Clippy and
change finish() to handle the empty-union edge case: implement Default for
UnionCollector (initializing types = Vec::new()), update or remove new() to call
Default if kept, and modify finish() so that if self.types.is_empty() it returns
Cow::Owned(TypeData::Never) (or the crate's uninhabitable variant) instead of
constructing an empty Union; otherwise return the Union as before.

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

Labels

A-Project Area: project A-Type-Inference Area: type inference L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants