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    /// A quantity with a unit: `Ticks`, `Seconds`, `Blocks`, a mod's own.
33    Unit(String),
34    /// A content's id of a kind: an `ItemId` is `Content("item")` (`content_ids.rs`).
35    Content(String),
36    /// A tag of a content kind: a `TagId<ItemId>` is `Tag("item")`
37    /// (`content_ids.rs`); `""` of kinds not one.
38    Tag(String),
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    /// A type named where a value goes (`Math` in `Math.Max`), with the
48    /// type arguments written (`Comparer<i32>` in `Comparer<i32>.Default`).
49    Static(String, Vec<Type>),
50    /// A game class, by its internal name (`.yns` only).
51    Game(String),
52    /// Yog's: a handle (`Player`), a domain's group (`Chat`), a struct or
53    /// an enum an action answers with — by name.
54    Yog(String),
55    /// A namespace (`Game.World`), on the way to what's in it.
56    Namespace(String),
57    /// An array: `T[]` (rank 1), `T[,]` (rank 2); a Java array a game
58    /// class gives or takes too.
59    Array(Box<Type>, usize),
60    /// `(i32 X, string)`: its items' types, and their names where given
61    /// (names don't make tuple types apart: `(i32 X)` is `(i32 Y)`).
62    Tuple(Vec<Type>, Vec<Option<String>>),
63    /// A delegate: `Action<A, B>` (its result `void`), `Func<A, R>`.
64    Fn(Vec<Type>, Box<Type>),
65    Nullable(Box<Type>),
66    /// Not known here (the library's, or what couldn't be typed): checks
67    /// involving it pass, so one gap doesn't cascade.
68    Unknown,
69}
70
71/// The units a whole game has; a mod declares its own (`unit Energy;`).
72pub use yog_script_ids::units::{BLOCKS, SECONDS, TICKS};
73
74impl Type {
75    /// An own type that isn't generic.
76    pub fn own(name: impl Into<String>) -> Type {
77        Type::Own(name.into(), Vec::new())
78    }
79
80    pub fn nullable(self) -> Type {
81        match self {
82            Type::Nullable(_) | Type::Null | Type::Unknown => self,
83            other => Type::Nullable(Box::new(other)),
84        }
85    }
86
87    /// The type without its `?`.
88    pub fn non_null(&self) -> &Type {
89        match self {
90            Type::Nullable(t) => t,
91            t => t,
92        }
93    }
94
95    pub fn is_nullable(&self) -> bool {
96        matches!(self, Type::Nullable(_) | Type::Null)
97    }
98
99    pub fn known(&self) -> bool {
100        !matches!(self, Type::Unknown)
101    }
102
103    pub fn is_numeric(&self) -> bool {
104        builtin::is_number(self) || matches!(self, Type::Unit(_))
105    }
106
107    pub fn is_integer(&self) -> bool {
108        builtin::is_integer(self)
109    }
110
111    /// Whether a value of `self` goes where `to` is wanted without a cast.
112    pub fn converts_to(&self, to: &Type) -> bool {
113        if self == to || !self.known() || !to.known() {
114            return true;
115        }
116        if matches!(self, Type::Dynamic) || matches!(to, Type::Dynamic | Type::Object) {
117            return true;
118        }
119        match (self, to) {
120            (Type::Null, Type::Nullable(_)) => true,
121            (Type::Nullable(a), Type::Nullable(b)) => a.converts_to(b),
122            (a, Type::Nullable(b)) => a.converts_to(b),
123            // Quantities of different units never mix: seconds are real
124            // time, ticks the game's (its rate changes), made one another
125            // only by the game.
126            (Type::Unit(_), Type::Unit(_)) => false,
127            // A plain number is a quantity of any unit (`20` ticks), as
128            // literals are; quantities of different units never mix.
129            (n, Type::Unit(_)) => builtin::is_number(n),
130            (Type::Unit(_), n) => builtin::widens(&Type::I32, n),
131            (Type::Array(a, ra), Type::Array(b, rb)) => ra == rb && a == b,
132            (a, b) => builtin::widens(a, b),
133        }
134    }
135
136    /// The type both of two numbers become in an operation (`i32 + f64` is `f64`).
137    /// A whole number's width in bits and whether it's signed (`u8`: 8, no).
138    pub fn integer_width(&self) -> Option<(u32, bool)> {
139        builtin::integer_width(self)
140    }
141
142    pub fn wider(a: &Type, b: &Type) -> Type {
143        match (a, b) {
144            (Type::Unit(_), _) => a.clone(),
145            (_, Type::Unit(_)) => b.clone(),
146            _ if builtin::widens(a, b) => b.clone(),
147            _ => a.clone(),
148        }
149    }
150}
151
152impl fmt::Display for Type {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        match self {
155            Type::I8
156            | Type::I16
157            | Type::I32
158            | Type::I64
159            | Type::U8
160            | Type::U16
161            | Type::U32
162            | Type::U64
163            | Type::F32
164            | Type::F64
165            | Type::Decimal
166            | Type::Char
167            | Type::Bool
168            | Type::String
169            | Type::Object
170            | Type::Dynamic
171            | Type::Void => write!(f, "{}", builtin::word(self).unwrap_or_default()),
172            Type::Null => write!(f, "{}", crate::words::null::WORD.text),
173            Type::Own(name, args) if !args.is_empty() => {
174                let args: Vec<_> = args.iter().map(ToString::to_string).collect();
175                write!(f, "{name}<{}>", args.join(", "))
176            }
177            Type::Unit(u)
178            | Type::Own(u, _)
179            | Type::Static(u, _)
180            | Type::Param(u)
181            | Type::Yog(u)
182            | Type::Namespace(u) => {
183                write!(f, "{u}")
184            }
185            Type::Content(kind) => write!(f, "{}", super::content_ids::content_id_name(kind)),
186            Type::Tag(kind) => write!(f, "{}", super::content_ids::tag_id_name(kind)),
187            Type::Game(name) => write!(f, "{}", crate::index::simple_name(name)),
188            // As C# writes it: the item, then the ranks outermost first
189            // (`i32[][,]`, an array of `i32[,]`).
190            Type::Array(..) => {
191                let mut item = self;
192                let mut ranks = String::new();
193                while let Type::Array(inner, rank) = item {
194                    ranks.push_str(&format!("[{}]", ",".repeat(rank - 1)));
195                    item = inner;
196                }
197                write!(f, "{item}{ranks}")
198            }
199            Type::Tuple(items, names) => {
200                let items: Vec<String> = items
201                    .iter()
202                    .zip(names.iter().chain(std::iter::repeat(&None)))
203                    .map(|(t, n)| match n {
204                        Some(n) => format!("{t} {n}"),
205                        None => t.to_string(),
206                    })
207                    .collect();
208                write!(f, "({})", items.join(", "))
209            }
210            Type::Fn(params, ret) => {
211                let mut all: Vec<String> = params.iter().map(ToString::to_string).collect();
212                let word = if **ret == Type::Void {
213                    yog_script_ids::system::ACTION
214                } else {
215                    all.push(ret.to_string());
216                    yog_script_ids::system::FUNC
217                };
218                write!(f, "{word}<{}>", all.join(", "))
219            }
220            Type::Nullable(t) => write!(f, "{t}?"),
221            Type::Unknown => write!(f, "?"),
222        }
223    }
224}
225
226#[cfg(test)]
227mod tests;