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