in starlark/src/eval/runtime/arguments.rs [390:583]
fn collect_slow(
&self,
args: &Arguments<'v, '_>,
slots: &[Cell<Option<Value<'v>>>],
heap: &'v Heap,
) -> anyhow::Result<()> {
/// Lazily initialized `kwargs` object.
#[derive(Default)]
struct LazyKwargs<'v> {
kwargs: Option<Box<SmallMap<StringValue<'v>, Value<'v>>>>,
}
impl<'v> LazyKwargs<'v> {
// Return true if the value is a duplicate
#[inline(always)]
fn insert(&mut self, key: Hashed<StringValue<'v>>, val: Value<'v>) -> bool {
match &mut self.kwargs {
None => {
let mut mp = SmallMap::with_capacity_largest_vec();
mp.insert_hashed(key, val);
self.kwargs = Some(box mp);
false
}
Some(mp) => mp.insert_hashed(key, val).is_some(),
}
}
fn alloc(self, heap: &'v Heap) -> Value<'v> {
let kwargs = match self.kwargs {
Some(kwargs) => Dict::new(coerce(*kwargs)),
None => Dict::default(),
};
heap.alloc(kwargs)
}
}
let len = self.kinds.len();
// We might do unchecked stuff later on, so make sure we have as many slots as we expect
assert!(slots.len() >= len);
let mut star_args = Vec::new();
let mut kwargs = LazyKwargs::default();
let mut next_position = 0;
// First deal with positional parameters
if args.pos.len() <= self.positional {
// fast path for when we don't need to bounce down to filling in args
for (v, s) in args.pos.iter().zip(slots.iter()) {
s.set(Some(*v));
}
next_position = args.pos.len();
} else {
for v in args.pos {
if next_position < self.positional {
slots[next_position].set(Some(*v));
next_position += 1;
} else {
star_args.push(*v);
}
}
}
// Next deal with named parameters
// The lowest position at which we've written a name.
// If at the end lowest_name is less than next_position, we got the same variable twice.
// So no duplicate checking until after all positional arguments
let mut lowest_name = usize::MAX;
// Avoid a lot of loop setup etc in the common case
if !args.names.is_empty() {
for ((name, name_value), v) in args.names.iter().zip(args.named) {
// Safe to use new_unchecked because hash for the Value and str are the same
match self.names.get(name) {
None => {
kwargs.insert(Hashed::new_unchecked(name.small_hash(), *name_value), *v);
}
Some(i) => {
slots[*i].set(Some(*v));
lowest_name = cmp::min(lowest_name, *i);
}
}
}
}
// Next up are the *args parameters
if let Some(param_args) = args.args {
param_args
.with_iterator(heap, |it| {
for v in it {
if next_position < self.positional {
slots[next_position].set(Some(v));
next_position += 1;
} else {
star_args.push(v);
}
}
})
.map_err(|_| FunctionError::ArgsArrayIsNotIterable)?;
}
// Check if the named arguments clashed with the positional arguments
if unlikely(next_position > lowest_name) {
return Err(FunctionError::RepeatedParameter {
name: self.param_name_at(lowest_name),
}
.into());
}
// Now insert the kwargs, if there are any
if let Some(param_kwargs) = args.kwargs {
match Dict::from_value(param_kwargs) {
Some(y) => {
for (k, v) in y.iter_hashed() {
match StringValue::new(*k.key()) {
None => return Err(FunctionError::ArgsValueIsNotString.into()),
Some(s) => {
let repeat = match self
.names
.get_hashed_string_value(Hashed::new_unchecked(k.hash(), s))
{
None => kwargs.insert(Hashed::new_unchecked(k.hash(), s), v),
Some(i) => {
let this_slot = &slots[*i];
let repeat = this_slot.get().is_some();
this_slot.set(Some(v));
repeat
}
};
if unlikely(repeat) {
return Err(FunctionError::RepeatedParameter {
name: s.as_str().to_owned(),
}
.into());
}
}
}
}
}
None => return Err(FunctionError::KwArgsIsNotDict.into()),
}
}
// We have moved parameters into all the relevant slots, so need to finalise things.
// We need to set default values and error if any required values are missing
let kinds = &self.kinds;
// This code is very hot, and setting up iterators was a noticeable bottleneck.
for index in next_position..kinds.len() {
// The number of locals must be at least the number of parameters, see `collect`
// which reserves `max(_, kinds.len())`.
let slot = unsafe { slots.get_unchecked(index) };
let def = unsafe { kinds.get_unchecked(index) };
// We know that up to next_position got filled positionally, so we don't need to check those
if slot.get().is_some() {
continue;
}
match def {
ParameterKind::Required => {
return Err(FunctionError::MissingParameter {
name: self.param_name_at(index),
function: self.signature(),
}
.into());
}
ParameterKind::Defaulted(x) => {
slot.set(Some(x.to_value()));
}
_ => {}
}
}
// Now set the kwargs/args slots, if they are requested, and fail it they are absent but used
// Note that we deliberately give warnings about missing parameters _before_ giving warnings
// about unexpected extra parameters, so if a user mis-spells an argument they get a better error.
if let Some(args_pos) = self.args {
slots[args_pos].set(Some(heap.alloc_tuple(&star_args)));
} else if unlikely(!star_args.is_empty()) {
return Err(FunctionError::ExtraPositionalParameters {
count: star_args.len(),
function: self.signature(),
}
.into());
}
if let Some(kwargs_pos) = self.kwargs {
slots[kwargs_pos].set(Some(kwargs.alloc(heap)));
} else if let Some(kwargs) = kwargs.kwargs {
return Err(FunctionError::ExtraNamedParameters {
names: kwargs.keys().map(|x| x.as_str().to_owned()).collect(),
function: self.signature(),
}
.into());
}
Ok(())
}