+
Skip to content

feat(core): support import namespaces #6303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crates/biome_js_type_info/src/flattening.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,14 @@ fn flattened_expression(
}
}

TypeData::ImportNamespace(module_id) => {
let resolved_id =
resolver.resolve_import_namespace_member(*module_id, &expr.member)?;
return resolver
.get_by_resolved_id(resolved_id)
.map(ResolvedTypeData::to_data);
}

TypeData::Intersection(intersection) => {
let types: Vec<_> = intersection
.types()
Expand Down
7 changes: 7 additions & 0 deletions crates/biome_js_type_info/src/format_type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ impl Format<FormatTypeContext> for TypeData {
Self::Symbol => write!(f, [text("symbol")]),
Self::Undefined => write!(f, [text("undefined")]),
Self::Conditional => write!(f, [text("conditional")]),
Self::ImportNamespace(module_id) => write!(
f,
[dynamic_text(
&std::format!("namespace for {module_id:?}"),
TextSize::default()
)]
),
Self::Class(class) => write!(f, [&class.as_ref()]),
Self::Constructor(ty) => write!(f, [FmtVerbatim(ty.as_ref())]),
Self::Function(function) => write!(f, [&function.as_ref()]),
Expand Down
46 changes: 35 additions & 11 deletions crates/biome_js_type_info/src/resolver.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{borrow::Cow, fmt::Debug};

use biome_js_syntax::AnyJsExpression;
use biome_js_type_info_macros::Resolvable;
use biome_rowan::Text;

use crate::{
Expand All @@ -18,6 +19,14 @@ const LEVEL_MASK: u32 = 0xc000_0000; // Upper 2 bits.
/// `ResolvedTypeId` uses `u32` for its first field so that it can fit the
/// module ID and the resolver level together in 4 bytes, making the struct as
/// a whole still fit in 8 bytes without alignment issues.
///
/// **FIXME:** The second field, that is normally used for storing a `TypeId`,
/// is used instead to store a `BindingId` or a `ModuleId` if the
/// `ResolverId` is of level `TypeResolverLevel::Import`. See
/// [`TypeResolverLevel`] for details.
/// It would be cleaner and safer to avoid this by using an enum for
/// `ResolvedTypeId` instead, but I don't see a way to limit the size
/// of such an enum to 8 bytes, given how we use the [`ResolverId`].
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct ResolvedTypeId(ResolverId, TypeId);

Expand Down Expand Up @@ -218,16 +227,23 @@ pub enum TypeResolverLevel {
/// resolution time.
Thin,

/// Used for marking types that exist across modules that are beyond the
/// capability of the current resolver to resolve.
/// Used for two disjoint purposes, though both are related to the handling
/// of imports:
///
/// We don't store resolved IDs with this level in the module info. Instead,
/// we use it during a module's type collection to flag resolved types that
/// require imports from other modules. Such resolved IDs then get converted
/// to [`TypeReference::Import`] before storing them in the module info.
/// * The module info collector uses this level for marking types that exist
/// across modules that are beyond the capability of the current resolver
/// to resolve. Any resolved IDs with this level are **NOT** allowed to
/// leave the resolver. Instead, any references at this level are
/// converted to [`TypeReference::Import`] before storing them in the
/// module info.
/// * The module resolver uses this level for creating [`ResolvedTypeId`]s
/// that resolve to an ad-hoc namespace for a given module that is created
/// using the `import * as namespace` syntax.
///
/// **Important:** [`ResolvedTypeId`]s of this level store a `BindingId` in
/// the field that is used for `TypeId`s normally.
/// **Important:** [`ResolvedTypeId`]s of this level do not store a `TypeId`
/// where one is normally expected. Instead, the module info
/// collector stores a `BindingId` in its place, while the
/// module resolver stores a `ModuleId` there.
Import,

/// Used for language- and environment-level globals.
Expand Down Expand Up @@ -257,7 +273,7 @@ impl TypeResolverLevel {
}

/// Identifier that indicates which module a type is defined in.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Resolvable)]
pub struct ModuleId(u32);

impl ModuleId {
Expand Down Expand Up @@ -562,12 +578,20 @@ pub trait TypeResolver {
}
}

/// Resolves the given import qualifier, registering the result into this
/// resolver's type array if necessary.
/// Resolves the given import qualifier.
fn resolve_import(&self, _qualifier: &TypeImportQualifier) -> Option<ResolvedTypeId> {
None
}

/// Resolves a named symbol in a given module.
fn resolve_import_namespace_member(
&self,
_module_id: ModuleId,
_name: &str,
) -> Option<ResolvedTypeId> {
None
}

/// Resolves the given `expression` in the given `scope_id` to a type.
///
/// Depending on the resolver, this may return owned type data based on
Expand Down
20 changes: 18 additions & 2 deletions crates/biome_js_type_info/src/type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ use biome_js_type_info_macros::Resolvable;
use biome_resolver::ResolvedPath;
use biome_rowan::Text;

use crate::globals::{GLOBAL_PROMISE_ID, GLOBAL_STRING_ID, GLOBAL_UNKNOWN_ID};
use crate::globals::{GLOBAL_NUMBER_ID, GLOBAL_PROMISE_ID, GLOBAL_STRING_ID, GLOBAL_UNKNOWN_ID};
use crate::type_info::literal::{BooleanLiteral, NumberLiteral, StringLiteral};
use crate::{GLOBAL_RESOLVER, Resolvable, ResolvedTypeData, ResolvedTypeId, TypeResolver};
use crate::{
GLOBAL_RESOLVER, ModuleId, Resolvable, ResolvedTypeData, ResolvedTypeId, TypeResolver,
};

const UNKNOWN: TypeData = TypeData::Reference(TypeReference::Resolved(GLOBAL_UNKNOWN_ID));

Expand Down Expand Up @@ -126,6 +128,16 @@ impl Type {
}
}

/// Returns whether this type is a number or a literal number.
pub fn is_number(&self) -> bool {
self.id == GLOBAL_NUMBER_ID
|| self.as_raw_data().is_some_and(|ty| match ty {
TypeData::Number => true,
TypeData::Literal(literal) => matches!(literal.as_ref(), Literal::Number(_)),
_ => false,
})
}

/// Returns whether this type is the `Promise` class.
pub fn is_promise(&self) -> bool {
self.id == GLOBAL_PROMISE_ID
Expand Down Expand Up @@ -218,6 +230,10 @@ pub enum TypeData {
/// An example is the return value of the callback to `Array#filter()`.
Conditional,

/// Special type used to represent a module for which an ad-hoc namespace is
/// created through `import * as namespace` syntax.
ImportNamespace(ModuleId),

// Complex types
Class(Box<Class>),
Constructor(Box<Constructor>),
Expand Down
54 changes: 47 additions & 7 deletions crates/biome_module_graph/src/js_module_info/module_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ impl ModuleResolver {
let module_info = self.module_graph.module_info_for_path(path)?;
let module_id = ModuleId::new(self.modules.len());
self.modules.push(module_info);
self.types
.register_type(Cow::Owned(TypeData::ImportNamespace(module_id)));
Some(*entry.insert(module_id))
}
}
Expand Down Expand Up @@ -250,20 +252,20 @@ impl TypeResolver for ModuleResolver {

fn get_by_resolved_id(&self, id: ResolvedTypeId) -> Option<ResolvedTypeData> {
match id.level() {
TypeResolverLevel::Full => Some((id, self.get_by_id(id.id())).into()),
TypeResolverLevel::Full => Some(ResolvedTypeData::from((id, self.get_by_id(id.id())))),
TypeResolverLevel::Thin => {
let module_id = id.module_id();
let module = &self.modules[module_id.index()];
if let Some(ty) = module.types.get(id.index()) {
Some((id, ty).into())
Some(ResolvedTypeData::from((id, ty)))
} else {
debug_assert!(false, "Invalid type reference: {id:?}");
None
}
}
TypeResolverLevel::Import => {
panic!("import IDs should not be exposed outside the module info collector")
}
TypeResolverLevel::Import => self
.find_type(&TypeData::ImportNamespace(ModuleId::new(id.index())))
.map(|type_id| ResolvedTypeData::from((id, self.get_by_id(type_id)))),
TypeResolverLevel::Global => Some((id, GLOBAL_RESOLVER.get_by_id(id.id())).into()),
}
}
Expand Down Expand Up @@ -292,8 +294,12 @@ impl TypeResolver for ModuleResolver {
ImportSymbol::Default => "default",
ImportSymbol::Named(name) => name.text(),
ImportSymbol::All => {
// TODO: Register type for imported namespace.
return None;
// Create a `ResolvedTypeId` that resolves to a
// `TypeData::ImportNamespace` variant.
return Some(ResolvedTypeId::new(
TypeResolverLevel::Import,
TypeId::new(module_id.index()),
));
}
};

Expand Down Expand Up @@ -332,6 +338,40 @@ impl TypeResolver for ModuleResolver {
None
}

fn resolve_import_namespace_member(
&self,
module_id: ModuleId,
name: &str,
) -> Option<ResolvedTypeId> {
let module = &self.modules[module_id.index()];
let export = match module.exports.get(name) {
Some(JsExport::Own(export) | JsExport::OwnType(export)) => export,
Some(JsExport::Reexport(reexport)) => {
return self.resolve_import(&TypeImportQualifier {
symbol: reexport.import.symbol.clone(),
resolved_path: reexport.import.resolved_path.clone(),
type_only: false,
});
}
Some(JsExport::ReexportType(reexport)) => {
return self.resolve_import(&TypeImportQualifier {
symbol: reexport.import.symbol.clone(),
resolved_path: reexport.import.resolved_path.clone(),
type_only: true,
});
}
None => {
// TODO: Follow blanket reexports.
return None;
}
};

match resolve_from_export(module_id, module, export) {
ResolveFromExportResult::Resolved(resolved) => resolved,
ResolveFromExportResult::FollowImport(import) => self.resolve_import(import),
}
}

fn resolve_qualifier(&self, _qualifier: &TypeReferenceQualifier) -> Option<ResolvedTypeId> {
// We rely on qualifiers to have been resolved during the construction
// of the module graph.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,34 +165,38 @@ Module TypeId(3) => sync Function "foo" {
## Registered types

```
Full TypeId(0) => value: bar
Full TypeId(0) => namespace for ModuleId(1)

Full TypeId(1) => Object {
Full TypeId(1) => namespace for ModuleId(2)

Full TypeId(2) => value: bar

Full TypeId(3) => Object {
prototype: No prototype
members: ["bar": Module(0) TypeId(0)]
}

Full TypeId(2) => Object {
Full TypeId(4) => Object {
prototype: No prototype
members: ["bar": Module(1) TypeId(0)]
}

Full TypeId(3) => Module(1) TypeId(0)
Full TypeId(5) => Module(1) TypeId(0)

Full TypeId(4) => Module(2) TypeId(3)
Full TypeId(6) => Module(2) TypeId(3)

Full TypeId(5) => value: 1
Full TypeId(7) => value: 1

Full TypeId(6) => value: bar
Full TypeId(8) => value: bar

Full TypeId(7) => Object {
Full TypeId(9) => Object {
prototype: No prototype
members: ["bar": Module(1) TypeId(0)]
}

Full TypeId(8) => Module(1) TypeId(0)
Full TypeId(10) => Module(1) TypeId(0)

Full TypeId(9) => Module(2) TypeId(3)
Full TypeId(11) => Module(2) TypeId(3)

Full TypeId(10) => value: bar
Full TypeId(12) => value: bar
```
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载