Skip to content

Commit 20126a6

Browse files
authored
Support omitted start and stop in ranges (#2472)
Support omitted start and stop in ranges.
1 parent 490d9fc commit 20126a6

File tree

8 files changed

+169
-22
lines changed

8 files changed

+169
-22
lines changed

compiler/qsc_qasm/src/ast_builder.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -561,14 +561,19 @@ pub(crate) fn build_array_reverse_expr(expr: Expr) -> Expr {
561561
}
562562

563563
#[allow(clippy::similar_names)]
564-
pub(crate) fn build_range_expr(start: Expr, stop: Expr, step: Option<Expr>, span: Span) -> Expr {
564+
pub(crate) fn build_range_expr(
565+
start: Option<Expr>,
566+
step: Option<Expr>,
567+
stop: Option<Expr>,
568+
span: Span,
569+
) -> Expr {
565570
Expr {
566571
id: NodeId::default(),
567572
span,
568573
kind: Box::new(ExprKind::Range(
569-
Some(Box::new(start)),
574+
start.map(Box::new),
570575
step.map(Box::new),
571-
Some(Box::new(stop)),
576+
stop.map(Box::new),
572577
)),
573578
}
574579
}

compiler/qsc_qasm/src/compiler.rs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,19 +1506,10 @@ impl QasmCompiler {
15061506
}
15071507

15081508
fn compile_range_expr(&mut self, range: &semast::Range) -> qsast::Expr {
1509-
let Some(start) = &range.start else {
1510-
self.push_unimplemented_error_message("omitted range start", range.span);
1511-
return err_expr(range.span);
1512-
};
1513-
let Some(end) = &range.end else {
1514-
self.push_unimplemented_error_message("omitted range end", range.span);
1515-
return err_expr(range.span);
1516-
};
1517-
1518-
let start = self.compile_expr(start);
1519-
let end = self.compile_expr(end);
1509+
let start = range.start.as_ref().map(|expr| self.compile_expr(expr));
15201510
let step = range.step.as_ref().map(|expr| self.compile_expr(expr));
1521-
build_range_expr(start, end, step, range.span)
1511+
let end = range.end.as_ref().map(|expr| self.compile_expr(expr));
1512+
build_range_expr(start, step, end, range.span)
15221513
}
15231514

15241515
fn compile_array_literal(&mut self, array: &Array, span: Span) -> qsast::Expr {

compiler/qsc_qasm/src/parser/stmt.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,15 +1205,18 @@ pub fn parse_if_stmt(s: &mut ParserContext) -> Result<IfStmt> {
12051205
/// Ranges in for loops are a bit different. They must have explicit start and end.
12061206
/// Grammar `LBRACKET start=expression COLON (step=expression COLON)? stop=expression]`.
12071207
/// Reference: <https://openqasm.com/language/classical.html#for-loops>.
1208+
///
1209+
/// However, we allow the range to have omitted start and end at this point,
1210+
/// and push a semantic error later at the lowering stage.
12081211
fn for_loop_range_expr(s: &mut ParserContext) -> Result<Range> {
12091212
let lo = s.peek().span.lo;
12101213
token(s, TokenKind::Open(Delim::Bracket))?;
1211-
let start = Some(expr::expr(s)?);
1214+
let start = opt(s, expr::expr)?;
12121215
token(s, TokenKind::Colon)?;
12131216

12141217
// QASM ranges have the pattern [start : (step :)? end]
12151218
// We assume the second expr is the `end`.
1216-
let mut end = Some(expr::expr(s)?);
1219+
let mut end = opt(s, expr::expr)?;
12171220
let mut step = None;
12181221

12191222
// If we find a third expr, then the second expr was the `step`.

compiler/qsc_qasm/src/parser/stmt/tests/for_loops.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,66 @@ fn for_stmt_iterating_over_range_no_step() {
143143
);
144144
}
145145

146+
#[test]
147+
fn for_stmt_iterating_over_range_no_start() {
148+
check(
149+
parse,
150+
"
151+
for int x in [:7] {
152+
a = 0;
153+
}",
154+
&expect![[r#"
155+
Stmt [5-45]:
156+
annotations: <empty>
157+
kind: ForStmt [5-45]:
158+
variable_type: ScalarType [9-12]: IntType [9-12]:
159+
size: <none>
160+
variable_name: Ident [13-14] "x"
161+
iterable: Range [18-22]:
162+
start: <none>
163+
step: <none>
164+
end: Expr [20-21]: Lit: Int(7)
165+
body: Stmt [23-45]:
166+
annotations: <empty>
167+
kind: Block [23-45]:
168+
Stmt [33-39]:
169+
annotations: <empty>
170+
kind: AssignStmt [33-39]:
171+
lhs: Ident [33-34] "a"
172+
rhs: Expr [37-38]: Lit: Int(0)"#]],
173+
);
174+
}
175+
176+
#[test]
177+
fn for_stmt_iterating_over_range_no_end() {
178+
check(
179+
parse,
180+
"
181+
for int x in [0:] {
182+
a = 0;
183+
}",
184+
&expect![[r#"
185+
Stmt [5-45]:
186+
annotations: <empty>
187+
kind: ForStmt [5-45]:
188+
variable_type: ScalarType [9-12]: IntType [9-12]:
189+
size: <none>
190+
variable_name: Ident [13-14] "x"
191+
iterable: Range [18-22]:
192+
start: Expr [19-20]: Lit: Int(0)
193+
step: <none>
194+
end: <none>
195+
body: Stmt [23-45]:
196+
annotations: <empty>
197+
kind: Block [23-45]:
198+
Stmt [33-39]:
199+
annotations: <empty>
200+
kind: AssignStmt [33-39]:
201+
lhs: Ident [33-34] "a"
202+
rhs: Expr [37-38]: Lit: Int(0)"#]],
203+
);
204+
}
205+
146206
#[test]
147207
fn for_stmt_iterating_over_expr() {
148208
check(

compiler/qsc_qasm/src/semantic/error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,10 @@ pub enum SemanticErrorKind {
181181
#[error("quantum typed values cannot be used in binary expressions")]
182182
#[diagnostic(code("Qasm.Lowerer.QuantumTypesInBinaryExpression"))]
183183
QuantumTypesInBinaryExpression(#[label] Span),
184-
#[error("range expressions must have a start")]
184+
#[error("range expressions must have a start when used in for loops")]
185185
#[diagnostic(code("Qasm.Lowerer.RangeExpressionsMustHaveStart"))]
186186
RangeExpressionsMustHaveStart(#[label] Span),
187-
#[error("range expressions must have a stop")]
187+
#[error("range expressions must have a stop when used in for loops")]
188188
#[diagnostic(code("Qasm.Lowerer.RangeExpressionsMustHaveStop"))]
189189
RangeExpressionsMustHaveStop(#[label] Span),
190190
#[error("redefined symbol: {0}")]

compiler/qsc_qasm/src/semantic/lowerer.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3715,13 +3715,23 @@ impl Lowerer {
37153715
})
37163716
}
37173717

3718-
/// Ranges used for array initialization don't need to be const evaluatable
3719-
/// and can be computed at runtime.
3718+
/// These ranges as iterators in for loops. The spec says
3719+
/// that `start` and `stop` are mandatory in this case.
3720+
///
3721+
/// Spec: <https://openqasm.com/language/classical.html#for-loops>
37203722
fn lower_range(&mut self, range: &syntax::Range) -> semantic::Range {
37213723
let start = range.start.as_ref().map(|e| self.lower_expr(e));
37223724
let step = range.step.as_ref().map(|e| self.lower_expr(e));
37233725
let end = range.end.as_ref().map(|e| self.lower_expr(e));
37243726

3727+
if start.is_none() {
3728+
self.push_semantic_error(SemanticErrorKind::RangeExpressionsMustHaveStart(range.span));
3729+
}
3730+
3731+
if end.is_none() {
3732+
self.push_semantic_error(SemanticErrorKind::RangeExpressionsMustHaveStop(range.span));
3733+
}
3734+
37253735
semantic::Range {
37263736
span: range.span,
37273737
start,

compiler/qsc_qasm/src/semantic/tests/statements/for_stmt.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
use crate::semantic::tests::check_stmt_kinds;
4+
use crate::{semantic::tests::check_stmt_kinds, tests::check_qasm_to_qsharp};
55
use expect_test::expect;
66

77
#[test]
@@ -95,3 +95,47 @@ fn loop_creates_its_own_scope() {
9595
"#]],
9696
);
9797
}
98+
99+
#[test]
100+
fn omitted_start_in_for_range_fails() {
101+
let source = "
102+
for int i in [:5] {}
103+
";
104+
105+
check_qasm_to_qsharp(
106+
source,
107+
&expect![[r#"
108+
Qasm.Lowerer.RangeExpressionsMustHaveStart
109+
110+
x range expressions must have a start when used in for loops
111+
,-[Test.qasm:2:22]
112+
1 |
113+
2 | for int i in [:5] {}
114+
: ^^^^
115+
3 |
116+
`----
117+
"#]],
118+
);
119+
}
120+
121+
#[test]
122+
fn omitted_end_in_for_range_fails() {
123+
let source = "
124+
for int i in [1:] {}
125+
";
126+
127+
check_qasm_to_qsharp(
128+
source,
129+
&expect![[r#"
130+
Qasm.Lowerer.RangeExpressionsMustHaveStop
131+
132+
x range expressions must have a stop when used in for loops
133+
,-[Test.qasm:2:22]
134+
1 |
135+
2 | for int i in [1:] {}
136+
: ^^^^
137+
3 |
138+
`----
139+
"#]],
140+
);
141+
}

compiler/qsc_qasm/src/tests/expression/indexed.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,37 @@ fn index_set_in_non_alias_stmt_fails() {
169169
]"#]]
170170
.assert_eq(&format!("{errors:?}"));
171171
}
172+
173+
#[test]
174+
fn indexed_ident_with_omitted_start() {
175+
let source = r#"
176+
array[int, 5] a;
177+
a[:3];
178+
"#;
179+
180+
check_qasm_to_qsharp(
181+
source,
182+
&expect![[r#"
183+
import Std.OpenQASM.Intrinsic.*;
184+
mutable a = [0, 0, 0, 0, 0];
185+
a[...3];
186+
"#]],
187+
);
188+
}
189+
190+
#[test]
191+
fn indexed_ident_with_omitted_stop() {
192+
let source = r#"
193+
array[int, 5] a;
194+
a[2:];
195+
"#;
196+
197+
check_qasm_to_qsharp(
198+
source,
199+
&expect![[r#"
200+
import Std.OpenQASM.Intrinsic.*;
201+
mutable a = [0, 0, 0, 0, 0];
202+
a[2...];
203+
"#]],
204+
);
205+
}

0 commit comments

Comments
 (0)