Skip to main content

mago_codex/context/
mod.rs

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