Fix argument parsing bug

Things like `luma(1, key: "val")` didn't produce an error before because `args.finish()?` wasn't called. This changes `args: Args` to `args: &mut Args` to make it impossible for that to happen.
This commit is contained in:
Laurenz 2023-10-27 13:24:37 +02:00
parent fa81c3ece0
commit cbfd9884a9
7 changed files with 90 additions and 89 deletions

View File

@ -317,7 +317,7 @@ fn create_wrapper_closure(func: &Func) -> TokenStream {
.map(|tokens| quote! { #tokens, }); .map(|tokens| quote! { #tokens, });
let vm_ = func.special.vm.then(|| quote! { vm, }); let vm_ = func.special.vm.then(|| quote! { vm, });
let vt_ = func.special.vt.then(|| quote! { &mut vm.vt, }); let vt_ = func.special.vt.then(|| quote! { &mut vm.vt, });
let args_ = func.special.args.then(|| quote! { args.take(), }); let args_ = func.special.args.then(|| quote! { args, });
let span_ = func.special.span.then(|| quote! { args.span, }); let span_ = func.special.span.then(|| quote! { args.span, });
let forwarded = func.params.iter().filter(|param| !param.external).map(bind); let forwarded = func.params.iter().filter(|param| !param.external).map(bind);
quote! { quote! {

View File

@ -354,7 +354,7 @@ impl Array {
pub fn range( pub fn range(
/// The real arguments (the other arguments are just for the docs, this /// The real arguments (the other arguments are just for the docs, this
/// function is a bit involved, so we parse the arguments manually). /// function is a bit involved, so we parse the arguments manually).
args: Args, args: &mut Args,
/// The start of the range (inclusive). /// The start of the range (inclusive).
#[external] #[external]
#[default] #[default]
@ -367,13 +367,11 @@ impl Array {
#[default(NonZeroI64::new(1).unwrap())] #[default(NonZeroI64::new(1).unwrap())]
step: NonZeroI64, step: NonZeroI64,
) -> SourceResult<Array> { ) -> SourceResult<Array> {
let mut args = args;
let first = args.expect::<i64>("end")?; let first = args.expect::<i64>("end")?;
let (start, end) = match args.eat::<i64>()? { let (start, end) = match args.eat::<i64>()? {
Some(second) => (first, second), Some(second) => (first, second),
None => (0, first), None => (0, first),
}; };
args.finish()?;
let step = step.get(); let step = step.get();
@ -412,15 +410,15 @@ impl Array {
/// transformed with the given function. /// transformed with the given function.
#[func] #[func]
pub fn map( pub fn map(
&self, self,
/// The virtual machine. /// The virtual machine.
vm: &mut Vm, vm: &mut Vm,
/// The function to apply to each item. /// The function to apply to each item.
mapper: Func, mapper: Func,
) -> SourceResult<Array> { ) -> SourceResult<Array> {
self.iter() self.into_iter()
.map(|item| { .map(|item| {
let args = Args::new(mapper.span(), [item.clone()]); let args = Args::new(mapper.span(), [item]);
mapper.call_vm(vm, args) mapper.call_vm(vm, args)
}) })
.collect() .collect()
@ -433,20 +431,20 @@ impl Array {
/// a let binding or for loop. /// a let binding or for loop.
#[func] #[func]
pub fn enumerate( pub fn enumerate(
&self, self,
/// The index returned for the first pair of the returned list. /// The index returned for the first pair of the returned list.
#[named] #[named]
#[default(0)] #[default(0)]
start: i64, start: i64,
) -> StrResult<Array> { ) -> StrResult<Array> {
self.iter() self.into_iter()
.enumerate() .enumerate()
.map(|(i, value)| { .map(|(i, value)| {
Ok(array![ Ok(array![
start start
.checked_add_unsigned(i as u64) .checked_add_unsigned(i as u64)
.ok_or("array index is too large")?, .ok_or("array index is too large")?,
value.clone() value
] ]
.into_value()) .into_value())
}) })
@ -464,33 +462,29 @@ impl Array {
/// `{((1, 3, 6), (2, 4, 7), (3, 5, 8))}`. /// `{((1, 3, 6), (2, 4, 7), (3, 5, 8))}`.
#[func] #[func]
pub fn zip( pub fn zip(
&self, self,
/// The real arguments (the other arguments are just for the docs, this /// The real arguments (the other arguments are just for the docs, this
/// function is a bit involved, so we parse the arguments manually). /// function is a bit involved, so we parse the arguments manually).
args: Args, args: &mut Args,
/// The arrays to zip with. /// The arrays to zip with.
#[external] #[external]
#[variadic] #[variadic]
others: Vec<Array>, others: Vec<Array>,
) -> SourceResult<Array> { ) -> SourceResult<Array> {
let mut args = args; let remaining = args.remaining();
// Fast path for one array. // Fast path for one array.
if args.remaining() == 0 { if remaining == 0 {
return Ok(self return Ok(self.into_iter().map(|item| array![item].into_value()).collect());
.iter()
.map(|item| array![item.clone()].into_value())
.collect());
} }
// Fast path for just two arrays. // Fast path for just two arrays.
if args.remaining() == 1 { if remaining == 1 {
let other = args.expect::<Array>("others")?; let other = args.expect::<Array>("others")?;
args.finish()?;
return Ok(self return Ok(self
.iter() .into_iter()
.zip(other) .zip(other)
.map(|(first, second)| array![first.clone(), second].into_value()) .map(|(first, second)| array![first, second].into_value())
.collect()); .collect());
} }
@ -501,9 +495,8 @@ impl Array {
.into_iter() .into_iter()
.map(|i| i.into_iter()) .map(|i| i.into_iter())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
args.finish()?;
for this in self.iter() { for this in self {
let mut row = Self::with_capacity(1 + iterators.len()); let mut row = Self::with_capacity(1 + iterators.len());
row.push(this.clone()); row.push(this.clone());
@ -524,7 +517,7 @@ impl Array {
/// Folds all items into a single value using an accumulator function. /// Folds all items into a single value using an accumulator function.
#[func] #[func]
pub fn fold( pub fn fold(
&self, self,
/// The virtual machine. /// The virtual machine.
vm: &mut Vm, vm: &mut Vm,
/// The initial value to start with. /// The initial value to start with.
@ -534,8 +527,8 @@ impl Array {
folder: Func, folder: Func,
) -> SourceResult<Value> { ) -> SourceResult<Value> {
let mut acc = init; let mut acc = init;
for item in self.iter() { for item in self {
let args = Args::new(folder.span(), [acc, item.clone()]); let args = Args::new(folder.span(), [acc, item]);
acc = folder.call_vm(vm, args)?; acc = folder.call_vm(vm, args)?;
} }
Ok(acc) Ok(acc)
@ -544,20 +537,19 @@ impl Array {
/// Sums all items (works for all types that can be added). /// Sums all items (works for all types that can be added).
#[func] #[func]
pub fn sum( pub fn sum(
&self, self,
/// What to return if the array is empty. Must be set if the array can /// What to return if the array is empty. Must be set if the array can
/// be empty. /// be empty.
#[named] #[named]
default: Option<Value>, default: Option<Value>,
) -> StrResult<Value> { ) -> StrResult<Value> {
let mut acc = self let mut iter = self.into_iter();
.0 let mut acc = iter
.first() .next()
.cloned()
.or(default) .or(default)
.ok_or("cannot calculate sum of empty array with no default")?; .ok_or("cannot calculate sum of empty array with no default")?;
for i in self.iter().skip(1) { for item in iter {
acc = add(acc, i.clone())?; acc = add(acc, item)?;
} }
Ok(acc) Ok(acc)
} }
@ -566,20 +558,19 @@ impl Array {
/// multiplied). /// multiplied).
#[func] #[func]
pub fn product( pub fn product(
&self, self,
/// What to return if the array is empty. Must be set if the array can /// What to return if the array is empty. Must be set if the array can
/// be empty. /// be empty.
#[named] #[named]
default: Option<Value>, default: Option<Value>,
) -> StrResult<Value> { ) -> StrResult<Value> {
let mut acc = self let mut iter = self.into_iter();
.0 let mut acc = iter
.first() .next()
.cloned()
.or(default) .or(default)
.ok_or("cannot calculate product of empty array with no default")?; .ok_or("cannot calculate product of empty array with no default")?;
for i in self.iter().skip(1) { for item in iter {
acc = mul(acc, i.clone())?; acc = mul(acc, item)?;
} }
Ok(acc) Ok(acc)
} }
@ -587,14 +578,14 @@ impl Array {
/// Whether the given function returns `{true}` for any item in the array. /// Whether the given function returns `{true}` for any item in the array.
#[func] #[func]
pub fn any( pub fn any(
&self, self,
/// The virtual machine. /// The virtual machine.
vm: &mut Vm, vm: &mut Vm,
/// The function to apply to each item. Must return a boolean. /// The function to apply to each item. Must return a boolean.
test: Func, test: Func,
) -> SourceResult<bool> { ) -> SourceResult<bool> {
for item in self.iter() { for item in self {
let args = Args::new(test.span(), [item.clone()]); let args = Args::new(test.span(), [item]);
if test.call_vm(vm, args)?.cast::<bool>().at(test.span())? { if test.call_vm(vm, args)?.cast::<bool>().at(test.span())? {
return Ok(true); return Ok(true);
} }
@ -606,14 +597,14 @@ impl Array {
/// Whether the given function returns `{true}` for all items in the array. /// Whether the given function returns `{true}` for all items in the array.
#[func] #[func]
pub fn all( pub fn all(
&self, self,
/// The virtual machine. /// The virtual machine.
vm: &mut Vm, vm: &mut Vm,
/// The function to apply to each item. Must return a boolean. /// The function to apply to each item. Must return a boolean.
test: Func, test: Func,
) -> SourceResult<bool> { ) -> SourceResult<bool> {
for item in self.iter() { for item in self {
let args = Args::new(test.span(), [item.clone()]); let args = Args::new(test.span(), [item]);
if !test.call_vm(vm, args)?.cast::<bool>().at(test.span())? { if !test.call_vm(vm, args)?.cast::<bool>().at(test.span())? {
return Ok(false); return Ok(false);
} }
@ -624,13 +615,13 @@ impl Array {
/// Combine all nested arrays into a single flat one. /// Combine all nested arrays into a single flat one.
#[func] #[func]
pub fn flatten(&self) -> Array { pub fn flatten(self) -> Array {
let mut flat = EcoVec::with_capacity(self.0.len()); let mut flat = EcoVec::with_capacity(self.0.len());
for item in self.iter() { for item in self {
if let Value::Array(nested) = item { if let Value::Array(nested) = item {
flat.extend(nested.flatten().into_iter()); flat.extend(nested.flatten());
} else { } else {
flat.push(item.clone()); flat.push(item);
} }
} }
flat.into() flat.into()
@ -638,8 +629,8 @@ impl Array {
/// Return a new array with the same items, but in reverse order. /// Return a new array with the same items, but in reverse order.
#[func(title = "Reverse")] #[func(title = "Reverse")]
pub fn rev(&self) -> Array { pub fn rev(self) -> Array {
self.0.iter().cloned().rev().collect() self.into_iter().rev().collect()
} }
/// Split the array at occurrences of the specified value. /// Split the array at occurrences of the specified value.
@ -658,7 +649,7 @@ impl Array {
/// Combine all items in the array into one. /// Combine all items in the array into one.
#[func] #[func]
pub fn join( pub fn join(
&self, self,
/// A value to insert between each item of the array. /// A value to insert between each item of the array.
#[default] #[default]
separator: Option<Value>, separator: Option<Value>,
@ -671,7 +662,7 @@ impl Array {
let mut last = last; let mut last = last;
let mut result = Value::None; let mut result = Value::None;
for (i, value) in self.iter().cloned().enumerate() { for (i, value) in self.into_iter().enumerate() {
if i > 0 { if i > 0 {
if i + 1 == len && last.is_some() { if i + 1 == len && last.is_some() {
result = ops::join(result, last.take().unwrap())?; result = ops::join(result, last.take().unwrap())?;
@ -690,7 +681,7 @@ impl Array {
/// adjacent elements. /// adjacent elements.
#[func] #[func]
pub fn intersperse( pub fn intersperse(
&self, self,
/// The value that will be placed between each adjacent element. /// The value that will be placed between each adjacent element.
separator: Value, separator: Value,
) -> Array { ) -> Array {
@ -701,7 +692,7 @@ impl Array {
n => (2 * n) - 1, n => (2 * n) - 1,
}; };
let mut vec = EcoVec::with_capacity(size); let mut vec = EcoVec::with_capacity(size);
let mut iter = self.iter().cloned(); let mut iter = self.into_iter();
if let Some(first) = iter.next() { if let Some(first) = iter.next() {
vec.push(first); vec.push(first);
@ -722,7 +713,7 @@ impl Array {
/// function (if given) yields an error. /// function (if given) yields an error.
#[func] #[func]
pub fn sorted( pub fn sorted(
&self, self,
/// The virtual machine. /// The virtual machine.
vm: &mut Vm, vm: &mut Vm,
/// The callsite span. /// The callsite span.
@ -733,7 +724,7 @@ impl Array {
key: Option<Func>, key: Option<Func>,
) -> SourceResult<Array> { ) -> SourceResult<Array> {
let mut result = Ok(()); let mut result = Ok(());
let mut vec = self.0.clone(); let mut vec = self.0;
let mut key_of = |x: Value| match &key { let mut key_of = |x: Value| match &key {
// NOTE: We are relying on `comemo`'s memoization of function // NOTE: We are relying on `comemo`'s memoization of function
// evaluation to not excessively reevaluate the `key`. // evaluation to not excessively reevaluate the `key`.
@ -772,7 +763,7 @@ impl Array {
/// ``` /// ```
#[func(title = "Deduplicate")] #[func(title = "Deduplicate")]
pub fn dedup( pub fn dedup(
&self, self,
/// The virtual machine. /// The virtual machine.
vm: &mut Vm, vm: &mut Vm,
/// If given, applies this function to the elements in the array to /// If given, applies this function to the elements in the array to
@ -791,10 +782,10 @@ impl Array {
// This algorithm is O(N^2) because we cannot rely on `HashSet` since: // This algorithm is O(N^2) because we cannot rely on `HashSet` since:
// 1. We would like to preserve the order of the elements. // 1. We would like to preserve the order of the elements.
// 2. We cannot hash arbitrary `Value`. // 2. We cannot hash arbitrary `Value`.
'outer: for value in self.iter() { 'outer: for value in self {
let key = key_of(value.clone())?; let key = key_of(value.clone())?;
if out.is_empty() { if out.is_empty() {
out.push(value.clone()); out.push(value);
continue; continue;
} }
@ -804,7 +795,7 @@ impl Array {
} }
} }
out.push(value.clone()); out.push(value);
} }
Ok(Self(out)) Ok(Self(out))

View File

@ -331,14 +331,17 @@ impl Func {
self, self,
/// The real arguments (the other argument is just for the docs). /// The real arguments (the other argument is just for the docs).
/// The docs argument cannot be called `args`. /// The docs argument cannot be called `args`.
args: Args, args: &mut Args,
/// The arguments to apply to the function. /// The arguments to apply to the function.
#[external] #[external]
#[variadic] #[variadic]
arguments: Vec<Args>, arguments: Vec<Args>,
) -> Func { ) -> Func {
let span = self.span; let span = self.span;
Self { repr: Repr::With(Arc::new((self, args))), span } Self {
repr: Repr::With(Arc::new((self, args.take()))),
span,
}
} }
/// Returns a selector that filters for elements belonging to this function /// Returns a selector that filters for elements belonging to this function
@ -348,13 +351,12 @@ impl Func {
self, self,
/// The real arguments (the other argument is just for the docs). /// The real arguments (the other argument is just for the docs).
/// The docs argument cannot be called `args`. /// The docs argument cannot be called `args`.
args: Args, args: &mut Args,
/// The fields to filter for. /// The fields to filter for.
#[variadic] #[variadic]
#[external] #[external]
fields: Vec<Args>, fields: Vec<Args>,
) -> StrResult<Selector> { ) -> StrResult<Selector> {
let mut args = args;
let fields = args.to_named(); let fields = args.to_named();
args.items.retain(|arg| arg.name.is_none()); args.items.retain(|arg| arg.name.is_none());
Ok(self Ok(self

View File

@ -254,7 +254,7 @@ impl Color {
pub fn luma( pub fn luma(
/// The real arguments (the other arguments are just for the docs, this /// The real arguments (the other arguments are just for the docs, this
/// function is a bit involved, so we parse the arguments manually). /// function is a bit involved, so we parse the arguments manually).
args: Args, args: &mut Args,
/// The lightness component. /// The lightness component.
#[external] #[external]
lightness: Component, lightness: Component,
@ -264,7 +264,6 @@ impl Color {
#[external] #[external]
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
let mut args = args;
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_luma() color.to_luma()
} else { } else {
@ -300,7 +299,7 @@ impl Color {
pub fn oklab( pub fn oklab(
/// The real arguments (the other arguments are just for the docs, this /// The real arguments (the other arguments are just for the docs, this
/// function is a bit involved, so we parse the arguments manually). /// function is a bit involved, so we parse the arguments manually).
args: Args, args: &mut Args,
/// The cyan component. /// The cyan component.
#[external] #[external]
lightness: RatioComponent, lightness: RatioComponent,
@ -319,7 +318,6 @@ impl Color {
#[external] #[external]
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
let mut args = args;
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_oklab() color.to_oklab()
} else { } else {
@ -363,7 +361,7 @@ impl Color {
pub fn linear_rgb( pub fn linear_rgb(
/// The real arguments (the other arguments are just for the docs, this /// The real arguments (the other arguments are just for the docs, this
/// function is a bit involved, so we parse the arguments manually). /// function is a bit involved, so we parse the arguments manually).
args: Args, args: &mut Args,
/// The red component. /// The red component.
#[external] #[external]
red: Component, red: Component,
@ -382,7 +380,6 @@ impl Color {
#[external] #[external]
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
let mut args = args;
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_linear_rgb() color.to_linear_rgb()
} else { } else {
@ -421,7 +418,7 @@ impl Color {
pub fn rgb( pub fn rgb(
/// The real arguments (the other arguments are just for the docs, this /// The real arguments (the other arguments are just for the docs, this
/// function is a bit involved, so we parse the arguments manually). /// function is a bit involved, so we parse the arguments manually).
args: Args, args: &mut Args,
/// The red component. /// The red component.
#[external] #[external]
red: Component, red: Component,
@ -454,7 +451,6 @@ impl Color {
#[external] #[external]
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
let mut args = args;
Ok(if let Some(string) = args.find::<Spanned<Str>>()? { Ok(if let Some(string) = args.find::<Spanned<Str>>()? {
Self::from_str(&string.v).at(string.span)? Self::from_str(&string.v).at(string.span)?
} else if let Some(color) = args.find::<Color>()? { } else if let Some(color) = args.find::<Color>()? {
@ -497,7 +493,7 @@ impl Color {
pub fn cmyk( pub fn cmyk(
/// The real arguments (the other arguments are just for the docs, this /// The real arguments (the other arguments are just for the docs, this
/// function is a bit involved, so we parse the arguments manually). /// function is a bit involved, so we parse the arguments manually).
args: Args, args: &mut Args,
/// The cyan component. /// The cyan component.
#[external] #[external]
cyan: RatioComponent, cyan: RatioComponent,
@ -516,7 +512,6 @@ impl Color {
#[external] #[external]
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
let mut args = args;
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_cmyk() color.to_cmyk()
} else { } else {
@ -557,7 +552,7 @@ impl Color {
pub fn hsl( pub fn hsl(
/// The real arguments (the other arguments are just for the docs, this /// The real arguments (the other arguments are just for the docs, this
/// function is a bit involved, so we parse the arguments manually). /// function is a bit involved, so we parse the arguments manually).
args: Args, args: &mut Args,
/// The hue angle. /// The hue angle.
#[external] #[external]
hue: Angle, hue: Angle,
@ -576,7 +571,6 @@ impl Color {
#[external] #[external]
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
let mut args = args;
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_hsl() color.to_hsl()
} else { } else {
@ -617,7 +611,7 @@ impl Color {
pub fn hsv( pub fn hsv(
/// The real arguments (the other arguments are just for the docs, this /// The real arguments (the other arguments are just for the docs, this
/// function is a bit involved, so we parse the arguments manually). /// function is a bit involved, so we parse the arguments manually).
args: Args, args: &mut Args,
/// The hue angle. /// The hue angle.
#[external] #[external]
hue: Angle, hue: Angle,
@ -636,7 +630,6 @@ impl Color {
#[external] #[external]
color: Color, color: Color,
) -> SourceResult<Color> { ) -> SourceResult<Color> {
let mut args = args;
Ok(if let Some(color) = args.find::<Color>()? { Ok(if let Some(color) = args.find::<Color>()? {
color.to_hsv() color.to_hsv()
} else { } else {

View File

@ -182,7 +182,7 @@ impl Gradient {
#[func(title = "Linear Gradient")] #[func(title = "Linear Gradient")]
pub fn linear( pub fn linear(
/// The args of this function. /// The args of this function.
args: Args, args: &mut Args,
/// The call site of this function. /// The call site of this function.
span: Span, span: Span,
/// The color [stops](#stops) of the gradient. /// The color [stops](#stops) of the gradient.
@ -212,12 +212,6 @@ impl Gradient {
#[external] #[external]
angle: Angle, angle: Angle,
) -> SourceResult<Gradient> { ) -> SourceResult<Gradient> {
let mut args = args;
if stops.len() < 2 {
bail!(error!(span, "a gradient must have at least two stops")
.with_hint("try filling the shape with a single color instead"));
}
let angle = if let Some(angle) = args.named::<Angle>("angle")? { let angle = if let Some(angle) = args.named::<Angle>("angle")? {
angle angle
} else if let Some(dir) = args.named::<Dir>("dir")? { } else if let Some(dir) = args.named::<Dir>("dir")? {
@ -231,6 +225,11 @@ impl Gradient {
Angle::rad(0.0) Angle::rad(0.0)
}; };
if stops.len() < 2 {
bail!(error!(span, "a gradient must have at least two stops")
.with_hint("try filling the shape with a single color instead"));
}
Ok(Self::Linear(Arc::new(LinearGradient { Ok(Self::Linear(Arc::new(LinearGradient {
stops: process_stops(&stops)?, stops: process_stops(&stops)?,
angle, angle,

View File

@ -248,6 +248,10 @@
#test((2, 1, 3, -10, -5, 8, 6, -7, 2).sorted(key: x => x), (-10, -7, -5, 1, 2, 2, 3, 6, 8)) #test((2, 1, 3, -10, -5, 8, 6, -7, 2).sorted(key: x => x), (-10, -7, -5, 1, 2, 2, 3, 6, 8))
#test((2, 1, 3, -10, -5, 8, 6, -7, 2).sorted(key: x => x * x), (1, 2, 2, 3, -5, 6, -7, 8, -10)) #test((2, 1, 3, -10, -5, 8, 6, -7, 2).sorted(key: x => x * x), (1, 2, 2, 3, -5, 6, -7, 8, -10))
---
// Error: 12-18 unexpected argument
#().sorted(x => x)
--- ---
// Test the `zip` method. // Test the `zip` method.
#test(().zip(()), ()) #test(().zip(()), ())
@ -262,7 +266,7 @@
#test((1,).zip((2,), (3,)), ((1, 2, 3),)) #test((1,).zip((2,), (3,)), ((1, 2, 3),))
#test((1, 2, 3).zip(), ((1,), (2,), (3,))) #test((1, 2, 3).zip(), ((1,), (2,), (3,)))
#test(array.zip(()), ()) #test(array.zip(()), ())
#test(array.zip(("a", "b")), (("a",), ("b",)))
--- ---
// Test the `enumerate` method. // Test the `enumerate` method.
@ -289,6 +293,14 @@
#test(("Hello", "World", "Hi", "There").dedup(key: x => x.len()), ("Hello", "Hi")) #test(("Hello", "World", "Hi", "There").dedup(key: x => x.len()), ("Hello", "Hi"))
#test(("Hello", "World", "Hi", "There").dedup(key: x => x.at(0)), ("Hello", "World", "There")) #test(("Hello", "World", "Hi", "There").dedup(key: x => x.at(0)), ("Hello", "World", "There"))
---
// Error: 9-26 unexpected argument: val
#().zip(val: "applicable")
---
// Error: 13-30 unexpected argument: val
#().zip((), val: "applicable")
--- ---
// Error: 32-37 cannot divide by zero // Error: 32-37 cannot divide by zero
#(1, 2, 0, 3).sorted(key: x => 5 / x) #(1, 2, 0, 3).sorted(key: x => 5 / x)

View File

@ -116,6 +116,10 @@
// Error: 21-26 expected integer or ratio, found boolean // Error: 21-26 expected integer or ratio, found boolean
#rgb(10%, 20%, 30%, false) #rgb(10%, 20%, 30%, false)
---
// Error: 10-20 unexpected argument: key
#luma(1, key: "val")
--- ---
// Error: 12-24 expected float or ratio, found string // Error: 12-24 expected float or ratio, found string
// Error: 26-39 expected float or ratio, found string // Error: 26-39 expected float or ratio, found string