Skip to main content

yog_script_analyze/typeck/
mod.rs

1//! The type checker: each expression's type found, and what doesn't fit
2//! reported — a value of one type where another is wanted, `null` where it
3//! can't be, a member a type doesn't have, a call that doesn't match, a
4//! game class of the other side. The game's types come from the class
5//! index of a targeted version (C#'s names over Java's); what neither the
6//! source nor the index knows (the library's) is not checked yet.
7
8mod arrays;
9mod attributes;
10mod awaits;
11mod betterness;
12mod bits;
13mod builtin;
14mod callees;
15mod calls;
16mod collections;
17pub mod commands;
18mod constraints;
19mod content;
20mod content_ids;
21mod context;
22mod copy;
23mod ctors;
24mod decls;
25mod delegates;
26mod exprs;
27mod game;
28mod generics;
29mod groups;
30mod indexers;
31mod inherit;
32mod iterators;
33mod list_patterns;
34mod lambdas;
35mod lineage;
36mod macro_calls;
37mod members;
38mod messages;
39mod model;
40mod mutation;
41mod names;
42mod nulls;
43mod operators;
44mod overloads;
45mod own;
46mod passing;
47mod pick;
48mod shapes;
49mod required;
50mod saved;
51mod stmts;
52mod templates;
53pub(crate) mod surface;
54mod threads;
55mod tuples;
56mod types;
57mod variance;
58mod variants;
59mod written;
60
61use std::collections::HashMap;
62
63pub use content::content_path;
64pub use iterators::{body_yields, item_of};
65pub use model::Type;
66pub use nulls::leaves;
67pub use list_patterns::list_item;
68pub use templates::{MessageTemplate, holes};
69pub use types::{Message, MethodUse, OperatorUse, Quoted, Types};
70
71use crate::ast::File;
72use crate::index::{ClassIndex, Lookup, Side};
73use own::OwnTypes;
74use yog_analysis::Report;
75
76/// A local or a parameter: its type, and whether it's read-only.
77struct Slot {
78    ty: Type,
79    fixed: bool,
80}
81
82pub(crate) struct Checker<'r, 's, 'i> {
83    report: &'r mut Report<'s>,
84    lookup: Option<Lookup<'i>>,
85    own: OwnTypes,
86    /// Locals and parameters, innermost scope last.
87    scopes: Vec<HashMap<String, Slot>>,
88    /// The code is a `readonly` member's: `this` is lent read-only.
89    /// The members being checked are a content declaration's: its
90    /// properties its kind's (`content.rs`).
91    in_content: bool,
92    readonly_this: bool,
93    /// The type whose members the code is in (`this`).
94    this: Option<Type>,
95    /// The side the code runs on.
96    side: Side,
97    /// What a `return` in the code must give (`None`: not in a method).
98    returns: Option<Type>,
99    /// An iterator's item type, while its body is checked (`yield`).
100    yields: Option<Type>,
101    /// What the `switch` statements around the code match, innermost last.
102    switched: Vec<Type>,
103    /// The type being checked owns state: its members may be `saved` (`saved.rs`).
104    state_owner: bool,
105    /// While a lambda's result is inferred: what its `return`s give.
106    inferred: Option<Vec<Type>>,
107    /// The file's `using` directives.
108    directives: names::Directives,
109    /// The namespace the code is in.
110    ns: Vec<String>,
111    /// A `.yns`: the game's classes reachable.
112    nanites: bool,
113    /// Its mod says it sends to the server.
114    to_server: bool,
115    /// The mod's content files, each (kind, id); `None`: not known.
116    content: Option<Vec<(String, String)>>,
117    /// The type parameters in scope, innermost last.
118    type_params: Vec<generics::Bound>,
119    /// Each expression's type found.
120    types: Types,
121    /// Each own type's bases, typed.
122    bases: lineage::Bases,
123    /// Each declared delegate's signature, typed.
124    delegates: delegates::Delegates,
125    /// Each type's operators, typed.
126    operators: operators::Operators,
127    /// The lambdas being checked, innermost last: each by its expression,
128    /// and the scopes outside it (a local of one of those it captures).
129    lambdas: Vec<(usize, usize)>,
130    /// The `unsafe` blocks the code is in: the threads checks skipped.
131    unsafe_code: usize,
132    /// The type parameters' `where` constraints in scope, innermost last.
133    constraints: Vec<constraints::Constraints>,
134    /// Constraints are being checked (a bound's own type isn't, again).
135    meeting: bool,
136    /// The names known not null (`nulls.rs`), each with how many scopes
137    /// deep it was proven: it holds for locals of the scopes outside that.
138    non_null: Vec<(usize, String)>,
139    /// The local functions in reach, each with how many scopes deep its
140    /// block is (`stmts.rs`).
141    local_fns: Vec<(usize, crate::ast::Method)>,
142    /// An index's arguments are being checked: `^n` counts from the end.
143    indexing: bool,
144}
145
146/// What a source may reach: the game's classes (a `.yns`), sending to the
147/// server (its mod says so).
148pub(crate) struct Allowed {
149    pub nanites: bool,
150    pub to_server: bool,
151    /// The mod's content files, each (kind, id), when known: what its
152    /// content declarations bind to.
153    pub content: Option<Vec<(String, String)>>,
154}
155
156/// The built-in type its keyword names (`i32`, `string`): the one table
157/// of them, for whatever lowers types too.
158pub fn builtin_type(word: &str) -> Option<Type> {
159    builtin::by_word(word)
160}
161
162/// The built-in type `System`'s type `name` is (`Int32` is `i32`, `Void`
163/// `void`): the same table read the other way.
164pub fn system_type(name: &str) -> Option<Type> {
165    builtin::by_system_name(name)
166        .or_else(|| content_ids::content_id_kind(name).map(|k| Type::Content(k.to_string())))
167}
168
169/// A built-in type's type in `System` (`string`'s `String`), which a
170/// `[Host]` declaration gives its members.
171pub fn system_name(t: &Type) -> Option<&'static str> {
172    builtin::system_name(t)
173}
174
175/// `file`'s types checked against `index` (a targeted version's) and its
176/// project's `others`, what doesn't fit reported.
177pub(crate) fn check<'d>(
178    file: &'d File,
179    allowed: Allowed,
180    (index, others): (Option<&ClassIndex>, &[&'d [crate::ast::Decl]]),
181    report: &mut Report<'_>,
182) -> Types {
183    let mut own = OwnTypes::collect_project(&crate::system::DECLS, others, &file.decls);
184    for decls in others {
185        own.extensions(decls);
186    }
187    own.extensions(&file.decls);
188    let mut c = Checker {
189        report,
190        lookup: index.map(ClassIndex::lookup),
191        own,
192        scopes: vec![HashMap::new()],
193        this: None,
194        side: Side::Shared,
195        returns: None,
196        yields: None,
197        switched: Vec::new(),
198        state_owner: false,
199        inferred: None,
200        directives: names::Directives::collect(&file.decls),
201        ns: Vec::new(),
202        nanites: allowed.nanites,
203        to_server: allowed.to_server,
204        content: allowed.content,
205        type_params: Vec::new(),
206        readonly_this: false,
207        in_content: false,
208        types: Types::default(),
209        bases: lineage::Bases::new(),
210        delegates: delegates::Delegates::new(),
211        operators: operators::Operators::new(),
212        lambdas: Vec::new(),
213        unsafe_code: 0,
214        constraints: Vec::new(),
215        meeting: false,
216        non_null: Vec::new(),
217        local_fns: Vec::new(),
218        indexing: false,
219    };
220    c.type_bases();
221    c.type_delegates();
222    c.type_operators();
223    c.decls(&file.decls);
224    inherit::check((&crate::system::DECLS, others), &file.decls, c.report);
225    c.types
226}
227
228impl Checker<'_, '_, '_> {
229    /// Runs `f` in a scope of its own.
230    fn scoped<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
231        self.scopes.push(HashMap::new());
232        let out = f(self);
233        self.scopes.pop();
234        self.scopes_left(self.scopes.len());
235        let depth = self.scopes.len();
236        self.local_fns.retain(|(d, _)| *d <= depth);
237        out
238    }
239
240    fn bind(&mut self, name: &str, t: Type) {
241        self.bind_as(name, t, false);
242    }
243
244    /// `name` bound to a `t`; `fixed`: what it holds is read-only here (a
245    /// `let`, an `in` parameter, a `foreach` item, a pattern's name).
246    fn bind_as(&mut self, name: &str, ty: Type, fixed: bool) {
247        // A new local of the name: nothing proven of an older one holds.
248        self.non_null.retain(|(_, n)| n != name);
249        if let Some(s) = self.scopes.last_mut() {
250            s.insert(name.to_string(), Slot { ty, fixed });
251        }
252    }
253
254    fn local(&self, name: &str) -> Option<&Type> {
255        self.slot(name).map(|s| &s.ty)
256    }
257
258    fn slot(&self, name: &str) -> Option<&Slot> {
259        self.scopes.iter().rev().find_map(|s| s.get(name))
260    }
261
262    /// Whether a `got` goes where `wanted` is: as it is, or through an
263    /// implicit conversion one of them declares.
264    fn converts(&self, got: &Type, wanted: &Type) -> bool {
265        self.converts_plainly(got, wanted) || self.implicit(got, wanted).is_some()
266    }
267
268    /// Whether a `got` goes where `wanted` is as it is: a built-in
269    /// conversion, or an own type that is the one wanted (inherits or
270    /// implements it).
271    fn converts_plainly(&self, got: &Type, wanted: &Type) -> bool {
272        if got.converts_to(wanted) {
273            return true;
274        }
275        if got.is_nullable() && !wanted.is_nullable() {
276            return false;
277        }
278        match (got.non_null(), wanted.non_null()) {
279            // An own type, or one it inherits, given the arguments wanted.
280            (Type::Own(a, xs), Type::Own(b, ys)) => self.own_converts(a, xs, (b, ys), true),
281            (Type::Array(..), wanted) => self.array_converts(got.non_null(), wanted),
282            // An `ItemId` is a `ContentId<T>` (generic code's), a tag a `TagId<T>` too.
283            (got @ (Type::Content(_) | Type::Tag(_)), wanted) => content_ids::id_converts(got, wanted),
284            (Type::Tuple(got, _), Type::Tuple(wanted, _)) => self.tuple_converts(got, wanted),
285            // A lambda or a method where a delegate of its shape is wanted.
286            (Type::Fn(..), Type::Own(..)) => {
287                match (self.delegate_sig(got), self.delegate_sig(wanted)) {
288                    (Some((gp, gr)), Some((wp, wr))) => {
289                        gp.len() == wp.len()
290                            && wp.iter().zip(&gp).all(|(w, g)| self.converts(w, g))
291                            && self.converts(&gr, &wr)
292                    }
293                    _ => false,
294                }
295            }
296            _ => false,
297        }
298    }
299
300    /// `base`: `this` as the class it inherits.
301    fn base(&self) -> Type {
302        match &self.this {
303            Some(Type::Own(name, _)) => self
304                .own
305                .base_class(name)
306                .map_or(Type::Unknown, |b| Type::own(b.to_string())),
307            _ => Type::Unknown,
308        }
309    }
310
311    /// Runs `f` with the code's `this`, side and expected result set.
312    fn within<R>(
313        &mut self,
314        this: Option<Type>,
315        side: Side,
316        returns: Option<Type>,
317        f: impl FnOnce(&mut Self) -> R,
318    ) -> R {
319        let saved = (
320            std::mem::replace(&mut self.this, this),
321            self.side,
322            std::mem::replace(&mut self.returns, returns),
323        );
324        self.side = side;
325        let out = self.scoped(f);
326        (self.this, self.side, self.returns) = saved;
327        out
328    }
329}