yog-script-analyze 0.2.4

Yog Script (.ys, .yns) analyzed: its lexer, parser and syntax tree, names resolved, the class index model — what the build and the editors read a source with.
Documentation
//! Types as written (`ast::Type`) to types as checked: the built-in ones,
//! the units, the source's own, collections, and by namespace Yog's and
//! the game's (`names`) — whose side must be the code's.

use yog_ids::script_problem;

use super::Checker;
use super::model::Type;
use crate::ast::{self, TypeKind};
use crate::index::Side;
use crate::source::token::Span;
use crate::words;
use yog_script_ids::system::ROOT;

impl Checker<'_, '_, '_> {
    pub(super) fn type_of(&mut self, t: &ast::Type) -> Type {
        match &t.kind {
            TypeKind::Nullable(inner) => self.type_of(inner).nullable(),
            TypeKind::Array { item, rank } => Type::Array(Box::new(self.type_of(item)), *rank),
            TypeKind::Tuple(items) => Type::Tuple(
                items.iter().map(|(t, _)| self.type_of(t)).collect(),
                items
                    .iter()
                    .map(|(_, n)| n.as_ref().map(|n| n.text.clone()))
                    .collect(),
            ),
            // A borrow is typed as what it lends; the borrow checker sees the rest.
            TypeKind::Borrow { ty, .. } => self.type_of(ty),
            TypeKind::Lifetime(_) => Type::Unknown,
            // `A & B`: an object with both; checked as the first.
            TypeKind::Both(all) => all.first().map_or(Type::Unknown, |t| self.type_of(t)),
            TypeKind::Path { path, args } => {
                let args: Vec<Type> = args.iter().map(|a| self.type_of(a)).collect();
                let typed = match path.as_slice() {
                    [one] => self.named(&one.text, args, t.span),
                    // `System.Collections.Generic.List<T>`: its arguments too.
                    _ => match self.qualified(path, t.span) {
                        Type::Own(name, given) if given.is_empty() => Type::Own(name, args),
                        other => other,
                    },
                };
                self.type_meets(&typed, t.span);
                typed
            }
        }
    }

    /// A type by its one name.
    fn named(&mut self, name: &str, args: Vec<Type>, at: Span) -> Type {
        if let Some(t) = super::builtin::by_word(name) {
            return t;
        }
        if yog_script_ids::units::GAME_UNITS.contains(&name) {
            return Type::Unit(name.to_string());
        }
        // `ValueTuple<A, B>` is `(A, B)`, as C#'s.
        if name == yog_script_ids::system::VALUE_TUPLE && !args.is_empty() {
            let names = vec![None; args.len()];
            return Type::Tuple(args, names);
        }
        if self.own.units.iter().any(|u| u == name) {
            return Type::Unit(name.to_string());
        }
        if let Some(t) = self.type_param(name) {
            return t;
        }
        // `String` is `string`, `Int32` `i32` — unless the code's own
        // namespaces declare a type of the name.
        if let Some(b) = super::builtin::by_system_name(name)
            && self.own.namespaces.get(name).is_none_or(|ns| *ns == [ROOT])
        {
            return b;
        }
        // `ItemId`, `BlockId`, …; `ContentId<T>`: content's ids, unless the
        // code declares its own of the name.
        let own = self.own.get(name, args.len()).is_some() && self.own_in_reach(name);
        let yog = |c: &Self| c.own.namespaces.get(name).is_none_or(|ns| *ns == super::names::yog_ns());
        if let Some(kind) = super::content_ids::content_id_kind(name)
            && args.is_empty()
            && (!own || yog(self))
        {
            return Type::Content(kind.to_string());
        }
        if name == yog_script_ids::system::CONTENT_ID && args.len() == 1 && yog(self) {
            return super::content_ids::content_id_of(args);
        }
        if name == yog_script_ids::system::TAG_ID && args.len() == 1 && yog(self) {
            return super::content_ids::tag_id_of(args);
        }
        if own {
            return Type::Own(name.to_string(), args);
        }
        let t = self.imported(name, at);
        // A `.yns` checked without the game's classes can't know them.
        let blind = self.nanites && self.lookup.is_none();
        if !t.known() && !blind {
            self.report.error(
                script_problem::UNKNOWN_TYPE,
                at,
                format!("no type `{name}` here: is a `using` missing?"),
            );
        }
        t
    }

    /// A game class of `side` used where the code runs on `self.side`.
    pub(super) fn check_side(&mut self, side: Side, name: &str, at: Span) {
        if side == Side::Shared || side == self.side {
            return;
        }
        let (theirs, ours) = (side_word(side), side_word(self.side));
        self.report.error(
            script_problem::WRONG_SIDE,
            at,
            format!("`{name}` exists only on the {theirs}: {ours} code can't reach it (mark the method `{theirs}`)"),
        );
    }
}

fn side_word(side: Side) -> &'static str {
    match side {
        Side::Client => words::client::WORD.text,
        Side::Server => words::server::WORD.text,
        Side::Shared => words::shared::WORD.text,
    }
}