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