Skip to main content

yog_script_analyze/typeck/
model.rs

1//! Types as the checker knows them, how they're shown, and which convert
2//! to which without a cast (`ys-grammar.md` §8).
3
4use std::fmt;
5
6use super::builtin;
7
8/// A type.
9#[derive(Debug, Clone, PartialEq)]
10pub enum Type {
11    I8,
12    I16,
13    I32,
14    I64,
15    U8,
16    U16,
17    U32,
18    U64,
19    F32,
20    F64,
21    Decimal,
22    Char,
23    Bool,
24    String,
25    /// `object`: every type converts to it.
26    Object,
27    /// `dynamic`: checked when the code runs, not here.
28    Dynamic,
29    Void,
30    /// `null` before it meets a type.
31    Null,
32    /// Translatable text: `t"key"`.
33    Text,
34    /// A quantity with a unit: `Ticks`, `Seconds`, `Blocks`, a mod's own.
35    Unit(String),
36    /// A content reference of a kind: `@item:…` is `Content("item")`.
37    Content(String),
38    Tag,
39    /// A type the source declares, by name, with its type arguments
40    /// (`Box<i32>`; none when it isn't generic).
41    Own(String, Vec<Type>),
42    /// A type parameter (`T`) where its argument isn't known: in its
43    /// generic's own code, or a method's before inference.
44    Param(String),
45    /// An own type named where a value goes (`Mode` in `Mode.Idle`): its
46    /// static members and its cases.
47    Static(String),
48    /// A game class, by its internal name (`.yns` only).
49    Game(String),
50    /// Yog's: a handle (`Player`), a domain's group (`Chat`), a struct or
51    /// an enum an action answers with — by name.
52    Yog(String),
53    /// A namespace (`Game.World`), on the way to what's in it.
54    Namespace(String),
55    List(Box<Type>),
56    Map(Box<Type>, Box<Type>),
57    Tuple(Vec<Type>),
58    /// A delegate: `Action<A, B>` (its result `void`), `Func<A, R>`.
59    Fn(Vec<Type>, Box<Type>),
60    Nullable(Box<Type>),
61    /// Not known here (the library's, or what couldn't be typed): checks
62    /// involving it pass, so one gap doesn't cascade.
63    Unknown,
64}
65
66/// The units a whole game has; a mod declares its own (`unit Energy;`).
67pub const TICKS: &str = "Ticks";
68pub const SECONDS: &str = "Seconds";
69pub const BLOCKS: &str = "Blocks";
70
71impl Type {
72    /// An own type that isn't generic.
73    pub fn own(name: impl Into<String>) -> Type {
74        Type::Own(name.into(), Vec::new())
75    }
76
77    pub fn nullable(self) -> Type {
78        match self {
79            Type::Nullable(_) | Type::Null | Type::Unknown => self,
80            other => Type::Nullable(Box::new(other)),
81        }
82    }
83
84    /// The type without its `?`.
85    pub fn non_null(&self) -> &Type {
86        match self {
87            Type::Nullable(t) => t,
88            t => t,
89        }
90    }
91
92    pub fn is_nullable(&self) -> bool {
93        matches!(self, Type::Nullable(_) | Type::Null)
94    }
95
96    pub fn known(&self) -> bool {
97        !matches!(self, Type::Unknown)
98    }
99
100    pub fn is_numeric(&self) -> bool {
101        builtin::is_number(self) || matches!(self, Type::Unit(_))
102    }
103
104    /// Whether a value of `self` goes where `to` is wanted without a cast.
105    pub fn converts_to(&self, to: &Type) -> bool {
106        if self == to || !self.known() || !to.known() {
107            return true;
108        }
109        if matches!(self, Type::Dynamic) || matches!(to, Type::Dynamic | Type::Object) {
110            return true;
111        }
112        match (self, to) {
113            (Type::Null, Type::Nullable(_)) => true,
114            (Type::Nullable(a), Type::Nullable(b)) => a.converts_to(b),
115            (a, Type::Nullable(b)) => a.converts_to(b),
116            (Type::Unit(a), Type::Unit(b)) => a == SECONDS && b == TICKS,
117            // A plain number is a quantity of any unit (`20` ticks), as
118            // literals are; quantities of different units never mix.
119            (n, Type::Unit(_)) => builtin::is_number(n),
120            (Type::Unit(_), n) => builtin::widens(&Type::I32, n),
121            (Type::String, Type::Text) => true,
122            (Type::List(a), Type::List(b)) => a.converts_to(b),
123            (a, b) => builtin::widens(a, b),
124        }
125    }
126
127    /// The type both of two numbers become in an operation (`i32 + f64` is `f64`).
128    pub fn wider(a: &Type, b: &Type) -> Type {
129        match (a, b) {
130            (Type::Unit(_), _) => a.clone(),
131            (_, Type::Unit(_)) => b.clone(),
132            _ if builtin::widens(a, b) => b.clone(),
133            _ => a.clone(),
134        }
135    }
136}
137
138impl fmt::Display for Type {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        match self {
141            Type::I8
142            | Type::I16
143            | Type::I32
144            | Type::I64
145            | Type::U8
146            | Type::U16
147            | Type::U32
148            | Type::U64
149            | Type::F32
150            | Type::F64
151            | Type::Decimal
152            | Type::Char
153            | Type::Bool
154            | Type::String
155            | Type::Object
156            | Type::Dynamic
157            | Type::Void => write!(f, "{}", builtin::word(self).unwrap_or_default()),
158            Type::Null => write!(f, "{}", crate::words::null::WORD.text),
159            Type::Text => write!(f, "Text"),
160            Type::Own(name, args) if !args.is_empty() => {
161                let args: Vec<_> = args.iter().map(ToString::to_string).collect();
162                write!(f, "{name}<{}>", args.join(", "))
163            }
164            Type::Unit(u)
165            | Type::Own(u, _)
166            | Type::Static(u)
167            | Type::Param(u)
168            | Type::Yog(u)
169            | Type::Namespace(u) => {
170                write!(f, "{u}")
171            }
172            Type::Content(kind) => write!(f, "@{kind}"),
173            Type::Tag => write!(f, "{}", yog_ids::kind::TAG),
174            Type::Game(name) => write!(f, "{}", crate::index::simple_name(name)),
175            Type::List(t) => write!(f, "List<{t}>"),
176            Type::Map(k, v) => write!(f, "Dictionary<{k}, {v}>"),
177            Type::Tuple(items) => {
178                let items: Vec<_> = items.iter().map(ToString::to_string).collect();
179                write!(f, "({})", items.join(", "))
180            }
181            Type::Fn(params, ret) => {
182                let mut all: Vec<String> = params.iter().map(ToString::to_string).collect();
183                let word = if **ret == Type::Void {
184                    super::written::ACTION
185                } else {
186                    all.push(ret.to_string());
187                    super::written::FUNC
188                };
189                write!(f, "{word}<{}>", all.join(", "))
190            }
191            Type::Nullable(t) => write!(f, "{t}?"),
192            Type::Unknown => write!(f, "?"),
193        }
194    }
195}
196
197#[cfg(test)]
198mod tests;