Skip to content

Commit 5f5546f

Browse files
authored
Fix RCA panic when compiling lambda (#2102)
Compiling a lambda that has more than one parameter can trigger a panic in RCA due to processing the fixed arguments for captures. This adds new tests for the lambda cases and confirms the dynamic runtime features for paramters and captures are detected as expected.
1 parent 342f40a commit 5f5546f

File tree

3 files changed

+236
-5
lines changed

3 files changed

+236
-5
lines changed

compiler/qsc_rca/src/core.rs

+16-5
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ impl<'a> Analyzer<'a> {
449449
callee_input_pattern_id,
450450
args_input_id,
451451
self.package_store,
452-
fixed_args.as_ref().map_or(0, Vec::len),
452+
fixed_args.as_ref().map(Vec::len),
453453
);
454454
let application_instance = self.get_current_application_instance();
455455

@@ -2509,17 +2509,28 @@ fn map_input_pattern_to_input_expressions(
25092509
pat_id: StorePatId,
25102510
expr_id: StoreExprId,
25112511
package_store: &impl PackageStoreLookup,
2512-
skip_ahead: usize,
2512+
skip_ahead: Option<usize>,
25132513
) -> Vec<ExprId> {
25142514
let pat = package_store.get_pat(pat_id);
25152515
match &pat.kind {
25162516
PatKind::Bind(_) | PatKind::Discard => vec![expr_id.expr],
25172517
PatKind::Tuple(pats) => {
25182518
// Map each one of the elements in the pattern to an expression in the tuple.
2519-
let pats = &pats[skip_ahead..];
2519+
let pats = &pats[skip_ahead.unwrap_or_default()..];
25202520
let expr = package_store.get_expr(expr_id);
25212521
if let ExprKind::Tuple(exprs) = &expr.kind {
2522-
assert!(pats.len() == exprs.len());
2522+
let pats = if skip_ahead.is_some() && exprs.len() > 1 {
2523+
// When skip_ahead is not None we know we are processing a lambda, so the final pattern is itself a tuple.
2524+
// Unpack the tuple pattern to get the individual elements and map input to those.
2525+
let PatKind::Tuple(pats) =
2526+
&package_store.get_pat((pat_id.package, pats[0]).into()).kind
2527+
else {
2528+
panic!("expected tuple pattern for lambda receiving multiple arguments");
2529+
};
2530+
pats
2531+
} else {
2532+
pats
2533+
};
25232534
let mut input_param_exprs = Vec::<ExprId>::with_capacity(pats.len());
25242535
for (local_pat_id, local_expr_id) in pats.iter().zip(exprs.iter()) {
25252536
let global_pat_id = StorePatId::from((pat_id.package, *local_pat_id));
@@ -2528,7 +2539,7 @@ fn map_input_pattern_to_input_expressions(
25282539
global_pat_id,
25292540
global_expr_id,
25302541
package_store,
2531-
0,
2542+
None,
25322543
);
25332544
input_param_exprs.append(&mut sub_input_param_exprs);
25342545
}

compiler/qsc_rca/src/tests.rs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod calls;
1010
mod cycles;
1111
mod ifs;
1212
mod intrinsics;
13+
mod lambdas;
1314
mod loops;
1415
mod measurements;
1516
mod overrides;

compiler/qsc_rca/src/tests/lambdas.rs

+219
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use super::{check_last_statement_compute_properties, CompilationContext};
5+
use expect_test::expect;
6+
7+
#[test]
8+
fn check_rca_for_classical_lambda_one_parameter() {
9+
let mut compilation_context = CompilationContext::default();
10+
compilation_context.update(
11+
r#"
12+
let myOp = a -> {};
13+
myOp(1)"#,
14+
);
15+
let package_store_compute_properties = compilation_context.get_compute_properties();
16+
check_last_statement_compute_properties(
17+
package_store_compute_properties,
18+
&expect![[r#"
19+
ApplicationsGeneratorSet:
20+
inherent: Classical
21+
dynamic_param_applications: <empty>"#]],
22+
);
23+
}
24+
25+
#[test]
26+
fn check_rca_for_classical_lambda_two_parameters() {
27+
let mut compilation_context = CompilationContext::default();
28+
compilation_context.update(
29+
r#"
30+
let myOp = (a, b) -> {};
31+
myOp(1, 2)"#,
32+
);
33+
let package_store_compute_properties = compilation_context.get_compute_properties();
34+
check_last_statement_compute_properties(
35+
package_store_compute_properties,
36+
&expect![[r#"
37+
ApplicationsGeneratorSet:
38+
inherent: Classical
39+
dynamic_param_applications: <empty>"#]],
40+
);
41+
}
42+
43+
#[test]
44+
fn check_rca_for_classical_lambda_one_parameter_one_capture() {
45+
let mut compilation_context = CompilationContext::default();
46+
compilation_context.update(
47+
r#"
48+
let x = 0.0;
49+
let myOp = a -> {x};
50+
myOp(1)"#,
51+
);
52+
let package_store_compute_properties = compilation_context.get_compute_properties();
53+
check_last_statement_compute_properties(
54+
package_store_compute_properties,
55+
&expect![[r#"
56+
ApplicationsGeneratorSet:
57+
inherent: Classical
58+
dynamic_param_applications: <empty>"#]],
59+
);
60+
}
61+
62+
#[test]
63+
fn check_rca_for_classical_lambda_one_parameter_two_captures() {
64+
let mut compilation_context = CompilationContext::default();
65+
compilation_context.update(
66+
r#"
67+
let x = 0.0;
68+
let y = 0.0;
69+
let myOp = a -> {x+y};
70+
myOp(1)"#,
71+
);
72+
let package_store_compute_properties = compilation_context.get_compute_properties();
73+
check_last_statement_compute_properties(
74+
package_store_compute_properties,
75+
&expect![[r#"
76+
ApplicationsGeneratorSet:
77+
inherent: Classical
78+
dynamic_param_applications: <empty>"#]],
79+
);
80+
}
81+
82+
#[test]
83+
fn check_rca_for_classical_lambda_two_parameters_one_capture() {
84+
let mut compilation_context = CompilationContext::default();
85+
compilation_context.update(
86+
r#"
87+
let x = 0.0;
88+
let myOp = (a, b) -> {x};
89+
myOp(1, 2)"#,
90+
);
91+
let package_store_compute_properties = compilation_context.get_compute_properties();
92+
check_last_statement_compute_properties(
93+
package_store_compute_properties,
94+
&expect![[r#"
95+
ApplicationsGeneratorSet:
96+
inherent: Classical
97+
dynamic_param_applications: <empty>"#]],
98+
);
99+
}
100+
101+
#[test]
102+
fn check_rca_for_classical_lambda_two_parameters_two_captures() {
103+
let mut compilation_context = CompilationContext::default();
104+
compilation_context.update(
105+
r#"
106+
let x = 0.0;
107+
let y = 0.0;
108+
let myOp = (a, b) -> {x+y};
109+
myOp(1, 2)"#,
110+
);
111+
let package_store_compute_properties = compilation_context.get_compute_properties();
112+
check_last_statement_compute_properties(
113+
package_store_compute_properties,
114+
&expect![[r#"
115+
ApplicationsGeneratorSet:
116+
inherent: Classical
117+
dynamic_param_applications: <empty>"#]],
118+
);
119+
}
120+
121+
#[test]
122+
fn check_rca_for_dynamic_lambda_two_classical_parameters_one_dynamic_capture() {
123+
let mut compilation_context = CompilationContext::default();
124+
compilation_context.update(
125+
r#"
126+
let x = {
127+
use q = Qubit();
128+
if MResetZ(q) == One {
129+
1.0
130+
} else {
131+
0.0
132+
}
133+
};
134+
let myOp = (a, b) -> {x};
135+
myOp(1, 2)"#,
136+
);
137+
let package_store_compute_properties = compilation_context.get_compute_properties();
138+
check_last_statement_compute_properties(
139+
package_store_compute_properties,
140+
&expect![[r#"
141+
ApplicationsGeneratorSet:
142+
inherent: Quantum: QuantumProperties:
143+
runtime_features: RuntimeFeatureFlags(UseOfDynamicDouble)
144+
value_kind: Element(Dynamic)
145+
dynamic_param_applications: <empty>"#]],
146+
);
147+
}
148+
149+
#[test]
150+
fn check_rca_for_dynamic_lambda_two_dynamic_parameters_one_classical_capture() {
151+
let mut compilation_context = CompilationContext::default();
152+
compilation_context.update(
153+
r#"
154+
let x = 0.0;
155+
let myOp = (a, b) -> {a + x};
156+
let a = {
157+
use q = Qubit();
158+
if MResetZ(q) == One {
159+
1.0
160+
} else {
161+
0.0
162+
}
163+
};
164+
myOp(a, 2)"#,
165+
);
166+
let package_store_compute_properties = compilation_context.get_compute_properties();
167+
check_last_statement_compute_properties(
168+
package_store_compute_properties,
169+
&expect![[r#"
170+
ApplicationsGeneratorSet:
171+
inherent: Quantum: QuantumProperties:
172+
runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | UseOfDynamicDouble)
173+
value_kind: Element(Dynamic)
174+
dynamic_param_applications: <empty>"#]],
175+
);
176+
}
177+
178+
#[test]
179+
fn check_rca_for_operation_lambda_two_parameters() {
180+
let mut compilation_context = CompilationContext::default();
181+
compilation_context.update(
182+
r#"
183+
use (q0, q1) = (Qubit(), Qubit());
184+
let myOp = (a, b) => MResetEachZ([a, b]);
185+
myOp(q0, q1)"#,
186+
);
187+
let package_store_compute_properties = compilation_context.get_compute_properties();
188+
check_last_statement_compute_properties(
189+
package_store_compute_properties,
190+
&expect![[r#"
191+
ApplicationsGeneratorSet:
192+
inherent: Quantum: QuantumProperties:
193+
runtime_features: RuntimeFeatureFlags(0x0)
194+
value_kind: Array(Content: Dynamic, Size: Static)
195+
dynamic_param_applications: <empty>"#]],
196+
);
197+
}
198+
199+
#[test]
200+
fn check_rca_for_operation_lambda_two_parameters_with_controls() {
201+
let mut compilation_context = CompilationContext::default();
202+
compilation_context.update(
203+
r#"
204+
use (q0, q1) = (Qubit(), Qubit());
205+
use (qs0, qs1) = (Qubit[2], Qubit[2]);
206+
let myOp = (a, b) => CNOT(a, b);
207+
Controlled Controlled myOp(qs0, (qs1, (q0, q1)))"#,
208+
);
209+
let package_store_compute_properties = compilation_context.get_compute_properties();
210+
check_last_statement_compute_properties(
211+
package_store_compute_properties,
212+
&expect![[r#"
213+
ApplicationsGeneratorSet:
214+
inherent: Quantum: QuantumProperties:
215+
runtime_features: RuntimeFeatureFlags(UseOfDynamicBool)
216+
value_kind: Element(Static)
217+
dynamic_param_applications: <empty>"#]],
218+
);
219+
}

0 commit comments

Comments
 (0)