diff --git a/src/builtin/functions/list.rs b/src/builtin/functions/list.rs index 56ac391..22aa996 100644 --- a/src/builtin/functions/list.rs +++ b/src/builtin/functions/list.rs @@ -25,7 +25,9 @@ pub(crate) fn nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult num, - Value::Dimension(None, ..) => todo!(), + Value::Dimension(None, u, ..) => { + return Err((format!("$n: NaN{} is not an int.", u), args.span()).into()) + } v => { return Err(( format!("$n: {} is not a number.", v.inspect(args.span())?), @@ -83,7 +85,9 @@ pub(crate) fn set_nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult }; let n = match args.get_err(1, "n")? { Value::Dimension(Some(num), ..) => num, - Value::Dimension(None, ..) => todo!(), + Value::Dimension(None, u, ..) => { + return Err((format!("$n: NaN{} is not an int.", u), args.span()).into()) + } v => { return Err(( format!("$n: {} is not a number.", v.inspect(args.span())?), diff --git a/src/builtin/functions/math.rs b/src/builtin/functions/math.rs index 470937b..84a51ee 100644 --- a/src/builtin/functions/math.rs +++ b/src/builtin/functions/math.rs @@ -16,8 +16,8 @@ use crate::{ pub(crate) fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let num = match args.get_err(0, "number")? { - Value::Dimension(Some(n), Unit::None, _) => n * Number::from(100), - Value::Dimension(None, ..) => todo!(), + Value::Dimension(Some(n), Unit::None, _) => Some(n * Number::from(100)), + Value::Dimension(None, ..) => None, v @ Value::Dimension(..) => { return Err(( format!( @@ -36,14 +36,14 @@ pub(crate) fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassRes .into()) } }; - Ok(Value::Dimension(Some(num), Unit::Percent, true)) + Ok(Value::Dimension(num, Unit::Percent, true)) } pub(crate) fn round(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "number")? { Value::Dimension(Some(n), u, _) => Ok(Value::Dimension(Some(n.round()), u, true)), - Value::Dimension(None, ..) => todo!(), + Value::Dimension(None, ..) => Err(("Infinity or NaN toInt", args.span()).into()), v => Err(( format!("$number: {} is not a number.", v.inspect(args.span())?), args.span(), @@ -56,7 +56,7 @@ pub(crate) fn ceil(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult Ok(Value::Dimension(Some(n.ceil()), u, true)), - Value::Dimension(None, ..) => todo!(), + Value::Dimension(None, ..) => Err(("Infinity or NaN toInt", args.span()).into()), v => Err(( format!("$number: {} is not a number.", v.inspect(args.span())?), args.span(), @@ -69,7 +69,7 @@ pub(crate) fn floor(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult Ok(Value::Dimension(Some(n.floor()), u, true)), - Value::Dimension(None, ..) => todo!(), + Value::Dimension(None, ..) => Err(("Infinity or NaN toInt", args.span()).into()), v => Err(( format!("$number: {} is not a number.", v.inspect(args.span())?), args.span(), @@ -82,7 +82,7 @@ pub(crate) fn abs(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult Ok(Value::Dimension(Some(n.abs()), u, true)), - Value::Dimension(None, ..) => todo!(), + Value::Dimension(None, u, ..) => Ok(Value::Dimension(None, u, true)), v => Err(( format!("$number: {} is not a number.", v.inspect(args.span())?), args.span(), @@ -123,7 +123,9 @@ pub(crate) fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult< args.max_args(1)?; let limit = match args.default_arg(0, "limit", Value::Null)? { Value::Dimension(Some(n), ..) => n, - Value::Dimension(None, ..) => todo!(), + Value::Dimension(None, u, ..) => { + return Err((format!("$limit: NaN{} is not an int.", u), args.span()).into()) + } Value::Null => { let mut rng = rand::thread_rng(); return Ok(Value::Dimension( @@ -183,22 +185,29 @@ pub(crate) fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult .get_variadic()? .into_iter() .map(|val| match val.node { - Value::Dimension(Some(number), unit, _) => Ok((number, unit)), - Value::Dimension(None, ..) => todo!(), + Value::Dimension(number, unit, _) => Ok((number, unit)), v => Err((format!("{} is not a number.", v.inspect(span)?), span).into()), }) - .collect::>>()? + .collect::, Unit)>>>()? .into_iter(); - // we know that there *must* be at least one item - let mut min = nums.next().unwrap(); + let mut min = match nums.next() { + Some((Some(n), u)) => (n, u), + Some((None, u)) => return Ok(Value::Dimension(None, u, true)), + None => unreachable!(), + }; + + for (num, unit) in nums { + let num = match num { + Some(n) => n, + None => continue, + }; - for num in nums { if ValueVisitor::new(parser, span) .less_than( HigherIntermediateValue::Literal(Value::Dimension( - Some(num.0.clone()), - num.1.clone(), + Some(num.clone()), + unit.clone(), true, )), HigherIntermediateValue::Literal(Value::Dimension( @@ -209,7 +218,7 @@ pub(crate) fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult )? .is_true() { - min = num; + min = (num, unit); } } Ok(Value::Dimension(Some(min.0), min.1, true)) @@ -222,22 +231,29 @@ pub(crate) fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult .get_variadic()? .into_iter() .map(|val| match val.node { - Value::Dimension(Some(number), unit, _) => Ok((number, unit)), - Value::Dimension(None, ..) => todo!(), + Value::Dimension(number, unit, _) => Ok((number, unit)), v => Err((format!("{} is not a number.", v.inspect(span)?), span).into()), }) - .collect::>>()? + .collect::, Unit)>>>()? .into_iter(); - // we know that there *must* be at least one item - let mut max = nums.next().unwrap(); + let mut max = match nums.next() { + Some((Some(n), u)) => (n, u), + Some((None, u)) => return Ok(Value::Dimension(None, u, true)), + None => unreachable!(), + }; + + for (num, unit) in nums { + let num = match num { + Some(n) => n, + None => continue, + }; - for num in nums { if ValueVisitor::new(parser, span) .greater_than( HigherIntermediateValue::Literal(Value::Dimension( - Some(num.0.clone()), - num.1.clone(), + Some(num.clone()), + unit.clone(), true, )), HigherIntermediateValue::Literal(Value::Dimension( @@ -248,7 +264,7 @@ pub(crate) fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult )? .is_true() { - max = num; + max = (num, unit); } } Ok(Value::Dimension(Some(max.0), max.1, true)) diff --git a/src/builtin/functions/string.rs b/src/builtin/functions/string.rs index edcbbd7..eb58b4e 100644 --- a/src/builtin/functions/string.rs +++ b/src/builtin/functions/string.rs @@ -110,7 +110,9 @@ pub(crate) fn str_slice(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResu Value::Dimension(Some(n), Unit::None, _) => (n.to_integer() + BigInt::from(str_len + 1)) .to_usize() .unwrap(), - Value::Dimension(None, ..) => todo!(), + Value::Dimension(None, u, ..) => { + return Err((format!("NaN{} is not an int.", u), args.span()).into()) + } v @ Value::Dimension(..) => { return Err(( format!( @@ -141,7 +143,9 @@ pub(crate) fn str_slice(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResu Value::Dimension(Some(n), Unit::None, _) => (n.to_integer() + BigInt::from(str_len + 1)) .to_usize() .unwrap_or(str_len + 1), - Value::Dimension(None, ..) => todo!(), + Value::Dimension(None, u, ..) => { + return Err((format!("NaN{} is not an int.", u), args.span()).into()) + } v @ Value::Dimension(..) => { return Err(( format!( @@ -239,7 +243,9 @@ pub(crate) fn str_insert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassRes return Err((format!("$index: {} is not an int.", n), args.span()).into()) } Value::Dimension(Some(n), Unit::None, _) => n, - Value::Dimension(None, ..) => todo!(), + Value::Dimension(None, u, ..) => { + return Err((format!("$index: NaN{} is not an int.", u), args.span()).into()) + } v @ Value::Dimension(..) => { return Err(( format!( diff --git a/tests/nan.rs b/tests/nan.rs new file mode 100644 index 0000000..9b5ef7d --- /dev/null +++ b/tests/nan.rs @@ -0,0 +1,79 @@ +#[macro_use] +mod macros; + +error!( + unitless_nan_str_slice_start_at, + "a {\n color: str-slice(\"\", (0/0));\n}\n", "Error: NaN is not an int." +); +error!( + unitless_nan_str_slice_end_at, + "a {\n color: str-slice(\"\", 0, (0/0));\n}\n", "Error: NaN is not an int." +); +error!( + unitless_nan_str_insert_index, + "a {\n color: str-insert(\"\", \"\", (0/0));\n}\n", "Error: $index: NaN is not an int." +); +test!( + unitless_nan_percentage_number, + "a {\n color: percentage((0/0));\n}\n", + "a {\n color: NaN%;\n}\n" +); +test!( + unitless_nan_abs_number, + "a {\n color: abs((0/0));\n}\n", + "a {\n color: NaN;\n}\n" +); +error!( + unitless_nan_round_number, + "a {\n color: round((0/0));\n}\n", "Error: Infinity or NaN toInt" +); +error!( + unitless_nan_ceil_number, + "a {\n color: ceil((0/0));\n}\n", "Error: Infinity or NaN toInt" +); +error!( + unitless_nan_floor_number, + "a {\n color: floor((0/0));\n}\n", "Error: Infinity or NaN toInt" +); +error!( + unitless_nan_random_limit, + "a {\n color: random((0/0));\n}\n", "Error: $limit: NaN is not an int." +); +error!( + unitless_nan_nth_n, + "a {\n color: nth([a], (0/0));\n}\n", "Error: $n: NaN is not an int." +); +error!( + unitless_nan_set_nth_n, + "a {\n color: set-nth([a], (0/0), b);\n}\n", "Error: $n: NaN is not an int." +); +test!( + unitless_nan_min_first_arg, + "$n: (0/0);\na {\n color: min($n, 1px);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + unitless_nan_min_last_arg, + "$n: (0/0);\na {\n color: min(1px, $n);\n}\n", + "a {\n color: 1px;\n}\n" +); +test!( + unitless_nan_min_middle_arg, + "$n: (0/0);\na {\n color: min(1px, $n, 0);\n}\n", + "a {\n color: 0;\n}\n" +); +test!( + unitless_nan_max_first_arg, + "$n: (0/0);\na {\n color: max($n, 1px);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + unitless_nan_max_last_arg, + "$n: (0/0);\na {\n color: max(1px, $n);\n}\n", + "a {\n color: 1px;\n}\n" +); +test!( + unitless_nan_max_middle_arg, + "$n: (0/0);\na {\n color: max(1px, $n, 0);\n}\n", + "a {\n color: 1px;\n}\n" +);