yog-script-analyze 0.2.2

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
//! Indexing (`x[i]`): a call of the indexer the indexed type declares or
//! inherits (`this[i32 i]`), its parameters matched as a method's are.

use yog_ids::script_problem;

use super::Checker;
use super::members::Callee;
use super::model::Type;
use crate::ast::{Arg, Expr};
use crate::source::token::Span;

impl Checker<'_, '_, '_> {
    /// `on[index]`: what the indexer gives.
    pub(super) fn indexed(&mut self, on: Type, index: &Expr, at: Span) -> Type {
        match on {
            Type::List(item) => {
                self.expr(index);
                return *item;
            }
            Type::Map(_, value) => {
                self.expr(index);
                return *value;
            }
            _ => {}
        }
        let Some(callee) = self.indexer(&on) else {
            self.expr(index);
            return Type::Unknown;
        };
        let given = vec![self.expr(index)];
        let args = [Arg {
            name: None,
            pass: None,
            value: index.clone(),
        }];
        self.called(callee, &given, &args, at)
    }

    /// `on[…] = v` where `on`'s indexers are all `get`-only: said so.
    pub(super) fn index_written(&mut self, on: &Expr, at: Span) {
        let Some(Type::Own(type_name, _)) = self.types.of(on).map(Type::non_null).cloned() else {
            return;
        };
        let indexers: Vec<bool> = self
            .own
            .lineage(&type_name)
            .iter()
            .filter_map(|of| self.own.types.get(of))
            .flat_map(|t| t.indexers.iter().map(|i| i.settable))
            .collect();
        if !indexers.is_empty() && !indexers.contains(&true) {
            self.report.error(
                script_problem::ASSIGN_TO_CONSTANT,
                at,
                format!(
                    "`{type_name}`'s indexer has no `set`: its items are read here, never written"
                ),
            );
        }
    }

    /// The indexers of `on`'s type and of what it inherits, as a callee.
    fn indexer(&self, on: &Type) -> Option<Callee> {
        let Type::Own(type_name, args) = on.non_null() else {
            return None;
        };
        let getters: Vec<_> = self
            .own
            .lineage(type_name)
            .iter()
            .filter_map(|of| self.own.types.get(of))
            .flat_map(|t| t.indexers.iter().map(|i| i.get.clone()))
            .collect();
        (!getters.is_empty()).then(|| Callee::Own(getters, self.bound_of(type_name, args)))
    }
}