mago_codex/context/
mod.rs

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