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}