Range refactor (#12405)

# Description
Currently, `Range` is a struct with a `from`, `to`, and `incr` field,
which are all type `Value`. This PR changes `Range` to be an enum over
`IntRange` and `FloatRange` for better type safety / stronger compile
time guarantees.

Fixes: #11778 Fixes: #11777 Fixes: #11776 Fixes: #11775 Fixes: #11774
Fixes: #11773 Fixes: #11769.

# User-Facing Changes
Hopefully none, besides bug fixes.

Although, the `serde` representation might have changed.
This commit is contained in:
Ian Manske
2024-04-06 14:04:56 +00:00
committed by GitHub
parent 75fedcc8dd
commit 7a7d43344e
27 changed files with 1126 additions and 798 deletions

View File

@ -125,7 +125,7 @@ fn into_record(
),
},
Value::Range { val, .. } => Value::record(
val.into_range_iter(engine_state.ctrlc.clone())?
val.into_range_iter(span, engine_state.ctrlc.clone())
.enumerate()
.map(|(idx, val)| (format!("{idx}"), val))
.collect(),

View File

@ -257,13 +257,7 @@ pub fn debug_string_without_formatting(value: &Value) -> String {
Value::Filesize { val, .. } => val.to_string(),
Value::Duration { val, .. } => val.to_string(),
Value::Date { val, .. } => format!("{val:?}"),
Value::Range { val, .. } => {
format!(
"{}..{}",
debug_string_without_formatting(&val.from),
debug_string_without_formatting(&val.to)
)
}
Value::Range { val, .. } => val.to_string(),
Value::String { val, .. } => val.clone(),
Value::Glob { val, .. } => val.clone(),
Value::List { vals: val, .. } => format!(

View File

@ -1,6 +1,7 @@
use itertools::Either;
use nu_engine::command_prelude::*;
use nu_protocol::{ast::RangeInclusion, PipelineIterator, Range};
use nu_protocol::{PipelineIterator, Range};
use std::ops::Bound;
#[derive(Clone)]
pub struct DropNth;
@ -101,8 +102,8 @@ impl Command for DropNth {
) -> Result<PipelineData, ShellError> {
let metadata = input.metadata();
let number_or_range = extract_int_or_range(engine_state, stack, call)?;
let mut lower_bound = None;
let rows = match number_or_range {
let rows = match number_or_range.item {
Either::Left(row_number) => {
let and_rows: Vec<Spanned<i64>> = call.rest(engine_state, stack, 1)?;
let mut rows: Vec<_> = and_rows.into_iter().map(|x| x.item as usize).collect();
@ -110,66 +111,71 @@ impl Command for DropNth {
rows.sort_unstable();
rows
}
Either::Right(row_range) => {
let from = row_range.from.as_int()?; // as usize;
let to = row_range.to.as_int()?; // as usize;
Either::Right(Range::FloatRange(_)) => {
return Err(ShellError::UnsupportedInput {
msg: "float range".into(),
input: "value originates from here".into(),
msg_span: call.head,
input_span: number_or_range.span,
});
}
Either::Right(Range::IntRange(range)) => {
// check for negative range inputs, e.g., (2..-5)
if from.is_negative() || to.is_negative() {
let span: Spanned<Range> = call.req(engine_state, stack, 0)?;
return Err(ShellError::TypeMismatch {
err_message: "drop nth accepts only positive ints".to_string(),
span: span.span,
let end_negative = match range.end() {
Bound::Included(end) | Bound::Excluded(end) => end < 0,
Bound::Unbounded => false,
};
if range.start().is_negative() || end_negative {
return Err(ShellError::UnsupportedInput {
msg: "drop nth accepts only positive ints".into(),
input: "value originates from here".into(),
msg_span: call.head,
input_span: number_or_range.span,
});
}
// check if the upper bound is smaller than the lower bound, e.g., do not accept 4..2
if to < from {
let span: Spanned<Range> = call.req(engine_state, stack, 0)?;
return Err(ShellError::TypeMismatch {
err_message:
"The upper bound needs to be equal or larger to the lower bound"
.to_string(),
span: span.span,
if range.step() < 0 {
return Err(ShellError::UnsupportedInput {
msg: "The upper bound needs to be equal or larger to the lower bound"
.into(),
input: "value originates from here".into(),
msg_span: call.head,
input_span: number_or_range.span,
});
}
// check for equality to isize::MAX because for some reason,
// the parser returns isize::MAX when we provide a range without upper bound (e.g., 5.. )
let mut to = to as usize;
let from = from as usize;
let start = range.start() as usize;
if let PipelineData::Value(Value::List { ref vals, .. }, _) = input {
let max = from + vals.len() - 1;
if to > max {
to = max;
let end = match range.end() {
Bound::Included(end) => end as usize,
Bound::Excluded(end) => (end - 1) as usize,
Bound::Unbounded => {
return Ok(input
.into_iter()
.take(start)
.into_pipeline_data_with_metadata(
metadata,
engine_state.ctrlc.clone(),
))
}
};
if to > 0 && to as isize == isize::MAX {
lower_bound = Some(from);
vec![from]
} else if matches!(row_range.inclusion, RangeInclusion::Inclusive) {
(from..=to).collect()
let end = if let PipelineData::Value(Value::List { vals, .. }, _) = &input {
end.min(vals.len() - 1)
} else {
(from..to).collect()
}
end
};
(start..=end).collect()
}
};
if let Some(lower_bound) = lower_bound {
Ok(input
.into_iter()
.take(lower_bound)
.collect::<Vec<_>>()
.into_pipeline_data_with_metadata(metadata, engine_state.ctrlc.clone()))
} else {
Ok(DropNthIterator {
input: input.into_iter(),
rows,
current: 0,
}
.into_pipeline_data_with_metadata(metadata, engine_state.ctrlc.clone()))
Ok(DropNthIterator {
input: input.into_iter(),
rows,
current: 0,
}
.into_pipeline_data_with_metadata(metadata, engine_state.ctrlc.clone()))
}
}
@ -177,11 +183,11 @@ fn extract_int_or_range(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
) -> Result<Either<i64, Range>, ShellError> {
let value = call.req::<Value>(engine_state, stack, 0)?;
) -> Result<Spanned<Either<i64, Range>>, ShellError> {
let value: Value = call.req(engine_state, stack, 0)?;
let int_opt = value.as_int().map(Either::Left).ok();
let range_opt = value.as_range().map(|r| Either::Right(r.clone())).ok();
let range_opt = value.as_range().map(Either::Right).ok();
int_opt
.or(range_opt)
@ -189,6 +195,10 @@ fn extract_int_or_range(
err_message: "int or range".into(),
span: value.span(),
})
.map(|either| Spanned {
item: either,
span: value.span(),
})
}
struct DropNthIterator {

View File

@ -133,11 +133,15 @@ fn first_helper(
}
}
Value::Range { val, .. } => {
let mut iter = val.into_range_iter(span, ctrlc.clone());
if return_single_element {
Ok(val.from.into_pipeline_data())
if let Some(v) = iter.next() {
Ok(v.into_pipeline_data())
} else {
Err(ShellError::AccessEmptyContent { span: head })
}
} else {
Ok(val
.into_range_iter(ctrlc.clone())?
Ok(iter
.take(rows_desired)
.into_pipeline_data_with_metadata(metadata, ctrlc))
}

View File

@ -139,84 +139,104 @@ impl Command for ParEach {
match input {
PipelineData::Empty => Ok(PipelineData::Empty),
PipelineData::Value(Value::Range { val, .. }, ..) => Ok(create_pool(max_threads)?
.install(|| {
let vec = val
.into_range_iter(ctrlc.clone())
.expect("unable to create a range iterator")
.enumerate()
.par_bridge()
.map(move |(index, x)| {
let block = engine_state.get_block(block_id);
PipelineData::Value(value, ..) => {
let span = value.span();
match value {
Value::List { vals, .. } => Ok(create_pool(max_threads)?.install(|| {
let vec = vals
.par_iter()
.enumerate()
.map(move |(index, x)| {
let block = engine_state.get_block(block_id);
let mut stack = stack.clone();
let mut stack = stack.clone();
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
}
let val_span = x.span();
let x_is_error = x.is_error();
let val_span = x.span();
let x_is_error = x.is_error();
let val = match eval_block_with_early_return(
engine_state,
&mut stack,
block,
x.into_pipeline_data(),
) {
Ok(v) => v.into_value(span),
Err(error) => Value::error(
chain_error_with_input(error, x_is_error, val_span),
val_span,
),
};
let val = match eval_block_with_early_return(
engine_state,
&mut stack,
block,
x.clone().into_pipeline_data(),
) {
Ok(v) => v.into_value(span),
Err(error) => Value::error(
chain_error_with_input(error, x_is_error, val_span),
val_span,
),
};
(index, val)
})
.collect::<Vec<_>>();
(index, val)
})
.collect::<Vec<_>>();
apply_order(vec).into_pipeline_data(ctrlc)
})),
PipelineData::Value(Value::List { vals: val, .. }, ..) => Ok(create_pool(max_threads)?
.install(|| {
let vec = val
.par_iter()
.enumerate()
.map(move |(index, x)| {
let block = engine_state.get_block(block_id);
apply_order(vec).into_pipeline_data(ctrlc)
})),
Value::Range { val, .. } => Ok(create_pool(max_threads)?.install(|| {
let vec = val
.into_range_iter(span, ctrlc.clone())
.enumerate()
.par_bridge()
.map(move |(index, x)| {
let block = engine_state.get_block(block_id);
let mut stack = stack.clone();
let mut stack = stack.clone();
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
let val_span = x.span();
let x_is_error = x.is_error();
let val = match eval_block_with_early_return(
engine_state,
&mut stack,
block,
x.into_pipeline_data(),
) {
Ok(v) => v.into_value(span),
Err(error) => Value::error(
chain_error_with_input(error, x_is_error, val_span),
val_span,
),
};
(index, val)
})
.collect::<Vec<_>>();
apply_order(vec).into_pipeline_data(ctrlc)
})),
// This match allows non-iterables to be accepted,
// which is currently considered undesirable (Nov 2022).
value => {
let block = engine_state.get_block(block_id);
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, value.clone());
}
}
let val_span = x.span();
let x_is_error = x.is_error();
let val = match eval_block_with_early_return(
engine_state,
&mut stack,
block,
x.clone().into_pipeline_data(),
) {
Ok(v) => v.into_value(span),
Err(error) => Value::error(
chain_error_with_input(error, x_is_error, val_span),
val_span,
),
};
(index, val)
})
.collect::<Vec<_>>();
apply_order(vec).into_pipeline_data(ctrlc)
})),
eval_block_with_early_return(
engine_state,
&mut stack,
block,
value.into_pipeline_data(),
)
}
}
}
PipelineData::ListStream(stream, ..) => Ok(create_pool(max_threads)?.install(|| {
let vec = stream
.enumerate()
@ -294,24 +314,6 @@ impl Command for ParEach {
apply_order(vec).into_pipeline_data(ctrlc)
})),
// This match allows non-iterables to be accepted,
// which is currently considered undesirable (Nov 2022).
PipelineData::Value(x, ..) => {
let block = engine_state.get_block(block_id);
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
eval_block_with_early_return(
engine_state,
&mut stack,
block,
x.into_pipeline_data(),
)
}
}
.and_then(|x| x.filter(|v| !v.is_nothing(), outer_ctrlc))
.map(|res| res.set_metadata(metadata))

View File

@ -1,5 +1,6 @@
use nu_engine::command_prelude::*;
use nu_protocol::ast::RangeInclusion;
use nu_protocol::Range as NumRange;
use std::ops::Bound;
#[derive(Clone)]
pub struct Range;
@ -64,59 +65,68 @@ impl Command for Range {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let metadata = input.metadata();
let rows: nu_protocol::Range = call.req(engine_state, stack, 0)?;
let rows: Spanned<NumRange> = call.req(engine_state, stack, 0)?;
let rows_from = get_range_val(rows.from);
let rows_to = if rows.inclusion == RangeInclusion::RightExclusive {
get_range_val(rows.to) - 1
} else {
get_range_val(rows.to)
};
match rows.item {
NumRange::IntRange(range) => {
let start = range.start();
let end = match range.end() {
Bound::Included(end) => end,
Bound::Excluded(end) => end - 1,
Bound::Unbounded => {
if range.step() < 0 {
i64::MIN
} else {
i64::MAX
}
}
};
// only collect the input if we have any negative indices
if rows_from < 0 || rows_to < 0 {
let v: Vec<_> = input.into_iter().collect();
let vlen: i64 = v.len() as i64;
// only collect the input if we have any negative indices
if start < 0 || end < 0 {
let v: Vec<_> = input.into_iter().collect();
let vlen: i64 = v.len() as i64;
let from = if rows_from < 0 {
(vlen + rows_from) as usize
} else {
rows_from as usize
};
let from = if start < 0 {
(vlen + start) as usize
} else {
start as usize
};
let to = if rows_to < 0 {
(vlen + rows_to) as usize
} else if rows_to > v.len() as i64 {
v.len()
} else {
rows_to as usize
};
let to = if end < 0 {
(vlen + end) as usize
} else if end > v.len() as i64 {
v.len()
} else {
end as usize
};
if from > to {
Ok(PipelineData::Value(Value::nothing(call.head), None))
} else {
let iter = v.into_iter().skip(from).take(to - from + 1);
Ok(iter.into_pipeline_data(engine_state.ctrlc.clone()))
}
} else {
let from = rows_from as usize;
let to = rows_to as usize;
if from > to {
Ok(PipelineData::Value(Value::nothing(call.head), None))
} else {
let iter = input.into_iter().skip(from).take(to - from + 1);
Ok(iter.into_pipeline_data(engine_state.ctrlc.clone()))
if from > to {
Ok(PipelineData::Value(Value::nothing(call.head), None))
} else {
let iter = v.into_iter().skip(from).take(to - from + 1);
Ok(iter.into_pipeline_data(engine_state.ctrlc.clone()))
}
} else {
let from = start as usize;
let to = end as usize;
if from > to {
Ok(PipelineData::Value(Value::nothing(call.head), None))
} else {
let iter = input.into_iter().skip(from).take(to - from + 1);
Ok(iter.into_pipeline_data(engine_state.ctrlc.clone()))
}
}
.map(|x| x.set_metadata(metadata))
}
NumRange::FloatRange(_) => Err(ShellError::UnsupportedInput {
msg: "float range".into(),
input: "value originates from here".into(),
msg_span: call.head,
input_span: rows.span,
}),
}
.map(|x| x.set_metadata(metadata))
}
}
fn get_range_val(rows_val: Value) -> i64 {
match rows_val {
Value::Int { val: x, .. } => x,
_ => 0,
}
}

View File

@ -60,7 +60,7 @@ impl Command for Take {
Ok(PipelineData::Value(Value::binary(slice, span), metadata))
}
Value::Range { val, .. } => Ok(val
.into_range_iter(ctrlc.clone())?
.into_range_iter(span, ctrlc.clone())
.take(rows_desired)
.into_pipeline_data_with_metadata(metadata, ctrlc)),
// Propagate errors by explicitly matching them before the final case.

View File

@ -292,7 +292,7 @@ fn convert_to_value(
};
Ok(Value::range(
Range::new(expr.span, from, next, to, &operator)?,
Range::new(from, next, to, operator.inclusion, expr.span)?,
expr.span,
))
}

View File

@ -2,8 +2,9 @@ use core::fmt::Write;
use fancy_regex::Regex;
use nu_engine::{command_prelude::*, get_columns};
use nu_parser::escape_quote_string;
use nu_protocol::ast::RangeInclusion;
use nu_protocol::Range;
use once_cell::sync::Lazy;
use std::ops::Bound;
#[derive(Clone)]
pub struct ToNuon;
@ -234,16 +235,26 @@ pub fn value_to_string(
}
}
Value::Nothing { .. } => Ok("null".to_string()),
Value::Range { val, .. } => Ok(format!(
"{}..{}{}",
value_to_string(&val.from, span, depth + 1, indent)?,
if val.inclusion == RangeInclusion::RightExclusive {
"<"
} else {
""
},
value_to_string(&val.to, span, depth + 1, indent)?
)),
Value::Range { val, .. } => match val {
Range::IntRange(range) => Ok(range.to_string()),
Range::FloatRange(range) => {
let start =
value_to_string(&Value::float(range.start(), span), span, depth + 1, indent)?;
match range.end() {
Bound::Included(end) => Ok(format!(
"{}..{}",
start,
value_to_string(&Value::float(end, span), span, depth + 1, indent)?
)),
Bound::Excluded(end) => Ok(format!(
"{}..<{}",
start,
value_to_string(&Value::float(end, span), span, depth + 1, indent)?
)),
Bound::Unbounded => Ok(format!("{start}..",)),
}
}
},
Value::Record { val, .. } => {
let mut collection = vec![];
for (col, val) in &**val {

View File

@ -116,13 +116,7 @@ fn local_into_string(value: Value, separator: &str, config: &Config) -> String {
Value::Date { val, .. } => {
format!("{} ({})", val.to_rfc2822(), HumanTime::from(val))
}
Value::Range { val, .. } => {
format!(
"{}..{}",
local_into_string(val.from, ", ", config),
local_into_string(val.to, ", ", config)
)
}
Value::Range { val, .. } => val.to_string(),
Value::String { val, .. } => val,
Value::Glob { val, .. } => val,
Value::List { vals: val, .. } => val

View File

@ -92,7 +92,7 @@ pub fn calculate(
}
PipelineData::Value(Value::Range { val, .. }, ..) => {
let new_vals: Result<Vec<Value>, ShellError> = val
.into_range_iter(None)?
.into_range_iter(span, None)
.map(|val| mf(&[val], span, name))
.collect();

View File

@ -1,7 +1,7 @@
use nu_engine::command_prelude::*;
use nu_protocol::Range;
use nu_protocol::{FloatRange, Range};
use rand::prelude::{thread_rng, Rng};
use std::cmp::Ordering;
use std::ops::Bound;
#[derive(Clone)]
pub struct SubCommand;
@ -68,43 +68,39 @@ fn float(
stack: &mut Stack,
call: &Call,
) -> Result<PipelineData, ShellError> {
let mut range_span = call.head;
let span = call.head;
let range: Option<Spanned<Range>> = call.opt(engine_state, stack, 0)?;
let (min, max) = if let Some(spanned_range) = range {
let r = spanned_range.item;
range_span = spanned_range.span;
let mut thread_rng = thread_rng();
if r.is_end_inclusive() {
(r.from.coerce_float()?, r.to.coerce_float()?)
} else if r.to.coerce_float()? >= 1.0 {
(r.from.coerce_float()?, r.to.coerce_float()? - 1.0)
} else {
(0.0, 0.0)
match range {
Some(range) => {
let range_span = range.span;
let range = FloatRange::from(range.item);
if range.step() < 0.0 {
return Err(ShellError::InvalidRange {
left_flank: range.start().to_string(),
right_flank: match range.end() {
Bound::Included(end) | Bound::Excluded(end) => end.to_string(),
Bound::Unbounded => "".into(),
},
span: range_span,
});
}
let value = match range.end() {
Bound::Included(end) => thread_rng.gen_range(range.start()..=end),
Bound::Excluded(end) => thread_rng.gen_range(range.start()..end),
Bound::Unbounded => thread_rng.gen_range(range.start()..f64::INFINITY),
};
Ok(PipelineData::Value(Value::float(value, span), None))
}
} else {
(0.0, 1.0)
};
match min.partial_cmp(&max) {
Some(Ordering::Greater) => Err(ShellError::InvalidRange {
left_flank: min.to_string(),
right_flank: max.to_string(),
span: range_span,
}),
Some(Ordering::Equal) => Ok(PipelineData::Value(
Value::float(min, Span::new(64, 64)),
None => Ok(PipelineData::Value(
Value::float(thread_rng.gen_range(0.0..1.0), span),
None,
)),
_ => {
let mut thread_rng = thread_rng();
let result: f64 = thread_rng.gen_range(min..max);
Ok(PipelineData::Value(
Value::float(result, Span::new(64, 64)),
None,
))
}
}
}

View File

@ -1,7 +1,7 @@
use nu_engine::command_prelude::*;
use nu_protocol::Range;
use rand::prelude::{thread_rng, Rng};
use std::cmp::Ordering;
use std::ops::Bound;
#[derive(Clone)]
pub struct SubCommand;
@ -71,34 +71,44 @@ fn integer(
let span = call.head;
let range: Option<Spanned<Range>> = call.opt(engine_state, stack, 0)?;
let mut range_span = call.head;
let (min, max) = if let Some(spanned_range) = range {
let r = spanned_range.item;
range_span = spanned_range.span;
if r.is_end_inclusive() {
(r.from.as_int()?, r.to.as_int()?)
} else if r.to.as_int()? > 0 {
(r.from.as_int()?, r.to.as_int()? - 1)
} else {
(0, 0)
}
} else {
(0, i64::MAX)
};
let mut thread_rng = thread_rng();
match min.partial_cmp(&max) {
Some(Ordering::Greater) => Err(ShellError::InvalidRange {
left_flank: min.to_string(),
right_flank: max.to_string(),
span: range_span,
}),
Some(Ordering::Equal) => Ok(PipelineData::Value(Value::int(min, span), None)),
_ => {
let mut thread_rng = thread_rng();
let result: i64 = thread_rng.gen_range(min..=max);
match range {
Some(range) => {
let range_span = range.span;
match range.item {
Range::IntRange(range) => {
if range.step() < 0 {
return Err(ShellError::InvalidRange {
left_flank: range.start().to_string(),
right_flank: match range.end() {
Bound::Included(end) | Bound::Excluded(end) => end.to_string(),
Bound::Unbounded => "".into(),
},
span: range_span,
});
}
Ok(PipelineData::Value(Value::int(result, span), None))
let value = match range.end() {
Bound::Included(end) => thread_rng.gen_range(range.start()..=end),
Bound::Excluded(end) => thread_rng.gen_range(range.start()..end),
Bound::Unbounded => thread_rng.gen_range(range.start()..=i64::MAX),
};
Ok(PipelineData::Value(Value::int(value, span), None))
}
Range::FloatRange(_) => Err(ShellError::UnsupportedInput {
msg: "float range".into(),
input: "value originates from here".into(),
msg_span: call.head,
input_span: range.span,
}),
}
}
None => Ok(PipelineData::Value(
Value::int(thread_rng.gen_range(0..=i64::MAX), span),
None,
)),
}
}

View File

@ -251,7 +251,6 @@ mod tests {
let options = Arguments {
substring: String::from("Lm"),
range: None,
cell_paths: None,
end: false,
@ -266,12 +265,14 @@ mod tests {
#[test]
fn returns_index_of_next_substring() {
let word = Value::test_string("Cargo.Cargo");
let range = Range {
from: Value::int(1, Span::test_data()),
incr: Value::int(1, Span::test_data()),
to: Value::nothing(Span::test_data()),
inclusion: RangeInclusion::Inclusive,
};
let range = Range::new(
Value::int(1, Span::test_data()),
Value::nothing(Span::test_data()),
Value::nothing(Span::test_data()),
RangeInclusion::Inclusive,
Span::test_data(),
)
.expect("valid range");
let spanned_range = Spanned {
item: range,
@ -294,12 +295,14 @@ mod tests {
#[test]
fn index_does_not_exist_due_to_end_index() {
let word = Value::test_string("Cargo.Banana");
let range = Range {
from: Value::nothing(Span::test_data()),
inclusion: RangeInclusion::Inclusive,
incr: Value::int(1, Span::test_data()),
to: Value::int(5, Span::test_data()),
};
let range = Range::new(
Value::nothing(Span::test_data()),
Value::nothing(Span::test_data()),
Value::int(5, Span::test_data()),
RangeInclusion::Inclusive,
Span::test_data(),
)
.expect("valid range");
let spanned_range = Spanned {
item: range,
@ -322,12 +325,14 @@ mod tests {
#[test]
fn returns_index_of_nums_in_middle_due_to_index_limit_from_both_ends() {
let word = Value::test_string("123123123");
let range = Range {
from: Value::int(2, Span::test_data()),
incr: Value::int(1, Span::test_data()),
to: Value::int(6, Span::test_data()),
inclusion: RangeInclusion::Inclusive,
};
let range = Range::new(
Value::int(2, Span::test_data()),
Value::nothing(Span::test_data()),
Value::int(6, Span::test_data()),
RangeInclusion::Inclusive,
Span::test_data(),
)
.expect("valid range");
let spanned_range = Spanned {
item: range,
@ -350,12 +355,14 @@ mod tests {
#[test]
fn index_does_not_exists_due_to_strict_bounds() {
let word = Value::test_string("123456");
let range = Range {
from: Value::int(2, Span::test_data()),
incr: Value::int(1, Span::test_data()),
to: Value::int(5, Span::test_data()),
inclusion: RangeInclusion::RightExclusive,
};
let range = Range::new(
Value::int(2, Span::test_data()),
Value::nothing(Span::test_data()),
Value::int(5, Span::test_data()),
RangeInclusion::RightExclusive,
Span::test_data(),
)
.expect("valid range");
let spanned_range = Spanned {
item: range,
@ -381,7 +388,6 @@ mod tests {
let options = Arguments {
substring: String::from("ふが"),
range: None,
cell_paths: None,
end: false,
@ -396,12 +402,14 @@ mod tests {
fn index_is_not_a_char_boundary() {
let word = Value::string(String::from("💛"), Span::test_data());
let range = Range {
from: Value::int(0, Span::test_data()),
incr: Value::int(1, Span::test_data()),
to: Value::int(3, Span::test_data()),
inclusion: RangeInclusion::Inclusive,
};
let range = Range::new(
Value::int(0, Span::test_data()),
Value::int(1, Span::test_data()),
Value::int(3, Span::test_data()),
RangeInclusion::Inclusive,
Span::test_data(),
)
.expect("valid range");
let spanned_range = Spanned {
item: range,
@ -425,12 +433,14 @@ mod tests {
fn index_is_out_of_bounds() {
let word = Value::string(String::from("hello"), Span::test_data());
let range = Range {
from: Value::int(-1, Span::test_data()),
incr: Value::int(1, Span::test_data()),
to: Value::int(3, Span::test_data()),
inclusion: RangeInclusion::Inclusive,
};
let range = Range::new(
Value::int(-1, Span::test_data()),
Value::int(1, Span::test_data()),
Value::int(3, Span::test_data()),
RangeInclusion::Inclusive,
Span::test_data(),
)
.expect("valid range");
let spanned_range = Spanned {
item: range,

View File

@ -409,7 +409,7 @@ fn handle_table_command(
}
PipelineData::Value(Value::Range { val, .. }, metadata) => {
let ctrlc = input.engine_state.ctrlc.clone();
let stream = ListStream::from_stream(val.into_range_iter(ctrlc.clone())?, ctrlc);
let stream = ListStream::from_stream(val.into_range_iter(span, ctrlc.clone()), ctrlc);
input.data = PipelineData::Empty;
handle_row_stream(input, cfg, stream, metadata)
}

View File

@ -229,11 +229,13 @@ fn unbounded_from_in_range_fails() {
#[test]
fn inf_in_range_fails() {
let actual = nu!(r#"inf..5 | to json"#);
assert!(actual.err.contains("Cannot create range"));
assert!(actual.err.contains("can't convert to countable values"));
let actual = nu!(r#"5..inf | to json"#);
assert!(actual.err.contains("Cannot create range"));
assert!(actual
.err
.contains("Unbounded ranges are not allowed when converting to this format"));
let actual = nu!(r#"-inf..inf | to json"#);
assert!(actual.err.contains("Cannot create range"));
assert!(actual.err.contains("can't convert to countable values"));
}
#[test]