mago_codex/metadata/
function_like.rs

1use std::collections::BTreeMap;
2
3use ahash::HashMap;
4use serde::Deserialize;
5use serde::Serialize;
6
7use mago_interner::StringIdentifier;
8use mago_reporting::Issue;
9use mago_span::Span;
10
11use crate::assertion::Assertion;
12use crate::metadata::attribute::AttributeMetadata;
13use crate::metadata::parameter::FunctionLikeParameterMetadata;
14use crate::metadata::ttype::TypeMetadata;
15use crate::misc::GenericParent;
16use crate::ttype::resolution::TypeResolutionContext;
17use crate::ttype::union::TUnion;
18use crate::visibility::Visibility;
19
20pub type TemplateTuple = (StringIdentifier, Vec<(GenericParent, TUnion)>);
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)]
26pub struct MethodMetadata {
27    /// Marks whether this method is declared as `final`, preventing further overriding.
28    pub is_final: bool,
29
30    /// Marks whether this method is declared as `abstract`, requiring implementation in subclasses.
31    pub is_abstract: bool,
32
33    /// Marks whether this method is declared as `static`, allowing it to be called without an instance.
34    pub is_static: bool,
35
36    /// Marks whether this method is a constructor (`__construct`).
37    pub is_constructor: bool,
38
39    /// Marks whether this method is declared as `public`, `protected`, or `private`.
40    pub visibility: Visibility,
41
42    /// A map of constraints defined by `@where` docblock tags.
43    ///
44    /// The key is the name of a class-level template parameter (e.g., `T`), and the value
45    /// is the `TUnion` type constraint that `T` must satisfy for this specific method
46    /// to be considered callable.
47    pub where_constraints: HashMap<StringIdentifier, TypeMetadata>,
48}
49
50/// Distinguishes between different kinds of callable constructs in PHP.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
52pub enum FunctionLikeKind {
53    /// Represents a standard function declared in the global scope or a namespace (`function foo() {}`).
54    Function,
55    /// Represents a method defined within a class, trait, enum, or interface (`class C { function bar() {} }`).
56    Method,
57    /// Represents an anonymous function created using `function() {}`.
58    Closure,
59    /// Represents an arrow function (short closure syntax) introduced in PHP 7.4 (`fn() => ...`).
60    ArrowFunction,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct FunctionLikeMetadata {
65    /// The kind of function-like structure this metadata represents.
66    pub kind: FunctionLikeKind,
67
68    /// The source code location (span) covering the entire function/method/closure definition.
69    /// For closures/arrow functions, this covers the `function(...) { ... }` or `fn(...) => ...` part.
70    pub span: Span,
71
72    /// The name of the function or method, if applicable.
73    /// `None` for closures and arrow functions unless assigned to a variable later.
74    /// Example: `processRequest`, `__construct`, `my_global_func`.
75    pub name: Option<StringIdentifier>,
76
77    /// The specific source code location (span) of the function or method name identifier.
78    /// `None` if the function/method has no name (closures/arrow functions).
79    pub name_span: Option<Span>,
80
81    /// Ordered list of metadata for each parameter defined in the signature.
82    pub parameters: Vec<FunctionLikeParameterMetadata>,
83
84    /// The explicit return type declaration (type hint).
85    ///
86    /// Example: For `function getName(): string`, this holds metadata for `string`.
87    /// `None` if no return type is specified.
88    pub return_type_declaration_metadata: Option<TypeMetadata>,
89
90    /// The explicit return type declaration (type hint) or docblock type (`@return`).
91    ///
92    /// Example: For `function getName(): string`, this holds metadata for `string`,
93    /// or for ` /** @return string */ function getName() { .. }`, this holds metadata for `string`.
94    /// `None` if neither is specified.
95    pub return_type_metadata: Option<TypeMetadata>,
96
97    /// Generic type parameters (templates) defined for the function/method (e.g., `@template T`).
98    /// Stores the template name and its constraints (parent type and bound type).
99    /// Example: `[("T", [(GenericParent::Function("funcName"), Arc<TUnion::object()>)])]`
100    pub template_types: Vec<TemplateTuple>,
101
102    /// Attributes attached to the function/method/closure declaration (`#[Attribute] function foo() {}`).
103    pub attributes: Vec<AttributeMetadata>,
104
105    /// Specific metadata relevant only to methods (visibility, final, static, etc.).
106    /// This is `Some` if `kind` is `FunctionLikeKind::Method`, `None` otherwise.
107    pub method_metadata: Option<MethodMetadata>,
108
109    /// Contains context information needed for resolving types within this function's scope
110    /// (e.g., `use` statements, current namespace, class context). Often populated during analysis.
111    pub type_resolution_context: Option<TypeResolutionContext>,
112
113    /// `true` if this function/method is defined in user-controlled code (vs. internal stubs/PHP core).
114    /// Often determined from the source file info within the `span`.
115    pub(crate) user_defined: bool,
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    /// `true` if the function/method body contains a `yield` statement, indicating it's a generator.
122    pub has_yield: bool,
123
124    /// `true` if the function/method is marked with `@psalm-must-use`, `@phpstan-must-use`,
125    /// or `@must-use`, indicating its return value should not be ignored.
126    pub must_use: bool,
127
128    /// `true` if the function/method body contains a `throw` statement.
129    pub has_throw: bool,
130
131    pub specialize_call: bool,
132
133    /// Internal flag indicating whether this metadata structure has been fully populated
134    /// by all analysis stages. Used to control analysis flow. (User requested minimal documentation).
135    pub(crate) is_populated: bool,
136
137    /// `true` if the function/method is marked as deprecated via docblock tags
138    /// (`@deprecated`, `@psalm-deprecated`, `@phpstan-deprecated`).
139    pub is_deprecated: bool,
140
141    /// `true` if the function/method is marked as internal via docblock tags
142    /// (`@internal`, `@psalm-internal`, `@phpstan-internal`), indicating it's not part of the lic API.
143    pub is_internal: bool,
144
145    /// `true` if the function/method is marked as pure via docblock tags
146    /// (`@pure`, `@psalm-pure`, `@phpstan-pure`), indicating it has no side effects.
147    pub is_pure: bool,
148
149    /// `true` if marked with `@psalm-ignore-nullable-return` or equivalent, suppressing
150    /// issues related to returning `null` when the signature doesn't explicitly allow it.
151    pub ignore_nullable_return: bool,
152
153    /// `true` if marked with `@psalm-ignore-falsable-return` or equivalent, suppressing
154    /// issues related to returning `false` when the signature doesn't explicitly allow it.
155    pub ignore_falsable_return: bool,
156
157    /// `true` if the function/method's docblock includes `{@inheritdoc}` or implicitly inherits
158    /// documentation from a parent method.
159    pub inherits_docs: bool,
160
161    /// `true` if marked with `@psalm-mutation-free`, `@phpstan-mutation-free`, indicating the function
162    /// does not modify any state (including object properties or global state). Implies `@pure`.
163    pub is_mutation_free: bool,
164
165    /// `true` if marked with `@psalm-external-mutation-free`, `@phpstan-external-mutation-free`,
166    /// indicating the function does not modify *external* state but may modify its own arguments
167    /// or locally created objects.
168    pub is_external_mutation_free: bool,
169
170    /// `true` if the function/method accepts named arguments (PHP 8.0+ default).
171    /// Can be set to `false` if the `#[NoNamedArguments]` attribute is present (PHP 8.2+).
172    pub allows_named_arguments: bool,
173
174    /// List of issues specifically related to parsing or interpreting this function's docblock.
175    pub issues: Vec<Issue>,
176
177    /// Assertions about parameter types or variable types that are guaranteed to be true
178    /// *after* this function/method returns normally. From `@psalm-assert`, `@phpstan-assert`, etc.
179    /// Maps variable/parameter name to a list of type assertions.
180    pub assertions: BTreeMap<StringIdentifier, Vec<Assertion>>,
181
182    /// Assertions about parameter/variable types that are guaranteed to be true if this
183    /// function/method returns `true`. From `@psalm-assert-if-true`, etc.
184    pub if_true_assertions: BTreeMap<StringIdentifier, Vec<Assertion>>,
185
186    /// Assertions about parameter/variable types that are guaranteed to be true if this
187    /// function/method returns `false`. From `@psalm-assert-if-false`, etc.
188    pub if_false_assertions: BTreeMap<StringIdentifier, Vec<Assertion>>,
189
190    /// A flag indicating if this function-like should be treated as unchecked.
191    pub unchecked: bool,
192}
193
194impl FunctionLikeKind {
195    /// Checks if this kind represents a class/trait/enum/interface method.
196    #[inline]
197    pub const fn is_method(&self) -> bool {
198        matches!(self, Self::Method)
199    }
200
201    /// Checks if this kind represents a globally/namespace-scoped function.
202    #[inline]
203    pub const fn is_function(&self) -> bool {
204        matches!(self, Self::Function)
205    }
206
207    /// Checks if this kind represents an anonymous function (`function() {}`).
208    #[inline]
209    pub const fn is_closure(&self) -> bool {
210        matches!(self, Self::Closure)
211    }
212
213    /// Checks if this kind represents an arrow function (`fn() => ...`).
214    #[inline]
215    pub const fn is_arrow_function(&self) -> bool {
216        matches!(self, Self::ArrowFunction)
217    }
218}
219
220/// Contains comprehensive metadata for any function-like structure in PHP.
221/// Provides a flexible API with setters (`set_*`), consuming builders (`with_*`),
222/// adders (`add_*`), consuming adders (`with_added_*`), unsetters (`unset_*`),
223/// and consuming unsetters (`without_*`), along with clearly named getters
224/// (`get_*`, `is_*`, `has_*`, `allows_*`).
225impl FunctionLikeMetadata {
226    /// Creates new `FunctionLikeMetadata` with basic information and default flags.
227    pub fn new(kind: FunctionLikeKind, span: Span) -> Self {
228        let user_defined = span.start.source.1.is_user_defined();
229        let method_metadata = if kind.is_method() { Some(MethodMetadata::default()) } else { None };
230
231        Self {
232            kind,
233            span,
234            user_defined,
235            name: None,
236            name_span: None,
237            parameters: vec![],
238            return_type_declaration_metadata: None,
239            return_type_metadata: None,
240            template_types: vec![],
241            attributes: vec![],
242            method_metadata,
243            type_resolution_context: None,
244            has_throw: false,
245            thrown_types: vec![],
246            is_pure: false,
247            must_use: false,
248            is_deprecated: false,
249            specialize_call: false,
250            is_populated: false,
251            has_yield: false,
252            is_internal: false,
253            ignore_nullable_return: false,
254            ignore_falsable_return: false,
255            inherits_docs: false,
256            is_mutation_free: false,
257            is_external_mutation_free: false,
258            allows_named_arguments: true,
259            assertions: BTreeMap::new(),
260            if_true_assertions: BTreeMap::new(),
261            if_false_assertions: BTreeMap::new(),
262            unchecked: false,
263            issues: vec![],
264        }
265    }
266
267    /// Returns the kind of function-like (Function, Method, Closure, ArrowFunction).
268    #[inline]
269    pub fn get_kind(&self) -> FunctionLikeKind {
270        self.kind
271    }
272
273    /// Returns a mutable slice of the parameter metadata.
274    #[inline]
275    pub fn get_parameters_mut(&mut self) -> &mut [FunctionLikeParameterMetadata] {
276        &mut self.parameters
277    }
278
279    /// Returns a reference to specific parameter metadata by name, if it exists.
280    #[inline]
281    pub fn get_parameter(&self, name: StringIdentifier) -> Option<&FunctionLikeParameterMetadata> {
282        self.parameters.iter().find(|parameter| parameter.get_name().0 == name)
283    }
284
285    /// Returns a mutable reference to specific parameter metadata by name, if it exists.
286    #[inline]
287    pub fn get_parameter_mut(&mut self, name: StringIdentifier) -> Option<&mut FunctionLikeParameterMetadata> {
288        self.parameters.iter_mut().find(|parameter| parameter.get_name().0 == name)
289    }
290
291    /// Returns a mutable slice of the template type parameters.
292    #[inline]
293    pub fn get_template_types_mut(&mut self) -> &mut [TemplateTuple] {
294        &mut self.template_types
295    }
296
297    /// Returns a slice of the attributes.
298    #[inline]
299    pub fn get_attributes(&self) -> &[AttributeMetadata] {
300        &self.attributes
301    }
302
303    /// Returns a mutable reference to the method-specific info, if this is a method.
304    #[inline]
305    pub fn get_method_metadata_mut(&mut self) -> Option<&mut MethodMetadata> {
306        self.method_metadata.as_mut()
307    }
308
309    /// Returns a mutable slice of docblock issues.
310    #[inline]
311    pub fn take_issues(&mut self) -> Vec<Issue> {
312        std::mem::take(&mut self.issues)
313    }
314
315    /// Checks if the function contains `yield`.
316    #[inline]
317    pub const fn has_yield(&self) -> bool {
318        self.has_yield
319    }
320
321    /// Sets the name and corresponding name span. Clears both if name is `None`. Updates constructor status.
322    #[inline]
323    pub fn set_name(&mut self, name: Option<StringIdentifier>, name_span: Option<Span>) {
324        if name.is_some() {
325            self.name = name;
326            self.name_span = name_span;
327        } else {
328            self.unset_name();
329        }
330    }
331
332    /// Returns a new instance with the name and name span set. Updates constructor status.
333    #[inline]
334    pub fn with_name(mut self, name: Option<StringIdentifier>, name_span: Option<Span>) -> Self {
335        self.set_name(name, name_span);
336        self
337    }
338
339    /// Sets the name and name span to `None`. Updates constructor status.
340    #[inline]
341    pub fn unset_name(&mut self) {
342        self.name = None;
343        self.name_span = None;
344        if let Some(ref mut info) = self.method_metadata {
345            info.is_constructor = false;
346        }
347    }
348
349    /// Sets the parameters, replacing existing ones.
350    #[inline]
351    pub fn set_parameters(&mut self, parameters: impl IntoIterator<Item = FunctionLikeParameterMetadata>) {
352        self.parameters = parameters.into_iter().collect();
353    }
354
355    /// Returns a new instance with the parameters replaced.
356    #[inline]
357    pub fn with_parameters(mut self, parameters: impl IntoIterator<Item = FunctionLikeParameterMetadata>) -> Self {
358        self.set_parameters(parameters);
359        self
360    }
361
362    #[inline]
363    pub fn set_return_type_metadata(&mut self, return_type: Option<TypeMetadata>) {
364        self.return_type_metadata = return_type;
365    }
366
367    #[inline]
368    pub fn set_return_type_declaration_metadata(&mut self, return_type: Option<TypeMetadata>) {
369        if self.return_type_metadata.is_none() {
370            self.return_type_metadata = return_type.clone();
371        }
372
373        self.return_type_declaration_metadata = return_type;
374    }
375
376    /// Adds a single template type definition.
377    #[inline]
378    pub fn add_template_type(&mut self, template: TemplateTuple) {
379        self.template_types.push(template);
380    }
381}