1use bumpalo::Bump;
2
3use mago_atom::Atom;
4use mago_atom::ascii_lowercase_atom;
5use mago_atom::atom;
6use mago_atom::empty_atom;
7use mago_atom::u32_atom;
8use mago_atom::u64_atom;
9use mago_database::file::File;
10use mago_names::ResolvedNames;
11use mago_names::scope::NamespaceScope;
12use mago_span::HasSpan;
13use mago_syntax::ast::*;
14use mago_syntax::comments::docblock::get_docblock_for_node;
15use mago_syntax::walker::MutWalker;
16use mago_syntax::walker::walk_anonymous_class_mut;
17use mago_syntax::walker::walk_class_mut;
18use mago_syntax::walker::walk_enum_mut;
19use mago_syntax::walker::walk_interface_mut;
20use mago_syntax::walker::walk_trait_mut;
21
22use crate::identifier::method::MethodIdentifier;
23use crate::metadata::CodebaseMetadata;
24use crate::metadata::flags::MetadataFlags;
25use crate::metadata::function_like::FunctionLikeKind;
26use crate::metadata::function_like::FunctionLikeMetadata;
27use crate::misc::GenericParent;
28use crate::scanner::class_like::*;
29use crate::scanner::constant::*;
30use crate::scanner::function_like::*;
31use crate::scanner::property::scan_promoted_property;
32use crate::ttype::resolution::TypeResolutionContext;
33use crate::ttype::union::TUnion;
34
35mod attribute;
36mod class_like;
37mod class_like_constant;
38mod constant;
39mod docblock;
40mod enum_case;
41mod function_like;
42mod inference;
43mod parameter;
44mod property;
45mod ttype;
46
47#[inline]
48pub fn scan_program<'arena, 'ctx>(
49 arena: &'arena Bump,
50 file: &'ctx File,
51 program: &'arena Program<'arena>,
52 resolved_names: &'ctx ResolvedNames<'arena>,
53) -> CodebaseMetadata {
54 let mut context = Context::new(arena, file, program, resolved_names);
55 let mut scanner = Scanner::new();
56
57 scanner.walk_program(program, &mut context);
58 scanner.codebase
59}
60
61#[derive(Clone, Debug)]
62struct Context<'ctx, 'arena> {
63 pub arena: &'arena Bump,
64 pub file: &'ctx File,
65 pub program: &'arena Program<'arena>,
66 pub resolved_names: &'arena ResolvedNames<'arena>,
67}
68
69impl<'ctx, 'arena> Context<'ctx, 'arena> {
70 pub fn new(
71 arena: &'arena Bump,
72 file: &'ctx File,
73 program: &'arena Program<'arena>,
74 resolved_names: &'arena ResolvedNames<'arena>,
75 ) -> Self {
76 Self { arena, file, program, resolved_names }
77 }
78
79 pub fn get_docblock(&self, node: impl HasSpan) -> Option<&'arena Trivia<'arena>> {
80 get_docblock_for_node(self.program, self.file, node)
81 }
82}
83
84type TemplateConstraint = (Atom, Vec<(GenericParent, TUnion)>);
85type TemplateConstraintList = Vec<TemplateConstraint>;
86
87#[derive(Debug, Default)]
88struct Scanner {
89 codebase: CodebaseMetadata,
90 stack: Vec<Atom>,
91 template_constraints: Vec<TemplateConstraintList>,
92 scope: NamespaceScope,
93 has_constructor: bool,
94}
95
96impl Scanner {
97 pub fn new() -> Self {
98 Self::default()
99 }
100
101 fn get_current_type_resolution_context(&self) -> TypeResolutionContext {
102 let mut context = TypeResolutionContext::new();
103 for template_constraint_list in self.template_constraints.iter().rev() {
104 for (name, constraints) in template_constraint_list {
105 if !context.has_template_definition(name) {
106 context = context.with_template_definition(*name, constraints.clone());
107 }
108 }
109 }
110
111 context
112 }
113}
114
115impl<'ctx, 'arena> MutWalker<'arena, 'arena, Context<'ctx, 'arena>> for Scanner {
116 #[inline]
117 fn walk_in_namespace(&mut self, namespace: &'arena Namespace<'arena>, _context: &mut Context<'ctx, 'arena>) {
118 self.scope = match &namespace.name {
119 Some(name) => NamespaceScope::for_namespace(name.value()),
120 None => NamespaceScope::global(),
121 };
122 }
123
124 #[inline]
125 fn walk_out_namespace(&mut self, _namespace: &'arena Namespace<'arena>, _context: &mut Context<'ctx, 'arena>) {
126 self.scope = NamespaceScope::global();
127 }
128
129 #[inline]
130 fn walk_in_use(&mut self, r#use: &'arena Use<'arena>, _context: &mut Context<'ctx, 'arena>) {
131 self.scope.populate_from_use(r#use);
132 }
133
134 #[inline]
135 fn walk_in_function(&mut self, function: &'arena Function<'arena>, context: &mut Context<'ctx, 'arena>) {
136 let type_context = self.get_current_type_resolution_context();
137
138 let name = ascii_lowercase_atom(context.resolved_names.get(&function.name));
139 let identifier = (empty_atom(), name);
140 let metadata =
141 scan_function(identifier, function, self.stack.last().copied(), context, &mut self.scope, type_context);
142
143 self.template_constraints.push({
144 let mut constraints: TemplateConstraintList = vec![];
145 for (template_name, template_constraints) in &metadata.template_types {
146 constraints.push((*template_name, template_constraints.to_vec()));
147 }
148
149 constraints
150 });
151
152 self.codebase.function_likes.insert(identifier, metadata);
153 }
154
155 #[inline]
156 fn walk_out_function(&mut self, _function: &'arena Function<'arena>, _context: &mut Context<'ctx, 'arena>) {
157 self.template_constraints.pop().expect("Expected template stack to be non-empty");
158 }
159
160 #[inline]
161 fn walk_in_closure(&mut self, closure: &'arena Closure<'arena>, context: &mut Context<'ctx, 'arena>) {
162 let span = closure.span();
163
164 let file_ref = u64_atom(span.file_id.as_u64());
165 let closure_ref = u32_atom(span.start.offset);
166 let identifier = (file_ref, closure_ref);
167
168 let type_resolution_context = self.get_current_type_resolution_context();
169 let metadata = scan_closure(
170 identifier,
171 closure,
172 self.stack.last().copied(),
173 context,
174 &mut self.scope,
175 type_resolution_context,
176 );
177
178 self.template_constraints.push({
179 let mut constraints: TemplateConstraintList = vec![];
180 for (template_name, template_constraints) in &metadata.template_types {
181 constraints.push((*template_name, template_constraints.to_vec()));
182 }
183
184 constraints
185 });
186
187 self.codebase.function_likes.insert(identifier, metadata);
188 }
189
190 #[inline]
191 fn walk_out_closure(&mut self, _closure: &'arena Closure<'arena>, _context: &mut Context<'ctx, 'arena>) {
192 self.template_constraints.pop().expect("Expected template stack to be non-empty");
193 }
194
195 #[inline]
196 fn walk_in_arrow_function(
197 &mut self,
198 arrow_function: &'arena ArrowFunction<'arena>,
199 context: &mut Context<'ctx, 'arena>,
200 ) {
201 let span = arrow_function.span();
202
203 let file_ref = u64_atom(span.file_id.as_u64());
204 let closure_ref = u32_atom(span.start.offset);
205 let identifier = (file_ref, closure_ref);
206
207 let type_resolution_context = self.get_current_type_resolution_context();
208
209 let metadata = scan_arrow_function(
210 identifier,
211 arrow_function,
212 self.stack.last().copied(),
213 context,
214 &mut self.scope,
215 type_resolution_context,
216 );
217
218 self.template_constraints.push({
219 let mut constraints: TemplateConstraintList = vec![];
220 for (template_name, template_constraints) in &metadata.template_types {
221 constraints.push((*template_name, template_constraints.to_vec()));
222 }
223
224 constraints
225 });
226 self.codebase.function_likes.insert(identifier, metadata);
227 }
228
229 #[inline]
230 fn walk_out_arrow_function(
231 &mut self,
232 _arrow_function: &'arena ArrowFunction<'arena>,
233 _context: &mut Context<'ctx, 'arena>,
234 ) {
235 self.template_constraints.pop().expect("Expected template stack to be non-empty");
236 }
237
238 #[inline]
239 fn walk_in_constant(&mut self, constant: &'arena Constant<'arena>, context: &mut Context<'ctx, 'arena>) {
240 let constants = scan_constant(constant, context, self.get_current_type_resolution_context(), &self.scope);
241
242 for constant_metadata in constants {
243 self.codebase.constants.insert(constant_metadata.name, constant_metadata);
244 }
245 }
246
247 #[inline]
248 fn walk_in_function_call(
249 &mut self,
250 function_call: &'arena FunctionCall<'arena>,
251 context: &mut Context<'ctx, 'arena>,
252 ) {
253 let Some(constant_metadata) =
254 scan_defined_constant(function_call, context, self.get_current_type_resolution_context(), &self.scope)
255 else {
256 return;
257 };
258
259 self.codebase.constants.insert(constant_metadata.name, constant_metadata);
260 }
261
262 #[inline]
263 fn walk_anonymous_class(
264 &mut self,
265 anonymous_class: &'arena AnonymousClass<'arena>,
266 context: &mut Context<'ctx, 'arena>,
267 ) {
268 if let Some((id, template_definition)) =
269 register_anonymous_class(&mut self.codebase, anonymous_class, context, &mut self.scope)
270 {
271 self.stack.push(id);
272 self.template_constraints.push(template_definition);
273
274 walk_anonymous_class_mut(self, anonymous_class, context);
275 } else {
276 }
278 }
279
280 #[inline]
281 fn walk_class(&mut self, class: &'arena Class<'arena>, context: &mut Context<'ctx, 'arena>) {
282 if let Some((id, templates)) = register_class(&mut self.codebase, class, context, &mut self.scope) {
283 self.stack.push(id);
284 self.template_constraints.push(templates);
285
286 walk_class_mut(self, class, context);
287 } else {
288 }
290 }
291
292 #[inline]
293 fn walk_trait(&mut self, r#trait: &'arena Trait<'arena>, context: &mut Context<'ctx, 'arena>) {
294 if let Some((id, templates)) = register_trait(&mut self.codebase, r#trait, context, &mut self.scope) {
295 self.stack.push(id);
296 self.template_constraints.push(templates);
297
298 walk_trait_mut(self, r#trait, context);
299 } else {
300 }
302 }
303
304 #[inline]
305 fn walk_enum(&mut self, r#enum: &'arena Enum<'arena>, context: &mut Context<'ctx, 'arena>) {
306 if let Some((id, templates)) = register_enum(&mut self.codebase, r#enum, context, &mut self.scope) {
307 self.stack.push(id);
308 self.template_constraints.push(templates);
309
310 walk_enum_mut(self, r#enum, context);
311 } else {
312 }
314 }
315
316 #[inline]
317 fn walk_interface(&mut self, interface: &'arena Interface<'arena>, context: &mut Context<'ctx, 'arena>) {
318 if let Some((id, templates)) = register_interface(&mut self.codebase, interface, context, &mut self.scope) {
319 self.stack.push(id);
320 self.template_constraints.push(templates);
321
322 walk_interface_mut(self, interface, context);
323 }
324 }
325
326 #[inline]
327 fn walk_in_method(&mut self, method: &'arena Method<'arena>, context: &mut Context<'ctx, 'arena>) {
328 let current_class = self.stack.last().copied().expect("Expected class-like stack to be non-empty");
329 let mut class_like_metadata =
330 self.codebase.class_likes.remove(¤t_class).expect("Expected class-like metadata to be present");
331
332 let name = ascii_lowercase_atom(method.name.value);
333
334 if class_like_metadata.methods.contains(&name) {
335 if class_like_metadata.pseudo_methods.contains(&name)
336 && let Some(existing_method) = self.codebase.function_likes.get_mut(&(class_like_metadata.name, name))
337 {
338 class_like_metadata.pseudo_methods.remove(&name);
339 existing_method.flags.remove(MetadataFlags::MAGIC_METHOD);
340 }
341
342 self.codebase.class_likes.insert(current_class, class_like_metadata);
343 self.template_constraints.push(vec![]);
344
345 return;
346 }
347
348 let method_id = (class_like_metadata.name, name);
349 let type_resolution = if method.is_static() { None } else { Some(self.get_current_type_resolution_context()) };
350
351 let function_like_metadata =
352 scan_method(method_id, method, &class_like_metadata, context, &mut self.scope, type_resolution);
353 let Some(method_metadata) = &function_like_metadata.method_metadata else {
354 unreachable!("Method info should be present for method.",);
355 };
356
357 let mut is_constructor = false;
358 let mut is_clone = false;
359 if method_metadata.is_constructor {
360 is_constructor = true;
361 self.has_constructor = true;
362
363 for (index, param) in method.parameter_list.parameters.iter().enumerate() {
364 if !param.is_promoted_property() {
365 continue;
366 }
367
368 let Some(parameter_info) = function_like_metadata.parameters.get(index) else {
369 continue;
370 };
371
372 let property_metadata = scan_promoted_property(param, parameter_info, &class_like_metadata, context);
373
374 class_like_metadata.add_property_metadata(property_metadata);
375 }
376 } else {
377 is_clone = name == atom("__clone");
378 }
379
380 class_like_metadata.methods.insert(name);
381 let method_identifier = MethodIdentifier::new(class_like_metadata.name, name);
382 class_like_metadata.add_declaring_method_id(name, method_identifier);
383 if !method_metadata.visibility.is_private() || is_constructor || is_clone || class_like_metadata.kind.is_trait()
384 {
385 class_like_metadata.inheritable_method_ids.insert(name, method_identifier);
386 }
387
388 if method_metadata.is_final && is_constructor {
389 class_like_metadata.flags |= MetadataFlags::CONSISTENT_CONSTRUCTOR;
390 }
391
392 self.template_constraints.push({
393 let mut constraints: TemplateConstraintList = vec![];
394 for (template_name, template_constraints) in &function_like_metadata.template_types {
395 constraints.push((*template_name, template_constraints.to_vec()));
396 }
397
398 constraints
399 });
400
401 self.codebase.class_likes.insert(current_class, class_like_metadata);
402 self.codebase.function_likes.insert(method_id, function_like_metadata);
403 }
404
405 #[inline]
406 fn walk_out_method(&mut self, _method: &'arena Method<'arena>, _context: &mut Context<'ctx, 'arena>) {
407 self.template_constraints.pop().expect("Expected template stack to be non-empty");
408 }
409
410 #[inline]
411 fn walk_out_anonymous_class(
412 &mut self,
413 _anonymous_class: &'arena AnonymousClass<'arena>,
414 _context: &mut Context<'ctx, 'arena>,
415 ) {
416 self.stack.pop().expect("Expected class stack to be non-empty");
417 self.template_constraints.pop().expect("Expected template stack to be non-empty");
418 }
419
420 #[inline]
421 fn walk_out_class(&mut self, _class: &'arena Class<'arena>, context: &mut Context<'ctx, 'arena>) {
422 finalize_class_like(self, context);
423 }
424
425 #[inline]
426 fn walk_out_trait(&mut self, _trait: &'arena Trait<'arena>, context: &mut Context<'ctx, 'arena>) {
427 finalize_class_like(self, context);
428 }
429
430 #[inline]
431 fn walk_out_enum(&mut self, _enum: &'arena Enum<'arena>, context: &mut Context<'ctx, 'arena>) {
432 finalize_class_like(self, context);
433 }
434
435 #[inline]
436 fn walk_out_interface(&mut self, _interface: &'arena Interface<'arena>, context: &mut Context<'ctx, 'arena>) {
437 finalize_class_like(self, context);
438 }
439}
440
441fn finalize_class_like<'ctx, 'arena>(scanner: &mut Scanner, context: &mut Context<'ctx, 'arena>) {
442 let has_constructor = scanner.has_constructor;
443 scanner.has_constructor = false;
444
445 let class_like_id = scanner.stack.pop().expect("Expected class stack to be non-empty");
446 scanner.template_constraints.pop().expect("Expected template stack to be non-empty");
447
448 if has_constructor {
449 return;
450 }
451
452 let Some(mut class_like_metadata) = scanner.codebase.class_likes.remove(&class_like_id) else {
453 return;
454 };
455
456 if class_like_metadata.flags.has_consistent_constructor() {
457 let constructor_name = atom("__construct");
458
459 class_like_metadata.methods.insert(constructor_name);
460 let constructor_method_id = MethodIdentifier::new(class_like_metadata.name, constructor_name);
461 class_like_metadata.add_declaring_method_id(constructor_name, constructor_method_id);
462 class_like_metadata.inheritable_method_ids.insert(constructor_name, constructor_method_id);
463
464 let mut flags = MetadataFlags::PURE;
465 if context.file.file_type.is_host() {
466 flags |= MetadataFlags::USER_DEFINED;
467 } else if context.file.file_type.is_builtin() {
468 flags |= MetadataFlags::BUILTIN;
469 }
470
471 scanner.codebase.function_likes.insert(
472 (class_like_metadata.name, constructor_name),
473 FunctionLikeMetadata::new(FunctionLikeKind::Method, class_like_metadata.span, flags),
474 );
475 }
476
477 scanner.codebase.class_likes.insert(class_like_id, class_like_metadata);
478}