mago_codex/context/
mod.rs

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