Skip to main content

nifty_check/checker/
args.rs

1//! Matching call arguments to parameters: positional ones by order, named ones by name.
2
3use nifty_syntax::Span;
4use nifty_syntax::ast::{Arg, Expr};
5
6use super::Checker;
7use crate::elements::Param;
8
9pub(super) struct Args<'a> {
10    callee: String,
11    /// A built-in's parameters from its table, which every name the checker asks for must
12    /// be in, so tools reading the table see the same arguments.
13    declared: Option<&'static [Param]>,
14    positional: Vec<&'a Expr>,
15    positional_taken: usize,
16    named: Vec<(&'a str, &'a Expr, Span, bool)>,
17}
18
19impl<'a> Args<'a> {
20    /// `callee` names what is called in error messages, such as "`label`".
21    pub(super) fn new(checker: &mut Checker, callee: String, args: &'a [Arg]) -> Self {
22        let mut positional = Vec::new();
23        let mut named: Vec<(&str, &Expr, Span, bool)> = Vec::new();
24        for arg in args {
25            match &arg.label {
26                None => positional.push(&arg.value),
27                Some(label) => {
28                    if named.iter().any(|(name, ..)| *name == label.name) {
29                        checker.error(format!("`{}` is given twice", label.name), label.span);
30                    } else {
31                        named.push((&label.name, &arg.value, label.span, false));
32                    }
33                }
34            }
35        }
36        Self {
37            callee,
38            declared: None,
39            positional,
40            positional_taken: 0,
41            named,
42        }
43    }
44
45    /// Arguments to the built-in `name`, whose table entry declares `params`.
46    pub(super) fn built_in(
47        checker: &mut Checker,
48        name: &str,
49        params: &'static [Param],
50        args: &'a [Arg],
51    ) -> Self {
52        Self {
53            declared: Some(params),
54            ..Self::new(checker, format!("`{name}`"), args)
55        }
56    }
57
58    /// The next positional argument, if one was given.
59    pub(super) fn positional(&mut self) -> Option<&'a Expr> {
60        let arg = self.positional.get(self.positional_taken).copied();
61        if arg.is_some() {
62            self.positional_taken += 1;
63        }
64        arg
65    }
66
67    pub(super) fn named(&mut self, name: &str) -> Option<&'a Expr> {
68        debug_assert!(
69            self.declared
70                .is_none_or(|params| params.iter().any(|param| param.name == name)),
71            "{} reads `{name}`, which its table entry doesn't declare",
72            self.callee
73        );
74        let entry = self.named.iter_mut().find(|(label, ..)| *label == name)?;
75        entry.3 = true;
76        Some(entry.1)
77    }
78
79    /// The parameter's argument, given by position or by name.
80    pub(super) fn param(&mut self, name: &str) -> Option<&'a Expr> {
81        self.positional().or_else(|| self.named(name))
82    }
83
84    /// Reports arguments nothing asked for.
85    pub(super) fn finish(self, checker: &mut Checker) {
86        for extra in &self.positional[self.positional_taken..] {
87            checker.error(
88                format!("{} doesn't take this many arguments", self.callee),
89                extra.span,
90            );
91        }
92        for (name, _, span, used) in &self.named {
93            if !used {
94                checker.error(
95                    format!("{} has no argument named `{name}`", self.callee),
96                    *span,
97                );
98            }
99        }
100    }
101}