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}