Skip to main content

react_compiler_hir/
object_shape.rs

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
11use std::collections::HashMap;
12
13use crate::type_config::{AliasingEffectConfig, AliasingSignatureConfig, ValueKind, ValueReason};
14use crate::Effect;
15use crate::Type;
16
17// =============================================================================
18// Shape ID constants (matching TS ObjectShape.ts)
19// =============================================================================
20
21pub const BUILT_IN_PROPS_ID: &str = "BuiltInProps";
22pub const BUILT_IN_ARRAY_ID: &str = "BuiltInArray";
23pub const BUILT_IN_SET_ID: &str = "BuiltInSet";
24pub const BUILT_IN_MAP_ID: &str = "BuiltInMap";
25pub const BUILT_IN_WEAK_SET_ID: &str = "BuiltInWeakSet";
26pub const BUILT_IN_WEAK_MAP_ID: &str = "BuiltInWeakMap";
27pub const BUILT_IN_FUNCTION_ID: &str = "BuiltInFunction";
28pub const BUILT_IN_JSX_ID: &str = "BuiltInJsx";
29pub const BUILT_IN_OBJECT_ID: &str = "BuiltInObject";
30pub const BUILT_IN_USE_STATE_ID: &str = "BuiltInUseState";
31pub const BUILT_IN_SET_STATE_ID: &str = "BuiltInSetState";
32pub const BUILT_IN_USE_ACTION_STATE_ID: &str = "BuiltInUseActionState";
33pub const BUILT_IN_SET_ACTION_STATE_ID: &str = "BuiltInSetActionState";
34pub const BUILT_IN_USE_REF_ID: &str = "BuiltInUseRefId";
35pub const BUILT_IN_REF_VALUE_ID: &str = "BuiltInRefValue";
36pub const BUILT_IN_MIXED_READONLY_ID: &str = "BuiltInMixedReadonly";
37pub const BUILT_IN_USE_EFFECT_HOOK_ID: &str = "BuiltInUseEffectHook";
38pub const BUILT_IN_USE_LAYOUT_EFFECT_HOOK_ID: &str = "BuiltInUseLayoutEffectHook";
39pub const BUILT_IN_USE_INSERTION_EFFECT_HOOK_ID: &str = "BuiltInUseInsertionEffectHook";
40pub const BUILT_IN_USE_OPERATOR_ID: &str = "BuiltInUseOperator";
41pub const BUILT_IN_USE_REDUCER_ID: &str = "BuiltInUseReducer";
42pub const BUILT_IN_DISPATCH_ID: &str = "BuiltInDispatch";
43pub const BUILT_IN_USE_CONTEXT_HOOK_ID: &str = "BuiltInUseContextHook";
44pub const BUILT_IN_USE_TRANSITION_ID: &str = "BuiltInUseTransition";
45pub const BUILT_IN_USE_OPTIMISTIC_ID: &str = "BuiltInUseOptimistic";
46pub const BUILT_IN_SET_OPTIMISTIC_ID: &str = "BuiltInSetOptimistic";
47pub const BUILT_IN_START_TRANSITION_ID: &str = "BuiltInStartTransition";
48pub const BUILT_IN_USE_EFFECT_EVENT_ID: &str = "BuiltInUseEffectEvent";
49pub const BUILT_IN_EFFECT_EVENT_ID: &str = "BuiltInEffectEventFunction";
50pub const REANIMATED_SHARED_VALUE_ID: &str = "ReanimatedSharedValueId";
51
52// =============================================================================
53// Core types
54// =============================================================================
55
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub 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
75impl 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)]
100pub 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)]
121pub struct ObjectShape {
122    pub properties: HashMap<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 HashMap, used during
130///   `build_builtin_shapes` / `build_default_globals` to construct the static base.
131/// - **Overlay mode** (`base=Some`): holds a `&'static HashMap` base plus a small
132///   extras HashMap. Lookups check extras first, then base. Inserts go into extras.
133///   Cloning only copies the extras map (the base pointer is shared).
134pub struct ShapeRegistry {
135    base: Option<&'static HashMap<String, ObjectShape>>,
136    entries: HashMap<String, ObjectShape>,
137}
138
139impl ShapeRegistry {
140    /// Create an empty builder-mode registry.
141    pub fn new() -> Self {
142        Self {
143            base: None,
144            entries: HashMap::new(),
145        }
146    }
147
148    /// Create an overlay-mode registry backed by a static base.
149    pub fn with_base(base: &'static HashMap<String, ObjectShape>) -> Self {
150        Self {
151            base: Some(base),
152            entries: HashMap::new(),
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 HashMap.
167    /// Only valid in builder mode (no base).
168    pub fn into_inner(self) -> HashMap<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
177impl 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.
192fn 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.
205pub 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.
243pub fn add_hook(
244    registry: &mut ShapeRegistry,
245    sig: HookSignatureBuilder,
246    id: Option<&str>,
247) -> Type {
248    let shape_id = id.map(|s| s.to_string()).unwrap_or_else(next_anon_id);
249    let return_type = sig.return_type.clone();
250    add_shape(
251        registry,
252        &shape_id,
253        Vec::new(),
254        Some(FunctionSignature {
255            positional_params: sig.positional_params,
256            rest_param: sig.rest_param,
257            return_type: sig.return_type,
258            return_value_kind: sig.return_value_kind,
259            return_value_reason: sig.return_value_reason,
260            callee_effect: sig.callee_effect,
261            hook_kind: Some(sig.hook_kind),
262            no_alias: sig.no_alias,
263            mutable_only_if_operands_are_mutable: false,
264            impure: false,
265            known_incompatible: sig.known_incompatible,
266            canonical_name: None,
267            aliasing: sig.aliasing,
268        }),
269    );
270    Type::Function {
271        shape_id: Some(shape_id),
272        return_type: Box::new(return_type),
273        is_constructor: false,
274    }
275}
276
277/// Add an object to a ShapeRegistry.
278/// Returns a `Type::Object` representing the added object.
279pub fn add_object(
280    registry: &mut ShapeRegistry,
281    id: Option<&str>,
282    properties: Vec<(String, Type)>,
283) -> Type {
284    let shape_id = id.map(|s| s.to_string()).unwrap_or_else(next_anon_id);
285    add_shape(registry, &shape_id, properties, None);
286    Type::Object {
287        shape_id: Some(shape_id),
288    }
289}
290
291fn add_shape(
292    registry: &mut ShapeRegistry,
293    id: &str,
294    properties: Vec<(String, Type)>,
295    function_type: Option<FunctionSignature>,
296) {
297    let shape = ObjectShape {
298        properties: properties.into_iter().collect(),
299        function_type,
300    };
301    // Note: TS has an invariant that the id doesn't already exist. We use
302    // insert which overwrites. In practice duplicates don't occur for built-in
303    // shapes, and for user configs we want last-write-wins behavior.
304    registry.insert(id.to_string(), shape);
305}
306
307// =============================================================================
308// Builder structs (to avoid large parameter lists)
309// =============================================================================
310
311/// Builder for non-hook function signatures.
312pub struct FunctionSignatureBuilder {
313    pub positional_params: Vec<Effect>,
314    pub rest_param: Option<Effect>,
315    pub return_type: Type,
316    pub return_value_kind: ValueKind,
317    pub return_value_reason: Option<ValueReason>,
318    pub callee_effect: Effect,
319    pub no_alias: bool,
320    pub mutable_only_if_operands_are_mutable: bool,
321    pub impure: bool,
322    pub known_incompatible: Option<String>,
323    pub canonical_name: Option<String>,
324    pub aliasing: Option<AliasingSignatureConfig>,
325}
326
327impl Default for FunctionSignatureBuilder {
328    fn default() -> Self {
329        Self {
330            positional_params: Vec::new(),
331            rest_param: None,
332            return_type: Type::Poly,
333            return_value_kind: ValueKind::Mutable,
334            return_value_reason: None,
335            callee_effect: Effect::Read,
336            no_alias: false,
337            mutable_only_if_operands_are_mutable: false,
338            impure: false,
339            known_incompatible: None,
340            canonical_name: None,
341            aliasing: None,
342        }
343    }
344}
345
346/// Builder for hook signatures.
347pub struct HookSignatureBuilder {
348    pub positional_params: Vec<Effect>,
349    pub rest_param: Option<Effect>,
350    pub return_type: Type,
351    pub return_value_kind: ValueKind,
352    pub return_value_reason: Option<ValueReason>,
353    pub callee_effect: Effect,
354    pub hook_kind: HookKind,
355    pub no_alias: bool,
356    pub known_incompatible: Option<String>,
357    pub aliasing: Option<AliasingSignatureConfig>,
358}
359
360impl Default for HookSignatureBuilder {
361    fn default() -> Self {
362        Self {
363            positional_params: Vec::new(),
364            rest_param: None,
365            return_type: Type::Poly,
366            return_value_kind: ValueKind::Frozen,
367            return_value_reason: None,
368            callee_effect: Effect::Read,
369            hook_kind: HookKind::Custom,
370            no_alias: false,
371            known_incompatible: None,
372            aliasing: None,
373        }
374    }
375}
376
377// =============================================================================
378// Default hook types used for unknown hooks
379// =============================================================================
380
381/// Default type for hooks when enableAssumeHooksFollowRulesOfReact is true.
382/// Matches TS `DefaultNonmutatingHook`.
383pub fn default_nonmutating_hook(registry: &mut ShapeRegistry) -> Type {
384    add_hook(
385        registry,
386        HookSignatureBuilder {
387            rest_param: Some(Effect::Freeze),
388            return_type: Type::Poly,
389            return_value_kind: ValueKind::Frozen,
390            hook_kind: HookKind::Custom,
391            aliasing: Some(AliasingSignatureConfig {
392                receiver: "@receiver".to_string(),
393                params: Vec::new(),
394                rest: Some("@rest".to_string()),
395                returns: "@returns".to_string(),
396                temporaries: Vec::new(),
397                effects: vec![
398                    // Freeze the arguments
399                    AliasingEffectConfig::Freeze {
400                        value: "@rest".to_string(),
401                        reason: ValueReason::HookCaptured,
402                    },
403                    // Returns a frozen value
404                    AliasingEffectConfig::Create {
405                        into: "@returns".to_string(),
406                        value: ValueKind::Frozen,
407                        reason: ValueReason::HookReturn,
408                    },
409                    // May alias any arguments into the return
410                    AliasingEffectConfig::Alias {
411                        from: "@rest".to_string(),
412                        into: "@returns".to_string(),
413                    },
414                ],
415            }),
416            ..Default::default()
417        },
418        Some("DefaultNonmutatingHook"),
419    )
420}
421
422/// Default type for hooks when enableAssumeHooksFollowRulesOfReact is false.
423/// Matches TS `DefaultMutatingHook`.
424pub fn default_mutating_hook(registry: &mut ShapeRegistry) -> Type {
425    add_hook(
426        registry,
427        HookSignatureBuilder {
428            rest_param: Some(Effect::ConditionallyMutate),
429            return_type: Type::Poly,
430            return_value_kind: ValueKind::Mutable,
431            hook_kind: HookKind::Custom,
432            ..Default::default()
433        },
434        Some("DefaultMutatingHook"),
435    )
436}