Skip to main content

mir_php/
types.rs

1/// PHP type representation used throughout mir-php.
2use std::collections::HashMap;
3
4/// A PHP type.
5#[derive(Debug, Clone, PartialEq)]
6pub enum Ty {
7    /// Type is not known / not yet inferred.
8    Unknown,
9    /// The `never` return type (function never returns normally).
10    Never,
11    /// The `void` return type.
12    Void,
13    /// `null`.
14    Null,
15    /// `bool`.
16    Bool,
17    /// `int`.
18    Int,
19    /// `float`.
20    Float,
21    /// `string`.
22    Str,
23    /// Untyped `array`.
24    Array,
25    /// `callable`.
26    Callable,
27    /// A named object type (fully-qualified or short class/interface name).
28    Object(String),
29    /// `T1|T2|…`
30    Union(Vec<Ty>),
31    /// `T1&T2&…`
32    Intersection(Vec<Ty>),
33}
34
35impl Ty {
36    /// Parse from a PHP type-hint string (e.g. `"?int"`, `"Foo|null"`).
37    pub fn from_str(s: &str) -> Self {
38        let s = s.trim();
39        if s.contains('|') {
40            let parts: Vec<Ty> = s.split('|').map(Ty::from_str).collect();
41            return Ty::Union(parts);
42        }
43        if s.contains('&') {
44            let parts: Vec<Ty> = s.split('&').map(Ty::from_str).collect();
45            return Ty::Intersection(parts);
46        }
47        if let Some(inner) = s.strip_prefix('?') {
48            return Ty::Union(vec![Ty::from_str(inner), Ty::Null]);
49        }
50        match s {
51            "null" | "NULL" => Ty::Null,
52            "bool" | "boolean" => Ty::Bool,
53            "int" | "integer" => Ty::Int,
54            "float" | "double" => Ty::Float,
55            "string" => Ty::Str,
56            "array" => Ty::Array,
57            "callable" => Ty::Callable,
58            "void" => Ty::Void,
59            "never" => Ty::Never,
60            "mixed" | "" => Ty::Unknown,
61            name => Ty::Object(name.to_string()),
62        }
63    }
64
65    /// If this is a single named object type, return its class name.
66    pub fn class_name(&self) -> Option<&str> {
67        match self {
68            Ty::Object(n) => Some(n),
69            _ => None,
70        }
71    }
72
73    /// True if `null` is a valid value for this type.
74    pub fn is_nullable(&self) -> bool {
75        match self {
76            Ty::Null | Ty::Unknown => true,
77            Ty::Union(ts) => ts.iter().any(|t| matches!(t, Ty::Null)),
78            _ => false,
79        }
80    }
81}
82
83/// Maps variable name (with `$`) → inferred `Ty`.
84#[derive(Debug, Default, Clone)]
85pub struct TypeEnv(pub HashMap<String, Ty>);
86
87impl TypeEnv {
88    pub fn get(&self, var: &str) -> &Ty {
89        self.0.get(var).unwrap_or(&Ty::Unknown)
90    }
91
92    /// Convenience: return the class name when the type is `Object(_)`.
93    pub fn class_name(&self, var: &str) -> Option<&str> {
94        self.0.get(var)?.class_name()
95    }
96
97    pub fn insert(&mut self, var: String, ty: Ty) {
98        self.0.insert(var, ty);
99    }
100}