安装
npx skills add https://github.com/biomejs/biome --skill type-inference
复制
Purpose
Use this skill when working with Biome's type inference system and module graph. Covers type references, resolution phases, and the architecture designed for IDE performance.
Prerequisites
Read
crates/biome_js_type_info/CONTRIBUTING.md
for architecture details
Understand Biome's focus on IDE support and instant updates
Familiarity with TypeScript type system concepts
Key Concepts
Module Graph Constraint
Critical rule
No module may copy or clone data from another module, not even behind
Arc
.
Why
Any module can be updated at any time (IDE file changes). Copying data would create stale references that are hard to invalidate.
Solution
Use
TypeReference
instead of direct type references.
Type Data Structure
Types are stored in
TypeData
enum with many variants:
// Simplified — see crates/biome_js_type_info/src/type_data.rs for the full enum
enum
TypeData
{
Unknown
,
// Inference not implemented
Global
,
// Global type reference
BigInt
,
Boolean
,
Null
,
Number
,
// Primitive types
String
,
Symbol
,
Undefined
,
Function
(
Box
<
Function
>
)
,
// Function with parameters
Object
(
Box
<
Object
>
)
,
// Object with properties
Class
(
Box
<
Class
>
)
,
// Class definition
Interface
(
Box
<
Interface
>
)
,
// Interface definition
Union
(
Box
<
Union
>
)
,
// Union type (A | B)
Intersection
(
Box
<
Intersection
>
)
,
// Intersection type (A & B)
Tuple
(
Box
<
Tuple
>
)
,
// Tuple type
Literal
(
Box
<
Literal
>
)
,
// Literal type ("foo", 42)
Reference
(
TypeReference
)
,
// Reference to another type
TypeofExpression
(
Box
<
TypeofExpression
>
)
,
// typeof an expression
// ... plus Conditional, Generic, TypeOperator, InstanceOf,
// keyword variants (AnyKeyword, NeverKeyword, VoidKeyword, etc.)
}
Type References
Instead of direct type references, use
TypeReference
:
enum
TypeReference
{
Qualifier
(
Box
<
TypeReferenceQualifier
>
)
,
// Name-based reference
Resolved
(
ResolvedTypeId
)
,
// Resolved to type ID
Import
(
Box
<
TypeImportQualifier
>
)
,
// Import reference
}
Note:
There is no
Unknown
variant. Unknown types are represented as
TypeReference::Resolved(GLOBAL_UNKNOWN_ID)
. Use
TypeReference::unknown()
to create one.
Type Resolution Phases
1. Local Inference
What
Derives types from expressions without surrounding context.
Example
For
a + b
, creates:
TypeData
::
TypeofExpression
(
TypeofExpression
::
Addition
{
left
:
TypeReference
::
from
(
TypeReferenceQualifier
::
from_name
(
"a"
)
)
,
right
:
TypeReference
::
from
(
TypeReferenceQualifier
::
from_name
(
"b"
)
)
}
)
Where
Implemented in
local_inference.rs
Output
Types with unresolved
TypeReference::Qualifier
references
2. Module-Level ("Thin") Inference
What
Resolves references within a single module's scope.
Process
:
Takes results from local inference
Looks up qualifiers in local scopes
Converts to
TypeReference::Resolved
if found locally
Converts to
TypeReference::Import
if from import statement
Falls back to globals (like
Array
,
Promise
)
Uses
TypeReference::Unknown
if nothing found
Where
Implemented in
js_module_info/collector.rs
Output
Types with resolved local references, import markers, or unknown
3. Full Inference
What
Resolves import references across module boundaries.
Process
:
Has access to entire module graph
Resolves
TypeReference::Import
by following imports
Converts to
TypeReference::Resolved
after following imports
Where
Implemented in
js_module_info/module_resolver.rs
Limitation
Results cannot be cached (would become stale on file changes)
Working with Type Resolvers
Available Resolvers
// 1. For tests
HardcodedSymbolResolver
// 2. For globals (Array, Promise, etc.)
GlobalsResolver
// 3. For thin inference (single module)
JsModuleInfoCollector
// 4. For full inference (across modules)
ModuleResolver
Using a Resolver
use
biome_js_type_info
::
{
TypeResolver
,
ResolvedTypeData
}
;
fn
analyze_type
(
resolver
:
&
impl
TypeResolver
,
type_ref
:
TypeReference
)
{
// Resolve the reference
let
resolved_data
:
ResolvedTypeData
=
resolver
.
resolve_type
(
type_ref
)
;
// Get raw data for pattern matching
match
resolved_data
.
as_raw_data
(
)
{
TypeData
::
String
=>
{
/ handle string /
}
,
TypeData
::
Number
=>
{
/ handle number /
}
,
TypeData
::
Function
(
func
)
=>
{
/ handle function /
}
,
_
=>
{
/ handle others /
}
}
// Resolve nested references
if
let
TypeData
::
Reference
(
inner_ref
)
=
resolved_data
.
as_raw_data
(
)
{
let
inner_data
=
resolver
.
resolve_type
(
*
inner_ref
)
;
// Process inner type
}
}
Type Flattening
What
Converts complex type expressions to concrete types.
Example
After resolving
a + b
:
If both are
TypeData::Number
→ Flatten to
TypeData::Number
Otherwise → Usually flatten to
TypeData::String
Where
Implemented in
flattening.rs
Common Workflows
Implement Type-Aware Lint Rule
use
biome_analyze
::
Semantic
;
use
biome_js_type_info
::
{
TypeResolver
,
TypeData
}
;
impl
Rule
for
MyTypeRule
{
type
Query
=
Semantic
<
JsCallExpression
>
;
fn
run
(
ctx
:
&
RuleContext
<
Self
>
)
->
Self
::
Signals
{
let
node
=
ctx
.
query
(
)
;
let
model
=
ctx
.
model
(
)
;
// Get type resolver from model
let
resolver
=
model
.
type_resolver
(
)
;
// Get type of expression
let
expr_type
=
node
.
callee
(
)
.
ok
(
)
?
.
infer_type
(
resolver
)
;
// Check the type
match
expr_type
.
as_raw_data
(
)
{
TypeData
::
Function
(
_
)
=>
{
/ valid /
}
,
TypeData
::
Unknown
=>
{
/ might be valid, can't tell /
}
,
_
=>
{
return
Some
(
(
)
)
;
/ not callable /
}
}
None
}
}
Navigate Type References
fn
is_string_type
(
resolver
:
&
impl
TypeResolver
,
type_ref
:
TypeReference
)
->
bool
{
let
resolved
=
resolver
.
resolve_type
(
type_ref
)
;
// Follow references
let
data
=
match
resolved
.
as_raw_data
(
)
{
TypeData
::
Reference
(
ref_to
)
=>
resolver
.
resolve_type
(
*
ref_to
)
,
_other
=>
resolved
,
}
;
// Check the resolved type
matches!
(
data
.
as_raw_data
(
)
,
TypeData
::
String
)
}
Work with Function Types
fn
analyze_function
(
resolver
:
&
impl
TypeResolver
,
type_ref
:
TypeReference
)
{
let
resolved
=
resolver
.
resolve_type
(
type_ref
)
;
if
let
TypeData
::
Function
(
func_type
)
=
resolved
.
as_raw_data
(
)
{
// Access parameters
for
param
in
func_type
.
parameters
(
)
{
let
param_type
=
resolver
.
resolve_type
(
param
.
type_ref
(
)
)
;
// Analyze parameter type
}
// Access return type
let
return_type
=
resolver
.
resolve_type
(
func_type
.
return_type
(
)
)
;
}
}
Architecture Principles
Why Type References?
Advantages
:
No stale data
Module updates don't leave old types in memory
Better performance
Types stored in vectors (data locality)
Easier debugging
Can inspect all types in vector
Simpler algorithms
Process vectors instead of traversing graphs
Trade-off
Must explicitly resolve references (not automatic like
Arc
)
ResolvedTypeId Structure
struct
ResolvedTypeId
(
ResolverId
,
TypeId
)
TypeId
(u32): Index into a type vector
ResolverId
(u32): Identifies which vector to use
Total: 64 bits (compact representation)
ResolvedTypeData
Always work with
ResolvedTypeData
from resolver, not raw
&TypeData
:
// Good - tracks resolver context
let
resolved_data
:
ResolvedTypeData
=
resolver
.
resolve_type
(
type_ref
)
;
// Be careful - loses resolver context
let
raw_data
:
&
TypeData
=
resolved_data
.
as_raw_data
(
)
;
// Can't resolve nested TypeReferences without ResolverId!
Tips
Unknown types
:
TypeData::Unknown
means inference not implemented, treat as "could be anything"
Follow references
Always follow
TypeData::Reference
to get actual type
Resolver context
Keep
ResolvedTypeData
when possible, don't extract raw
TypeData
early
Performance
Type vectors are fast - iterate directly instead of recursive traversal
IDE focus
All design decisions prioritize instant IDE updates over CLI performance
No caching
Full inference results can't be cached (would become stale)
Globals
Currently hardcoded, eventually should use TypeScript's
.d.ts
files
Common Patterns
// Pattern 1: Resolve and flatten
let
type_ref
=
expr
.
infer_type
(
resolver
)
;
let
flattened
=
type_ref
.
flatten
(
resolver
)
;
// Pattern 2: Check if type matches
fn
is_string_type
(
resolver
:
&
impl
TypeResolver
,
type_ref
:
TypeReference
)
->
bool
{
let
resolved
=
resolver
.
resolve_type
(
type_ref
)
;
matches!
(
resolved
.
as_raw_data
(
)
,
TypeData
::
String
)
}
// Pattern 3: Handle unknown gracefully
match
resolved
.
as_raw_data
(
)
{
TypeData
::
Unknown
|
TypeData
::
UnknownKeyword
=>
{
// Can't verify, assume valid
return
None
;
}
TypeData
::
String
=>
{
/ handle /
}
_
=>
{
/ handle /
}
}
References
Architecture guide:
crates/biome_js_type_info/CONTRIBUTING.md
Module graph:
crates/biome_module_graph/
Type resolver trait:
crates/biome_js_type_info/src/resolver.rs
Flattening:
crates/biome_js_type_info/src/flattening.rs
← 返回排行榜