fix(inference): harden Salsa type inference - #10932
Conversation
|
✅ Organic activityNo automation signals detected in the analyzed events. This is an automated analysis by AgentScan |
5d9a0ea to
8589845
Compare
dfdba71 to
9f77a8c
Compare
9f77a8c to
2dfb40b
Compare
8589845 to
f7279d8
Compare
2dfb40b to
6ba5c8a
Compare
f7279d8 to
1cfdad0
Compare
6ba5c8a to
5171884
Compare
5171884 to
be52ca5
Compare
716c2a2 to
724ae92
Compare
724ae92 to
899c60d
Compare
be52ca5 to
ff456b5
Compare
WalkthroughThe PR adds a public Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
crates/biome_js_type_info/src/inferred_type.rs (1)
10-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftAdd rustdoc to the new public APIs.
crates/biome_js_type_info/src/inferred_type.rs#L10-L47: document enum variants, evidence fields, and result semantics.crates/biome_js_semantic/src/semantic_model/model.rs#L347-L352: document invalid-ID behaviour and model ownership.As per coding guidelines, “Use rustdoc documentation for documenting new features, rule changes, and rule/assist options in Rust code.”
🤖 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_js_type_info/src/inferred_type.rs` around lines 10 - 47, Add rustdoc for the public APIs in crates/biome_js_type_info/src/inferred_type.rs:10-47, documenting each InferredSwitchCase and StringificationMode variant, the meaning of every ReturnTypeEvidence field, and the semantics of MisleadingReturnType results. Also document the public API at crates/biome_js_semantic/src/semantic_model/model.rs:347-352, including its invalid-ID behavior and ownership of the semantic model.Source: Coding guidelines
crates/biome_module_graph/src/db/type_inference/qualifiers.rs (1)
20-63: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate "walk remaining path members" loop.
The new member-walking loop in
resolve_qualifier(lines 55-62) is essentially the same pattern already used inresolve_global_member_qualifier(lines 196-198): repeatedly callresolve_static_member_expressionon each remaining path segment. Worth extracting into a small shared helper (e.g.resolve_member_path(&mut self, target, members: &[Text]) -> Option<InferredTypeData<'db>>) to avoid the two implementations drifting apart later.Separately, this is genuinely intricate control flow (scope walk + qualifier-specific binding lookup + member-path walk + several structural fallbacks below). Could you confirm multi-segment qualified-name resolution (e.g. namespace member access) has dedicated spec-test coverage in
spec_tests_v2.rs?♻️ Sketch of a shared helper
+ fn resolve_member_path( + &mut self, + mut target: InferredTypeData<'db>, + members: impl IntoIterator<Item = impl AsRef<str>>, + ) -> Option<InferredTypeData<'db>> { + for member in members { + target = self.resolve_static_member_expression(target, member.as_ref())?; + } + Some(target) + }Also applies to: 177-201
🤖 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/db/type_inference/qualifiers.rs` around lines 20 - 63, Extract the repeated member-path traversal from resolve_qualifier and resolve_global_member_qualifier into a shared helper such as resolve_member_path, preserving the existing resolve_static_member_expression behavior and Unknown/None fallback semantics. Add or confirm dedicated spec-test coverage in spec_tests_v2.rs for multi-segment qualified-name and namespace-member resolution.
🤖 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_js_analyze/src/services/typed.rs`:
- Around line 411-420: Update the call-argument type resolution around the
InferredTypeData::Interface and InferredTypeData::Object branches, plus the
related paths at the referenced locations, so it does not select the first
matching call signature, union arm, or constructor via find_map. Resolve
overloaded candidates using the call context; otherwise return None unless every
applicable candidate agrees on the argument type, eliminating declaration-order
dependence.
In `@crates/biome_js_type_info/src/inferred_type.rs`:
- Around line 598-630: Update the shared type traversals, including
all_variants_match and the helpers covering is_all_string_like,
has_number_like_variant, and could_equal_string_literal, to descend into
TypeData::Intersection constituents. Apply all semantics where every primitive
constituent must satisfy the predicate and any semantics where at least one
constituent can satisfy it, preserving existing union and generic handling so
branded intersections such as string & Brand are classified correctly.
In `@crates/biome_module_graph/src/db/type_inference/expressions.rs`:
- Around line 1096-1117: Update promise_member_type to preserve the target
Promise’s generic type arguments and model callback parameters and return types
for then, catch, and finally. Resolve the real Promise member signature when
available, or synthesize an equivalent function using target’s instance type
parameters and the callback’s inferred return type, so Promise<number>.finally()
retains number and then callbacks produce the resulting Promise type.
In `@crates/biome_module_graph/src/db/type_inference/imports.rs`:
- Around line 100-104: Update the namespace collection loop that processes
collection.stack in the import type-inference flow so exhausting
collection.remaining_steps does not return the partially collected Namespace.
Preserve the prior fail-closed Unknown result, or propagate an explicit
truncation state that prevents incomplete exports from being treated as absent.
---
Nitpick comments:
In `@crates/biome_js_type_info/src/inferred_type.rs`:
- Around line 10-47: Add rustdoc for the public APIs in
crates/biome_js_type_info/src/inferred_type.rs:10-47, documenting each
InferredSwitchCase and StringificationMode variant, the meaning of every
ReturnTypeEvidence field, and the semantics of MisleadingReturnType results.
Also document the public API at
crates/biome_js_semantic/src/semantic_model/model.rs:347-352, including its
invalid-ID behavior and ownership of the semantic model.
In `@crates/biome_module_graph/src/db/type_inference/qualifiers.rs`:
- Around line 20-63: Extract the repeated member-path traversal from
resolve_qualifier and resolve_global_member_qualifier into a shared helper such
as resolve_member_path, preserving the existing resolve_static_member_expression
behavior and Unknown/None fallback semantics. Add or confirm dedicated spec-test
coverage in spec_tests_v2.rs for multi-segment qualified-name and
namespace-member 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: 51dd0803-ba37-45be-8200-1ec09381f73f
⛔ Files ignored due to path filters (5)
crates/biome_js_type_info/tests/snapshots/infer_resolved_type_of_async_disposable_object.snapis excluded by!**/*.snapand included by**crates/biome_js_type_info/tests/snapshots/infer_resolved_type_of_disposable_object.snapis excluded by!**/*.snapand included by**crates/biome_module_graph/tests/snapshots/test_infer_module_types_merges_mixed_intersections_on_build.snapis excluded by!**/*.snapand included by**crates/biome_module_graph/tests/snapshots/test_infer_module_types_preserves_floating_promise_shapes.snapis excluded by!**/*.snapand included by**crates/biome_module_graph/tests/snapshots/test_infer_module_types_resolves_promise_member_chain.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (17)
crates/biome_js_analyze/src/services/typed.rscrates/biome_js_semantic/src/semantic_model/model.rscrates/biome_js_type_info/src/builders.rscrates/biome_js_type_info/src/format_inferred_type_info.rscrates/biome_js_type_info/src/inferred_type.rscrates/biome_js_type_info/src/interned_types.rscrates/biome_js_type_info/src/lib.rscrates/biome_js_type_info/src/local_inference.rscrates/biome_module_graph/src/db/queries.rscrates/biome_module_graph/src/db/type_inference/expressions.rscrates/biome_module_graph/src/db/type_inference/globals.rscrates/biome_module_graph/src/db/type_inference/imports.rscrates/biome_module_graph/src/db/type_inference/lookup.rscrates/biome_module_graph/src/db/type_inference/qualifiers.rscrates/biome_module_graph/src/db/type_inference/resolver.rscrates/biome_module_graph/src/js_module_info.rscrates/biome_module_graph/tests/spec_tests_v2.rs
| fn all_variants_match(self, mut predicate: impl FnMut(TypeData<'db>) -> bool) -> bool { | ||
| let mut saw_variant = false; | ||
| let mut seen = FxHashSet::default(); | ||
| let mut pending = vec![self.data]; | ||
|
|
||
| for _ in 0..MAX_TYPE_VARIANT_STEPS { | ||
| let Some(data) = pending.pop() else { | ||
| return saw_variant; | ||
| }; | ||
| if !seen.insert(data) { | ||
| continue; | ||
| } | ||
|
|
||
| match data { | ||
| TypeData::Union(union) => { | ||
| if union.types(self.db).is_empty() { | ||
| return false; | ||
| } | ||
| pending.extend(union.types(self.db).iter().copied()); | ||
| } | ||
| TypeData::Generic(generic) => { | ||
| let Some(constraint) = generic.constraint(self.db) else { | ||
| return false; | ||
| }; | ||
| pending.push(constraint); | ||
| } | ||
| _ if predicate(data) => saw_variant = true, | ||
| _ => return false, | ||
| } | ||
| } | ||
|
|
||
| false | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Handle preserved branded intersections in the shared traversals.
string & Brand reaches these helpers as an Intersection; none descends into it, so predicates such as is_all_string_like, has_number_like_variant, and could_equal_string_literal incorrectly return false. Traverse primitive intersection constituents with the appropriate all/any semantics.
Also applies to: 689-729, 731-761
🤖 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_js_type_info/src/inferred_type.rs` around lines 598 - 630,
Update the shared type traversals, including all_variants_match and the helpers
covering is_all_string_like, has_number_like_variant, and
could_equal_string_literal, to descend into TypeData::Intersection constituents.
Apply all semantics where every primitive constituent must satisfy the predicate
and any semantics where at least one constituent can satisfy it, preserving
existing union and generic handling so branded intersections such as string &
Brand are classified correctly.
| fn promise_member_type( | ||
| &self, | ||
| target: InferredTypeData<'db>, | ||
| member_name: &str, | ||
| ) -> Option<(InferredTypeData<'db>, bool)> { | ||
| if !target.is_promise_class(self.db) || !matches!(member_name, "catch" | "finally" | "then") | ||
| { | ||
| return None; | ||
| } | ||
|
|
||
| let return_type = InferredTypeData::instance_of(self.db, target, Box::default()); | ||
| Some(( | ||
| InferredTypeData::Function(InferredFunction::new( | ||
| self.db, | ||
| Box::default(), | ||
| Box::default(), | ||
| InferredReturnType::Type(return_type), | ||
| false, | ||
| None, | ||
| )), | ||
| false, | ||
| )) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Preserve Promise generic semantics instead of returning a bare Promise.
The synthetic then/catch/finally function has no parameters and returns Promise without type arguments. Consequently, Promise<number>.finally() loses number, while .then(() => "value") cannot infer Promise<string>. Resolve the real member signature or synthesise it using the instance type parameters and callback return type.
🤖 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/db/type_inference/expressions.rs` around lines
1096 - 1117, Update promise_member_type to preserve the target Promise’s generic
type arguments and model callback parameters and return types for then, catch,
and finally. Resolve the real Promise member signature when available, or
synthesize an equivalent function using target’s instance type parameters and
the callback’s inferred return type, so Promise<number>.finally() retains number
and then callbacks produce the resulting Promise type.
|
I'll apply the fixes to the comments from CodeRabbit to the |
Summary
Harden the Salsa-backed type-inference layer before migrating type-aware lint rules in the following PRs.
This introduces
InferredTypeas a query API over interned Salsa types and adds normalised inference queries toTypedServicefor expressions, bindings, function and member return types, callable members, and call arguments. It also fixes resolution and normalisation gaps involving Promise chains, optional calls, tuples, unions, qualified names, computed well-known symbols, getter members, namespace exports, and branded primitive intersections.Test Plan
Added module-graph regression tests for unresolved unions, shorthand members, multi-segment qualifiers, large namespace exports, Promise member chains, floating Promise shapes, optional calls, and branded intersections. Updated the affected inference snapshots.
Docs