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 rustc_hash::FxHashMap;
12
13use crate::Effect;
14use crate::Type;
15use crate::type_config::{AliasingEffectConfig, AliasingSignatureConfig, ValueKind, ValueReason};
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: 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).
134pub struct ShapeRegistry {
135    base: Option<&'static FxHashMap<String, ObjectShape>>,
136    entries: FxHashMap<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: 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
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(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.
275pub 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
287fn 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.
308pub 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
323impl 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.
343pub 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
356impl 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`.
379pub 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`.
420pub 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}