Skip to content

Commit 902ec5f

Browse files
authored
Support code lenses on more callables (microsoft#2174)
This change adds support for code lenses on any callable that qualifies for them instead of just the entry point. In practice, this means any function or operation that doesn't take arguments can be used as the starting place for a run, debug, estimate, histogram, or (in the case of operations) circuit. This is paticularly helpful for unit tests identifed by the `@Test()` attribute, as it lets them be run or debug outside of the test infra with a single click.
1 parent 06060a1 commit 902ec5f

File tree

12 files changed

+548
-441
lines changed

12 files changed

+548
-441
lines changed

compiler/qsc_circuit/src/operations.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub enum Error {
3333
/// If the item is not a callable, returns `None`.
3434
/// If the callable takes any non-qubit parameters, returns `None`.
3535
///
36-
/// If the callable only takes qubit parameters, (including qubit arrays):
36+
/// If the callable only takes qubit parameters (including qubit arrays) or no parameters:
3737
///
3838
/// The first element of the return tuple is a vector,
3939
/// where each element corresponds to a parameter, and the
@@ -47,6 +47,11 @@ pub enum Error {
4747
#[must_use]
4848
pub fn qubit_param_info(item: &Item) -> Option<(Vec<u32>, u32)> {
4949
if let ItemKind::Callable(decl) = &item.kind {
50+
if decl.input.ty == Ty::UNIT {
51+
// Support no parameters by allocating 0 qubits.
52+
return Some((vec![], 0));
53+
}
54+
5055
let (qubit_param_dimensions, total_num_qubits) = get_qubit_param_info(&decl.input.ty);
5156

5257
if !qubit_param_dimensions.is_empty() {

compiler/qsc_circuit/src/operations/tests.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ fn no_params() {
6262
);
6363
let expr = entry_expr_for_qubit_operation(&item, FunctorApp::default(), &operation);
6464
expect![[r#"
65-
Err(
66-
NoQubitParameters,
65+
Ok(
66+
"{\n use qs = Qubit[0];\n (Test.Test)();\n let r: Result[] = [];\n r\n }",
6767
)
6868
"#]]
6969
.assert_debug_eq(&expr);

language_service/src/code_lens.rs

+64-87
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{
1111
};
1212
use qsc::{
1313
circuit::qubit_param_info,
14-
hir::{Expr, ExprKind, ItemId, ItemKind, LocalItemId, Package, Res},
14+
hir::{ty::Ty, CallableKind, ItemKind},
1515
line_column::Encoding,
1616
};
1717

@@ -29,95 +29,72 @@ pub(crate) fn get_code_lenses(
2929
let package = &user_unit.package;
3030
let source_span = compilation.package_span_of_source(source_name);
3131

32-
let entry_item_id = entry_callable(package);
32+
package
33+
.items
34+
.iter()
35+
.fold(Vec::new(), |mut accum, (_, item)| {
36+
if source_span.contains(item.span.lo) {
37+
if let ItemKind::Callable(decl) = &item.kind {
38+
if let Some(ItemKind::Namespace(ns, _)) = item
39+
.parent
40+
.and_then(|parent_id| package.items.get(parent_id))
41+
.map(|parent| &parent.kind)
42+
{
43+
let namespace = ns.name();
44+
let range = into_range(position_encoding, decl.span, &user_unit.sources);
45+
let name = decl.name.name.clone();
3346

34-
// Get callables in the current source file.
35-
let callables = package.items.iter().filter_map(|(item_id, item)| {
36-
if source_span.contains(item.span.lo) {
37-
if let ItemKind::Callable(decl) = &item.kind {
38-
if let Some(ItemKind::Namespace(ns, _)) = item
39-
.parent
40-
.and_then(|parent_id| package.items.get(parent_id))
41-
.map(|parent| &parent.kind)
42-
{
43-
let namespace = ns.name();
44-
let range = into_range(position_encoding, decl.span, &user_unit.sources);
45-
let name = decl.name.name.clone();
47+
if decl.input.ty == Ty::UNIT {
48+
// For a callable that takes no input, always show all lenses except for circuit.
49+
let expr = format!("{namespace}.{name}()");
50+
accum = accum
51+
.into_iter()
52+
.chain([
53+
CodeLens {
54+
range,
55+
command: CodeLensCommand::Run(expr.clone()),
56+
},
57+
CodeLens {
58+
range,
59+
command: CodeLensCommand::Histogram(expr.clone()),
60+
},
61+
CodeLens {
62+
range,
63+
command: CodeLensCommand::Estimate(expr.clone()),
64+
},
65+
CodeLens {
66+
range,
67+
command: CodeLensCommand::Debug(expr),
68+
},
69+
])
70+
.collect();
71+
}
72+
if decl.kind == CallableKind::Operation {
73+
// If this is either an operation that takes no arguments or one that takes only qubit arguments,
74+
// show the circuit lens.
75+
if decl.input.ty == Ty::UNIT {
76+
accum.push(CodeLens {
77+
range,
78+
command: CodeLensCommand::Circuit(OperationInfo {
79+
operation: format!("{namespace}.{name}"),
80+
total_num_qubits: 0,
81+
}),
82+
});
83+
} else if let Some((_, total_num_qubits)) = qubit_param_info(item) {
84+
accum.push(CodeLens {
85+
range,
86+
command: CodeLensCommand::Circuit(OperationInfo {
87+
operation: format!("{namespace}.{name}"),
88+
total_num_qubits,
89+
}),
90+
});
91+
}
92+
}
4693

47-
return Some((item, range, namespace, name, Some(item_id) == entry_item_id));
94+
return accum;
95+
}
4896
}
4997
}
50-
}
51-
None
52-
});
53-
54-
callables
55-
.flat_map(|(item, range, namespace, name, is_entry_point)| {
56-
if is_entry_point {
57-
vec![
58-
CodeLens {
59-
range,
60-
command: CodeLensCommand::Run,
61-
},
62-
CodeLens {
63-
range,
64-
command: CodeLensCommand::Histogram,
65-
},
66-
CodeLens {
67-
range,
68-
command: CodeLensCommand::Estimate,
69-
},
70-
CodeLens {
71-
range,
72-
command: CodeLensCommand::Debug,
73-
},
74-
CodeLens {
75-
range,
76-
command: CodeLensCommand::Circuit(None),
77-
},
78-
]
79-
} else {
80-
if let Some((_, total_num_qubits)) = qubit_param_info(item) {
81-
return vec![CodeLens {
82-
range,
83-
command: CodeLensCommand::Circuit(Some(OperationInfo {
84-
operation: format!("{namespace}.{name}"),
85-
total_num_qubits,
86-
})),
87-
}];
88-
}
89-
vec![]
90-
}
98+
accum
9199
})
92-
.collect()
93-
}
94-
95-
/// Uses the entry expression in the package to find the
96-
/// entrypoint callable item id. The entry expression has to
97-
/// be a call to a parameterless operation or function. This is the
98-
/// specific pattern generated by the "generate entry expression" pass.
99-
/// In practice, this callable will be the one tagged with the
100-
/// `@EntryPoint()` attribute or named "Main".
101-
fn entry_callable(package: &Package) -> Option<LocalItemId> {
102-
if let Some(Expr {
103-
kind: ExprKind::Call(callee, args),
104-
..
105-
}) = &package.entry
106-
{
107-
if let ExprKind::Tuple(args) = &args.kind {
108-
if args.is_empty() {
109-
if let ExprKind::Var(
110-
Res::Item(ItemId {
111-
package: None,
112-
item,
113-
}),
114-
..,
115-
) = callee.kind
116-
{
117-
return Some(item);
118-
}
119-
}
120-
}
121-
}
122-
None
123100
}

language_service/src/code_lens/tests.rs

+117-29
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,23 @@ fn one_entrypoint() {
5858
(
5959
0,
6060
[
61-
Run,
62-
Histogram,
63-
Estimate,
64-
Debug,
61+
Run(
62+
"Test.Test()",
63+
),
64+
Histogram(
65+
"Test.Test()",
66+
),
67+
Estimate(
68+
"Test.Test()",
69+
),
70+
Debug(
71+
"Test.Test()",
72+
),
6573
Circuit(
66-
None,
74+
OperationInfo {
75+
operation: "Test.Test",
76+
total_num_qubits: 0,
77+
},
6778
),
6879
],
6980
),
@@ -78,15 +89,62 @@ fn two_entrypoints() {
7889
r#"
7990
namespace Test {
8091
@EntryPoint()
81-
operation Main() : Unit{
82-
}
92+
operation Main() : Unit{
93+
}
8394
8495
@EntryPoint()
85-
operation Foo() : Unit{
86-
}
96+
operation Foo() : Unit{
97+
}
8798
}"#,
8899
&expect![[r#"
89-
[]
100+
[
101+
(
102+
0,
103+
[
104+
Run(
105+
"Test.Main()",
106+
),
107+
Histogram(
108+
"Test.Main()",
109+
),
110+
Estimate(
111+
"Test.Main()",
112+
),
113+
Debug(
114+
"Test.Main()",
115+
),
116+
Circuit(
117+
OperationInfo {
118+
operation: "Test.Main",
119+
total_num_qubits: 0,
120+
},
121+
),
122+
],
123+
),
124+
(
125+
1,
126+
[
127+
Run(
128+
"Test.Foo()",
129+
),
130+
Histogram(
131+
"Test.Foo()",
132+
),
133+
Estimate(
134+
"Test.Foo()",
135+
),
136+
Debug(
137+
"Test.Foo()",
138+
),
139+
Circuit(
140+
OperationInfo {
141+
operation: "Test.Foo",
142+
total_num_qubits: 0,
143+
},
144+
),
145+
],
146+
),
147+
]
90148
"#]],
91149
);
92150
}
@@ -99,20 +157,54 @@ fn main_function() {
99157
◉operation Main() : Unit {
100158
}◉
101159
102-
operation Foo() : Unit{
103-
}
160+
operation Foo() : Unit{
161+
}
104162
}"#,
105163
&expect![[r#"
106164
[
107165
(
108166
0,
109167
[
110-
Run,
111-
Histogram,
112-
Estimate,
113-
Debug,
168+
Run(
169+
"Test.Main()",
170+
),
171+
Histogram(
172+
"Test.Main()",
173+
),
174+
Estimate(
175+
"Test.Main()",
176+
),
177+
Debug(
178+
"Test.Main()",
179+
),
180+
Circuit(
181+
OperationInfo {
182+
operation: "Test.Main",
183+
total_num_qubits: 0,
184+
},
185+
),
186+
],
187+
),
188+
(
189+
1,
190+
[
191+
Run(
192+
"Test.Foo()",
193+
),
194+
Histogram(
195+
"Test.Foo()",
196+
),
197+
Estimate(
198+
"Test.Foo()",
199+
),
200+
Debug(
201+
"Test.Foo()",
202+
),
114203
Circuit(
115-
None,
204+
OperationInfo {
205+
operation: "Test.Foo",
206+
total_num_qubits: 0,
207+
},
116208
),
117209
],
118210
),
@@ -153,12 +245,10 @@ fn qubit_operation_circuit() {
153245
0,
154246
[
155247
Circuit(
156-
Some(
157-
OperationInfo {
158-
operation: "Test.Foo",
159-
total_num_qubits: 1,
160-
},
161-
),
248+
OperationInfo {
249+
operation: "Test.Foo",
250+
total_num_qubits: 1,
251+
},
162252
),
163253
],
164254
),
@@ -181,12 +271,10 @@ fn qubit_arrays_operation_circuit() {
181271
0,
182272
[
183273
Circuit(
184-
Some(
185-
OperationInfo {
186-
operation: "Test.Foo",
187-
total_num_qubits: 7,
188-
},
189-
),
274+
OperationInfo {
275+
operation: "Test.Foo",
276+
total_num_qubits: 7,
277+
},
190278
),
191279
],
192280
),

0 commit comments

Comments
 (0)