mago_typing/
resolver.rs

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
19/// A basic type resolver designed to initialize types at the beginning of type checking.
20/// This resolver is intentionally simple and acts as a "bootstrap" to get type information
21/// that can later be narrowed down for more precise type inference.
22///
23/// While this type resolver is limited in detail, it provides enough information for initial
24/// analysis and bootstrapping. However, it requires a subsequent pass to refine types.
25///
26/// ### Capabilities
27///
28/// This resolver can completely evaluate constant expressions in most cases and produce a type
29/// with a known value, such as `Value::Float(16.5)` for expressions like `((12 / 2) + 5) * 1.5`.
30///
31/// ### Improved Type Resolution with Codebase Context
32///
33/// If a codebase is available, this resolver can leverage it to retrieve types of functions,
34/// methods, and constants, making it slightly more powerful in providing initial type information.
35pub 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                // could be better..
85                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                // could be better..
96                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                        // fallback to short name, welcome to PHP.
407                        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            // Non-readable expressions
554            Expression::ArrayAppend(_) => never_kind(),
555            Expression::List(_) => never_kind(),
556            // Requires more context
557            _ => mixed_kind(false),
558        }
559    }
560}