diff --git a/Cargo.lock b/Cargo.lock index 9762cf4325ca..c03ad48a7c2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1063,6 +1063,7 @@ dependencies = [ "camino", "hashbrown 0.15.5", "insta", + "paste", "rustc-hash 2.1.1", ] diff --git a/crates/biome_js_type_info/Cargo.toml b/crates/biome_js_type_info/Cargo.toml index 6e4191c85f38..57cdbff7ece3 100644 --- a/crates/biome_js_type_info/Cargo.toml +++ b/crates/biome_js_type_info/Cargo.toml @@ -22,6 +22,7 @@ biome_resolver = { workspace = true } biome_rowan = { workspace = true } camino = { workspace = true } hashbrown = { workspace = true } +paste = "1.0" rustc-hash = { workspace = true } [dev-dependencies] diff --git a/crates/biome_js_type_info/src/format_type_info.rs b/crates/biome_js_type_info/src/format_type_info.rs index a7902c4c6ba3..f0f0b7224a20 100644 --- a/crates/biome_js_type_info/src/format_type_info.rs +++ b/crates/biome_js_type_info/src/format_type_info.rs @@ -589,8 +589,12 @@ impl Format for TypeReference { let level = resolved.level(); let id = resolved.id(); if level == TypeResolverLevel::Global { - if resolved.index() < NUM_PREDEFINED_TYPES { - write!(f, [token(global_type_name(id))]) + // GlobalsResolverBuilder makes sure the type store is fully filled. + // Every global TypeId whose index is less than NUM_PREDEFINED_TYPES + // must have a name returned by global_type_name(). + // GLOBAL_TYPE_MEMBERS ensures this invariant. + if let Some(name) = global_type_name(id) { + write!(f, [token(name)]) } else { // Start counting from `NUM_PREDEFINED_TYPES` so // snapshots remain stable even if we add new predefined diff --git a/crates/biome_js_type_info/src/globals.rs b/crates/biome_js_type_info/src/globals.rs index 29df93aa8747..a737805dc510 100644 --- a/crates/biome_js_type_info/src/globals.rs +++ b/crates/biome_js_type_info/src/globals.rs @@ -17,6 +17,11 @@ use crate::{ TypeResolver, TypeResolverLevel, TypeStore, Union, flattening::MAX_FLATTEN_DEPTH, }; +use super::globals_builder::GlobalsResolverBuilder; + +// Re-export all type ID constants from globals_ids +pub use super::globals_ids::*; + pub(super) const GLOBAL_LEVEL: TypeResolverLevel = TypeResolverLevel::Global; pub(super) const GLOBAL_RESOLVER_ID: ResolverId = ResolverId::from_level(GLOBAL_LEVEL); @@ -26,148 +31,67 @@ pub static GLOBAL_RESOLVER: LazyLock> = pub static GLOBAL_TYPE_MEMBERS: LazyLock> = LazyLock::new(|| { (0..NUM_PREDEFINED_TYPES) .map(TypeId::new) - .map(|id| TypeMember { - kind: TypeMemberKind::Named(Text::new_static(global_type_name(id))), - ty: ResolvedTypeId::new(GLOBAL_LEVEL, id).into(), + .map(|id| { + let name = global_type_name(id).unwrap_or("unknown"); + TypeMember { + kind: TypeMemberKind::Named(Text::new_static(name)), + ty: ResolvedTypeId::new(GLOBAL_LEVEL, id).into(), + } }) .collect() }); -pub const UNKNOWN_ID: TypeId = TypeId::new(0); -pub const UNDEFINED_ID: TypeId = TypeId::new(1); -pub const VOID_ID: TypeId = TypeId::new(2); -pub const CONDITIONAL_ID: TypeId = TypeId::new(3); -pub const NUMBER_ID: TypeId = TypeId::new(4); -pub const STRING_ID: TypeId = TypeId::new(5); -pub const INSTANCEOF_ARRAY_T_ID: TypeId = TypeId::new(6); -pub const INSTANCEOF_ARRAY_U_ID: TypeId = TypeId::new(7); -pub const ARRAY_ID: TypeId = TypeId::new(8); -pub const ARRAY_FILTER_ID: TypeId = TypeId::new(9); -pub const ARRAY_FOREACH_ID: TypeId = TypeId::new(10); -pub const ARRAY_MAP_ID: TypeId = TypeId::new(11); -pub const GLOBAL_ID: TypeId = TypeId::new(12); -pub const INSTANCEOF_PROMISE_ID: TypeId = TypeId::new(13); -pub const PROMISE_ID: TypeId = TypeId::new(14); -pub const PROMISE_CONSTRUCTOR_ID: TypeId = TypeId::new(15); -pub const PROMISE_CATCH_ID: TypeId = TypeId::new(16); -pub const PROMISE_FINALLY_ID: TypeId = TypeId::new(17); -pub const PROMISE_THEN_ID: TypeId = TypeId::new(18); -pub const PROMISE_ALL_ID: TypeId = TypeId::new(19); -pub const PROMISE_ALL_SETTLED_ID: TypeId = TypeId::new(20); -pub const PROMISE_ANY_ID: TypeId = TypeId::new(21); -pub const PROMISE_RACE_ID: TypeId = TypeId::new(22); -pub const PROMISE_REJECT_ID: TypeId = TypeId::new(23); -pub const PROMISE_RESOLVE_ID: TypeId = TypeId::new(24); -pub const PROMISE_TRY_ID: TypeId = TypeId::new(25); -pub const INSTANCEOF_REGEXP_ID: TypeId = TypeId::new(26); -pub const REGEXP_ID: TypeId = TypeId::new(27); -pub const REGEXP_EXEC_ID: TypeId = TypeId::new(28); -pub const BIGINT_STRING_LITERAL_ID: TypeId = TypeId::new(29); -pub const BOOLEAN_STRING_LITERAL_ID: TypeId = TypeId::new(30); -pub const FUNCTION_STRING_LITERAL_ID: TypeId = TypeId::new(31); -pub const NUMBER_STRING_LITERAL_ID: TypeId = TypeId::new(32); -pub const OBJECT_STRING_LITERAL_ID: TypeId = TypeId::new(33); -pub const STRING_STRING_LITERAL_ID: TypeId = TypeId::new(34); -pub const SYMBOL_STRING_LITERAL_ID: TypeId = TypeId::new(35); -pub const UNDEFINED_STRING_LITERAL_ID: TypeId = TypeId::new(36); -pub const TYPEOF_OPERATOR_RETURN_UNION_ID: TypeId = TypeId::new(37); -pub const T_ID: TypeId = TypeId::new(38); -pub const U_ID: TypeId = TypeId::new(39); -pub const CONDITIONAL_CALLBACK_ID: TypeId = TypeId::new(40); -pub const MAP_CALLBACK_ID: TypeId = TypeId::new(41); -pub const VOID_CALLBACK_ID: TypeId = TypeId::new(42); -pub const FETCH_ID: TypeId = TypeId::new(43); -pub const NUM_PREDEFINED_TYPES: usize = 44; // Must be one more than the highest `TypeId` above. - -pub const GLOBAL_UNKNOWN_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, UNKNOWN_ID); -pub const GLOBAL_UNDEFINED_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, UNDEFINED_ID); -pub const GLOBAL_VOID_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, VOID_ID); -pub const GLOBAL_CONDITIONAL_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, CONDITIONAL_ID); -pub const GLOBAL_NUMBER_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, NUMBER_ID); -pub const GLOBAL_STRING_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, STRING_ID); -pub const GLOBAL_ARRAY_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, ARRAY_ID); -pub const GLOBAL_INSTANCEOF_REGEXP_ID: ResolvedTypeId = - ResolvedTypeId::new(GLOBAL_LEVEL, INSTANCEOF_REGEXP_ID); -pub const GLOBAL_REGEXP_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, REGEXP_ID); -pub const GLOBAL_GLOBAL_ID /* :smirk: */: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, GLOBAL_ID); -pub const GLOBAL_INSTANCEOF_PROMISE_ID: ResolvedTypeId = - ResolvedTypeId::new(GLOBAL_LEVEL, INSTANCEOF_PROMISE_ID); -pub const GLOBAL_PROMISE_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, PROMISE_ID); -pub const GLOBAL_PROMISE_CONSTRUCTOR_ID: ResolvedTypeId = - ResolvedTypeId::new(GLOBAL_LEVEL, PROMISE_CONSTRUCTOR_ID); -pub const GLOBAL_BIGINT_STRING_LITERAL_ID: ResolvedTypeId = - ResolvedTypeId::new(GLOBAL_LEVEL, BIGINT_STRING_LITERAL_ID); -pub const GLOBAL_BOOLEAN_STRING_LITERAL_ID: ResolvedTypeId = - ResolvedTypeId::new(GLOBAL_LEVEL, BOOLEAN_STRING_LITERAL_ID); -pub const GLOBAL_FUNCTION_STRING_LITERAL_ID: ResolvedTypeId = - ResolvedTypeId::new(GLOBAL_LEVEL, FUNCTION_STRING_LITERAL_ID); -pub const GLOBAL_NUMBER_STRING_LITERAL_ID: ResolvedTypeId = - ResolvedTypeId::new(GLOBAL_LEVEL, NUMBER_STRING_LITERAL_ID); -pub const GLOBAL_OBJECT_STRING_LITERAL_ID: ResolvedTypeId = - ResolvedTypeId::new(GLOBAL_LEVEL, OBJECT_STRING_LITERAL_ID); -pub const GLOBAL_STRING_STRING_LITERAL_ID: ResolvedTypeId = - ResolvedTypeId::new(GLOBAL_LEVEL, STRING_STRING_LITERAL_ID); -pub const GLOBAL_SYMBOL_STRING_LITERAL_ID: ResolvedTypeId = - ResolvedTypeId::new(GLOBAL_LEVEL, SYMBOL_STRING_LITERAL_ID); -pub const GLOBAL_UNDEFINED_STRING_LITERAL_ID: ResolvedTypeId = - ResolvedTypeId::new(GLOBAL_LEVEL, UNDEFINED_STRING_LITERAL_ID); -pub const GLOBAL_TYPEOF_OPERATOR_RETURN_UNION_ID: ResolvedTypeId = - ResolvedTypeId::new(GLOBAL_LEVEL, TYPEOF_OPERATOR_RETURN_UNION_ID); -pub const GLOBAL_T_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, T_ID); -pub const GLOBAL_U_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, U_ID); -pub const GLOBAL_FETCH_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, FETCH_ID); - -/// Returns a string for formatting global IDs in test snapshots. -pub fn global_type_name(id: TypeId) -> &'static str { - match id.index() { - 0 => "unknown", - 1 => "undefined", - 2 => "void", - 3 => "conditional", - 4 => "number", - 5 => "string", - 6 => "instanceof Array", - 7 => "instanceof Array", - 8 => "Array", - 9 => "Array.prototype.filter", - 10 => "Array.prototype.forEach", - 11 => "Array.prototype.map", - 12 => "globalThis", - 13 => "instanceof Promise", - 14 => "Promise", - 15 => "Promise.constructor", - 16 => "Promise.prototype.catch", - 17 => "Promise.prototype.finally", - 18 => "Promise.prototype.then", - 19 => "Promise.all", - 20 => "Promise.allSettled", - 21 => "Promise.any", - 22 => "Promise.race", - 23 => "Promise.reject", - 24 => "Promise.resolve", - 25 => "Promise.try", - 26 => "instanceof RegExp", - 27 => "RegExp", - 28 => "RegExp.exec", - 29 => "\"bigint\"", - 30 => "\"boolean\"", - 31 => "\"function\"", - 32 => "\"number\"", - 33 => "\"object\"", - 34 => "\"string\"", - 35 => "\"symbol\"", - 36 => "\"undefined\"", - 37 => { - "\"bigint\" | \"boolean\" | \"function\" | \"number\" | \"object\" \ - | \"string\" | \"symbol\" | \"undefined\"" - } - 38 => "T", - 39 => "U", - 40 => "() => conditional", - 41 => "(item: T) => U", - 42 => "() => void", - 43 => "fetch", - _ => "inferred type", +// TODO(tidefield): Generate this function in codegen +// Returns a string for formatting global IDs in test snapshots. +pub fn global_type_name(id: TypeId) -> Option<&'static str> { + use crate::globals_ids::*; + // TODO(tidefield): Enforce exhaustiveness in this list + match id { + UNKNOWN_ID => Some(UNKNOWN_ID_NAME), + UNDEFINED_ID => Some(UNDEFINED_ID_NAME), + VOID_ID => Some(VOID_ID_NAME), + CONDITIONAL_ID => Some(CONDITIONAL_ID_NAME), + NUMBER_ID => Some(NUMBER_ID_NAME), + STRING_ID => Some(STRING_ID_NAME), + INSTANCEOF_ARRAY_T_ID => Some(INSTANCEOF_ARRAY_T_ID_NAME), + INSTANCEOF_ARRAY_U_ID => Some(INSTANCEOF_ARRAY_U_ID_NAME), + ARRAY_ID => Some(ARRAY_ID_NAME), + ARRAY_FILTER_ID => Some(ARRAY_FILTER_ID_NAME), + ARRAY_FOREACH_ID => Some(ARRAY_FOREACH_ID_NAME), + ARRAY_MAP_ID => Some(ARRAY_MAP_ID_NAME), + GLOBAL_ID => Some(GLOBAL_ID_NAME), + INSTANCEOF_PROMISE_ID => Some(INSTANCEOF_PROMISE_ID_NAME), + PROMISE_ID => Some(PROMISE_ID_NAME), + PROMISE_CONSTRUCTOR_ID => Some(PROMISE_CONSTRUCTOR_ID_NAME), + PROMISE_CATCH_ID => Some(PROMISE_CATCH_ID_NAME), + PROMISE_FINALLY_ID => Some(PROMISE_FINALLY_ID_NAME), + PROMISE_THEN_ID => Some(PROMISE_THEN_ID_NAME), + PROMISE_ALL_ID => Some(PROMISE_ALL_ID_NAME), + PROMISE_ALL_SETTLED_ID => Some(PROMISE_ALL_SETTLED_ID_NAME), + PROMISE_ANY_ID => Some(PROMISE_ANY_ID_NAME), + PROMISE_RACE_ID => Some(PROMISE_RACE_ID_NAME), + PROMISE_REJECT_ID => Some(PROMISE_REJECT_ID_NAME), + PROMISE_RESOLVE_ID => Some(PROMISE_RESOLVE_ID_NAME), + PROMISE_TRY_ID => Some(PROMISE_TRY_ID_NAME), + BIGINT_STRING_LITERAL_ID => Some(BIGINT_STRING_LITERAL_ID_NAME), + BOOLEAN_STRING_LITERAL_ID => Some(BOOLEAN_STRING_LITERAL_ID_NAME), + FUNCTION_STRING_LITERAL_ID => Some(FUNCTION_STRING_LITERAL_ID_NAME), + NUMBER_STRING_LITERAL_ID => Some(NUMBER_STRING_LITERAL_ID_NAME), + OBJECT_STRING_LITERAL_ID => Some(OBJECT_STRING_LITERAL_ID_NAME), + STRING_STRING_LITERAL_ID => Some(STRING_STRING_LITERAL_ID_NAME), + SYMBOL_STRING_LITERAL_ID => Some(SYMBOL_STRING_LITERAL_ID_NAME), + UNDEFINED_STRING_LITERAL_ID => Some(UNDEFINED_STRING_LITERAL_ID_NAME), + TYPEOF_OPERATOR_RETURN_UNION_ID => Some(TYPEOF_OPERATOR_RETURN_UNION_ID_NAME), + T_ID => Some(T_ID_NAME), + U_ID => Some(U_ID_NAME), + CONDITIONAL_CALLBACK_ID => Some(CONDITIONAL_CALLBACK_ID_NAME), + MAP_CALLBACK_ID => Some(MAP_CALLBACK_ID_NAME), + VOID_CALLBACK_ID => Some(VOID_CALLBACK_ID_NAME), + FETCH_ID => Some(FETCH_ID_NAME), + INSTANCEOF_REGEXP_ID => Some(INSTANCEOF_REGEXP_ID_NAME), + REGEXP_ID => Some(REGEXP_ID_NAME), + REGEXP_EXEC_ID => Some(REGEXP_EXEC_ID_NAME), + _ => None, } } @@ -177,7 +101,7 @@ pub fn global_type_name(id: TypeId) -> &'static str { /// been shadowed by local declarations, so it should generally only be used /// after all other resolvers have failed. pub struct GlobalsResolver { - types: TypeStore, + pub(crate) types: TypeStore, } impl Default for GlobalsResolver { @@ -200,7 +124,7 @@ impl Default for GlobalsResolver { TypeData::from(Function { is_async: false, type_parameters, - name: Some(Text::new_static(global_type_name(id))), + name: Some(Text::new_static(global_type_name(id).unwrap_or("unknown"))), parameters: [FunctionParameter::Pattern(PatternFunctionParameter { bindings: Default::default(), is_optional: false, @@ -218,38 +142,42 @@ impl Default for GlobalsResolver { TypeData::from(Function { is_async: false, type_parameters: Default::default(), - name: Some(Text::new_static(global_type_name(id))), + name: Some(Text::new_static(global_type_name(id).unwrap_or("unknown"))), parameters: Default::default(), return_type: ReturnType::Type(GLOBAL_INSTANCEOF_PROMISE_ID.into()), }) }; - let regexp_method_definition = |id: TypeId| { - TypeData::from(Function { - is_async: false, - type_parameters: Default::default(), - name: Some(Text::new_static(global_type_name(id))), - parameters: Default::default(), - return_type: ReturnType::Type(GLOBAL_INSTANCEOF_REGEXP_ID.into()), - }) - }; - let string_literal = |value: &'static str| -> TypeData { TypeData::from(Literal::String(Text::new_static(value).into())) }; - let types = vec![ - TypeData::Unknown, - TypeData::Undefined, - TypeData::VoidKeyword, - TypeData::Conditional, - TypeData::Number, - TypeData::String, + let mut builder = GlobalsResolverBuilder::with_capacity(NUM_PREDEFINED_TYPES); + + // Primitive types + builder.set_type_data(UNKNOWN_ID, TypeData::Unknown); + builder.set_type_data(UNDEFINED_ID, TypeData::Undefined); + builder.set_type_data(VOID_ID, TypeData::VoidKeyword); + builder.set_type_data(CONDITIONAL_ID, TypeData::Conditional); + builder.set_type_data(NUMBER_ID, TypeData::Number); + builder.set_type_data(STRING_ID, TypeData::String); + + // TODO(tidefield): Use biome parser to parse Typescript .d.ts files + // and generate the following `TypeData`s as much as possible + + builder.set_type_data( + INSTANCEOF_ARRAY_T_ID, TypeData::instance_of(TypeReference::from(GLOBAL_ARRAY_ID)), + ); + builder.set_type_data( + INSTANCEOF_ARRAY_U_ID, TypeData::instance_of(TypeInstance { ty: TypeReference::from(GLOBAL_ARRAY_ID), type_parameters: [GLOBAL_U_ID.into()].into(), }), + ); + builder.set_type_data( + ARRAY_ID, TypeData::Class(Box::new(Class { name: Some(Text::new_static("Array")), type_parameters: Box::new([TypeReference::from(GLOBAL_T_ID)]), @@ -265,26 +193,42 @@ impl Default for GlobalsResolver { }, ]), })), + ); + builder.set_type_data( + ARRAY_FILTER_ID, array_method_definition( ARRAY_FILTER_ID, CONDITIONAL_CALLBACK_ID, INSTANCEOF_ARRAY_T_ID, Default::default(), ), + ); + builder.set_type_data( + ARRAY_FOREACH_ID, array_method_definition( ARRAY_FOREACH_ID, VOID_CALLBACK_ID, VOID_ID, Default::default(), ), + ); + builder.set_type_data( + ARRAY_MAP_ID, array_method_definition( ARRAY_MAP_ID, MAP_CALLBACK_ID, INSTANCEOF_ARRAY_U_ID, [GLOBAL_U_ID.into()].into(), ), - TypeData::Global, + ); + builder.set_type_data(GLOBAL_ID, TypeData::Global); + builder.set_type_data( + INSTANCEOF_PROMISE_ID, TypeData::instance_of(TypeReference::from(GLOBAL_PROMISE_ID)), + ); + // Promise class + builder.set_type_data( + PROMISE_ID, TypeData::Class(Box::new(Class { name: Some(Text::new_static("Promise")), type_parameters: Box::new([TypeReference::from(GLOBAL_T_ID)]), @@ -307,10 +251,14 @@ impl Default for GlobalsResolver { static_method("try", PROMISE_TRY_ID), ]), })), + ); + + builder.set_type_data( + PROMISE_CONSTRUCTOR_ID, TypeData::from(Function { is_async: false, type_parameters: Default::default(), - name: Some(Text::new_static(global_type_name(PROMISE_CONSTRUCTOR_ID))), + name: Some(Text::new_static(PROMISE_CONSTRUCTOR_ID_NAME)), parameters: [FunctionParameter::Pattern(PatternFunctionParameter { bindings: Default::default(), is_optional: false, @@ -320,33 +268,43 @@ impl Default for GlobalsResolver { .into(), return_type: ReturnType::Type(GLOBAL_VOID_ID.into()), }), + ); + builder.set_type_data( + PROMISE_CATCH_ID, promise_method_definition(PROMISE_CATCH_ID), + ); + builder.set_type_data( + PROMISE_FINALLY_ID, promise_method_definition(PROMISE_FINALLY_ID), - promise_method_definition(PROMISE_THEN_ID), - promise_method_definition(PROMISE_ALL_ID), + ); + builder.set_type_data(PROMISE_THEN_ID, promise_method_definition(PROMISE_THEN_ID)); + builder.set_type_data(PROMISE_ALL_ID, promise_method_definition(PROMISE_ALL_ID)); + builder.set_type_data( + PROMISE_ALL_SETTLED_ID, promise_method_definition(PROMISE_ALL_SETTLED_ID), - promise_method_definition(PROMISE_ANY_ID), - promise_method_definition(PROMISE_RACE_ID), + ); + builder.set_type_data(PROMISE_ANY_ID, promise_method_definition(PROMISE_ANY_ID)); + builder.set_type_data(PROMISE_RACE_ID, promise_method_definition(PROMISE_RACE_ID)); + builder.set_type_data( + PROMISE_REJECT_ID, promise_method_definition(PROMISE_REJECT_ID), + ); + builder.set_type_data( + PROMISE_RESOLVE_ID, promise_method_definition(PROMISE_RESOLVE_ID), - promise_method_definition(PROMISE_TRY_ID), - TypeData::instance_of(TypeReference::from(GLOBAL_REGEXP_ID)), - TypeData::Class(Box::new(Class { - name: Some(Text::new_static("RegExp")), - type_parameters: Box::default(), - extends: None, - implements: [].into(), - members: Box::new([method("exec", REGEXP_EXEC_ID)]), - })), - regexp_method_definition(REGEXP_EXEC_ID), - string_literal("bigint"), - string_literal("boolean"), - string_literal("function"), - string_literal("number"), - string_literal("object"), - string_literal("string"), - string_literal("symbol"), - string_literal("undefined"), + ); + builder.set_type_data(PROMISE_TRY_ID, promise_method_definition(PROMISE_TRY_ID)); + // String literals for typeof operator + builder.set_type_data(BIGINT_STRING_LITERAL_ID, string_literal("bigint")); + builder.set_type_data(BOOLEAN_STRING_LITERAL_ID, string_literal("boolean")); + builder.set_type_data(FUNCTION_STRING_LITERAL_ID, string_literal("function")); + builder.set_type_data(NUMBER_STRING_LITERAL_ID, string_literal("number")); + builder.set_type_data(OBJECT_STRING_LITERAL_ID, string_literal("object")); + builder.set_type_data(STRING_STRING_LITERAL_ID, string_literal("string")); + builder.set_type_data(SYMBOL_STRING_LITERAL_ID, string_literal("symbol")); + builder.set_type_data(UNDEFINED_STRING_LITERAL_ID, string_literal("undefined")); + builder.set_type_data( + TYPEOF_OPERATOR_RETURN_UNION_ID, TypeData::Union(Box::new(Union(Box::new([ GLOBAL_BIGINT_STRING_LITERAL_ID.into(), GLOBAL_BOOLEAN_STRING_LITERAL_ID.into(), @@ -357,27 +315,41 @@ impl Default for GlobalsResolver { GLOBAL_SYMBOL_STRING_LITERAL_ID.into(), GLOBAL_UNDEFINED_STRING_LITERAL_ID.into(), ])))), + ); + // Generic type parameters + builder.set_type_data( + T_ID, TypeData::from(GenericTypeParameter { name: Text::new_static("T"), constraint: TypeReference::unknown(), default: TypeReference::unknown(), }), + ); + builder.set_type_data( + U_ID, TypeData::from(GenericTypeParameter { name: Text::new_static("U"), constraint: TypeReference::unknown(), default: TypeReference::unknown(), }), + ); + // Callback functions + builder.set_type_data( + CONDITIONAL_CALLBACK_ID, TypeData::from(Function { is_async: false, type_parameters: Default::default(), - name: Some(Text::new_static(global_type_name(CONDITIONAL_CALLBACK_ID))), + name: Some(Text::new_static(CONDITIONAL_CALLBACK_ID_NAME)), parameters: Default::default(), return_type: ReturnType::Type(GLOBAL_CONDITIONAL_ID.into()), }), + ); + builder.set_type_data( + MAP_CALLBACK_ID, TypeData::from(Function { is_async: false, type_parameters: Default::default(), - name: Some(Text::new_static(global_type_name(MAP_CALLBACK_ID))), + name: Some(Text::new_static(MAP_CALLBACK_ID_NAME)), parameters: [FunctionParameter::Pattern(PatternFunctionParameter { ty: GLOBAL_U_ID.into(), bindings: Default::default(), @@ -387,26 +359,54 @@ impl Default for GlobalsResolver { .into(), return_type: ReturnType::Type(GLOBAL_U_ID.into()), }), + ); + builder.set_type_data( + VOID_CALLBACK_ID, TypeData::from(Function { is_async: false, type_parameters: Default::default(), - name: Some(Text::new_static(global_type_name(VOID_CALLBACK_ID))), + name: Some(Text::new_static(VOID_CALLBACK_ID_NAME)), parameters: Default::default(), return_type: ReturnType::Type(GLOBAL_VOID_ID.into()), }), + ); + // Fetch function + builder.set_type_data( + FETCH_ID, TypeData::from(Function { is_async: false, type_parameters: Default::default(), - name: Some(Text::new_static(global_type_name(FETCH_ID))), + name: Some(Text::new_static(FETCH_ID_NAME)), parameters: Default::default(), return_type: ReturnType::Type(GLOBAL_INSTANCEOF_PROMISE_ID.into()), }), - ]; + ); + builder.set_type_data( + INSTANCEOF_REGEXP_ID, + TypeData::instance_of(TypeReference::from(GLOBAL_REGEXP_ID)), + ); + builder.set_type_data( + REGEXP_ID, + TypeData::Class(Box::new(Class { + name: Some(Text::new_static(REGEXP_ID_NAME)), + type_parameters: Box::default(), + extends: None, + implements: [].into(), + members: Box::new([method("exec", REGEXP_EXEC_ID)]), + })), + ); + builder.set_type_data( + REGEXP_EXEC_ID, + TypeData::from(Function { + is_async: false, + type_parameters: Default::default(), + name: Some(Text::new_static(REGEXP_EXEC_ID_NAME)), + parameters: Default::default(), + return_type: ReturnType::Type(GLOBAL_INSTANCEOF_REGEXP_ID.into()), + }), + ); - let types: Vec<_> = types.into_iter().map(Arc::new).collect(); - Self { - types: TypeStore::from_types(types), - } + builder.build() } } diff --git a/crates/biome_js_type_info/src/globals_builder.rs b/crates/biome_js_type_info/src/globals_builder.rs new file mode 100644 index 000000000000..b5db136658e9 --- /dev/null +++ b/crates/biome_js_type_info/src/globals_builder.rs @@ -0,0 +1,55 @@ +//! Builder for constructing GlobalsResolver with forward references support. + +use std::sync::Arc; + +use crate::{NUM_PREDEFINED_TYPES, TypeData, TypeId, TypeStore}; + +use super::globals::GlobalsResolver; + +/// Builder for constructing a GlobalsResolver +pub struct GlobalsResolverBuilder { + /// Types being built. None = reserved but not yet filled. + types: Vec>, +} + +impl GlobalsResolverBuilder { + pub fn with_capacity(capacity: usize) -> Self { + Self { + types: vec![None; capacity], + } + } + + /// Fill a previously reserved type slot with actual type data. + pub fn set_type_data(&mut self, id: TypeId, data: TypeData) { + let index = id.index(); + debug_assert!( + index < self.types.len(), + "TypeId {index} out of bounds (len: {})", + self.types.len() + ); + debug_assert!( + self.types[index].is_none(), + "Type at index {index} already set" + ); + self.types[index] = Some(data); + } + + /// Build the final GlobalsResolver. + pub fn build(self) -> GlobalsResolver { + let types: Vec> = self + .types + .into_iter() + .map(|opt| Arc::new(opt.unwrap_or(TypeData::Unknown))) + .collect(); + + GlobalsResolver { + types: TypeStore::from_types(types), + } + } +} + +impl Default for GlobalsResolverBuilder { + fn default() -> Self { + Self::with_capacity(NUM_PREDEFINED_TYPES) + } +} diff --git a/crates/biome_js_type_info/src/globals_ids.rs b/crates/biome_js_type_info/src/globals_ids.rs new file mode 100644 index 000000000000..993b34fcdf62 --- /dev/null +++ b/crates/biome_js_type_info/src/globals_ids.rs @@ -0,0 +1,116 @@ +//! Type ID constants for global types. +//! +//! TODO(tidefield): Implement a codegen for this file from TypeScript .d.ts files. + +// use crate::define_global_type; +use crate::{ResolvedTypeId, TypeId}; + +use super::globals::GLOBAL_LEVEL; + +// FIXME(tidefield): Get rid of the macro when implementing the codegen to improve compile time +// Right now, I'm preserving the names so that the snapshot tests don't break the snapshot tests +// to make sure I'm not breaking anything. +#[macro_export] +macro_rules! define_global_type { + ($name:ident, $index:expr, $name_str:expr) => { + pub const $name: TypeId = TypeId::new($index); + paste::paste! { + pub const [<$name _NAME>]: &str = $name_str; + } + }; +} +// define_global_type!(ARRAY_ID, 8, "Array"); // Creates ARRAY_ID and ARRAY_ID_NAME + +// Type ID constants with their names defined together +define_global_type!(UNKNOWN_ID, 0, "unknown"); +define_global_type!(UNDEFINED_ID, 1, "undefined"); +define_global_type!(VOID_ID, 2, "void"); +define_global_type!(CONDITIONAL_ID, 3, "conditional"); +define_global_type!(NUMBER_ID, 4, "number"); +define_global_type!(STRING_ID, 5, "string"); +define_global_type!(INSTANCEOF_ARRAY_T_ID, 6, "instanceof Array"); +define_global_type!(INSTANCEOF_ARRAY_U_ID, 7, "instanceof Array"); +define_global_type!(ARRAY_ID, 8, "Array"); +define_global_type!(ARRAY_FILTER_ID, 9, "Array.prototype.filter"); +define_global_type!(ARRAY_FOREACH_ID, 10, "Array.prototype.forEach"); +define_global_type!(ARRAY_MAP_ID, 11, "Array.prototype.map"); +define_global_type!(GLOBAL_ID, 12, "globalThis"); +define_global_type!(INSTANCEOF_PROMISE_ID, 13, "instanceof Promise"); +define_global_type!(PROMISE_ID, 14, "Promise"); +define_global_type!(PROMISE_CONSTRUCTOR_ID, 15, "Promise.constructor"); +define_global_type!(PROMISE_CATCH_ID, 16, "Promise.prototype.catch"); +define_global_type!(PROMISE_FINALLY_ID, 17, "Promise.prototype.finally"); +define_global_type!(PROMISE_THEN_ID, 18, "Promise.prototype.then"); +define_global_type!(PROMISE_ALL_ID, 19, "Promise.all"); +define_global_type!(PROMISE_ALL_SETTLED_ID, 20, "Promise.allSettled"); +define_global_type!(PROMISE_ANY_ID, 21, "Promise.any"); +define_global_type!(PROMISE_RACE_ID, 22, "Promise.race"); +define_global_type!(PROMISE_REJECT_ID, 23, "Promise.reject"); +define_global_type!(PROMISE_RESOLVE_ID, 24, "Promise.resolve"); +define_global_type!(PROMISE_TRY_ID, 25, "Promise.try"); +define_global_type!(BIGINT_STRING_LITERAL_ID, 26, "\"bigint\""); +define_global_type!(BOOLEAN_STRING_LITERAL_ID, 27, "\"boolean\""); +define_global_type!(FUNCTION_STRING_LITERAL_ID, 28, "\"function\""); +define_global_type!(NUMBER_STRING_LITERAL_ID, 29, "\"number\""); +define_global_type!(OBJECT_STRING_LITERAL_ID, 30, "\"object\""); +define_global_type!(STRING_STRING_LITERAL_ID, 31, "\"string\""); +define_global_type!(SYMBOL_STRING_LITERAL_ID, 32, "\"symbol\""); +define_global_type!(UNDEFINED_STRING_LITERAL_ID, 33, "\"undefined\""); +define_global_type!( + TYPEOF_OPERATOR_RETURN_UNION_ID, + 34, + "\"bigint\" | \"boolean\" | \"function\" | \"number\" | \"object\" | \"string\" | \"symbol\" | \"undefined\"" +); +define_global_type!(T_ID, 35, "T"); +define_global_type!(U_ID, 36, "U"); +define_global_type!(CONDITIONAL_CALLBACK_ID, 37, "() => conditional"); +define_global_type!(MAP_CALLBACK_ID, 38, "(item: T) => U"); +define_global_type!(VOID_CALLBACK_ID, 39, "() => void"); +define_global_type!(FETCH_ID, 40, "fetch"); +define_global_type!(INSTANCEOF_REGEXP_ID, 41, "instanceof RegExp"); +define_global_type!(REGEXP_ID, 42, "RegExp"); +define_global_type!(REGEXP_EXEC_ID, 43, "RegExp.exec"); + +/// Total number of predefined types. +/// Must be one more than the highest TypeId above. +pub const NUM_PREDEFINED_TYPES: usize = 44; + +// Resolved type ID constants (TypeId wrapped with GlobalLevel) +pub const GLOBAL_UNKNOWN_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, UNKNOWN_ID); +pub const GLOBAL_UNDEFINED_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, UNDEFINED_ID); +pub const GLOBAL_VOID_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, VOID_ID); +pub const GLOBAL_CONDITIONAL_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, CONDITIONAL_ID); +pub const GLOBAL_NUMBER_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, NUMBER_ID); +pub const GLOBAL_STRING_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, STRING_ID); +pub const GLOBAL_ARRAY_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, ARRAY_ID); +pub const GLOBAL_GLOBAL_ID /* :smirk: */: ResolvedTypeId = + ResolvedTypeId::new(GLOBAL_LEVEL, GLOBAL_ID); +pub const GLOBAL_INSTANCEOF_PROMISE_ID: ResolvedTypeId = + ResolvedTypeId::new(GLOBAL_LEVEL, INSTANCEOF_PROMISE_ID); +pub const GLOBAL_PROMISE_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, PROMISE_ID); +pub const GLOBAL_PROMISE_CONSTRUCTOR_ID: ResolvedTypeId = + ResolvedTypeId::new(GLOBAL_LEVEL, PROMISE_CONSTRUCTOR_ID); +pub const GLOBAL_BIGINT_STRING_LITERAL_ID: ResolvedTypeId = + ResolvedTypeId::new(GLOBAL_LEVEL, BIGINT_STRING_LITERAL_ID); +pub const GLOBAL_BOOLEAN_STRING_LITERAL_ID: ResolvedTypeId = + ResolvedTypeId::new(GLOBAL_LEVEL, BOOLEAN_STRING_LITERAL_ID); +pub const GLOBAL_FUNCTION_STRING_LITERAL_ID: ResolvedTypeId = + ResolvedTypeId::new(GLOBAL_LEVEL, FUNCTION_STRING_LITERAL_ID); +pub const GLOBAL_NUMBER_STRING_LITERAL_ID: ResolvedTypeId = + ResolvedTypeId::new(GLOBAL_LEVEL, NUMBER_STRING_LITERAL_ID); +pub const GLOBAL_OBJECT_STRING_LITERAL_ID: ResolvedTypeId = + ResolvedTypeId::new(GLOBAL_LEVEL, OBJECT_STRING_LITERAL_ID); +pub const GLOBAL_STRING_STRING_LITERAL_ID: ResolvedTypeId = + ResolvedTypeId::new(GLOBAL_LEVEL, STRING_STRING_LITERAL_ID); +pub const GLOBAL_SYMBOL_STRING_LITERAL_ID: ResolvedTypeId = + ResolvedTypeId::new(GLOBAL_LEVEL, SYMBOL_STRING_LITERAL_ID); +pub const GLOBAL_UNDEFINED_STRING_LITERAL_ID: ResolvedTypeId = + ResolvedTypeId::new(GLOBAL_LEVEL, UNDEFINED_STRING_LITERAL_ID); +pub const GLOBAL_TYPEOF_OPERATOR_RETURN_UNION_ID: ResolvedTypeId = + ResolvedTypeId::new(GLOBAL_LEVEL, TYPEOF_OPERATOR_RETURN_UNION_ID); +pub const GLOBAL_T_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, T_ID); +pub const GLOBAL_U_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, U_ID); +pub const GLOBAL_FETCH_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, FETCH_ID); +pub const GLOBAL_INSTANCEOF_REGEXP_ID: ResolvedTypeId = + ResolvedTypeId::new(GLOBAL_LEVEL, INSTANCEOF_REGEXP_ID); +pub const GLOBAL_REGEXP_ID: ResolvedTypeId = ResolvedTypeId::new(GLOBAL_LEVEL, REGEXP_ID); diff --git a/crates/biome_js_type_info/src/lib.rs b/crates/biome_js_type_info/src/lib.rs index 7f8406ae103a..efa3a5646df3 100644 --- a/crates/biome_js_type_info/src/lib.rs +++ b/crates/biome_js_type_info/src/lib.rs @@ -4,6 +4,8 @@ mod conditionals; mod flattening; mod format_type_info; mod globals; +mod globals_builder; +pub(crate) mod globals_ids; mod helpers; mod local_inference; mod resolver; @@ -13,7 +15,8 @@ mod type_store; pub use conditionals::*; pub use flattening::MAX_FLATTEN_DEPTH; -pub use globals::{GLOBAL_RESOLVER, GLOBAL_UNKNOWN_ID, GlobalsResolver, NUM_PREDEFINED_TYPES}; +pub use globals::{GLOBAL_RESOLVER, GlobalsResolver}; +pub use globals_ids::{GLOBAL_UNKNOWN_ID, NUM_PREDEFINED_TYPES}; pub use resolver::*; pub use r#type::Type; pub use type_data::*; diff --git a/crates/biome_js_type_info/src/resolver.rs b/crates/biome_js_type_info/src/resolver.rs index 161bb9f29f31..a21172b3ab94 100644 --- a/crates/biome_js_type_info/src/resolver.rs +++ b/crates/biome_js_type_info/src/resolver.rs @@ -34,8 +34,12 @@ pub struct ResolvedTypeId(ResolverId, TypeId); impl Debug for ResolvedTypeId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.0.level() == TypeResolverLevel::Global { - if self.1.index() < NUM_PREDEFINED_TYPES { - f.write_str(global_type_name(self.1)) + // GlobalsResolverBuilder makes sure the type store is fully filled. + // Every global TypeId whose index less than NUM_PREDEFINED_TYPES + // must have a name returned by global_type_name(). + // GLOBAL_TYPE_MEMBERS ensures this invariant. + if let Some(name) = global_type_name(self.1) { + f.write_str(name) } else { let id = self.1.index() - NUM_PREDEFINED_TYPES; f.write_fmt(format_args!("Global TypeId({id})"))