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