1use ahash::HashSet;
2
3use mago_interner::StringIdentifier;
4use mago_interner::ThreadedInterner;
5use mago_names::ResolvedNames;
6use mago_reflection::CodebaseReflection;
7use mago_reflection::identifier::ClassLikeName;
8use mago_reflection::identifier::FunctionLikeName;
9use mago_reflection::r#type::kind::*;
10use mago_source::Source;
11use mago_span::HasPosition;
12use mago_span::HasSpan;
13use mago_syntax::ast::*;
14use mago_trinary::Trinary;
15
16use crate::constant::ConstantTypeResolver;
17use crate::internal::*;
18
19pub struct TypeResolver<'i, 'c> {
36 interner: &'i ThreadedInterner,
37 source: &'c Source,
38 names: &'c ResolvedNames,
39 codebase: Option<&'c CodebaseReflection>,
40 constant_resolver: ConstantTypeResolver<'i, 'c>,
41}
42
43impl<'i, 'c> TypeResolver<'i, 'c> {
44 pub fn new(
45 interner: &'i ThreadedInterner,
46 source: &'c Source,
47 names: &'c ResolvedNames,
48 codebase: Option<&'c CodebaseReflection>,
49 ) -> Self {
50 Self {
51 interner,
52 source,
53 names,
54 codebase,
55 constant_resolver: ConstantTypeResolver::new(interner, names, codebase),
56 }
57 }
58
59 pub fn resolve(&self, expression: &Expression) -> TypeKind {
60 match expression {
61 Expression::Parenthesized(parenthesized) => self.resolve(&parenthesized.expression),
62 Expression::Binary(operation) => get_binary_operation_kind(self.interner, operation, |e| self.resolve(e)),
63 Expression::UnaryPrefix(operation) => {
64 get_unary_prefix_operation_kind(self.interner, operation, |e| self.resolve(e))
65 }
66 Expression::UnaryPostfix(operation) => get_unary_postfix_operation_kind(operation, |e| self.resolve(e)),
67 Expression::Literal(literal) => get_literal_kind(self.interner, literal),
68 Expression::CompositeString(composite_string) => {
69 get_composite_string_kind(composite_string, |e| self.resolve(e))
70 }
71 Expression::Assignment(assignment_operation) => self.resolve(&assignment_operation.rhs),
72 Expression::Conditional(conditional) => get_conditional_kind(conditional, |e| self.resolve(e)),
73 Expression::Array(array) => get_array_kind(&array.elements, |e| self.resolve(e)),
74 Expression::LegacyArray(legacy_array) => get_array_kind(&legacy_array.elements, |e| self.resolve(e)),
75 Expression::ArrayAccess(array_access) => get_array_index_kind(self.resolve(&array_access.array)),
76 Expression::AnonymousClass(anonymous_class) => anonymous_object_kind(anonymous_class.span()),
77 Expression::Closure(closure) => {
78 if let Some(codebase) = self.codebase
79 && let Some(function) = codebase.get_function_like(FunctionLikeName::ArrowFunction(closure.span()))
80 {
81 return TypeKind::from(function);
82 }
83
84 any_closure_kind()
86 }
87 Expression::ArrowFunction(arrow_function) => {
88 if let Some(codebase) = self.codebase
89 && let Some(function) =
90 codebase.get_function_like(FunctionLikeName::ArrowFunction(arrow_function.span()))
91 {
92 return TypeKind::from(function);
93 }
94
95 any_closure_kind()
97 }
98 Expression::ConstantAccess(access) => self.constant_resolver.resolve(&access.name),
99 Expression::Match(match_expression) => {
100 let mut kinds = HashSet::default();
101 for arm in match_expression.arms.iter() {
102 match &arm {
103 MatchArm::Expression(match_expression_arm) => {
104 kinds.insert(self.resolve(&match_expression_arm.expression));
105 }
106 MatchArm::Default(match_default_arm) => {
107 kinds.insert(self.resolve(&match_default_arm.expression));
108 }
109 }
110 }
111
112 if kinds.is_empty() { never_kind() } else { union_kind(kinds.into_iter().collect()) }
113 }
114 Expression::Construct(construct) => match construct {
115 Construct::Isset(_) => bool_kind(),
116 Construct::Empty(_) => bool_kind(),
117 Construct::Eval(_) => mixed_kind(false),
118 Construct::Include(_) => mixed_kind(false),
119 Construct::IncludeOnce(_) => mixed_kind(false),
120 Construct::Require(_) => mixed_kind(false),
121 Construct::RequireOnce(_) => mixed_kind(false),
122 Construct::Print(_) => value_integer_kind(1),
123 Construct::Exit(_) => never_kind(),
124 Construct::Die(_) => never_kind(),
125 },
126 Expression::Throw(_) => never_kind(),
127 Expression::Clone(clone) => {
128 let object_kind = self.resolve(&clone.object);
129 if object_kind.is_object() {
130 return object_kind;
131 }
132
133 any_object_kind()
134 }
135 Expression::Call(call) => match call {
136 Call::Function(function_call) => {
137 if let Some(codebase) = self.codebase
138 && let Expression::Identifier(identifier) = function_call.function.as_ref()
139 {
140 let (full_name, short_name) = resolve_name(self.interner, identifier.value());
141
142 if let Some(function) = codebase.get_function(self.interner, full_name) {
143 return function.return_type_reflection.as_ref().map_or_else(
144 || mixed_kind(false),
145 |return_type| return_type.type_reflection.kind.clone(),
146 );
147 }
148
149 if let Some(function) = codebase.get_function(self.interner, &short_name) {
150 return function.return_type_reflection.as_ref().map_or_else(
151 || mixed_kind(false),
152 |return_type| return_type.type_reflection.kind.clone(),
153 );
154 }
155 }
156
157 mixed_kind(false)
158 }
159 Call::Method(method_call) => {
160 let object_kind = self.resolve(&method_call.object);
161
162 if let TypeKind::Object(object_kind) = object_kind {
163 let ClassLikeMemberSelector::Identifier(method) = &method_call.method else {
164 return mixed_kind(false);
165 };
166
167 if let Some(codebase) = self.codebase {
168 let class_like_reflection = match &object_kind {
169 ObjectTypeKind::NamedObject { name, .. } => {
170 codebase.get_named_class_like(self.interner, name)
171 }
172 ObjectTypeKind::AnonymousObject { span } => {
173 codebase.get_class_like(&ClassLikeName::AnonymousClass(*span))
174 }
175 ObjectTypeKind::EnumCase { enum_name, .. } => {
176 codebase.get_enum(self.interner, enum_name)
177 }
178 _ => {
179 return mixed_kind(false);
180 }
181 };
182
183 if let Some(class_reflection) = class_like_reflection
184 && let Some(method) = class_reflection.methods.members.get(&method.value)
185 {
186 return method.return_type_reflection.as_ref().map_or_else(
187 || mixed_kind(false),
188 |return_type| return_type.type_reflection.kind.clone(),
189 );
190 }
191 }
192 }
193
194 mixed_kind(false)
195 }
196 Call::NullSafeMethod(null_safe_method_call) => {
197 let object_kind = self.resolve(&null_safe_method_call.object);
198
199 if let TypeKind::Object(object_kind) = object_kind {
200 let ClassLikeMemberSelector::Identifier(method) = &null_safe_method_call.method else {
201 return mixed_kind(false);
202 };
203
204 if let Some(codebase) = self.codebase {
205 let class_like_reflection = match &object_kind {
206 ObjectTypeKind::NamedObject { name, .. } => {
207 codebase.get_named_class_like(self.interner, name)
208 }
209 ObjectTypeKind::AnonymousObject { span } => {
210 codebase.get_class_like(&ClassLikeName::AnonymousClass(*span))
211 }
212 ObjectTypeKind::EnumCase { enum_name, .. } => {
213 codebase.get_enum(self.interner, enum_name)
214 }
215 _ => {
216 return mixed_kind(false);
217 }
218 };
219
220 if let Some(class_reflection) = class_like_reflection
221 && let Some(method) = class_reflection.methods.members.get(&method.value)
222 {
223 return method.return_type_reflection.as_ref().map_or_else(
224 || mixed_kind(false),
225 |return_type| return_type.type_reflection.kind.clone(),
226 );
227 }
228 }
229 }
230
231 mixed_kind(false)
232 }
233 Call::StaticMethod(static_method_call) => {
234 if let Some(codebase) = self.codebase
235 && let (Expression::Identifier(name), ClassLikeMemberSelector::Identifier(method)) =
236 (static_method_call.class.as_ref(), &static_method_call.method)
237 {
238 let class_name = self.names.get(name);
239
240 if let Some(class_reflection) = codebase.get_named_class_like(self.interner, class_name)
241 && let Some(method) = class_reflection.methods.members.get(&method.value)
242 {
243 return method.return_type_reflection.as_ref().map_or_else(
244 || mixed_kind(false),
245 |return_type| return_type.type_reflection.kind.clone(),
246 );
247 }
248 }
249
250 mixed_kind(false)
251 }
252 },
253 Expression::Access(access) => match access {
254 Access::Property(property_access) => {
255 let object_kind = self.resolve(&property_access.object);
256
257 if let TypeKind::Object(object_kind) = object_kind {
258 let ClassLikeMemberSelector::Identifier(property) = &property_access.property else {
259 return mixed_kind(false);
260 };
261
262 if let Some(codebase) = self.codebase {
263 let class_like_reflection = match &object_kind {
264 ObjectTypeKind::NamedObject { name, .. } => {
265 codebase.get_named_class_like(self.interner, name)
266 }
267 ObjectTypeKind::AnonymousObject { span } => {
268 codebase.get_class_like(&ClassLikeName::AnonymousClass(*span))
269 }
270 ObjectTypeKind::EnumCase { enum_name, .. } => {
271 codebase.get_enum(self.interner, enum_name)
272 }
273 _ => {
274 return mixed_kind(false);
275 }
276 };
277
278 let property = self.interner.intern(format!("${}", self.interner.lookup(&property.value)));
279 if let Some(class_reflection) = class_like_reflection
280 && let Some(property) = class_reflection.properties.members.get(&property)
281 {
282 return property
283 .type_reflection
284 .as_ref()
285 .map(|t| t.kind.clone())
286 .or_else(|| {
287 property
288 .default_value_reflection
289 .as_ref()
290 .map(|v| v.inferred_type_reflection.kind.clone())
291 })
292 .unwrap_or_else(|| mixed_kind(false));
293 }
294 }
295 }
296
297 mixed_kind(false)
298 }
299 Access::NullSafeProperty(null_safe_property_access) => {
300 let object_kind = self.resolve(&null_safe_property_access.object);
301
302 if let TypeKind::Object(object_kind) = object_kind {
303 let ClassLikeMemberSelector::Identifier(property) = &null_safe_property_access.property else {
304 return mixed_kind(false);
305 };
306
307 if let Some(codebase) = self.codebase {
308 let class_like_reflection = match &object_kind {
309 ObjectTypeKind::NamedObject { name, .. } => {
310 codebase.get_named_class_like(self.interner, name)
311 }
312 ObjectTypeKind::AnonymousObject { span } => {
313 codebase.get_class_like(&ClassLikeName::AnonymousClass(*span))
314 }
315 ObjectTypeKind::EnumCase { enum_name, .. } => {
316 codebase.get_enum(self.interner, enum_name)
317 }
318 _ => {
319 return mixed_kind(false);
320 }
321 };
322
323 let property = self.interner.intern(format!("${}", self.interner.lookup(&property.value)));
324 if let Some(class_reflection) = class_like_reflection
325 && let Some(property) = class_reflection.properties.members.get(&property)
326 {
327 return property
328 .type_reflection
329 .as_ref()
330 .map(|t| t.kind.clone())
331 .or_else(|| {
332 property
333 .default_value_reflection
334 .as_ref()
335 .map(|v| v.inferred_type_reflection.kind.clone())
336 })
337 .unwrap_or_else(|| mixed_kind(false));
338 }
339 }
340 }
341
342 mixed_kind(false)
343 }
344 Access::StaticProperty(static_property_access) => {
345 if let Some(codebase) = self.codebase
346 && let (Expression::Identifier(name), Variable::Direct(variable)) =
347 (static_property_access.class.as_ref(), &static_property_access.property)
348 {
349 let class_name = self.names.get(name);
350
351 if let Some(class_reflection) = codebase.get_named_class_like(self.interner, class_name)
352 && let Some(property) = class_reflection.properties.members.get(&variable.name)
353 {
354 return property
355 .type_reflection
356 .as_ref()
357 .map(|t| t.kind.clone())
358 .or_else(|| {
359 property
360 .default_value_reflection
361 .as_ref()
362 .map(|v| v.inferred_type_reflection.kind.clone())
363 })
364 .unwrap_or_else(|| mixed_kind(false));
365 }
366 }
367
368 mixed_kind(false)
369 }
370 Access::ClassConstant(class_constant_access) => {
371 if let Some(codebase) = self.codebase
372 && let (Expression::Identifier(name), ClassLikeConstantSelector::Identifier(constant)) =
373 (class_constant_access.class.as_ref(), &class_constant_access.constant)
374 {
375 let class_name = self.names.get(name);
376
377 if let Some(class_reflection) = codebase.get_named_class_like(self.interner, class_name) {
378 if let Some(constant) = class_reflection.constants.get(&constant.value) {
379 return constant
380 .type_reflection
381 .as_ref()
382 .map(|t| t.kind.clone())
383 .unwrap_or_else(|| constant.inferred_type_reflection.kind.clone());
384 }
385
386 if class_reflection.is_enum() && class_reflection.cases.contains_key(&constant.value) {
387 return enum_case_kind(*class_name, constant.value);
388 }
389 }
390 }
391
392 mixed_kind(false)
393 }
394 },
395 Expression::ClosureCreation(closure_creation) => match closure_creation {
396 ClosureCreation::Function(function_closure_creation) => {
397 if let Some(codebase) = &self.codebase
398 && let Expression::Identifier(name) = function_closure_creation.function.as_ref()
399 {
400 let (full_name, short_name) = resolve_name(self.interner, name.value());
401
402 if let Some(function) = codebase.get_function(self.interner, full_name) {
403 return TypeKind::from(function);
404 }
405
406 if let Some(function) = codebase.get_function(self.interner, &short_name) {
408 return TypeKind::from(function);
409 }
410 }
411
412 if let TypeKind::Callable(callable) = self.resolve(&function_closure_creation.function) {
413 match callable {
414 CallableTypeKind::Callable { pure, templates, parameters, return_kind } => {
415 return TypeKind::Callable(CallableTypeKind::Closure {
416 pure,
417 templates,
418 parameters,
419 return_kind,
420 });
421 }
422 closure @ CallableTypeKind::Closure { .. } => {
423 return TypeKind::Callable(closure);
424 }
425 }
426 }
427
428 any_closure_kind()
429 }
430 ClosureCreation::Method(method_closure_creation) => {
431 if let Some(codebase) = &self.codebase {
432 let ClassLikeMemberSelector::Identifier(method_name) = &method_closure_creation.method else {
433 return any_closure_kind();
434 };
435
436 let TypeKind::Object(object_kind) = self.resolve(&method_closure_creation.object) else {
437 return any_closure_kind();
438 };
439
440 let class_reflection = match object_kind {
441 ObjectTypeKind::NamedObject { name, .. } => {
442 if let Some(class_like) = codebase.get_named_class_like(self.interner, &name) {
443 class_like
444 } else {
445 return any_closure_kind();
446 }
447 }
448 ObjectTypeKind::EnumCase { enum_name, .. } => {
449 if let Some(class_like) = codebase.get_enum(self.interner, &enum_name) {
450 class_like
451 } else {
452 return any_closure_kind();
453 }
454 }
455 ObjectTypeKind::AnonymousObject { span } => {
456 if let Some(class) = codebase.get_class_like(&ClassLikeName::AnonymousClass(span)) {
457 class
458 } else {
459 return any_closure_kind();
460 }
461 }
462 _ => return any_closure_kind(),
463 };
464
465 if let Some(method) = class_reflection.methods.members.get(&method_name.value) {
466 return TypeKind::from(method);
467 } else {
468 return any_closure_kind();
469 }
470 }
471
472 any_closure_kind()
473 }
474 ClosureCreation::StaticMethod(static_method_closure_creation) => {
475 if let Some(codebase) = &self.codebase {
476 let Expression::Identifier(class_name) = static_method_closure_creation.class.as_ref() else {
477 return any_closure_kind();
478 };
479
480 let ClassLikeMemberSelector::Identifier(method_name) = &static_method_closure_creation.method
481 else {
482 return any_closure_kind();
483 };
484
485 let class_name = self.names.get(class_name);
486 let Some(class_reflection) = codebase.get_class(self.interner, class_name) else {
487 return any_closure_kind();
488 };
489
490 if let Some(method) = class_reflection.methods.members.get(&method_name.value) {
491 return TypeKind::from(method);
492 } else {
493 return any_closure_kind();
494 }
495 }
496
497 any_closure_kind()
498 }
499 },
500 Expression::Parent(_) => TypeKind::Scalar(ScalarTypeKind::ClassString(None)),
501 Expression::Static(_) => TypeKind::Scalar(ScalarTypeKind::ClassString(None)),
502 Expression::Self_(_) => TypeKind::Scalar(ScalarTypeKind::ClassString(None)),
503 Expression::Instantiation(instantiation) => {
504 let Expression::Identifier(class_name) = instantiation.class.as_ref() else {
505 return any_object_kind();
506 };
507
508 let class_name = self.names.get(class_name);
509
510 TypeKind::Object(ObjectTypeKind::NamedObject { name: *class_name, type_parameters: vec![] })
511 }
512 Expression::MagicConstant(magic_constant) => match &magic_constant {
513 MagicConstant::Line(local_identifier) => {
514 let line = self.source.line_number(local_identifier.offset());
515
516 value_integer_kind(line as i64)
517 }
518 MagicConstant::File(_) => {
519 if let Some(file) = &self.source.path {
520 let file_id = self.interner.intern(file.to_string_lossy());
521
522 get_literal_string_value_kind(self.interner, file_id, false)
523 } else {
524 non_empty_string_kind()
525 }
526 }
527 MagicConstant::Directory(_) => {
528 if let Some(directory) = self.source.path.as_ref().and_then(|p| p.parent()) {
529 let directory_id = self.interner.intern(directory.to_string_lossy());
530
531 get_literal_string_value_kind(self.interner, directory_id, false)
532 } else {
533 non_empty_string_kind()
534 }
535 }
536 MagicConstant::Trait(_) => union_kind(vec![
537 TypeKind::Scalar(ScalarTypeKind::TraitString),
538 value_string_kind(
539 StringIdentifier::empty(),
540 0,
541 Trinary::False,
542 Trinary::False,
543 Trinary::False,
544 Trinary::False,
545 ),
546 ]),
547 MagicConstant::Method(_)
548 | MagicConstant::Function(_)
549 | MagicConstant::Property(_)
550 | MagicConstant::Namespace(_) => TypeKind::Scalar(ScalarTypeKind::LiteralString),
551 MagicConstant::Class(_) => TypeKind::Scalar(ScalarTypeKind::ClassString(None)),
552 },
553 Expression::ArrayAppend(_) => never_kind(),
555 Expression::List(_) => never_kind(),
556 _ => mixed_kind(false),
558 }
559 }
560}