| 1 | // Copyright (c) Meta Platforms, Inc. and affiliates. |
| 2 | // |
| 3 | // This source code is licensed under the MIT license found in the |
| 4 | // LICENSE file in the root directory of this source tree. |
| 5 | |
| 6 | //! Object shapes and function signatures, ported from ObjectShape.ts. |
| 7 | //! |
| 8 | //! Defines the shape registry used by Environment to resolve property types |
| 9 | //! and function call signatures for built-in objects, hooks, and user-defined types. |
| 10 | |
| 11 | use rustc_hash::FxHashMap; |
| 12 | |
| 13 | use crate::Effect; |
| 14 | use crate::Type; |
| 15 | use crate::type_config::{AliasingEffectConfig, AliasingSignatureConfig, ValueKind, ValueReason}; |
| 16 | |
| 17 | // ============================================================================= |
| 18 | // Shape ID constants (matching TS ObjectShape.ts) |
| 19 | // ============================================================================= |
| 20 | |
| 21 | pub const BUILT_IN_PROPS_ID: &str = "BuiltInProps"; |
| 22 | pub const BUILT_IN_ARRAY_ID: &str = "BuiltInArray"; |
| 23 | pub const BUILT_IN_SET_ID: &str = "BuiltInSet"; |
| 24 | pub const BUILT_IN_MAP_ID: &str = "BuiltInMap"; |
| 25 | pub const BUILT_IN_WEAK_SET_ID: &str = "BuiltInWeakSet"; |
| 26 | pub const BUILT_IN_WEAK_MAP_ID: &str = "BuiltInWeakMap"; |
| 27 | pub const BUILT_IN_FUNCTION_ID: &str = "BuiltInFunction"; |
| 28 | pub const BUILT_IN_JSX_ID: &str = "BuiltInJsx"; |
| 29 | pub const BUILT_IN_OBJECT_ID: &str = "BuiltInObject"; |
| 30 | pub const BUILT_IN_USE_STATE_ID: &str = "BuiltInUseState"; |
| 31 | pub const BUILT_IN_SET_STATE_ID: &str = "BuiltInSetState"; |
| 32 | pub const BUILT_IN_USE_ACTION_STATE_ID: &str = "BuiltInUseActionState"; |
| 33 | pub const BUILT_IN_SET_ACTION_STATE_ID: &str = "BuiltInSetActionState"; |
| 34 | pub const BUILT_IN_USE_REF_ID: &str = "BuiltInUseRefId"; |
| 35 | pub const BUILT_IN_REF_VALUE_ID: &str = "BuiltInRefValue"; |
| 36 | pub const BUILT_IN_MIXED_READONLY_ID: &str = "BuiltInMixedReadonly"; |
| 37 | pub const BUILT_IN_USE_EFFECT_HOOK_ID: &str = "BuiltInUseEffectHook"; |
| 38 | pub const BUILT_IN_USE_LAYOUT_EFFECT_HOOK_ID: &str = "BuiltInUseLayoutEffectHook"; |
| 39 | pub const BUILT_IN_USE_INSERTION_EFFECT_HOOK_ID: &str = "BuiltInUseInsertionEffectHook"; |
| 40 | pub const BUILT_IN_USE_OPERATOR_ID: &str = "BuiltInUseOperator"; |
| 41 | pub const BUILT_IN_USE_REDUCER_ID: &str = "BuiltInUseReducer"; |
| 42 | pub const BUILT_IN_DISPATCH_ID: &str = "BuiltInDispatch"; |
| 43 | pub const BUILT_IN_USE_CONTEXT_HOOK_ID: &str = "BuiltInUseContextHook"; |
| 44 | pub const BUILT_IN_USE_TRANSITION_ID: &str = "BuiltInUseTransition"; |
| 45 | pub const BUILT_IN_USE_OPTIMISTIC_ID: &str = "BuiltInUseOptimistic"; |
| 46 | pub const BUILT_IN_SET_OPTIMISTIC_ID: &str = "BuiltInSetOptimistic"; |
| 47 | pub const BUILT_IN_START_TRANSITION_ID: &str = "BuiltInStartTransition"; |
| 48 | pub const BUILT_IN_USE_EFFECT_EVENT_ID: &str = "BuiltInUseEffectEvent"; |
| 49 | pub const BUILT_IN_EFFECT_EVENT_ID: &str = "BuiltInEffectEventFunction"; |
| 50 | pub const REANIMATED_SHARED_VALUE_ID: &str = "ReanimatedSharedValueId"; |
| 51 | |
| 52 | // ============================================================================= |
| 53 | // Core types |
| 54 | // ============================================================================= |
| 55 | |
| 56 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 57 | pub enum HookKind { |
| 58 | UseContext, |
| 59 | UseState, |
| 60 | UseActionState, |
| 61 | UseReducer, |
| 62 | UseRef, |
| 63 | UseEffect, |
| 64 | UseLayoutEffect, |
| 65 | UseInsertionEffect, |
| 66 | UseMemo, |
| 67 | UseCallback, |
| 68 | UseTransition, |
| 69 | UseImperativeHandle, |
| 70 | UseEffectEvent, |
| 71 | UseOptimistic, |
| 72 | Custom, |
| 73 | } |
| 74 | |
| 75 | impl std::fmt::Display for HookKind { |
| 76 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 77 | match self { |
| 78 | HookKind::UseContext => write!(f, "useContext"), |
| 79 | HookKind::UseState => write!(f, "useState"), |
| 80 | HookKind::UseActionState => write!(f, "useActionState"), |
| 81 | HookKind::UseReducer => write!(f, "useReducer"), |
| 82 | HookKind::UseRef => write!(f, "useRef"), |
| 83 | HookKind::UseEffect => write!(f, "useEffect"), |
| 84 | HookKind::UseLayoutEffect => write!(f, "useLayoutEffect"), |
| 85 | HookKind::UseInsertionEffect => write!(f, "useInsertionEffect"), |
| 86 | HookKind::UseMemo => write!(f, "useMemo"), |
| 87 | HookKind::UseCallback => write!(f, "useCallback"), |
| 88 | HookKind::UseTransition => write!(f, "useTransition"), |
| 89 | HookKind::UseImperativeHandle => write!(f, "useImperativeHandle"), |
| 90 | HookKind::UseEffectEvent => write!(f, "useEffectEvent"), |
| 91 | HookKind::UseOptimistic => write!(f, "useOptimistic"), |
| 92 | HookKind::Custom => write!(f, "Custom"), |
| 93 | } |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | /// Call signature of a function, used for type and effect inference. |
| 98 | /// Ported from TS `FunctionSignature`. |
| 99 | #[derive(Debug, Clone)] |
| 100 | pub struct FunctionSignature { |
| 101 | pub positional_params: Vec<Effect>, |
| 102 | pub rest_param: Option<Effect>, |
| 103 | pub return_type: Type, |
| 104 | pub return_value_kind: ValueKind, |
| 105 | pub return_value_reason: Option<ValueReason>, |
| 106 | pub callee_effect: Effect, |
| 107 | pub hook_kind: Option<HookKind>, |
| 108 | pub no_alias: bool, |
| 109 | pub mutable_only_if_operands_are_mutable: bool, |
| 110 | pub impure: bool, |
| 111 | pub known_incompatible: Option<String>, |
| 112 | pub canonical_name: Option<String>, |
| 113 | /// Aliasing signature in config form. Full parsing into AliasingSignature |
| 114 | /// with Place values is deferred until the aliasing effects system is ported. |
| 115 | pub aliasing: Option<AliasingSignatureConfig>, |
| 116 | } |
| 117 | |
| 118 | /// Shape of an object or function type. |
| 119 | /// Ported from TS `ObjectShape`. |
| 120 | #[derive(Debug, Clone)] |
| 121 | pub struct ObjectShape { |
| 122 | pub properties: FxHashMap<String, Type>, |
| 123 | pub function_type: Option<FunctionSignature>, |
| 124 | } |
| 125 | |
| 126 | /// Registry mapping shape IDs to their ObjectShape definitions. |
| 127 | /// |
| 128 | /// Supports two modes: |
| 129 | /// - **Builder mode** (`base=None`): wraps a single FxHashMap, used during |
| 130 | /// `build_builtin_shapes` / `build_default_globals` to construct the static base. |
| 131 | /// - **Overlay mode** (`base=Some`): holds a `&'static FxHashMap` base plus a small |
| 132 | /// extras FxHashMap. Lookups check extras first, then base. Inserts go into extras. |
| 133 | /// Cloning only copies the extras map (the base pointer is shared). |
| 134 | pub struct ShapeRegistry { |
| 135 | base: Option<&'static FxHashMap<String, ObjectShape>>, |
| 136 | entries: FxHashMap<String, ObjectShape>, |
| 137 | } |
| 138 | |
| 139 | impl ShapeRegistry { |
| 140 | /// Create an empty builder-mode registry. |
| 141 | pub fn new() -> Self { |
| 142 | Self { |
| 143 | base: None, |
| 144 | entries: FxHashMap::default(), |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | /// Create an overlay-mode registry backed by a static base. |
| 149 | pub fn with_base(base: &'static FxHashMap<String, ObjectShape>) -> Self { |
| 150 | Self { |
| 151 | base: Some(base), |
| 152 | entries: FxHashMap::default(), |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | pub fn get(&self, key: &str) -> Option<&ObjectShape> { |
| 157 | self.entries |
| 158 | .get(key) |
| 159 | .or_else(|| self.base.and_then(|b| b.get(key))) |
| 160 | } |
| 161 | |
| 162 | pub fn insert(&mut self, key: String, value: ObjectShape) { |
| 163 | self.entries.insert(key, value); |
| 164 | } |
| 165 | |
| 166 | /// Consume the registry and return the inner FxHashMap. |
| 167 | /// Only valid in builder mode (no base). |
| 168 | pub fn into_inner(self) -> FxHashMap<String, ObjectShape> { |
| 169 | debug_assert!( |
| 170 | self.base.is_none(), |
| 171 | "into_inner() called on overlay-mode ShapeRegistry" |
| 172 | ); |
| 173 | self.entries |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | impl Clone for ShapeRegistry { |
| 178 | fn clone(&self) -> Self { |
| 179 | Self { |
| 180 | base: self.base, |
| 181 | entries: self.entries.clone(), |
| 182 | } |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | // ============================================================================= |
| 187 | // Counter for anonymous shape IDs |
| 188 | // ============================================================================= |
| 189 | |
| 190 | /// Thread-local counter for generating unique anonymous shape IDs. |
| 191 | /// Mirrors TS `nextAnonId` in ObjectShape.ts. |
| 192 | fn next_anon_id() -> String { |
| 193 | use std::sync::atomic::{AtomicU32, Ordering}; |
| 194 | static COUNTER: AtomicU32 = AtomicU32::new(0); |
| 195 | let id = COUNTER.fetch_add(1, Ordering::Relaxed); |
| 196 | format!("<generated_{}>", id) |
| 197 | } |
| 198 | |
| 199 | // ============================================================================= |
| 200 | // Builder functions (matching TS addFunction, addHook, addObject) |
| 201 | // ============================================================================= |
| 202 | |
| 203 | /// Add a non-hook function to a ShapeRegistry. |
| 204 | /// Returns a `Type::Function` representing the added function. |
| 205 | pub fn add_function( |
| 206 | registry: &mut ShapeRegistry, |
| 207 | properties: Vec<(String, Type)>, |
| 208 | sig: FunctionSignatureBuilder, |
| 209 | id: Option<&str>, |
| 210 | is_constructor: bool, |
| 211 | ) -> Type { |
| 212 | let shape_id = id.map(|s| s.to_string()).unwrap_or_else(next_anon_id); |
| 213 | let return_type = sig.return_type.clone(); |
| 214 | add_shape( |
| 215 | registry, |
| 216 | &shape_id, |
| 217 | properties, |
| 218 | Some(FunctionSignature { |
| 219 | positional_params: sig.positional_params, |
| 220 | rest_param: sig.rest_param, |
| 221 | return_type: sig.return_type, |
| 222 | return_value_kind: sig.return_value_kind, |
| 223 | return_value_reason: sig.return_value_reason, |
| 224 | callee_effect: sig.callee_effect, |
| 225 | hook_kind: None, |
| 226 | no_alias: sig.no_alias, |
| 227 | mutable_only_if_operands_are_mutable: sig.mutable_only_if_operands_are_mutable, |
| 228 | impure: sig.impure, |
| 229 | known_incompatible: sig.known_incompatible, |
| 230 | canonical_name: sig.canonical_name, |
| 231 | aliasing: sig.aliasing, |
| 232 | }), |
| 233 | ); |
| 234 | Type::Function { |
| 235 | shape_id: Some(shape_id), |
| 236 | return_type: Box::new(return_type), |
| 237 | is_constructor, |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | /// Add a hook to a ShapeRegistry. |
| 242 | /// Returns a `Type::Function` representing the added hook. |
| 243 | pub fn add_hook(registry: &mut ShapeRegistry, sig: HookSignatureBuilder, id: Option<&str>) -> Type { |
| 244 | let shape_id = id.map(|s| s.to_string()).unwrap_or_else(next_anon_id); |
| 245 | let return_type = sig.return_type.clone(); |
| 246 | add_shape( |
| 247 | registry, |
| 248 | &shape_id, |
| 249 | Vec::new(), |
| 250 | Some(FunctionSignature { |
| 251 | positional_params: sig.positional_params, |
| 252 | rest_param: sig.rest_param, |
| 253 | return_type: sig.return_type, |
| 254 | return_value_kind: sig.return_value_kind, |
| 255 | return_value_reason: sig.return_value_reason, |
| 256 | callee_effect: sig.callee_effect, |
| 257 | hook_kind: Some(sig.hook_kind), |
| 258 | no_alias: sig.no_alias, |
| 259 | mutable_only_if_operands_are_mutable: false, |
| 260 | impure: false, |
| 261 | known_incompatible: sig.known_incompatible, |
| 262 | canonical_name: None, |
| 263 | aliasing: sig.aliasing, |
| 264 | }), |
| 265 | ); |
| 266 | Type::Function { |
| 267 | shape_id: Some(shape_id), |
| 268 | return_type: Box::new(return_type), |
| 269 | is_constructor: false, |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | /// Add an object to a ShapeRegistry. |
| 274 | /// Returns a `Type::Object` representing the added object. |
| 275 | pub fn add_object( |
| 276 | registry: &mut ShapeRegistry, |
| 277 | id: Option<&str>, |
| 278 | properties: Vec<(String, Type)>, |
| 279 | ) -> Type { |
| 280 | let shape_id = id.map(|s| s.to_string()).unwrap_or_else(next_anon_id); |
| 281 | add_shape(registry, &shape_id, properties, None); |
| 282 | Type::Object { |
| 283 | shape_id: Some(shape_id), |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | fn add_shape( |
| 288 | registry: &mut ShapeRegistry, |
| 289 | id: &str, |
| 290 | properties: Vec<(String, Type)>, |
| 291 | function_type: Option<FunctionSignature>, |
| 292 | ) { |
| 293 | let shape = ObjectShape { |
| 294 | properties: properties.into_iter().collect(), |
| 295 | function_type, |
| 296 | }; |
| 297 | // Note: TS has an invariant that the id doesn't already exist. We use |
| 298 | // insert which overwrites. In practice duplicates don't occur for built-in |
| 299 | // shapes, and for user configs we want last-write-wins behavior. |
| 300 | registry.insert(id.to_string(), shape); |
| 301 | } |
| 302 | |
| 303 | // ============================================================================= |
| 304 | // Builder structs (to avoid large parameter lists) |
| 305 | // ============================================================================= |
| 306 | |
| 307 | /// Builder for non-hook function signatures. |
| 308 | pub struct FunctionSignatureBuilder { |
| 309 | pub positional_params: Vec<Effect>, |
| 310 | pub rest_param: Option<Effect>, |
| 311 | pub return_type: Type, |
| 312 | pub return_value_kind: ValueKind, |
| 313 | pub return_value_reason: Option<ValueReason>, |
| 314 | pub callee_effect: Effect, |
| 315 | pub no_alias: bool, |
| 316 | pub mutable_only_if_operands_are_mutable: bool, |
| 317 | pub impure: bool, |
| 318 | pub known_incompatible: Option<String>, |
| 319 | pub canonical_name: Option<String>, |
| 320 | pub aliasing: Option<AliasingSignatureConfig>, |
| 321 | } |
| 322 | |
| 323 | impl Default for FunctionSignatureBuilder { |
| 324 | fn default() -> Self { |
| 325 | Self { |
| 326 | positional_params: Vec::new(), |
| 327 | rest_param: None, |
| 328 | return_type: Type::Poly, |
| 329 | return_value_kind: ValueKind::Mutable, |
| 330 | return_value_reason: None, |
| 331 | callee_effect: Effect::Read, |
| 332 | no_alias: false, |
| 333 | mutable_only_if_operands_are_mutable: false, |
| 334 | impure: false, |
| 335 | known_incompatible: None, |
| 336 | canonical_name: None, |
| 337 | aliasing: None, |
| 338 | } |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | /// Builder for hook signatures. |
| 343 | pub struct HookSignatureBuilder { |
| 344 | pub positional_params: Vec<Effect>, |
| 345 | pub rest_param: Option<Effect>, |
| 346 | pub return_type: Type, |
| 347 | pub return_value_kind: ValueKind, |
| 348 | pub return_value_reason: Option<ValueReason>, |
| 349 | pub callee_effect: Effect, |
| 350 | pub hook_kind: HookKind, |
| 351 | pub no_alias: bool, |
| 352 | pub known_incompatible: Option<String>, |
| 353 | pub aliasing: Option<AliasingSignatureConfig>, |
| 354 | } |
| 355 | |
| 356 | impl Default for HookSignatureBuilder { |
| 357 | fn default() -> Self { |
| 358 | Self { |
| 359 | positional_params: Vec::new(), |
| 360 | rest_param: None, |
| 361 | return_type: Type::Poly, |
| 362 | return_value_kind: ValueKind::Frozen, |
| 363 | return_value_reason: None, |
| 364 | callee_effect: Effect::Read, |
| 365 | hook_kind: HookKind::Custom, |
| 366 | no_alias: false, |
| 367 | known_incompatible: None, |
| 368 | aliasing: None, |
| 369 | } |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | // ============================================================================= |
| 374 | // Default hook types used for unknown hooks |
| 375 | // ============================================================================= |
| 376 | |
| 377 | /// Default type for hooks when enableAssumeHooksFollowRulesOfReact is true. |
| 378 | /// Matches TS `DefaultNonmutatingHook`. |
| 379 | pub fn default_nonmutating_hook(registry: &mut ShapeRegistry) -> Type { |
| 380 | add_hook( |
| 381 | registry, |
| 382 | HookSignatureBuilder { |
| 383 | rest_param: Some(Effect::Freeze), |
| 384 | return_type: Type::Poly, |
| 385 | return_value_kind: ValueKind::Frozen, |
| 386 | hook_kind: HookKind::Custom, |
| 387 | aliasing: Some(AliasingSignatureConfig { |
| 388 | receiver: "@receiver".to_string(), |
| 389 | params: Vec::new(), |
| 390 | rest: Some("@rest".to_string()), |
| 391 | returns: "@returns".to_string(), |
| 392 | temporaries: Vec::new(), |
| 393 | effects: vec![ |
| 394 | // Freeze the arguments |
| 395 | AliasingEffectConfig::Freeze { |
| 396 | value: "@rest".to_string(), |
| 397 | reason: ValueReason::HookCaptured, |
| 398 | }, |
| 399 | // Returns a frozen value |
| 400 | AliasingEffectConfig::Create { |
| 401 | into: "@returns".to_string(), |
| 402 | value: ValueKind::Frozen, |
| 403 | reason: ValueReason::HookReturn, |
| 404 | }, |
| 405 | // May alias any arguments into the return |
| 406 | AliasingEffectConfig::Alias { |
| 407 | from: "@rest".to_string(), |
| 408 | into: "@returns".to_string(), |
| 409 | }, |
| 410 | ], |
| 411 | }), |
| 412 | ..Default::default() |
| 413 | }, |
| 414 | Some("DefaultNonmutatingHook"), |
| 415 | ) |
| 416 | } |
| 417 | |
| 418 | /// Default type for hooks when enableAssumeHooksFollowRulesOfReact is false. |
| 419 | /// Matches TS `DefaultMutatingHook`. |
| 420 | pub fn default_mutating_hook(registry: &mut ShapeRegistry) -> Type { |
| 421 | add_hook( |
| 422 | registry, |
| 423 | HookSignatureBuilder { |
| 424 | rest_param: Some(Effect::ConditionallyMutate), |
| 425 | return_type: Type::Poly, |
| 426 | return_value_kind: ValueKind::Mutable, |
| 427 | hook_kind: HookKind::Custom, |
| 428 | ..Default::default() |
| 429 | }, |
| 430 | Some("DefaultMutatingHook"), |
| 431 | ) |
| 432 | } |