Skip to main content

mago_codex/metadata/
function_like.rs

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