Skip to main content

bytecode_lang/
call.rs

1//! Dynamic-call signatures: parameter lists, call shapes, and the binding
2//! rule every tier applies to them (`specs/LSB.md` §5.15).
3//!
4//! A typed call (`call`, `call_indirect`, `call_import`) passes exactly the
5//! callee's parameters, arranged at compile time. A *dynamic* call
6//! (`dcall`, `dcall_shape`) only learns its callee at run time, so the
7//! callee carries a [`ParamList`] (names, kinds, by-reference flags,
8//! defaults) and the call site carries a [`CallShape`] (which arguments are
9//! positional, named, or spread). Binding one to the other is written once,
10//! in [`ParamList::bind`], so the VM, the T0 evaluator, and a native tier
11//! cannot disagree on PHP's or Python's argument rules: each tier either
12//! calls it or differential-tests its own fast path against it.
13
14use alloc::collections::BTreeSet;
15use alloc::vec::Vec;
16use core::fmt;
17
18use crate::ids::StrId;
19use crate::module::Module;
20use crate::types::ValType;
21
22code_enum! {
23    /// How a parameter of a [`ParamList`] receives arguments.
24    ///
25    /// The kinds appear in this order in a list: positional-only, normal,
26    /// at most one rest ([`Rest`](ParamKind::Rest) or
27    /// [`RestMap`](ParamKind::RestMap)), named-only, at most one
28    /// [`RestNamed`](ParamKind::RestNamed). This is HIR's `Param` kind set
29    /// (`specs/HIR.md` §10) without the receiver, which is an ordinary
30    /// positional parameter at this level.
31    ///
32    /// # Examples
33    ///
34    /// ```
35    /// use bytecode_lang::ParamKind;
36    ///
37    /// assert_eq!(ParamKind::Normal.code(), 1);
38    /// assert_eq!(ParamKind::RestNamed.to_string(), "rest_named");
39    /// assert!(ParamKind::RestMap.is_rest());
40    /// assert!(ParamKind::NamedOnly.takes_names());
41    /// assert!(!ParamKind::PositionalOnly.takes_names());
42    /// ```
43    ParamKind {
44        /// Bound by position only (Python's parameters before `/`).
45        PositionalOnly = 0 => "positional_only",
46        /// Bound by position or by name (every PHP parameter).
47        Normal = 1 => "normal",
48        /// Bound by name only (Python's parameters after `*`).
49        NamedOnly = 2 => "named_only",
50        /// Collects the extra positional arguments into a new `dyn` array
51        /// (Python `*args`).
52        Rest = 3 => "rest",
53        /// Collects the extra positional arguments into a new `dyn` map under
54        /// the keys `0, 1, ...`, and, when the list has no
55        /// [`RestNamed`](ParamKind::RestNamed), the unknown named arguments
56        /// under their names (PHP `...$args` since 8.1).
57        RestMap = 4 => "rest_map",
58        /// Collects the unknown named arguments into a new `dyn` map from name
59        /// to value (Python `**kwargs`).
60        RestNamed = 5 => "rest_named",
61    }
62}
63
64impl ParamKind {
65    /// Whether the parameter collects arguments into a new container.
66    ///
67    /// # Examples
68    ///
69    /// ```
70    /// use bytecode_lang::ParamKind;
71    ///
72    /// assert!(ParamKind::Rest.is_rest());
73    /// assert!(!ParamKind::Normal.is_rest());
74    /// ```
75    #[must_use]
76    pub const fn is_rest(self) -> bool {
77        matches!(
78            self,
79            ParamKind::Rest | ParamKind::RestMap | ParamKind::RestNamed
80        )
81    }
82
83    /// Whether a named argument can bind to the parameter by its name.
84    ///
85    /// # Examples
86    ///
87    /// ```
88    /// use bytecode_lang::ParamKind;
89    ///
90    /// assert!(ParamKind::Normal.takes_names());
91    /// assert!(!ParamKind::Rest.takes_names());
92    /// ```
93    #[must_use]
94    pub const fn takes_names(self) -> bool {
95        matches!(self, ParamKind::Normal | ParamKind::NamedOnly)
96    }
97
98    /// Whether a positional argument can bind to the parameter.
99    const fn takes_positions(self) -> bool {
100        matches!(self, ParamKind::PositionalOnly | ParamKind::Normal)
101    }
102
103    /// The kind's position in the required order; rest kinds share one rank.
104    const fn rank(self) -> u8 {
105        match self {
106            ParamKind::PositionalOnly => 0,
107            ParamKind::Normal => 1,
108            ParamKind::Rest | ParamKind::RestMap => 2,
109            ParamKind::NamedOnly => 3,
110            ParamKind::RestNamed => 4,
111        }
112    }
113}
114
115/// One parameter of a [`ParamList`].
116///
117/// # Examples
118///
119/// ```
120/// use bytecode_lang::{Param, ParamKind, StrId};
121///
122/// let p = Param::normal(StrId(3)).by_ref();
123/// assert_eq!((p.kind, p.name, p.by_ref, p.default), (ParamKind::Normal, Some(StrId(3)), true, false));
124/// ```
125#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
126pub struct Param {
127    /// The parameter's name in the callee's module. Required for
128    /// [`Normal`](ParamKind::Normal) and [`NamedOnly`](ParamKind::NamedOnly)
129    /// parameters; optional (diagnostics only) for the others.
130    pub name: Option<StrId>,
131    /// How the parameter receives arguments.
132    pub kind: ParamKind,
133    /// Whether the parameter takes its argument **by reference** (PHP
134    /// `&$x`): it receives a reference (kind `reference`) aliasing the
135    /// caller's place instead of the value. For a rest parameter, every
136    /// collected argument is taken by reference.
137    pub by_ref: bool,
138    /// Whether the parameter has a default, which the callee computes
139    /// itself when the presence mask says no argument arrived. Never set on
140    /// a rest parameter.
141    pub default: bool,
142}
143
144impl Param {
145    /// A parameter of kind `kind` named `name`, by value, without default.
146    ///
147    /// # Examples
148    ///
149    /// ```
150    /// use bytecode_lang::{Param, ParamKind};
151    ///
152    /// assert_eq!(Param::new(ParamKind::Rest, None).kind, ParamKind::Rest);
153    /// ```
154    #[must_use]
155    pub const fn new(kind: ParamKind, name: Option<StrId>) -> Self {
156        Param {
157            name,
158            kind,
159            by_ref: false,
160            default: false,
161        }
162    }
163
164    /// A [`Normal`](ParamKind::Normal) parameter named `name`.
165    ///
166    /// # Examples
167    ///
168    /// ```
169    /// use bytecode_lang::{Param, ParamKind, StrId};
170    ///
171    /// assert_eq!(Param::normal(StrId(0)).kind, ParamKind::Normal);
172    /// ```
173    #[must_use]
174    pub const fn normal(name: StrId) -> Self {
175        Param::new(ParamKind::Normal, Some(name))
176    }
177
178    /// This parameter taking its argument by reference.
179    ///
180    /// # Examples
181    ///
182    /// ```
183    /// use bytecode_lang::{Param, StrId};
184    ///
185    /// assert!(Param::normal(StrId(0)).by_ref().by_ref);
186    /// ```
187    #[must_use]
188    pub const fn by_ref(mut self) -> Self {
189        self.by_ref = true;
190        self
191    }
192
193    /// This parameter with a default.
194    ///
195    /// # Examples
196    ///
197    /// ```
198    /// use bytecode_lang::{Param, StrId};
199    ///
200    /// assert!(Param::normal(StrId(0)).with_default().default);
201    /// ```
202    #[must_use]
203    pub const fn with_default(mut self) -> Self {
204        self.default = true;
205        self
206    }
207}
208
209/// Why a [`ParamList`] is malformed, or does not fit the signature it is
210/// attached to.
211///
212/// # Examples
213///
214/// ```
215/// use bytecode_lang::{Param, ParamError, ParamKind, ParamList};
216///
217/// let list = ParamList::new(vec![Param::new(ParamKind::Normal, None)]);
218/// assert_eq!(list.validate(), Err(ParamError::Unnamed { index: 0 }));
219/// ```
220#[derive(Clone, Copy, PartialEq, Eq, Debug)]
221#[non_exhaustive]
222pub enum ParamError {
223    /// More than 255 parameters, or more than 64 when a parameter has a
224    /// default (the presence mask is 64 bits).
225    TooMany,
226    /// A parameter's kind is out of the required order, or a second rest
227    /// parameter of one rank appears.
228    OutOfOrder {
229        /// The parameter.
230        index: u16,
231    },
232    /// A normal or named-only parameter has no name.
233    Unnamed {
234        /// The parameter.
235        index: u16,
236    },
237    /// Two parameters have the same name.
238    DuplicateName {
239        /// The second of them.
240        index: u16,
241    },
242    /// A rest parameter is marked as having a default.
243    RestDefault {
244        /// The parameter.
245        index: u16,
246    },
247    /// The list does not fit the function's signature: the signature must
248    /// have one parameter per entry, plus a trailing `i64` presence mask
249    /// when an entry has a default; rest parameters are `dyn`; by-reference
250    /// parameters are `dyn` or a `ref` (to a `cell dyn`).
251    Signature {
252        /// The first signature parameter that does not fit (the parameter
253        /// count, if the counts differ).
254        index: u16,
255    },
256}
257
258impl fmt::Display for ParamError {
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        match self {
261            ParamError::TooMany => {
262                f.write_str("too many parameters (255 at most, 64 when a parameter has a default)")
263            }
264            ParamError::OutOfOrder { index } => {
265                write!(f, "parameter {index} is out of the required kind order")
266            }
267            ParamError::Unnamed { index } => {
268                write!(f, "parameter {index} can be named but has no name")
269            }
270            ParamError::DuplicateName { index } => {
271                write!(f, "parameter {index} repeats an earlier name")
272            }
273            ParamError::RestDefault { index } => {
274                write!(f, "rest parameter {index} cannot have a default")
275            }
276            ParamError::Signature { index } => {
277                write!(
278                    f,
279                    "the signature does not fit the parameter list at {index}"
280                )
281            }
282        }
283    }
284}
285
286impl core::error::Error for ParamError {}
287
288/// The dynamic-call signature of a function or an import: what lets a
289/// `dcall` or `dcall_shape` pass named, spread, extra, missing, and
290/// by-reference arguments (`specs/LSB.md` §5.15).
291///
292/// The list describes the signature's parameters in order. When any
293/// parameter has a default, the signature has **one more** parameter, an
294/// `i64` *presence mask*: bit `i` is set when parameter `i` received an
295/// argument, so the callee computes the defaults of the others itself
296/// (PHP's and Python's defaults are expressions). A callee without a list
297/// binds as if it had one positional-only parameter per signature
298/// parameter: exact arity, positional arguments only.
299///
300/// # Examples
301///
302/// ```
303/// use bytecode_lang::{Param, ParamKind, ParamList, StrId};
304///
305/// // PHP: function f($a, &$b = null, ...$rest)
306/// let list = ParamList::new(vec![
307///     Param::normal(StrId(0)),
308///     Param::normal(StrId(1)).by_ref().with_default(),
309///     Param::new(ParamKind::RestMap, Some(StrId(2))),
310/// ]);
311/// assert!(list.validate().is_ok());
312/// assert!(list.has_defaults());
313/// assert_eq!(list.signature_len(), 4); // three parameters and the presence mask
314/// assert!(list.positional_by_ref(1));
315/// assert!(!list.positional_by_ref(7)); // a rest argument, by value
316/// ```
317#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
318pub struct ParamList {
319    /// The parameters, in signature order.
320    pub params: Vec<Param>,
321    /// Extra positional arguments with no rest parameter to take them are
322    /// dropped instead of raising `ArgumentError` (PHP user functions,
323    /// which see them only through `func_get_args`).
324    pub ignore_extra: bool,
325}
326
327/// The most entries a parameter list or a call shape may have (call windows
328/// and `argc` are 8-bit).
329pub(crate) const MAX_ARITY: usize = 255;
330
331/// The most parameters a list with defaults may have: the width of the
332/// presence mask.
333const MAX_WITH_DEFAULTS: usize = 64;
334
335impl ParamList {
336    /// A list of these parameters that raises on extra arguments.
337    ///
338    /// # Examples
339    ///
340    /// ```
341    /// use bytecode_lang::{Param, ParamList, StrId};
342    ///
343    /// let list = ParamList::new(vec![Param::normal(StrId(0))]);
344    /// assert!(!list.ignore_extra);
345    /// ```
346    #[must_use]
347    pub fn new(params: Vec<Param>) -> Self {
348        ParamList {
349            params,
350            ignore_extra: false,
351        }
352    }
353
354    /// This list, dropping extra positional arguments (PHP user functions).
355    ///
356    /// # Examples
357    ///
358    /// ```
359    /// use bytecode_lang::ParamList;
360    ///
361    /// assert!(ParamList::new(vec![]).ignoring_extra().ignore_extra);
362    /// ```
363    #[must_use]
364    pub fn ignoring_extra(mut self) -> Self {
365        self.ignore_extra = true;
366        self
367    }
368
369    /// Whether any parameter has a default, so the signature ends with the
370    /// presence mask.
371    ///
372    /// # Examples
373    ///
374    /// ```
375    /// use bytecode_lang::{Param, ParamList, StrId};
376    ///
377    /// assert!(!ParamList::new(vec![Param::normal(StrId(0))]).has_defaults());
378    /// ```
379    #[must_use]
380    pub fn has_defaults(&self) -> bool {
381        self.params.iter().any(|p| p.default)
382    }
383
384    /// The number of signature parameters the list describes: one per
385    /// entry, plus the presence mask when a parameter has a default.
386    ///
387    /// # Examples
388    ///
389    /// ```
390    /// use bytecode_lang::{Param, ParamList, StrId};
391    ///
392    /// let list = ParamList::new(vec![Param::normal(StrId(0)).with_default()]);
393    /// assert_eq!(list.signature_len(), 2);
394    /// ```
395    #[must_use]
396    pub fn signature_len(&self) -> usize {
397        self.params.len() + usize::from(self.has_defaults())
398    }
399
400    /// Checks the list's own rules: at most 255 parameters (64 with
401    /// defaults); kinds in order (positional-only, normal, one rest,
402    /// named-only, one named rest); normal and named-only parameters named;
403    /// names unique; no default on a rest parameter.
404    ///
405    /// # Errors
406    ///
407    /// The first rule broken, as a [`ParamError`].
408    ///
409    /// # Examples
410    ///
411    /// ```
412    /// use bytecode_lang::{Param, ParamError, ParamKind, ParamList, StrId};
413    ///
414    /// let rest_first = ParamList::new(vec![
415    ///     Param::new(ParamKind::Rest, None),
416    ///     Param::normal(StrId(0)),
417    /// ]);
418    /// assert_eq!(rest_first.validate(), Err(ParamError::OutOfOrder { index: 1 }));
419    /// ```
420    pub fn validate(&self) -> Result<(), ParamError> {
421        let limit = if self.has_defaults() {
422            MAX_WITH_DEFAULTS
423        } else {
424            MAX_ARITY
425        };
426        if self.params.len() > limit {
427            return Err(ParamError::TooMany);
428        }
429        let mut last_rank = 0u8;
430        let mut names = BTreeSet::new();
431        for (i, p) in self.params.iter().enumerate() {
432            // `i` < 255, checked above.
433            let index = u16::try_from(i).unwrap_or(u16::MAX);
434            let rank = p.kind.rank();
435            // Ranks never decrease, and the two rest ranks hold one each.
436            let repeated_rest = i > 0 && rank == last_rank && (rank == 2 || rank == 4);
437            if rank < last_rank || repeated_rest {
438                return Err(ParamError::OutOfOrder { index });
439            }
440            last_rank = rank;
441            if p.kind.takes_names() && p.name.is_none() {
442                return Err(ParamError::Unnamed { index });
443            }
444            if let Some(name) = p.name {
445                if !names.insert(name) {
446                    return Err(ParamError::DuplicateName { index });
447                }
448            }
449            if p.kind.is_rest() && p.default {
450                return Err(ParamError::RestDefault { index });
451            }
452        }
453        Ok(())
454    }
455
456    /// Checks that the list fits a signature with parameter types
457    /// `sig_params`: one parameter per entry plus a trailing `i64` presence
458    /// mask when an entry has a default; rest parameters `dyn`; by-reference
459    /// parameters `dyn` or `ref` (the verifier checks that the `ref` is a
460    /// `cell dyn`).
461    ///
462    /// # Errors
463    ///
464    /// [`ParamError::Signature`] naming the first parameter that does not
465    /// fit.
466    ///
467    /// # Examples
468    ///
469    /// ```
470    /// use bytecode_lang::{Param, ParamError, ParamKind, ParamList, StrId, ValType};
471    ///
472    /// let list = ParamList::new(vec![Param::new(ParamKind::Rest, None)]);
473    /// assert!(list.fits(&[ValType::Dyn]).is_ok());
474    /// assert_eq!(list.fits(&[ValType::I64]), Err(ParamError::Signature { index: 0 }));
475    /// ```
476    pub fn fits(&self, sig_params: &[ValType]) -> Result<(), ParamError> {
477        let n = self.params.len();
478        if sig_params.len() != self.signature_len() {
479            let index = u16::try_from(n.min(sig_params.len())).unwrap_or(u16::MAX);
480            return Err(ParamError::Signature { index });
481        }
482        for (i, (p, &ty)) in self.params.iter().zip(sig_params).enumerate() {
483            let ok = if p.kind.is_rest() && !p.by_ref {
484                ty == ValType::Dyn
485            } else if p.by_ref {
486                // A by-reference rest still collects into a `dyn` container.
487                ty == ValType::Dyn || (!p.kind.is_rest() && matches!(ty, ValType::Ref(_)))
488            } else {
489                true
490            };
491            if !ok {
492                let index = u16::try_from(i).unwrap_or(u16::MAX);
493                return Err(ParamError::Signature { index });
494            }
495        }
496        if self.has_defaults() && sig_params.get(n) != Some(&ValType::I64) {
497            let index = u16::try_from(n).unwrap_or(u16::MAX);
498            return Err(ParamError::Signature { index });
499        }
500        Ok(())
501    }
502
503    /// The rest parameter collecting extra positional arguments, if any.
504    fn positional_rest(&self) -> Option<(usize, &Param)> {
505        self.params
506            .iter()
507            .enumerate()
508            .find(|(_, p)| matches!(p.kind, ParamKind::Rest | ParamKind::RestMap))
509    }
510
511    /// The parameter collecting unknown named arguments, if any: the named
512    /// rest, or else a [`RestMap`](ParamKind::RestMap).
513    fn named_rest(&self) -> Option<(usize, &Param)> {
514        self.params
515            .iter()
516            .enumerate()
517            .find(|(_, p)| p.kind == ParamKind::RestNamed)
518            .or_else(|| {
519                self.params
520                    .iter()
521                    .enumerate()
522                    .find(|(_, p)| p.kind == ParamKind::RestMap)
523            })
524    }
525
526    /// Whether a positional argument at 0-based position `pos` would be
527    /// taken by reference: the semantics of `dparam_ref` (PHP decides
528    /// before evaluating each argument of a dynamic call whether to send a
529    /// reference). Positions past the positional parameters belong to the
530    /// rest parameter, if any; with none, `false`.
531    ///
532    /// # Examples
533    ///
534    /// ```
535    /// use bytecode_lang::{Param, ParamKind, ParamList, StrId};
536    ///
537    /// let list = ParamList::new(vec![
538    ///     Param::normal(StrId(0)),
539    ///     Param::new(ParamKind::RestMap, None).by_ref(),
540    /// ]);
541    /// assert!(!list.positional_by_ref(0));
542    /// assert!(list.positional_by_ref(5));
543    /// ```
544    #[must_use]
545    pub fn positional_by_ref(&self, pos: u64) -> bool {
546        let mut seen = 0u64;
547        for p in &self.params {
548            if p.kind.takes_positions() {
549                if seen == pos {
550                    return p.by_ref;
551                }
552                seen += 1;
553            }
554        }
555        self.positional_rest().is_some_and(|(_, p)| p.by_ref)
556    }
557
558    /// Whether a named argument `name` would be taken by reference: the
559    /// semantics of `dparam_ref_named`. Parameter names are strings of
560    /// `module`, the callee's module.
561    ///
562    /// # Examples
563    ///
564    /// ```
565    /// use bytecode_lang::{ModuleBuilder, Param, ParamList};
566    ///
567    /// let mut m = ModuleBuilder::new();
568    /// let x = m.string("x");
569    /// let module = m.finish().unwrap();
570    /// let list = ParamList::new(vec![Param::normal(x).by_ref()]);
571    /// assert!(list.named_by_ref(&module, b"x"));
572    /// assert!(!list.named_by_ref(&module, b"y"));
573    /// ```
574    #[must_use]
575    pub fn named_by_ref(&self, module: &Module, name: &[u8]) -> bool {
576        match self.find_named(module, name) {
577            Some(i) => self.params.get(i).is_some_and(|p| p.by_ref),
578            None => self.named_rest().is_some_and(|(_, p)| p.by_ref),
579        }
580    }
581
582    /// The normal or named-only parameter called `name`.
583    fn find_named(&self, module: &Module, name: &[u8]) -> Option<usize> {
584        self.params.iter().position(|p| {
585            p.kind.takes_names()
586                && p.name
587                    .and_then(|s| module.string(s))
588                    .is_some_and(|s| s.as_bytes() == name)
589        })
590    }
591
592    /// Binds a dynamic call's arguments to this list: the rule of
593    /// `specs/LSB.md` §5.15, written once so every tier applies it alike.
594    ///
595    /// `items` are the call's arguments in order, after spreads were
596    /// expanded (a spread array contributes positional items; a spread
597    /// map's integer keys positional items and its string keys named ones).
598    /// Parameter names are strings of `module`, the callee's module.
599    ///
600    /// 1. Positional items fill the positional-only and normal parameters
601    ///    in order; extra ones go to the rest parameter, else are dropped
602    ///    under [`ignore_extra`](ParamList::ignore_extra), else
603    ///    [`BindError::TooMany`]. A positional item after a named one is
604    ///    [`BindError::PositionalAfterNamed`].
605    /// 2. A named item fills the normal or named-only parameter of that
606    ///    name ([`BindError::Duplicate`] if it is already filled); with
607    ///    none, it goes to the named rest, else to a
608    ///    [`RestMap`](ParamKind::RestMap) (a repeated name there is
609    ///    `Duplicate`), else [`BindError::UnknownName`].
610    /// 3. A parameter left empty takes its default (its presence bit is
611    ///    clear), or is [`BindError::Missing`]; a rest parameter is an empty
612    ///    collection.
613    ///
614    /// The first violation in item order is reported, then the first missing
615    /// parameter. Runs in `O(items × params)` with `params ≤ 255`, plus
616    /// `O(n log n)` for duplicate names in a rest collection.
617    ///
618    /// # Errors
619    ///
620    /// A [`BindError`]; a VM raises `ArgumentError` (E0114) for every one.
621    ///
622    /// # Examples
623    ///
624    /// ```
625    /// use bytecode_lang::{ArgItem, Bound, ModuleBuilder, Param, ParamKind, ParamList};
626    ///
627    /// let mut m = ModuleBuilder::new();
628    /// let (a, b) = (m.string("a"), m.string("b"));
629    /// let module = m.finish().unwrap();
630    /// // function f($a, $b = 2, ...$rest)
631    /// let list = ParamList::new(vec![
632    ///     Param::normal(a),
633    ///     Param::normal(b).with_default(),
634    ///     Param::new(ParamKind::RestMap, None),
635    /// ]);
636    /// // f(1, 2, 3, x: 4)
637    /// let items = [ArgItem::Positional, ArgItem::Positional, ArgItem::Positional, ArgItem::Named(b"x")];
638    /// let bound = list.bind(&module, &items).unwrap();
639    /// assert_eq!(bound.slots(), &[Bound::Arg(0), Bound::Arg(1), Bound::Collected(vec![2, 3])]);
640    /// assert_eq!(bound.presence(), 0b111);
641    /// // f(b: 5, a: 6)
642    /// let bound = list.bind(&module, &[ArgItem::Named(b"b"), ArgItem::Named(b"a")]).unwrap();
643    /// assert_eq!(bound.slots(), &[Bound::Arg(1), Bound::Arg(0), Bound::Collected(vec![])]);
644    /// ```
645    pub fn bind(&self, module: &Module, items: &[ArgItem<'_>]) -> Result<Binding, BindError> {
646        let mut slots: Vec<Bound> = self
647            .params
648            .iter()
649            .map(|p| {
650                if p.kind.is_rest() {
651                    Bound::Collected(Vec::new())
652                } else {
653                    Bound::Default
654                }
655            })
656            .collect();
657        let rest = self.positional_rest().map(|(i, _)| i);
658        let named_rest = self.named_rest().map(|(i, _)| i);
659        // The next parameter that may take a positional argument. It only
660        // moves forward, so finding all of them is O(params) in total.
661        let mut cursor = 0usize;
662        let mut seen_named = false;
663        let mut rest_names: BTreeSet<&[u8]> = BTreeSet::new();
664        for (item, arg) in items.iter().enumerate() {
665            match *arg {
666                ArgItem::Positional => {
667                    if seen_named {
668                        return Err(BindError::PositionalAfterNamed { item });
669                    }
670                    while self
671                        .params
672                        .get(cursor)
673                        .is_some_and(|p| !p.kind.takes_positions())
674                    {
675                        cursor += 1;
676                    }
677                    if let Some(slot) = slots.get_mut(cursor) {
678                        cursor += 1;
679                        *slot = Bound::Arg(item);
680                    } else if let Some(Bound::Collected(list)) = rest.and_then(|r| slots.get_mut(r))
681                    {
682                        list.push(item);
683                    } else if !self.ignore_extra {
684                        return Err(BindError::TooMany { item });
685                    }
686                }
687                ArgItem::Named(name) => {
688                    seen_named = true;
689                    if let Some(param) = self.find_named(module, name) {
690                        match slots.get_mut(param) {
691                            Some(slot @ Bound::Default) => *slot = Bound::Arg(item),
692                            _ => return Err(BindError::Duplicate { item }),
693                        }
694                    } else if let Some(Bound::Collected(list)) =
695                        named_rest.and_then(|r| slots.get_mut(r))
696                    {
697                        if !rest_names.insert(name) {
698                            return Err(BindError::Duplicate { item });
699                        }
700                        list.push(item);
701                    } else {
702                        return Err(BindError::UnknownName { item });
703                    }
704                }
705            }
706        }
707        let mut presence = 0u64;
708        for (i, (slot, p)) in slots.iter().zip(&self.params).enumerate() {
709            let present = match slot {
710                Bound::Arg(_) => true,
711                Bound::Collected(list) => !list.is_empty(),
712                Bound::Default if p.default => false,
713                Bound::Default => return Err(BindError::Missing { param: i }),
714            };
715            if present && i < 64 {
716                presence |= 1 << i;
717            }
718        }
719        Ok(Binding { slots, presence })
720    }
721}
722
723/// One argument of a dynamic call, as [`ParamList::bind`] sees it.
724///
725/// # Examples
726///
727/// ```
728/// use bytecode_lang::ArgItem;
729///
730/// let items = [ArgItem::Positional, ArgItem::Named(b"x")];
731/// assert_eq!(items.len(), 2);
732/// ```
733#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
734pub enum ArgItem<'a> {
735    /// A positional argument.
736    Positional,
737    /// A named argument, its name as bytes (from a call shape's string or a
738    /// spread map's string key).
739    Named(&'a [u8]),
740}
741
742/// What one parameter received from [`ParamList::bind`].
743///
744/// # Examples
745///
746/// ```
747/// use bytecode_lang::Bound;
748///
749/// assert_ne!(Bound::Arg(0), Bound::Default);
750/// ```
751#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
752pub enum Bound {
753    /// The argument at this item index.
754    Arg(usize),
755    /// Nothing: the parameter has a default, which the callee computes
756    /// (its presence bit is clear).
757    Default,
758    /// A rest parameter's items, in order. A [`RestMap`](ParamKind::RestMap)
759    /// stores a positional item under the next integer key and a named one
760    /// under its name.
761    Collected(Vec<usize>),
762}
763
764/// The result of [`ParamList::bind`]: what each parameter receives, and the
765/// presence mask.
766///
767/// # Examples
768///
769/// ```
770/// use bytecode_lang::{ArgItem, Bound, ModuleBuilder, Param, ParamKind, ParamList};
771///
772/// let module = ModuleBuilder::new().finish().unwrap();
773/// let list = ParamList::new(vec![Param::new(ParamKind::PositionalOnly, None)]);
774/// let b = list.bind(&module, &[ArgItem::Positional]).unwrap();
775/// assert_eq!(b.slots(), &[Bound::Arg(0)]);
776/// assert_eq!(b.presence(), 1);
777/// ```
778#[derive(Clone, PartialEq, Eq, Debug)]
779pub struct Binding {
780    slots: Vec<Bound>,
781    presence: u64,
782}
783
784impl Binding {
785    /// What each parameter receives, in parameter order.
786    ///
787    /// # Examples
788    ///
789    /// ```
790    /// use bytecode_lang::{ModuleBuilder, ParamList};
791    ///
792    /// let module = ModuleBuilder::new().finish().unwrap();
793    /// assert!(ParamList::new(vec![]).bind(&module, &[]).unwrap().slots().is_empty());
794    /// ```
795    #[must_use]
796    pub fn slots(&self) -> &[Bound] {
797        &self.slots
798    }
799
800    /// The presence mask: bit `i` is set when parameter `i` received an
801    /// argument (for a rest parameter, at least one).
802    ///
803    /// # Examples
804    ///
805    /// ```
806    /// use bytecode_lang::{ModuleBuilder, Param, ParamList, StrId};
807    ///
808    /// let mut m = ModuleBuilder::new();
809    /// let x = m.string("x");
810    /// let module = m.finish().unwrap();
811    /// let list = ParamList::new(vec![Param::normal(x).with_default()]);
812    /// assert_eq!(list.bind(&module, &[]).unwrap().presence(), 0);
813    /// ```
814    #[must_use]
815    pub fn presence(&self) -> u64 {
816        self.presence
817    }
818}
819
820/// Why a dynamic call's arguments do not bind ([`ParamList::bind`]). A VM
821/// raises `ArgumentError` (E0114) for every variant; the variant is the
822/// detail a diagnostic can show.
823///
824/// # Examples
825///
826/// ```
827/// use bytecode_lang::{ArgItem, BindError, ModuleBuilder, ParamList};
828///
829/// let module = ModuleBuilder::new().finish().unwrap();
830/// let none = ParamList::new(vec![]);
831/// assert_eq!(none.bind(&module, &[ArgItem::Positional]), Err(BindError::TooMany { item: 0 }));
832/// ```
833#[derive(Clone, Copy, PartialEq, Eq, Debug)]
834#[non_exhaustive]
835pub enum BindError {
836    /// A positional argument with no parameter to take it.
837    TooMany {
838        /// The argument's item index.
839        item: usize,
840    },
841    /// A positional argument after a named one (PHP's "cannot use
842    /// positional argument after named argument").
843    PositionalAfterNamed {
844        /// The argument's item index.
845        item: usize,
846    },
847    /// A named argument no parameter takes.
848    UnknownName {
849        /// The argument's item index.
850        item: usize,
851    },
852    /// A named argument for a parameter already filled, or a name repeated
853    /// in a rest collection.
854    Duplicate {
855        /// The argument's item index.
856        item: usize,
857    },
858    /// A parameter without default received nothing.
859    Missing {
860        /// The parameter.
861        param: usize,
862    },
863}
864
865impl fmt::Display for BindError {
866    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
867        match self {
868            BindError::TooMany { item } => write!(f, "argument {item} has no parameter"),
869            BindError::PositionalAfterNamed { item } => {
870                write!(f, "positional argument {item} follows a named argument")
871            }
872            BindError::UnknownName { item } => {
873                write!(f, "named argument {item} matches no parameter")
874            }
875            BindError::Duplicate { item } => {
876                write!(f, "argument {item} names a parameter already given")
877            }
878            BindError::Missing { param } => write!(f, "parameter {param} received no argument"),
879        }
880    }
881}
882
883impl core::error::Error for BindError {}
884
885/// One argument of a [`CallShape`].
886///
887/// # Examples
888///
889/// ```
890/// use bytecode_lang::{ArgKind, StrId};
891///
892/// assert_eq!(ArgKind::Named(StrId(4)).to_string(), "s4:");
893/// assert_eq!(ArgKind::Spread.to_string(), "...");
894/// ```
895#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
896pub enum ArgKind {
897    /// A positional argument.
898    Positional,
899    /// A named argument (PHP `f(x: 1)`, Python `f(x=1)`); the name is a
900    /// string of the calling module.
901    Named(StrId),
902    /// A spread (PHP `...$a`, Python `*a`): an array contributes positional
903    /// arguments; a map contributes its integer-keyed values positionally
904    /// and its string-keyed values as named arguments.
905    Spread,
906    /// A named spread (Python `**kw`): a map whose keys must all be strings.
907    SpreadNamed,
908}
909
910impl ArgKind {
911    pub(crate) const fn tag(self) -> u8 {
912        match self {
913            ArgKind::Positional => 0,
914            ArgKind::Named(_) => 1,
915            ArgKind::Spread => 2,
916            ArgKind::SpreadNamed => 3,
917        }
918    }
919
920    /// Whether the argument can contribute positional arguments.
921    const fn is_positional(self) -> bool {
922        matches!(self, ArgKind::Positional | ArgKind::Spread)
923    }
924}
925
926/// `_` (positional), `s4:` (named), `...` (spread), `**` (named spread).
927impl fmt::Display for ArgKind {
928    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
929        match self {
930            ArgKind::Positional => f.write_str("_"),
931            ArgKind::Named(name) => write!(f, "{name}:"),
932            ArgKind::Spread => f.write_str("..."),
933            ArgKind::SpreadNamed => f.write_str("**"),
934        }
935    }
936}
937
938/// Why a [`CallShape`] is malformed.
939///
940/// # Examples
941///
942/// ```
943/// use bytecode_lang::{ArgKind, CallShape, ShapeError, StrId};
944///
945/// let shape = CallShape::new(vec![ArgKind::Named(StrId(0)), ArgKind::Positional]);
946/// assert_eq!(shape.validate(), Err(ShapeError::PositionalAfterNamed { index: 1 }));
947/// ```
948#[derive(Clone, Copy, PartialEq, Eq, Debug)]
949#[non_exhaustive]
950pub enum ShapeError {
951    /// More than 255 arguments (a call window holds at most 255).
952    TooMany,
953    /// A positional argument or a spread follows a named argument or a
954    /// named spread.
955    PositionalAfterNamed {
956        /// The argument.
957        index: u16,
958    },
959    /// A name appears twice.
960    DuplicateName {
961        /// The second occurrence.
962        index: u16,
963    },
964}
965
966impl fmt::Display for ShapeError {
967    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
968        match self {
969            ShapeError::TooMany => f.write_str("more than 255 arguments"),
970            ShapeError::PositionalAfterNamed { index } => {
971                write!(f, "argument {index} is positional after a named argument")
972            }
973            ShapeError::DuplicateName { index } => {
974                write!(f, "argument {index} repeats an earlier name")
975            }
976        }
977    }
978}
979
980impl core::error::Error for ShapeError {}
981
982/// The argument layout of a `dcall_shape` call site: one entry per window
983/// register (`dst+1 ..= dst+len`), saying whether that argument is
984/// positional, named, or spread. Shapes live in a per-function table and
985/// are named by a [`ShapeId`](crate::ShapeId).
986///
987/// # Examples
988///
989/// ```
990/// use bytecode_lang::{ArgKind, CallShape, StrId};
991///
992/// // f($a, ...$rest, flag: true)
993/// let shape = CallShape::new(vec![ArgKind::Positional, ArgKind::Spread, ArgKind::Named(StrId(2))]);
994/// assert!(shape.validate().is_ok());
995/// assert_eq!(shape.to_string(), "(_, ..., s2:)");
996/// ```
997#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
998pub struct CallShape {
999    /// The arguments, in window order.
1000    pub args: Vec<ArgKind>,
1001}
1002
1003impl CallShape {
1004    /// A shape of these arguments.
1005    ///
1006    /// # Examples
1007    ///
1008    /// ```
1009    /// use bytecode_lang::{ArgKind, CallShape};
1010    ///
1011    /// assert_eq!(CallShape::new(vec![ArgKind::Positional]).args.len(), 1);
1012    /// ```
1013    #[must_use]
1014    pub fn new(args: Vec<ArgKind>) -> Self {
1015        CallShape { args }
1016    }
1017
1018    /// Checks the shape: at most 255 arguments, no positional argument or
1019    /// spread after a named argument or named spread, no repeated name.
1020    ///
1021    /// # Errors
1022    ///
1023    /// The first rule broken, as a [`ShapeError`].
1024    ///
1025    /// # Examples
1026    ///
1027    /// ```
1028    /// use bytecode_lang::{ArgKind, CallShape, ShapeError, StrId};
1029    ///
1030    /// let twice = CallShape::new(vec![ArgKind::Named(StrId(1)), ArgKind::Named(StrId(1))]);
1031    /// assert_eq!(twice.validate(), Err(ShapeError::DuplicateName { index: 1 }));
1032    /// ```
1033    pub fn validate(&self) -> Result<(), ShapeError> {
1034        if self.args.len() > MAX_ARITY {
1035            return Err(ShapeError::TooMany);
1036        }
1037        let mut named = false;
1038        let mut names = BTreeSet::new();
1039        for (i, &arg) in self.args.iter().enumerate() {
1040            let index = u16::try_from(i).unwrap_or(u16::MAX);
1041            if arg.is_positional() {
1042                if named {
1043                    return Err(ShapeError::PositionalAfterNamed { index });
1044                }
1045            } else {
1046                named = true;
1047            }
1048            if let ArgKind::Named(name) = arg {
1049                if !names.insert(name) {
1050                    return Err(ShapeError::DuplicateName { index });
1051                }
1052            }
1053        }
1054        Ok(())
1055    }
1056}
1057
1058/// `(_, s3:, ...)`.
1059impl fmt::Display for CallShape {
1060    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1061        f.write_str("(")?;
1062        for (i, arg) in self.args.iter().enumerate() {
1063            if i > 0 {
1064                f.write_str(", ")?;
1065            }
1066            write!(f, "{arg}")?;
1067        }
1068        f.write_str(")")
1069    }
1070}
1071
1072/// `params (s0, &s1 = ?, ...s2) ignore_extra`-style rendering for listings:
1073/// each parameter as its kind marker, `&` when by reference, its name, and
1074/// ` = ?` when it has a default.
1075impl fmt::Display for ParamList {
1076    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1077        f.write_str("(")?;
1078        for (i, p) in self.params.iter().enumerate() {
1079            if i > 0 {
1080                f.write_str(", ")?;
1081            }
1082            f.write_str(match p.kind {
1083                ParamKind::PositionalOnly => "pos ",
1084                ParamKind::Normal => "",
1085                ParamKind::NamedOnly => "named ",
1086                ParamKind::Rest => "...",
1087                ParamKind::RestMap => "...map ",
1088                ParamKind::RestNamed => "**",
1089            })?;
1090            if p.by_ref {
1091                f.write_str("&")?;
1092            }
1093            match p.name {
1094                Some(name) => write!(f, "{name}")?,
1095                None => f.write_str("_")?,
1096            }
1097            if p.default {
1098                f.write_str(" = ?")?;
1099            }
1100        }
1101        f.write_str(")")?;
1102        if self.ignore_extra {
1103            f.write_str(" ignore_extra")?;
1104        }
1105        Ok(())
1106    }
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111    use alloc::string::ToString;
1112    use alloc::vec;
1113
1114    use super::*;
1115    use crate::ModuleBuilder;
1116
1117    fn module_with(names: &[&str]) -> (Module, Vec<StrId>) {
1118        let mut m = ModuleBuilder::new();
1119        let ids = names.iter().map(|n| m.string(n)).collect();
1120        (m.finish().unwrap_or_default(), ids)
1121    }
1122
1123    #[test]
1124    fn kinds_must_come_in_order_with_one_rest_each() {
1125        let ok = ParamList::new(vec![
1126            Param::new(ParamKind::PositionalOnly, None),
1127            Param::normal(StrId(0)),
1128            Param::new(ParamKind::Rest, None),
1129            Param::new(ParamKind::NamedOnly, Some(StrId(1))),
1130            Param::new(ParamKind::RestNamed, None),
1131        ]);
1132        assert_eq!(ok.validate(), Ok(()));
1133        let two_rests = ParamList::new(vec![
1134            Param::new(ParamKind::Rest, None),
1135            Param::new(ParamKind::RestMap, None),
1136        ]);
1137        assert_eq!(
1138            two_rests.validate(),
1139            Err(ParamError::OutOfOrder { index: 1 })
1140        );
1141        let two_named_rests = ParamList::new(vec![
1142            Param::new(ParamKind::RestNamed, None),
1143            Param::new(ParamKind::RestNamed, None),
1144        ]);
1145        assert_eq!(
1146            two_named_rests.validate(),
1147            Err(ParamError::OutOfOrder { index: 1 })
1148        );
1149        let named_twice = ParamList::new(vec![Param::normal(StrId(0)), Param::normal(StrId(0))]);
1150        assert_eq!(
1151            named_twice.validate(),
1152            Err(ParamError::DuplicateName { index: 1 })
1153        );
1154        let rest_default = ParamList::new(vec![Param::new(ParamKind::Rest, None).with_default()]);
1155        assert_eq!(
1156            rest_default.validate(),
1157            Err(ParamError::RestDefault { index: 0 })
1158        );
1159    }
1160
1161    #[test]
1162    fn the_presence_mask_caps_lists_with_defaults_at_64() {
1163        let names = |n: u32| (0..n).map(|i| Param::normal(StrId(i))).collect::<Vec<_>>();
1164        let mut many = names(65);
1165        assert_eq!(ParamList::new(many.clone()).validate(), Ok(()));
1166        if let Some(p) = many.first_mut() {
1167            p.default = true;
1168        }
1169        assert_eq!(ParamList::new(many).validate(), Err(ParamError::TooMany));
1170        assert_eq!(
1171            ParamList::new(names(256)).validate(),
1172            Err(ParamError::TooMany)
1173        );
1174    }
1175
1176    #[test]
1177    fn fits_checks_counts_mask_and_reference_types() {
1178        let list = ParamList::new(vec![
1179            Param::normal(StrId(0)).by_ref().with_default(),
1180            Param::new(ParamKind::Rest, None),
1181        ]);
1182        let ok = [ValType::Dyn, ValType::Dyn, ValType::I64];
1183        assert_eq!(list.fits(&ok), Ok(()));
1184        let by_ref_typed = [ValType::I32, ValType::Dyn, ValType::I64];
1185        assert_eq!(
1186            list.fits(&by_ref_typed),
1187            Err(ParamError::Signature { index: 0 })
1188        );
1189        let no_mask = [ValType::Dyn, ValType::Dyn];
1190        assert_eq!(list.fits(&no_mask), Err(ParamError::Signature { index: 2 }));
1191        let wrong_mask = [ValType::Dyn, ValType::Dyn, ValType::I32];
1192        assert_eq!(
1193            list.fits(&wrong_mask),
1194            Err(ParamError::Signature { index: 2 })
1195        );
1196    }
1197
1198    #[test]
1199    fn binding_follows_php_rules() {
1200        let (module, ids) = module_with(&["a", "b"]);
1201        let list = ParamList::new(vec![
1202            Param::normal(ids[0]),
1203            Param::normal(ids[1]).with_default(),
1204        ]);
1205        use ArgItem::{Named, Positional};
1206        assert_eq!(
1207            list.bind(&module, &[Positional, Positional, Positional]),
1208            Err(BindError::TooMany { item: 2 })
1209        );
1210        assert!(
1211            list.clone()
1212                .ignoring_extra()
1213                .bind(&module, &[Positional, Positional, Positional])
1214                .is_ok()
1215        );
1216        assert_eq!(
1217            list.bind(&module, &[Positional, Named(b"a")]),
1218            Err(BindError::Duplicate { item: 1 })
1219        );
1220        assert_eq!(
1221            list.bind(&module, &[Named(b"b"), Positional]),
1222            Err(BindError::PositionalAfterNamed { item: 1 })
1223        );
1224        assert_eq!(
1225            list.bind(&module, &[Named(b"c")]),
1226            Err(BindError::UnknownName { item: 0 })
1227        );
1228        assert_eq!(
1229            list.bind(&module, &[Named(b"b")]),
1230            Err(BindError::Missing { param: 0 })
1231        );
1232        let b = list
1233            .bind(&module, &[Positional])
1234            .unwrap_or_else(|_| Binding {
1235                slots: vec![],
1236                presence: u64::MAX,
1237            });
1238        assert_eq!(b.slots(), &[Bound::Arg(0), Bound::Default]);
1239        assert_eq!(b.presence(), 1);
1240    }
1241
1242    #[test]
1243    fn positional_only_names_go_to_the_named_rest() {
1244        let (module, ids) = module_with(&["a"]);
1245        let list = ParamList::new(vec![
1246            Param::new(ParamKind::PositionalOnly, Some(ids[0])),
1247            Param::new(ParamKind::RestNamed, None),
1248        ]);
1249        use ArgItem::{Named, Positional};
1250        let b = list.bind(&module, &[Positional, Named(b"a")]);
1251        assert_eq!(
1252            b.map(|b| b.slots().to_vec()),
1253            Ok(vec![Bound::Arg(0), Bound::Collected(vec![1])])
1254        );
1255        assert_eq!(
1256            list.bind(&module, &[Positional, Named(b"k"), Named(b"k")]),
1257            Err(BindError::Duplicate { item: 2 })
1258        );
1259    }
1260
1261    #[test]
1262    fn by_ref_queries_follow_the_binding() {
1263        let (module, ids) = module_with(&["a", "k"]);
1264        let list = ParamList::new(vec![
1265            Param::normal(ids[0]).by_ref(),
1266            Param::new(ParamKind::RestNamed, None).by_ref(),
1267        ]);
1268        assert!(list.positional_by_ref(0));
1269        assert!(!list.positional_by_ref(1));
1270        assert!(list.named_by_ref(&module, b"a"));
1271        assert!(list.named_by_ref(&module, b"k"));
1272    }
1273
1274    #[test]
1275    fn shapes_reject_positionals_after_names() {
1276        let s = CallShape::new(vec![ArgKind::SpreadNamed, ArgKind::Spread]);
1277        assert_eq!(
1278            s.validate(),
1279            Err(ShapeError::PositionalAfterNamed { index: 1 })
1280        );
1281        let ok = CallShape::new(vec![
1282            ArgKind::Spread,
1283            ArgKind::Named(StrId(0)),
1284            ArgKind::SpreadNamed,
1285        ]);
1286        assert_eq!(ok.validate(), Ok(()));
1287        let long = CallShape::new(vec![ArgKind::Positional; 256]);
1288        assert_eq!(long.validate(), Err(ShapeError::TooMany));
1289    }
1290
1291    #[test]
1292    fn lists_and_shapes_print_compactly() {
1293        let list = ParamList::new(vec![
1294            Param::new(ParamKind::PositionalOnly, None),
1295            Param::normal(StrId(1)).by_ref().with_default(),
1296            Param::new(ParamKind::RestMap, Some(StrId(2))),
1297        ])
1298        .ignoring_extra();
1299        assert_eq!(list.to_string(), "(pos _, &s1 = ?, ...map s2) ignore_extra");
1300        assert_eq!(
1301            CallShape::new(vec![ArgKind::Positional, ArgKind::SpreadNamed]).to_string(),
1302            "(_, **)"
1303        );
1304    }
1305}