use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use core::fmt;
use crate::ids::StrId;
use crate::module::Module;
use crate::types::ValType;
code_enum! {
ParamKind {
PositionalOnly = 0 => "positional_only",
Normal = 1 => "normal",
NamedOnly = 2 => "named_only",
Rest = 3 => "rest",
RestMap = 4 => "rest_map",
RestNamed = 5 => "rest_named",
}
}
impl ParamKind {
#[must_use]
pub const fn is_rest(self) -> bool {
matches!(
self,
ParamKind::Rest | ParamKind::RestMap | ParamKind::RestNamed
)
}
#[must_use]
pub const fn takes_names(self) -> bool {
matches!(self, ParamKind::Normal | ParamKind::NamedOnly)
}
const fn takes_positions(self) -> bool {
matches!(self, ParamKind::PositionalOnly | ParamKind::Normal)
}
const fn rank(self) -> u8 {
match self {
ParamKind::PositionalOnly => 0,
ParamKind::Normal => 1,
ParamKind::Rest | ParamKind::RestMap => 2,
ParamKind::NamedOnly => 3,
ParamKind::RestNamed => 4,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Param {
pub name: Option<StrId>,
pub kind: ParamKind,
pub by_ref: bool,
pub default: bool,
}
impl Param {
#[must_use]
pub const fn new(kind: ParamKind, name: Option<StrId>) -> Self {
Param {
name,
kind,
by_ref: false,
default: false,
}
}
#[must_use]
pub const fn normal(name: StrId) -> Self {
Param::new(ParamKind::Normal, Some(name))
}
#[must_use]
pub const fn by_ref(mut self) -> Self {
self.by_ref = true;
self
}
#[must_use]
pub const fn with_default(mut self) -> Self {
self.default = true;
self
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum ParamError {
TooMany,
OutOfOrder {
index: u16,
},
Unnamed {
index: u16,
},
DuplicateName {
index: u16,
},
RestDefault {
index: u16,
},
Signature {
index: u16,
},
}
impl fmt::Display for ParamError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParamError::TooMany => {
f.write_str("too many parameters (255 at most, 64 when a parameter has a default)")
}
ParamError::OutOfOrder { index } => {
write!(f, "parameter {index} is out of the required kind order")
}
ParamError::Unnamed { index } => {
write!(f, "parameter {index} can be named but has no name")
}
ParamError::DuplicateName { index } => {
write!(f, "parameter {index} repeats an earlier name")
}
ParamError::RestDefault { index } => {
write!(f, "rest parameter {index} cannot have a default")
}
ParamError::Signature { index } => {
write!(
f,
"the signature does not fit the parameter list at {index}"
)
}
}
}
}
impl core::error::Error for ParamError {}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct ParamList {
pub params: Vec<Param>,
pub ignore_extra: bool,
}
pub(crate) const MAX_ARITY: usize = 255;
const MAX_WITH_DEFAULTS: usize = 64;
impl ParamList {
#[must_use]
pub fn new(params: Vec<Param>) -> Self {
ParamList {
params,
ignore_extra: false,
}
}
#[must_use]
pub fn ignoring_extra(mut self) -> Self {
self.ignore_extra = true;
self
}
#[must_use]
pub fn has_defaults(&self) -> bool {
self.params.iter().any(|p| p.default)
}
#[must_use]
pub fn signature_len(&self) -> usize {
self.params.len() + usize::from(self.has_defaults())
}
pub fn validate(&self) -> Result<(), ParamError> {
let limit = if self.has_defaults() {
MAX_WITH_DEFAULTS
} else {
MAX_ARITY
};
if self.params.len() > limit {
return Err(ParamError::TooMany);
}
let mut last_rank = 0u8;
let mut names = BTreeSet::new();
for (i, p) in self.params.iter().enumerate() {
let index = u16::try_from(i).unwrap_or(u16::MAX);
let rank = p.kind.rank();
let repeated_rest = i > 0 && rank == last_rank && (rank == 2 || rank == 4);
if rank < last_rank || repeated_rest {
return Err(ParamError::OutOfOrder { index });
}
last_rank = rank;
if p.kind.takes_names() && p.name.is_none() {
return Err(ParamError::Unnamed { index });
}
if let Some(name) = p.name {
if !names.insert(name) {
return Err(ParamError::DuplicateName { index });
}
}
if p.kind.is_rest() && p.default {
return Err(ParamError::RestDefault { index });
}
}
Ok(())
}
pub fn fits(&self, sig_params: &[ValType]) -> Result<(), ParamError> {
let n = self.params.len();
if sig_params.len() != self.signature_len() {
let index = u16::try_from(n.min(sig_params.len())).unwrap_or(u16::MAX);
return Err(ParamError::Signature { index });
}
for (i, (p, &ty)) in self.params.iter().zip(sig_params).enumerate() {
let ok = if p.kind.is_rest() && !p.by_ref {
ty == ValType::Dyn
} else if p.by_ref {
ty == ValType::Dyn || (!p.kind.is_rest() && matches!(ty, ValType::Ref(_)))
} else {
true
};
if !ok {
let index = u16::try_from(i).unwrap_or(u16::MAX);
return Err(ParamError::Signature { index });
}
}
if self.has_defaults() && sig_params.get(n) != Some(&ValType::I64) {
let index = u16::try_from(n).unwrap_or(u16::MAX);
return Err(ParamError::Signature { index });
}
Ok(())
}
fn positional_rest(&self) -> Option<(usize, &Param)> {
self.params
.iter()
.enumerate()
.find(|(_, p)| matches!(p.kind, ParamKind::Rest | ParamKind::RestMap))
}
fn named_rest(&self) -> Option<(usize, &Param)> {
self.params
.iter()
.enumerate()
.find(|(_, p)| p.kind == ParamKind::RestNamed)
.or_else(|| {
self.params
.iter()
.enumerate()
.find(|(_, p)| p.kind == ParamKind::RestMap)
})
}
#[must_use]
pub fn positional_by_ref(&self, pos: u64) -> bool {
let mut seen = 0u64;
for p in &self.params {
if p.kind.takes_positions() {
if seen == pos {
return p.by_ref;
}
seen += 1;
}
}
self.positional_rest().is_some_and(|(_, p)| p.by_ref)
}
#[must_use]
pub fn named_by_ref(&self, module: &Module, name: &[u8]) -> bool {
match self.find_named(module, name) {
Some(i) => self.params.get(i).is_some_and(|p| p.by_ref),
None => self.named_rest().is_some_and(|(_, p)| p.by_ref),
}
}
fn find_named(&self, module: &Module, name: &[u8]) -> Option<usize> {
self.params.iter().position(|p| {
p.kind.takes_names()
&& p.name
.and_then(|s| module.string(s))
.is_some_and(|s| s.as_bytes() == name)
})
}
pub fn bind(&self, module: &Module, items: &[ArgItem<'_>]) -> Result<Binding, BindError> {
let mut slots: Vec<Bound> = self
.params
.iter()
.map(|p| {
if p.kind.is_rest() {
Bound::Collected(Vec::new())
} else {
Bound::Default
}
})
.collect();
let rest = self.positional_rest().map(|(i, _)| i);
let named_rest = self.named_rest().map(|(i, _)| i);
let mut cursor = 0usize;
let mut seen_named = false;
let mut rest_names: BTreeSet<&[u8]> = BTreeSet::new();
for (item, arg) in items.iter().enumerate() {
match *arg {
ArgItem::Positional => {
if seen_named {
return Err(BindError::PositionalAfterNamed { item });
}
while self
.params
.get(cursor)
.is_some_and(|p| !p.kind.takes_positions())
{
cursor += 1;
}
if let Some(slot) = slots.get_mut(cursor) {
cursor += 1;
*slot = Bound::Arg(item);
} else if let Some(Bound::Collected(list)) = rest.and_then(|r| slots.get_mut(r))
{
list.push(item);
} else if !self.ignore_extra {
return Err(BindError::TooMany { item });
}
}
ArgItem::Named(name) => {
seen_named = true;
if let Some(param) = self.find_named(module, name) {
match slots.get_mut(param) {
Some(slot @ Bound::Default) => *slot = Bound::Arg(item),
_ => return Err(BindError::Duplicate { item }),
}
} else if let Some(Bound::Collected(list)) =
named_rest.and_then(|r| slots.get_mut(r))
{
if !rest_names.insert(name) {
return Err(BindError::Duplicate { item });
}
list.push(item);
} else {
return Err(BindError::UnknownName { item });
}
}
}
}
let mut presence = 0u64;
for (i, (slot, p)) in slots.iter().zip(&self.params).enumerate() {
let present = match slot {
Bound::Arg(_) => true,
Bound::Collected(list) => !list.is_empty(),
Bound::Default if p.default => false,
Bound::Default => return Err(BindError::Missing { param: i }),
};
if present && i < 64 {
presence |= 1 << i;
}
}
Ok(Binding { slots, presence })
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum ArgItem<'a> {
Positional,
Named(&'a [u8]),
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Bound {
Arg(usize),
Default,
Collected(Vec<usize>),
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Binding {
slots: Vec<Bound>,
presence: u64,
}
impl Binding {
#[must_use]
pub fn slots(&self) -> &[Bound] {
&self.slots
}
#[must_use]
pub fn presence(&self) -> u64 {
self.presence
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum BindError {
TooMany {
item: usize,
},
PositionalAfterNamed {
item: usize,
},
UnknownName {
item: usize,
},
Duplicate {
item: usize,
},
Missing {
param: usize,
},
}
impl fmt::Display for BindError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BindError::TooMany { item } => write!(f, "argument {item} has no parameter"),
BindError::PositionalAfterNamed { item } => {
write!(f, "positional argument {item} follows a named argument")
}
BindError::UnknownName { item } => {
write!(f, "named argument {item} matches no parameter")
}
BindError::Duplicate { item } => {
write!(f, "argument {item} names a parameter already given")
}
BindError::Missing { param } => write!(f, "parameter {param} received no argument"),
}
}
}
impl core::error::Error for BindError {}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum ArgKind {
Positional,
Named(StrId),
Spread,
SpreadNamed,
}
impl ArgKind {
pub(crate) const fn tag(self) -> u8 {
match self {
ArgKind::Positional => 0,
ArgKind::Named(_) => 1,
ArgKind::Spread => 2,
ArgKind::SpreadNamed => 3,
}
}
const fn is_positional(self) -> bool {
matches!(self, ArgKind::Positional | ArgKind::Spread)
}
}
impl fmt::Display for ArgKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ArgKind::Positional => f.write_str("_"),
ArgKind::Named(name) => write!(f, "{name}:"),
ArgKind::Spread => f.write_str("..."),
ArgKind::SpreadNamed => f.write_str("**"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum ShapeError {
TooMany,
PositionalAfterNamed {
index: u16,
},
DuplicateName {
index: u16,
},
}
impl fmt::Display for ShapeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ShapeError::TooMany => f.write_str("more than 255 arguments"),
ShapeError::PositionalAfterNamed { index } => {
write!(f, "argument {index} is positional after a named argument")
}
ShapeError::DuplicateName { index } => {
write!(f, "argument {index} repeats an earlier name")
}
}
}
}
impl core::error::Error for ShapeError {}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct CallShape {
pub args: Vec<ArgKind>,
}
impl CallShape {
#[must_use]
pub fn new(args: Vec<ArgKind>) -> Self {
CallShape { args }
}
pub fn validate(&self) -> Result<(), ShapeError> {
if self.args.len() > MAX_ARITY {
return Err(ShapeError::TooMany);
}
let mut named = false;
let mut names = BTreeSet::new();
for (i, &arg) in self.args.iter().enumerate() {
let index = u16::try_from(i).unwrap_or(u16::MAX);
if arg.is_positional() {
if named {
return Err(ShapeError::PositionalAfterNamed { index });
}
} else {
named = true;
}
if let ArgKind::Named(name) = arg {
if !names.insert(name) {
return Err(ShapeError::DuplicateName { index });
}
}
}
Ok(())
}
}
impl fmt::Display for CallShape {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("(")?;
for (i, arg) in self.args.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{arg}")?;
}
f.write_str(")")
}
}
impl fmt::Display for ParamList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("(")?;
for (i, p) in self.params.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
f.write_str(match p.kind {
ParamKind::PositionalOnly => "pos ",
ParamKind::Normal => "",
ParamKind::NamedOnly => "named ",
ParamKind::Rest => "...",
ParamKind::RestMap => "...map ",
ParamKind::RestNamed => "**",
})?;
if p.by_ref {
f.write_str("&")?;
}
match p.name {
Some(name) => write!(f, "{name}")?,
None => f.write_str("_")?,
}
if p.default {
f.write_str(" = ?")?;
}
}
f.write_str(")")?;
if self.ignore_extra {
f.write_str(" ignore_extra")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use alloc::vec;
use super::*;
use crate::ModuleBuilder;
fn module_with(names: &[&str]) -> (Module, Vec<StrId>) {
let mut m = ModuleBuilder::new();
let ids = names.iter().map(|n| m.string(n)).collect();
(m.finish().unwrap_or_default(), ids)
}
#[test]
fn kinds_must_come_in_order_with_one_rest_each() {
let ok = ParamList::new(vec![
Param::new(ParamKind::PositionalOnly, None),
Param::normal(StrId(0)),
Param::new(ParamKind::Rest, None),
Param::new(ParamKind::NamedOnly, Some(StrId(1))),
Param::new(ParamKind::RestNamed, None),
]);
assert_eq!(ok.validate(), Ok(()));
let two_rests = ParamList::new(vec![
Param::new(ParamKind::Rest, None),
Param::new(ParamKind::RestMap, None),
]);
assert_eq!(
two_rests.validate(),
Err(ParamError::OutOfOrder { index: 1 })
);
let two_named_rests = ParamList::new(vec![
Param::new(ParamKind::RestNamed, None),
Param::new(ParamKind::RestNamed, None),
]);
assert_eq!(
two_named_rests.validate(),
Err(ParamError::OutOfOrder { index: 1 })
);
let named_twice = ParamList::new(vec![Param::normal(StrId(0)), Param::normal(StrId(0))]);
assert_eq!(
named_twice.validate(),
Err(ParamError::DuplicateName { index: 1 })
);
let rest_default = ParamList::new(vec![Param::new(ParamKind::Rest, None).with_default()]);
assert_eq!(
rest_default.validate(),
Err(ParamError::RestDefault { index: 0 })
);
}
#[test]
fn the_presence_mask_caps_lists_with_defaults_at_64() {
let names = |n: u32| (0..n).map(|i| Param::normal(StrId(i))).collect::<Vec<_>>();
let mut many = names(65);
assert_eq!(ParamList::new(many.clone()).validate(), Ok(()));
if let Some(p) = many.first_mut() {
p.default = true;
}
assert_eq!(ParamList::new(many).validate(), Err(ParamError::TooMany));
assert_eq!(
ParamList::new(names(256)).validate(),
Err(ParamError::TooMany)
);
}
#[test]
fn fits_checks_counts_mask_and_reference_types() {
let list = ParamList::new(vec![
Param::normal(StrId(0)).by_ref().with_default(),
Param::new(ParamKind::Rest, None),
]);
let ok = [ValType::Dyn, ValType::Dyn, ValType::I64];
assert_eq!(list.fits(&ok), Ok(()));
let by_ref_typed = [ValType::I32, ValType::Dyn, ValType::I64];
assert_eq!(
list.fits(&by_ref_typed),
Err(ParamError::Signature { index: 0 })
);
let no_mask = [ValType::Dyn, ValType::Dyn];
assert_eq!(list.fits(&no_mask), Err(ParamError::Signature { index: 2 }));
let wrong_mask = [ValType::Dyn, ValType::Dyn, ValType::I32];
assert_eq!(
list.fits(&wrong_mask),
Err(ParamError::Signature { index: 2 })
);
}
#[test]
fn binding_follows_php_rules() {
let (module, ids) = module_with(&["a", "b"]);
let list = ParamList::new(vec![
Param::normal(ids[0]),
Param::normal(ids[1]).with_default(),
]);
use ArgItem::{Named, Positional};
assert_eq!(
list.bind(&module, &[Positional, Positional, Positional]),
Err(BindError::TooMany { item: 2 })
);
assert!(
list.clone()
.ignoring_extra()
.bind(&module, &[Positional, Positional, Positional])
.is_ok()
);
assert_eq!(
list.bind(&module, &[Positional, Named(b"a")]),
Err(BindError::Duplicate { item: 1 })
);
assert_eq!(
list.bind(&module, &[Named(b"b"), Positional]),
Err(BindError::PositionalAfterNamed { item: 1 })
);
assert_eq!(
list.bind(&module, &[Named(b"c")]),
Err(BindError::UnknownName { item: 0 })
);
assert_eq!(
list.bind(&module, &[Named(b"b")]),
Err(BindError::Missing { param: 0 })
);
let b = list
.bind(&module, &[Positional])
.unwrap_or_else(|_| Binding {
slots: vec![],
presence: u64::MAX,
});
assert_eq!(b.slots(), &[Bound::Arg(0), Bound::Default]);
assert_eq!(b.presence(), 1);
}
#[test]
fn positional_only_names_go_to_the_named_rest() {
let (module, ids) = module_with(&["a"]);
let list = ParamList::new(vec![
Param::new(ParamKind::PositionalOnly, Some(ids[0])),
Param::new(ParamKind::RestNamed, None),
]);
use ArgItem::{Named, Positional};
let b = list.bind(&module, &[Positional, Named(b"a")]);
assert_eq!(
b.map(|b| b.slots().to_vec()),
Ok(vec![Bound::Arg(0), Bound::Collected(vec![1])])
);
assert_eq!(
list.bind(&module, &[Positional, Named(b"k"), Named(b"k")]),
Err(BindError::Duplicate { item: 2 })
);
}
#[test]
fn by_ref_queries_follow_the_binding() {
let (module, ids) = module_with(&["a", "k"]);
let list = ParamList::new(vec![
Param::normal(ids[0]).by_ref(),
Param::new(ParamKind::RestNamed, None).by_ref(),
]);
assert!(list.positional_by_ref(0));
assert!(!list.positional_by_ref(1));
assert!(list.named_by_ref(&module, b"a"));
assert!(list.named_by_ref(&module, b"k"));
}
#[test]
fn shapes_reject_positionals_after_names() {
let s = CallShape::new(vec![ArgKind::SpreadNamed, ArgKind::Spread]);
assert_eq!(
s.validate(),
Err(ShapeError::PositionalAfterNamed { index: 1 })
);
let ok = CallShape::new(vec![
ArgKind::Spread,
ArgKind::Named(StrId(0)),
ArgKind::SpreadNamed,
]);
assert_eq!(ok.validate(), Ok(()));
let long = CallShape::new(vec![ArgKind::Positional; 256]);
assert_eq!(long.validate(), Err(ShapeError::TooMany));
}
#[test]
fn lists_and_shapes_print_compactly() {
let list = ParamList::new(vec![
Param::new(ParamKind::PositionalOnly, None),
Param::normal(StrId(1)).by_ref().with_default(),
Param::new(ParamKind::RestMap, Some(StrId(2))),
])
.ignoring_extra();
assert_eq!(list.to_string(), "(pos _, &s1 = ?, ...map s2) ignore_extra");
assert_eq!(
CallShape::new(vec![ArgKind::Positional, ArgKind::SpreadNamed]).to_string(),
"(_, **)"
);
}
}