main
rs 432 lines 15.6 KB
Raw
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 }