Skip to content

Commit 9393192

Browse files
authored
Allow negative indexing (#2606)
This change relaxing the restriction on indexing into arrays to allow negative indices. Previously the index limit was 0 to N-1, this updates it such that the allowed range is -N to N-1, where negative indices count from the end of the array.
1 parent 46b18a3 commit 9393192

File tree

4 files changed

+79
-48
lines changed

4 files changed

+79
-48
lines changed

source/compiler/qsc_eval/src/lib.rs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,8 +1462,7 @@ impl State {
14621462
if index < 0 {
14631463
return Err(Error::InvalidNegativeInt(index, span));
14641464
}
1465-
let i = index.as_index(span)?;
1466-
self.update_array_index_single(env, globals, lhs, span, i, update)
1465+
self.update_array_index_single(env, globals, lhs, span, index, update)
14671466
}
14681467
range @ Value::Range(..) => {
14691468
self.update_array_index_range(env, globals, lhs, span, &range, update)
@@ -1598,16 +1597,14 @@ impl State {
15981597
globals: &impl PackageStoreLookup,
15991598
lhs: ExprId,
16001599
span: PackageSpan,
1601-
index: usize,
1600+
index: i64,
16021601
rhs: Value,
16031602
) -> Result<(), Error> {
16041603
let lhs = globals.get_expr((self.package, lhs).into());
16051604
match &lhs.kind {
16061605
&ExprKind::Var(Res::Local(id), _) => match env.get_mut(id) {
16071606
Some(var) => {
1608-
var.value.update_array(index, rhs).map_err(|idx| {
1609-
Error::IndexOutOfRange(idx.try_into().expect("index should be valid"), span)
1610-
})?;
1607+
var.value.update_array(index, rhs, span)?;
16111608
}
16121609
None => return Err(Error::UnboundName(self.to_global_span(lhs.span))),
16131610
},
@@ -1642,13 +1639,7 @@ impl State {
16421639
if idx < 0 {
16431640
return Err(Error::InvalidNegativeInt(idx, range_span));
16441641
}
1645-
let i = idx.as_index(range_span)?;
1646-
var.value.update_array(i, rhs.clone()).map_err(|idx| {
1647-
Error::IndexOutOfRange(
1648-
idx.try_into().expect("index should be valid"),
1649-
range_span,
1650-
)
1651-
})?;
1642+
var.value.update_array(idx, rhs.clone(), range_span)?;
16521643
}
16531644
}
16541645
None => return Err(Error::UnboundName(self.to_global_span(lhs.span))),

source/compiler/qsc_eval/src/tests.rs

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,19 +1734,24 @@ fn array_slice_out_of_range_expr() {
17341734

17351735
#[test]
17361736
fn array_index_negative_expr() {
1737+
check_expr("", "[1, 2, 3][-2]", &expect!["2"]);
1738+
}
1739+
1740+
#[test]
1741+
fn array_index_out_of_range_expr() {
17371742
check_expr(
17381743
"",
1739-
"[1, 2, 3][-2]",
1744+
"[1, 2, 3][4]",
17401745
&expect![[r#"
1741-
InvalidIndex(
1742-
-2,
1746+
IndexOutOfRange(
1747+
4,
17431748
PackageSpan {
17441749
package: PackageId(
17451750
2,
17461751
),
17471752
span: Span {
17481753
lo: 10,
1749-
hi: 12,
1754+
hi: 11,
17501755
},
17511756
},
17521757
)
@@ -1755,13 +1760,13 @@ fn array_index_negative_expr() {
17551760
}
17561761

17571762
#[test]
1758-
fn array_index_out_of_range_expr() {
1763+
fn array_index_out_of_range_with_length_expr() {
17591764
check_expr(
17601765
"",
1761-
"[1, 2, 3][4]",
1766+
"[1, 2, 3][3]",
17621767
&expect![[r#"
17631768
IndexOutOfRange(
1764-
4,
1769+
3,
17651770
PackageSpan {
17661771
package: PackageId(
17671772
2,
@@ -1776,6 +1781,28 @@ fn array_index_out_of_range_expr() {
17761781
);
17771782
}
17781783

1784+
#[test]
1785+
fn array_index_out_of_range_with_negative_length_minus_one_expr() {
1786+
check_expr(
1787+
"",
1788+
"[1, 2, 3][-4]",
1789+
&expect![[r#"
1790+
IndexOutOfRange(
1791+
-4,
1792+
PackageSpan {
1793+
package: PackageId(
1794+
2,
1795+
),
1796+
span: Span {
1797+
lo: 10,
1798+
hi: 12,
1799+
},
1800+
},
1801+
)
1802+
"#]],
1803+
);
1804+
}
1805+
17791806
#[test]
17801807
fn literal_big_int_expr() {
17811808
check_expr(
@@ -2273,24 +2300,11 @@ fn update_array_with_range_out_of_range_err() {
22732300
}
22742301

22752302
#[test]
2276-
fn update_array_with_range_negative_index_err() {
2303+
fn update_array_with_range_negative_index() {
22772304
check_expr(
22782305
"",
22792306
"[0, 1, 2, 3] w/ -1..0 <- [10, 11, 12, 13]",
2280-
&expect![[r#"
2281-
InvalidIndex(
2282-
-1,
2283-
PackageSpan {
2284-
package: PackageId(
2285-
2,
2286-
),
2287-
span: Span {
2288-
lo: 16,
2289-
hi: 21,
2290-
},
2291-
},
2292-
)
2293-
"#]],
2307+
&expect!["[11, 1, 2, 10]"],
22942308
);
22952309
}
22962310

source/compiler/qsc_eval/src/val.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -275,17 +275,23 @@ impl Value {
275275
/// Updates a value in an array in-place.
276276
/// # Panics
277277
/// This will panic if the [Value] is not a [`Value::Array`].
278-
pub fn update_array(&mut self, index: usize, value: Self) -> core::result::Result<(), usize> {
278+
pub fn update_array(
279+
&mut self,
280+
index: i64,
281+
value: Self,
282+
span: PackageSpan,
283+
) -> core::result::Result<(), Error> {
279284
let Value::Array(arr) = self else {
280285
panic!("value should be Array, got {}", self.type_name());
281286
};
282287
let arr = Rc::get_mut(arr).expect("array should be uniquely referenced");
283-
match arr.get_mut(index) {
288+
let i = index_allowing_negative(arr.len(), index, span)?;
289+
match arr.get_mut(i) {
284290
Some(v) => {
285291
*v = value;
286292
Ok(())
287293
}
288-
None => Err(index),
294+
None => Err(Error::IndexOutOfRange(index, span)),
289295
}
290296
}
291297

@@ -479,13 +485,29 @@ pub fn index_array(
479485
index: i64,
480486
span: PackageSpan,
481487
) -> std::result::Result<Value, Error> {
482-
let i = index.as_index(span)?;
488+
let i = index_allowing_negative(arr.len(), index, span)?;
483489
match arr.get(i) {
484490
Some(v) => Ok(v.clone()),
485491
None => Err(Error::IndexOutOfRange(index, span)),
486492
}
487493
}
488494

495+
/// Converts an index to a usize, allowing for negative indices that count from the end of the array.
496+
/// Valid range is `-len` to `len - 1`, where `len` is the length of the array.
497+
pub fn index_allowing_negative(
498+
len: usize,
499+
index: i64,
500+
span: PackageSpan,
501+
) -> std::result::Result<usize, Error> {
502+
let i = if index >= 0 {
503+
index.as_index(span)?
504+
} else {
505+
len.checked_sub((-index).as_index(span)?)
506+
.ok_or(Error::IndexOutOfRange(index, span))?
507+
};
508+
Ok(i)
509+
}
510+
489511
pub fn make_range(
490512
arr: &[Value],
491513
start: Option<i64>,
@@ -534,7 +556,7 @@ pub fn update_index_single(
534556
if index < 0 {
535557
return Err(Error::InvalidNegativeInt(index, span));
536558
}
537-
let i = index.as_index(span)?;
559+
let i = index_allowing_negative(values.len(), index, span)?;
538560
let mut values = values.to_vec();
539561
match values.get_mut(i) {
540562
Some(value) => {
@@ -557,7 +579,7 @@ pub fn update_index_range(
557579
let mut values = values.to_vec();
558580
let update = update.unwrap_array();
559581
for (idx, update) in range.into_iter().zip(update.iter()) {
560-
let i = idx.as_index(span)?;
582+
let i = index_allowing_negative(values.len(), idx, span)?;
561583
match values.get_mut(i) {
562584
Some(value) => {
563585
*value = update.clone();

source/compiler/qsc_partial_eval/src/tests/arrays.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,8 @@ fn result_array_value_at_index() {
269269
}
270270

271271
#[test]
272-
fn result_array_value_at_negative_index_raises_error() {
273-
let error = get_partial_evaluation_error(indoc! {r#"
272+
fn result_array_value_at_negative_index_works() {
273+
let program = get_rir_program(indoc! {r#"
274274
namespace Test {
275275
@EntryPoint()
276276
operation Main() : Result {
@@ -280,11 +280,15 @@ fn result_array_value_at_negative_index_raises_error() {
280280
}
281281
}
282282
"#});
283-
assert_error(
284-
&error,
285-
&expect![[
286-
r#"EvaluationFailed("value cannot be used as an index: -1", PackageSpan { package: PackageId(2), span: Span { lo: 177, hi: 179 } })"#
287-
]],
283+
assert_block_instructions(
284+
&program,
285+
BlockId(0),
286+
&expect![[r#"
287+
Block:
288+
Call id(1), args( Qubit(0), Result(0), )
289+
Call id(1), args( Qubit(1), Result(1), )
290+
Call id(2), args( Result(1), Pointer, )
291+
Return"#]],
288292
);
289293
}
290294

0 commit comments

Comments
 (0)