Skip to main content

mago_codex/metadata/
function_like.rs

1use std::collections::BTreeMap;
2
3use serde::Deserialize;
4use serde::Serialize;
5
6use mago_atom::Atom;
7use mago_atom::AtomMap;
8use mago_atom::AtomSet;
9use mago_reporting::Issue;
10use mago_span::Span;
11
12use crate::assertion::Assertion;
13use crate::metadata::attribute::AttributeMetadata;
14use crate::metadata::class_like::TemplateTypes;
15use crate::metadata::flags::MetadataFlags;
16use crate::metadata::parameter::FunctionLikeParameterMetadata;
17use crate::metadata::ttype::TypeMetadata;
18use crate::ttype::resolution::TypeResolutionContext;
19use crate::ttype::template::GenericTemplate;
20use crate::visibility::Visibility;
21
22/// Contains metadata specific to methods defined within classes, interfaces, enums, or traits.
23///
24/// This complements the more general `FunctionLikeMetadata`.
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
26#[non_exhaustive]
27pub struct MethodMetadata {
28    /// Marks whether this method is declared as `final`, preventing further overriding.
29    pub is_final: bool,
30
31    /// Marks whether this method is declared as `abstract`, requiring implementation in subclasses.
32    pub is_abstract: bool,
33
34    /// Marks whether this method is declared as `static`, allowing it to be called without an instance.
35    pub is_static: bool,
36
37    /// Marks whether this method is a constructor (`__construct`).
38    pub is_constructor: bool,
39
40    /// Marks whether this method is declared as `public`, `protected`, or `private`.
41    pub visibility: Visibility,
42
43    /// A map of constraints defined by `@where` docblock tags.
44    ///
45    /// The key is the name of a class-level template parameter (e.g., `T`), and the value
46    /// is the `TUnion` type constraint that `T` must satisfy for this specific method
47    /// to be considered callable.
48    pub where_constraints: AtomMap<TypeMetadata>,
49}
50
51/// Distinguishes between different kinds of callable constructs in PHP.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
53pub enum FunctionLikeKind {
54    /// Represents a standard function declared in the global scope or a namespace (`function foo() {}`).
55    Function,
56    /// Represents a method defined within a class, trait, enum, or interface (`class C { function bar() {} }`).
57    Method,
58    /// Represents an anonymous function created using `function() {}`.
59    Closure,
60    /// Represents an arrow function (short closure syntax) introduced in PHP 7.4 (`fn() => ...`).
61    ArrowFunction,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65pub struct FunctionLikeMetadata {
66    /// The kind of function-like structure this metadata represents.
67    pub kind: FunctionLikeKind,
68
69    /// The source code location (span) covering the entire function/method/closure definition.
70    /// For closures/arrow functions, this covers the `function(...) { ... }` or `fn(...) => ...` part.
71    pub span: Span,
72
73    /// The name of the function or method, lowercased, if applicable.
74    /// `None` for closures and arrow functions unless assigned to a variable later.
75    /// Example: `processRequest`, `__construct`, `my_global_func`.
76    pub name: Option<Atom>,
77
78    /// The original name of the function or method, in its original case.
79    pub original_name: Option<Atom>,
80
81    /// The specific source code location (span) of the function or method name identifier.
82    /// `None` if the function/method has no name (closures/arrow functions).
83    pub name_span: Option<Span>,
84
85    /// Ordered list of metadata for each parameter defined in the signature.
86    pub parameters: Vec<FunctionLikeParameterMetadata>,
87
88    /// The explicit return type declaration (type hint).
89    ///
90    /// Example: For `function getName(): string`, this holds metadata for `string`.
91    /// `None` if no return type is specified.
92    pub return_type_declaration_metadata: Option<TypeMetadata>,
93
94    /// The explicit return type declaration (type hint) or docblock type (`@return`).
95    ///
96    /// Example: For `function getName(): string`, this holds metadata for `string`,
97    /// or for ` /** @return string */ function getName() { .. }`, this holds metadata for `string`.
98    /// `None` if neither is specified.
99    pub return_type_metadata: Option<TypeMetadata>,
100
101    /// Generic type parameters (templates) defined for the function/method (e.g., `@template T`).
102    /// Stores the template name and its constraint (defining entity and bound type).
103    /// Example: `{ "T" => (GenericParent::FunctionLike(("funcName", "")), TUnion::object()) }`
104    pub template_types: TemplateTypes,
105
106    /// Attributes attached to the function/method/closure declaration (`#[Attribute] function foo() {}`).
107    pub attributes: Vec<AttributeMetadata>,
108
109    /// Specific metadata relevant only to methods (visibility, final, static, etc.).
110    /// This is `Some` if `kind` is `FunctionLikeKind::Method`, `None` otherwise.
111    pub method_metadata: Option<MethodMetadata>,
112
113    /// Contains context information needed for resolving types within this function's scope
114    /// (e.g., `use` statements, current namespace, class context). Often populated during analysis.
115    pub type_resolution_context: Option<TypeResolutionContext>,
116
117    /// A list of types that this function/method might throw, derived from `@throws` docblock tags
118    /// or inferred from `throw` statements within the body.
119    pub thrown_types: Vec<TypeMetadata>,
120
121    /// List of issues specifically related to parsing or interpreting this function's docblock.
122    pub issues: Vec<Issue>,
123
124    /// Assertions about parameter types or variable types that are guaranteed to be true
125    /// *after* this function/method returns normally. From `@psalm-assert`, `@phpstan-assert`, etc.
126    /// Maps variable/parameter name to a list of type assertions.
127    pub assertions: BTreeMap<Atom, Vec<Assertion>>,
128
129    /// Assertions about parameter/variable types that are guaranteed to be true if this
130    /// function/method returns `true`. From `@psalm-assert-if-true`, etc.
131    pub if_true_assertions: BTreeMap<Atom, Vec<Assertion>>,
132
133    /// Assertions about parameter/variable types that are guaranteed to be true if this
134    /// function/method returns `false`. From `@psalm-assert-if-false`, etc.
135    pub if_false_assertions: BTreeMap<Atom, Vec<Assertion>>,
136
137    /// Names of variables this function/method imports from the global scope via a
138    /// `global $x;` statement anywhere in its body. Used by the invocation post-processor
139    /// to invalidate caller-side narrowings of those globals on every call, since the
140    /// callee can reassign them behind the caller's back.
141    pub globals_accessed: AtomSet,
142
143    /// Tracks whether this function/method has a docblock comment.
144    /// Used to determine if docblock inheritance should occur implicitly.
145    pub has_docblock: bool,
146
147    pub flags: MetadataFlags,
148}
149
150impl FunctionLikeKind {
151    /// Checks if this kind represents a class/trait/enum/interface method.
152    #[inline]
153    #[must_use]
154    pub const fn is_method(&self) -> bool {
155        matches!(self, Self::Method)
156    }
157
158    /// Checks if this kind represents a globally/namespace-scoped function.
159    #[inline]
160    #[must_use]
161    pub const fn is_function(&self) -> bool {
162        matches!(self, Self::Function)
163    }
164
165    /// Checks if this kind represents an anonymous function (`function() {}`).
166    #[inline]
167    #[must_use]
168    pub const fn is_closure(&self) -> bool {
169        matches!(self, Self::Closure)
170    }
171
172    /// Checks if this kind represents an arrow function (`fn() => ...`).
173    #[inline]
174    #[must_use]
175    pub const fn is_arrow_function(&self) -> bool {
176        matches!(self, Self::ArrowFunction)
177    }
178}
179
180/// Contains comprehensive metadata for any function-like structure in PHP.
181impl FunctionLikeMetadata {
182    /// Creates new `FunctionLikeMetadata` with basic information and default flags.
183    #[must_use]
184    pub fn new(kind: FunctionLikeKind, span: Span, flags: MetadataFlags) -> Self {
185        let method_metadata = if kind.is_method() { Some(MethodMetadata::default()) } else { None };
186
187        Self {
188            kind,
189            span,
190            flags,
191            name: None,
192            original_name: None,
193            name_span: None,
194            parameters: vec![],
195            return_type_declaration_metadata: None,
196            return_type_metadata: None,
197            template_types: TemplateTypes::default(),
198            attributes: vec![],
199            method_metadata,
200            type_resolution_context: None,
201            thrown_types: vec![],
202            assertions: BTreeMap::new(),
203            if_true_assertions: BTreeMap::new(),
204            if_false_assertions: BTreeMap::new(),
205            globals_accessed: AtomSet::default(),
206            has_docblock: false,
207            issues: vec![],
208        }
209    }
210
211    /// Returns the kind of function-like (Function, Method, Closure, `ArrowFunction`).
212    #[inline]
213    #[must_use]
214    pub fn get_kind(&self) -> FunctionLikeKind {
215        self.kind
216    }
217
218    /// Returns a mutable slice of the parameter metadata.
219    #[inline]
220    pub fn get_parameters_mut(&mut self) -> &mut [FunctionLikeParameterMetadata] {
221        &mut self.parameters
222    }
223
224    /// Returns a reference to specific parameter metadata by name, if it exists.
225    #[inline]
226    #[must_use]
227    pub fn get_parameter(&self, name: Atom) -> Option<&FunctionLikeParameterMetadata> {
228        self.parameters.iter().find(|parameter| parameter.get_name().0 == name)
229    }
230
231    /// Returns a mutable reference to specific parameter metadata by name, if it exists.
232    #[inline]
233    pub fn get_parameter_mut(&mut self, name: Atom) -> Option<&mut FunctionLikeParameterMetadata> {
234        self.parameters.iter_mut().find(|parameter| parameter.get_name().0 == name)
235    }
236
237    /// Returns a mutable reference to the template type parameters.
238    #[inline]
239    pub fn get_template_types_mut(&mut self) -> &mut TemplateTypes {
240        &mut self.template_types
241    }
242
243    /// Returns a slice of the attributes.
244    #[inline]
245    #[must_use]
246    pub fn get_attributes(&self) -> &[AttributeMetadata] {
247        &self.attributes
248    }
249
250    /// Returns a mutable reference to the method-specific info, if this is a method.
251    #[inline]
252    pub fn get_method_metadata_mut(&mut self) -> Option<&mut MethodMetadata> {
253        self.method_metadata.as_mut()
254    }
255
256    /// Returns a mutable slice of docblock issues.
257    #[inline]
258    pub fn take_issues(&mut self) -> Vec<Issue> {
259        std::mem::take(&mut self.issues)
260    }
261
262    /// Sets the parameters, replacing existing ones.
263    #[inline]
264    pub fn set_parameters(&mut self, parameters: impl IntoIterator<Item = FunctionLikeParameterMetadata>) {
265        self.parameters = parameters.into_iter().collect();
266    }
267
268    /// Returns a new instance with the parameters replaced.
269    #[inline]
270    pub fn with_parameters(mut self, parameters: impl IntoIterator<Item = FunctionLikeParameterMetadata>) -> Self {
271        self.set_parameters(parameters);
272        self
273    }
274
275    #[inline]
276    pub fn set_return_type_metadata(&mut self, return_type: Option<TypeMetadata>) {
277        self.return_type_metadata = return_type;
278    }
279
280    #[inline]
281    pub fn set_return_type_declaration_metadata(&mut self, return_type: Option<TypeMetadata>) {
282        if self.return_type_metadata.is_none() {
283            self.return_type_metadata.clone_from(&return_type);
284        }
285
286        self.return_type_declaration_metadata = return_type;
287    }
288
289    /// Adds a single template type definition.
290    #[inline]
291    pub fn add_template_type(&mut self, name: Atom, constraint: GenericTemplate) {
292        self.template_types.insert(name, constraint);
293    }
294}