better support for NaN passed to builtin functions

This commit is contained in:
Connor Skees 2020-08-12 16:11:21 -04:00
parent a665cb13cc
commit 2d798a6386
4 changed files with 136 additions and 31 deletions

View File

@ -25,7 +25,9 @@ pub(crate) fn nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Val
let mut list = args.get_err(0, "list")?.as_list();
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())?),
@ -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())?),

View File

@ -16,8 +16,8 @@ use crate::{
pub(crate) fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
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<Value> {
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<Va
args.max_args(1)?;
match args.get_err(0, "number")? {
Value::Dimension(Some(n), u, _) => 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<V
args.max_args(1)?;
match args.get_err(0, "number")? {
Value::Dimension(Some(n), u, _) => 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<Val
args.max_args(1)?;
match args.get_err(0, "number")? {
Value::Dimension(Some(n), u, _) => 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<Value>
.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::<SassResult<Vec<(Number, Unit)>>>()?
.collect::<SassResult<Vec<(Option<Number>, 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<Value>
)?
.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<Value>
.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::<SassResult<Vec<(Number, Unit)>>>()?
.collect::<SassResult<Vec<(Option<Number>, 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<Value>
)?
.is_true()
{
max = num;
max = (num, unit);
}
}
Ok(Value::Dimension(Some(max.0), max.1, true))

View File

@ -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!(

79
tests/nan.rs Normal file
View File

@ -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"
);