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