Skip to main content

mago_codex/context/
mod.rs

1use mago_atom::Atom;
2
3use crate::identifier::function_like::FunctionLikeIdentifier;
4use crate::metadata::class_like::ClassLikeMetadata;
5use crate::metadata::function_like::FunctionLikeMetadata;
6use crate::metadata::property_hook::PropertyHookMetadata;
7use crate::reference::ReferenceSource;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub struct ScopeContext<'ctx> {
11    pub(crate) function_like: Option<&'ctx FunctionLikeMetadata>,
12    pub(crate) class_like: Option<&'ctx ClassLikeMetadata>,
13    pub(crate) property_hook: Option<(Atom, &'ctx PropertyHookMetadata)>,
14    pub(crate) is_static: bool,
15}
16
17impl Default for ScopeContext<'_> {
18    fn default() -> Self {
19        Self::new()
20    }
21}
22
23impl<'ctx> ScopeContext<'ctx> {
24    /// Creates a new `ScopeContext` representing a default global, static scope.
25    #[inline]
26    #[must_use]
27    pub fn new() -> Self {
28        Self { function_like: None, class_like: None, property_hook: None, is_static: true }
29    }
30
31    /// Returns whether the current scope is a global scope.
32    #[inline]
33    #[must_use]
34    pub const fn is_global(&self) -> bool {
35        self.function_like.is_none() && self.class_like.is_none()
36    }
37
38    /// Returns whether the current scope is pure.
39    #[inline]
40    #[must_use]
41    pub const fn is_pure(&self) -> bool {
42        if let Some(function_like) = self.function_like
43            && function_like.flags.is_pure()
44        {
45            return true;
46        }
47
48        false
49    }
50
51    /// Returns the calling class-like context, if available.
52    #[inline]
53    #[must_use]
54    pub fn get_class_like(&self) -> Option<&'ctx ClassLikeMetadata> {
55        self.class_like
56    }
57
58    /// Returns the calling class FQCN, if inside a class scope.
59    #[inline]
60    #[must_use]
61    pub fn get_class_like_name(&self) -> Option<Atom> {
62        self.class_like.map(|class| class.name)
63    }
64
65    /// Returns the calling function-like context, if available.
66    #[inline]
67    #[must_use]
68    pub fn get_function_like(&self) -> Option<&'ctx FunctionLikeMetadata> {
69        self.function_like
70    }
71
72    /// Returns the identifier of the calling function/method, if available.
73    #[inline]
74    #[must_use]
75    pub fn get_function_like_identifier(&self) -> Option<FunctionLikeIdentifier> {
76        let function_like = self.function_like?;
77
78        let Some(function_name) = function_like.name else {
79            return Some(FunctionLikeIdentifier::Closure(function_like.span.file_id, function_like.span.start));
80        };
81
82        Some(if function_like.get_kind().is_method() {
83            let Some(class_like) = self.class_like else {
84                return Some(FunctionLikeIdentifier::Function(function_name));
85            };
86
87            FunctionLikeIdentifier::Method(class_like.name, function_name)
88        } else {
89            FunctionLikeIdentifier::Function(function_name)
90        })
91    }
92
93    /// Checks if the calling class scope is marked as `final`.
94    #[inline]
95    #[must_use]
96    pub const fn is_class_like_final(&self) -> bool {
97        match self.class_like {
98            Some(class) => class.flags.is_final(),
99            None => false,
100        }
101    }
102
103    /// Checks if the calling scope is static.
104    #[inline]
105    #[must_use]
106    pub const fn is_static(&self) -> bool {
107        self.is_static
108    }
109
110    /// Sets the function-like metadata for the current scope.
111    #[inline]
112    pub fn set_function_like(&mut self, function_like: Option<&'ctx FunctionLikeMetadata>) {
113        self.function_like = function_like;
114    }
115
116    /// Sets the class-like metadata for the current scope.
117    #[inline]
118    pub fn set_class_like(&mut self, class_like: Option<&'ctx ClassLikeMetadata>) {
119        self.class_like = class_like;
120    }
121
122    /// Sets the static flag for the current scope.
123    #[inline]
124    pub fn set_static(&mut self, is_static: bool) {
125        self.is_static = is_static;
126    }
127
128    /// Returns the property hook context, if available.
129    ///
130    /// Returns a tuple of (`property_name`, `hook_metadata`) when analyzing a property hook body.
131    #[inline]
132    #[must_use]
133    pub fn get_property_hook(&self) -> Option<(Atom, &'ctx PropertyHookMetadata)> {
134        self.property_hook
135    }
136
137    /// Sets the property hook context for the current scope.
138    ///
139    /// Used when analyzing property hook bodies to enable proper return type validation.
140    #[inline]
141    pub fn set_property_hook(&mut self, property_hook: Option<(Atom, &'ctx PropertyHookMetadata)>) {
142        self.property_hook = property_hook;
143    }
144
145    /// Determines the `ReferenceSource` (symbol or member) based on the current function context.
146    /// Used to identify the origin of a code reference for dependency tracking.
147    #[inline]
148    #[must_use]
149    pub fn get_reference_source(&self) -> Option<ReferenceSource> {
150        if let Some(calling_functionlike_id) = self.get_function_like_identifier() {
151            match calling_functionlike_id {
152                FunctionLikeIdentifier::Function(name) => Some(ReferenceSource::Symbol(false, name)),
153                FunctionLikeIdentifier::Method(class_name, method_name) => {
154                    Some(ReferenceSource::ClassLikeMember(false, class_name, method_name))
155                }
156                _ => None,
157            }
158        } else {
159            None
160        }
161    }
162}