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_reporting::Issue;
9use mago_span::Span;
10
11use crate::assertion::Assertion;
12use crate::metadata::attribute::AttributeMetadata;
13use crate::metadata::flags::MetadataFlags;
14use crate::metadata::parameter::FunctionLikeParameterMetadata;
15use crate::metadata::ttype::TypeMetadata;
16use crate::misc::GenericParent;
17use crate::ttype::resolution::TypeResolutionContext;
18use crate::ttype::union::TUnion;
19use crate::visibility::Visibility;
20
21pub type TemplateTuple = (Atom, Vec<(GenericParent, TUnion)>);
22
23/// Contains metadata specific to methods defined within classes, interfaces, enums, or traits.
24///
25/// This complements the more general `FunctionLikeMetadata`.
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
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 constraints (parent type and bound type).
103 /// Example: `[("T", [(GenericParent::Function("funcName"), Arc<TUnion::object()>)])]`
104 pub template_types: Vec<TemplateTuple>,
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 /// Tracks whether this function/method has a docblock comment.
138 /// Used to determine if docblock inheritance should occur implicitly.
139 pub has_docblock: bool,
140
141 pub flags: MetadataFlags,
142}
143
144impl FunctionLikeKind {
145 /// Checks if this kind represents a class/trait/enum/interface method.
146 #[inline]
147 pub const fn is_method(&self) -> bool {
148 matches!(self, Self::Method)
149 }
150
151 /// Checks if this kind represents a globally/namespace-scoped function.
152 #[inline]
153 pub const fn is_function(&self) -> bool {
154 matches!(self, Self::Function)
155 }
156
157 /// Checks if this kind represents an anonymous function (`function() {}`).
158 #[inline]
159 pub const fn is_closure(&self) -> bool {
160 matches!(self, Self::Closure)
161 }
162
163 /// Checks if this kind represents an arrow function (`fn() => ...`).
164 #[inline]
165 pub const fn is_arrow_function(&self) -> bool {
166 matches!(self, Self::ArrowFunction)
167 }
168}
169
170/// Contains comprehensive metadata for any function-like structure in PHP.
171impl FunctionLikeMetadata {
172 /// Creates new `FunctionLikeMetadata` with basic information and default flags.
173 pub fn new(kind: FunctionLikeKind, span: Span, flags: MetadataFlags) -> Self {
174 let method_metadata = if kind.is_method() { Some(MethodMetadata::default()) } else { None };
175
176 Self {
177 kind,
178 span,
179 flags,
180 name: None,
181 original_name: None,
182 name_span: None,
183 parameters: vec![],
184 return_type_declaration_metadata: None,
185 return_type_metadata: None,
186 template_types: vec![],
187 attributes: vec![],
188 method_metadata,
189 type_resolution_context: None,
190 thrown_types: vec![],
191 assertions: BTreeMap::new(),
192 if_true_assertions: BTreeMap::new(),
193 if_false_assertions: BTreeMap::new(),
194 has_docblock: false,
195 issues: vec![],
196 }
197 }
198
199 /// Returns the kind of function-like (Function, Method, Closure, ArrowFunction).
200 #[inline]
201 pub fn get_kind(&self) -> FunctionLikeKind {
202 self.kind
203 }
204
205 /// Returns a mutable slice of the parameter metadata.
206 #[inline]
207 pub fn get_parameters_mut(&mut self) -> &mut [FunctionLikeParameterMetadata] {
208 &mut self.parameters
209 }
210
211 /// Returns a reference to specific parameter metadata by name, if it exists.
212 #[inline]
213 pub fn get_parameter(&self, name: Atom) -> Option<&FunctionLikeParameterMetadata> {
214 self.parameters.iter().find(|parameter| parameter.get_name().0 == name)
215 }
216
217 /// Returns a mutable reference to specific parameter metadata by name, if it exists.
218 #[inline]
219 pub fn get_parameter_mut(&mut self, name: Atom) -> Option<&mut FunctionLikeParameterMetadata> {
220 self.parameters.iter_mut().find(|parameter| parameter.get_name().0 == name)
221 }
222
223 /// Returns a mutable slice of the template type parameters.
224 #[inline]
225 pub fn get_template_types_mut(&mut self) -> &mut [TemplateTuple] {
226 &mut self.template_types
227 }
228
229 /// Returns a slice of the attributes.
230 #[inline]
231 pub fn get_attributes(&self) -> &[AttributeMetadata] {
232 &self.attributes
233 }
234
235 /// Returns a mutable reference to the method-specific info, if this is a method.
236 #[inline]
237 pub fn get_method_metadata_mut(&mut self) -> Option<&mut MethodMetadata> {
238 self.method_metadata.as_mut()
239 }
240
241 /// Returns a mutable slice of docblock issues.
242 #[inline]
243 pub fn take_issues(&mut self) -> Vec<Issue> {
244 std::mem::take(&mut self.issues)
245 }
246
247 /// Sets the parameters, replacing existing ones.
248 #[inline]
249 pub fn set_parameters(&mut self, parameters: impl IntoIterator<Item = FunctionLikeParameterMetadata>) {
250 self.parameters = parameters.into_iter().collect();
251 }
252
253 /// Returns a new instance with the parameters replaced.
254 #[inline]
255 pub fn with_parameters(mut self, parameters: impl IntoIterator<Item = FunctionLikeParameterMetadata>) -> Self {
256 self.set_parameters(parameters);
257 self
258 }
259
260 #[inline]
261 pub fn set_return_type_metadata(&mut self, return_type: Option<TypeMetadata>) {
262 self.return_type_metadata = return_type;
263 }
264
265 #[inline]
266 pub fn set_return_type_declaration_metadata(&mut self, return_type: Option<TypeMetadata>) {
267 if self.return_type_metadata.is_none() {
268 self.return_type_metadata = return_type.clone();
269 }
270
271 self.return_type_declaration_metadata = return_type;
272 }
273
274 /// Adds a single template type definition.
275 #[inline]
276 pub fn add_template_type(&mut self, template: TemplateTuple) {
277 self.template_types.push(template);
278 }
279
280 /// Determines if this function/method needs docblock inheritance.
281 ///
282 /// Returns `true` if:
283 /// - The method has an explicit `@inheritDoc` or `@inheritDocs` tag (INHERITS_DOCS flag set), OR
284 /// - The method has NO docblock at all (implicit inheritance)
285 ///
286 /// Returns `false` otherwise (method has a docblock but no @inheritDoc).
287 #[inline]
288 pub fn needs_docblock_inheritance(&self) -> bool {
289 self.flags.contains(MetadataFlags::INHERITS_DOCS) || !self.has_docblock
290 }
291}