microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.0.10-rc

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

language_service/src/completion.rs

441lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use std::rc::Rc;
8
9use crate::display::CodeDisplay;
10use crate::protocol::{self, CompletionItem, CompletionItemKind, CompletionList};
11use crate::qsc_utils::{map_offset, span_contains, Compilation};
12use qsc::ast::visit::{self, Visitor};
13use qsc::hir::{ItemKind, Package, PackageId};
14
15const PRELUDE: [&str; 3] = [
16 "Microsoft.Quantum.Canon",
17 "Microsoft.Quantum.Core",
18 "Microsoft.Quantum.Intrinsic",
19];
20
21pub(crate) fn get_completions(
22 compilation: &Compilation,
23 source_name: &str,
24 offset: u32,
25) -> CompletionList {
26 // Map the file offset into a SourceMap offset
27 let offset = map_offset(&compilation.unit.sources, source_name, offset);
28
29 // Determine context for the offset
30 let mut context_finder = ContextFinder {
31 offset,
32 context: if compilation.unit.ast.package.namespaces.as_ref().is_empty() {
33 // The parser failed entirely, no context to go on
34 Context::NoCompilation
35 } else {
36 // Starting context is top-level (i.e. outside a namespace block)
37 Context::TopLevel
38 },
39 opens: vec![],
40 start_of_namespace: None,
41 current_namespace_name: None,
42 };
43 context_finder.visit_package(&compilation.unit.ast.package);
44
45 // The PRELUDE namespaces are always implicitly opened.
46 context_finder
47 .opens
48 .extend(PRELUDE.into_iter().map(|ns| (Rc::from(ns), None)));
49
50 // We don't attempt to be comprehensive or accurate when determining completions,
51 // since that's not really possible without more sophisticated error recovery
52 // in the parser or the ability for the resolver to gather all
53 // appropriate names for a scope. These are not done at the moment.
54
55 // So the following is an attempt to get "good enough" completions, tuned
56 // based on the user experience of typing out a few samples in the editor.
57
58 let mut builder = CompletionListBuilder::new();
59 match context_finder.context {
60 Context::Namespace => {
61 // Include "open", "operation", etc
62 builder.push_item_decl_keywords();
63
64 // Typing into a callable decl sometimes breaks the
65 // parser and the context appears to be a namespace block,
66 // so just include everything that may be relevant
67 builder.push_stmt_keywords();
68 builder.push_expr_keywords();
69 builder.push_types();
70 builder.push_globals(
71 compilation,
72 &context_finder.opens,
73 context_finder.start_of_namespace,
74 &context_finder.current_namespace_name,
75 );
76 }
77
78 Context::CallableSignature => {
79 builder.push_types();
80 }
81 Context::Block => {
82 // Pretty much anything goes in a block
83 builder.push_stmt_keywords();
84 builder.push_expr_keywords();
85 builder.push_types();
86 builder.push_globals(
87 compilation,
88 &context_finder.opens,
89 context_finder.start_of_namespace,
90 &context_finder.current_namespace_name,
91 );
92
93 // Item decl keywords last, unlike in a namespace
94 builder.push_item_decl_keywords();
95 }
96 Context::NoCompilation | Context::TopLevel => {
97 builder.push_namespace_keyword();
98 }
99 }
100
101 CompletionList {
102 items: builder.into_items(),
103 }
104}
105
106struct CompletionListBuilder {
107 current_sort_group: u32,
108 items: Vec<CompletionItem>,
109}
110
111impl CompletionListBuilder {
112 fn new() -> Self {
113 CompletionListBuilder {
114 current_sort_group: 1,
115 items: Vec::new(),
116 }
117 }
118
119 fn into_items(self) -> Vec<CompletionItem> {
120 self.items
121 }
122
123 fn push_item_decl_keywords(&mut self) {
124 static ITEM_KEYWORDS: [&str; 5] = ["operation", "open", "internal", "function", "newtype"];
125
126 self.push_completions(
127 ITEM_KEYWORDS
128 .map(|key| CompletionItem::new(key.to_string(), CompletionItemKind::Keyword))
129 .into_iter(),
130 );
131 }
132
133 fn push_namespace_keyword(&mut self) {
134 self.push_completions(
135 [CompletionItem::new(
136 "namespace".to_string(),
137 CompletionItemKind::Keyword,
138 )]
139 .into_iter(),
140 );
141 }
142
143 fn push_types(&mut self) {
144 static PRIMITIVE_TYPES: [&str; 10] = [
145 "Qubit", "Int", "Unit", "Result", "Bool", "BigInt", "Double", "Pauli", "Range",
146 "String",
147 ];
148 static FUNCTOR_KEYWORDS: [&str; 3] = ["Adj", "Ctl", "is"];
149
150 self.push_completions(
151 PRIMITIVE_TYPES
152 .map(|key| CompletionItem::new(key.to_string(), CompletionItemKind::Interface))
153 .into_iter(),
154 );
155
156 self.push_completions(
157 FUNCTOR_KEYWORDS
158 .map(|key| CompletionItem::new(key.to_string(), CompletionItemKind::Keyword))
159 .into_iter(),
160 );
161 }
162
163 fn push_globals(
164 &mut self,
165 compilation: &Compilation,
166 opens: &[(Rc<str>, Option<Rc<str>>)],
167 start_of_namespace: Option<u32>,
168 current_namespace_name: &Option<Rc<str>>,
169 ) {
170 let current = &compilation.unit.package;
171 let std = &compilation
172 .package_store
173 .get(compilation.std_package_id)
174 .expect("expected to find std package")
175 .package;
176 let core = &compilation
177 .package_store
178 .get(PackageId::CORE)
179 .expect("expected to find core package")
180 .package;
181
182 let display = CodeDisplay { compilation };
183
184 let get_callables = |current, display| {
185 Self::get_callables(
186 current,
187 display,
188 opens,
189 start_of_namespace,
190 current_namespace_name.clone(),
191 )
192 };
193
194 self.push_sorted_completions(get_callables(current, &display));
195 self.push_sorted_completions(get_callables(std, &display));
196 self.push_sorted_completions(Self::get_core_callables(core, &display));
197 self.push_completions(Self::get_namespaces(current));
198 self.push_completions(Self::get_namespaces(std));
199 self.push_completions(Self::get_namespaces(core));
200 }
201
202 fn push_stmt_keywords(&mut self) {
203 static STMT_KEYWORDS: [&str; 5] = ["let", "return", "use", "mutable", "borrow"];
204
205 self.push_completions(
206 STMT_KEYWORDS
207 .map(|key| CompletionItem::new(key.to_string(), CompletionItemKind::Keyword))
208 .into_iter(),
209 );
210 }
211
212 fn push_expr_keywords(&mut self) {
213 static EXPR_KEYWORDS: [&str; 11] = [
214 "if", "for", "in", "within", "apply", "repeat", "until", "fixup", "set", "while",
215 "fail",
216 ];
217
218 self.push_completions(
219 EXPR_KEYWORDS
220 .map(|key| CompletionItem::new(key.to_string(), CompletionItemKind::Keyword))
221 .into_iter(),
222 );
223 }
224
225 /// Each invocation of this function increments the sort group so that
226 /// in the eventual completion list, the groups of items show up in the
227 /// order they were added.
228 /// The items are then sorted according to the input list order (not alphabetical)
229 fn push_completions(&mut self, iter: impl Iterator<Item = CompletionItem>) {
230 let mut current_sort_prefix = 0;
231
232 self.items.extend(iter.map(|item| CompletionItem {
233 sort_text: {
234 current_sort_prefix += 1;
235 Some(format!(
236 "{:02}{:02}{}",
237 self.current_sort_group, current_sort_prefix, item.label
238 ))
239 },
240 ..item
241 }));
242
243 self.current_sort_group += 1;
244 }
245
246 /// Push a group of completions that are themselves sorted into subgroups
247 fn push_sorted_completions(&mut self, iter: impl Iterator<Item = (CompletionItem, u32)>) {
248 self.items
249 .extend(iter.map(|(item, item_sort_group)| CompletionItem {
250 sort_text: Some(format!(
251 "{:02}{:02}{}",
252 self.current_sort_group, item_sort_group, item.label
253 )),
254 ..item
255 }));
256
257 self.current_sort_group += 1;
258 }
259
260 fn get_callables<'a>(
261 package: &'a Package,
262 display: &'a CodeDisplay,
263 opens: &'a [(Rc<str>, Option<Rc<str>>)],
264 start_of_namespace: Option<u32>,
265 current_namespace_name: Option<Rc<str>>,
266 ) -> impl Iterator<Item = (CompletionItem, u32)> + 'a {
267 package.items.values().filter_map(move |i| {
268 // We only want items whose parents are namespaces
269 if let Some(item_id) = i.parent {
270 if let Some(parent) = package.items.get(item_id) {
271 if let ItemKind::Namespace(namespace, _) = &parent.kind {
272 return match &i.kind {
273 ItemKind::Callable(callable_decl) => {
274 let name = callable_decl.name.name.as_ref();
275 let detail =
276 Some(display.hir_callable_decl(callable_decl).to_string());
277 // Everything that starts with a __ goes last in the list
278 let sort_group = u32::from(name.starts_with("__"));
279 let mut additional_edits = vec![];
280 let mut qualification: Option<Rc<str>> = None;
281 match &current_namespace_name {
282 Some(curr_ns) if *curr_ns == namespace.name => {}
283 None => {}
284 _ => {
285 // open is an option of option of Rc<str>
286 // the first option tells if it found an open with the namespace name
287 // the second, nested option tells if that open has an alias
288 let open = opens.iter().find_map(|(name, alias)| {
289 if *name == namespace.name {
290 Some(alias)
291 } else {
292 None
293 }
294 });
295 qualification = match open {
296 Some(alias) => alias.as_ref().cloned(),
297 None => match start_of_namespace {
298 Some(start) => {
299 additional_edits.push((
300 protocol::Span { start, end: start },
301 format!(
302 "open {};\n ",
303 namespace.name.clone()
304 ),
305 ));
306 None
307 }
308 None => Some(namespace.name.clone()),
309 },
310 }
311 }
312 }
313
314 let additional_text_edits = if additional_edits.is_empty() {
315 None
316 } else {
317 Some(additional_edits)
318 };
319
320 let label = if let Some(qualification) = qualification {
321 format!("{qualification}.{name}")
322 } else {
323 name.to_owned()
324 };
325 Some((
326 CompletionItem {
327 label,
328 kind: CompletionItemKind::Function,
329 sort_text: None, // This will get filled in during `push_sorted_completions`
330 detail,
331 additional_text_edits,
332 },
333 sort_group,
334 ))
335 }
336 _ => None,
337 };
338 }
339 }
340 }
341 None
342 })
343 }
344
345 fn get_core_callables<'a>(
346 package: &'a Package,
347 display: &'a CodeDisplay,
348 ) -> impl Iterator<Item = (CompletionItem, u32)> + 'a {
349 package.items.values().filter_map(move |i| match &i.kind {
350 ItemKind::Callable(callable_decl) => {
351 let name = callable_decl.name.name.as_ref();
352 let detail = Some(display.hir_callable_decl(callable_decl).to_string());
353 // Everything that starts with a __ goes last in the list
354 let sort_group = u32::from(name.starts_with("__"));
355 Some((
356 CompletionItem {
357 label: name.to_string(),
358 kind: CompletionItemKind::Function,
359 sort_text: None, // This will get filled in during `push_sorted_completions`
360 detail,
361 additional_text_edits: None,
362 },
363 sort_group,
364 ))
365 }
366 _ => None,
367 })
368 }
369
370 fn get_namespaces(package: &'_ Package) -> impl Iterator<Item = CompletionItem> + '_ {
371 package.items.values().filter_map(|i| match &i.kind {
372 ItemKind::Namespace(namespace, _) => Some(CompletionItem::new(
373 namespace.name.to_string(),
374 CompletionItemKind::Module,
375 )),
376 _ => None,
377 })
378 }
379}
380
381struct ContextFinder {
382 offset: u32,
383 context: Context,
384 opens: Vec<(Rc<str>, Option<Rc<str>>)>,
385 start_of_namespace: Option<u32>,
386 current_namespace_name: Option<Rc<str>>,
387}
388
389#[derive(Debug, PartialEq)]
390enum Context {
391 NoCompilation,
392 TopLevel,
393 Namespace,
394 CallableSignature,
395 Block,
396}
397
398impl Visitor<'_> for ContextFinder {
399 fn visit_namespace(&mut self, namespace: &'_ qsc::ast::Namespace) {
400 if span_contains(namespace.span, self.offset) {
401 self.current_namespace_name = Some(namespace.name.name.clone());
402 self.context = Context::Namespace;
403 self.opens = vec![];
404 self.start_of_namespace = None;
405 visit::walk_namespace(self, namespace);
406 }
407 }
408
409 fn visit_item(&mut self, item: &'_ qsc::ast::Item) {
410 if self.start_of_namespace.is_none() {
411 self.start_of_namespace = Some(item.span.lo);
412 }
413
414 if let qsc::ast::ItemKind::Open(name, alias) = &*item.kind {
415 self.opens.push((
416 name.name.clone(),
417 alias.as_ref().map(|alias| alias.name.clone()),
418 ));
419 }
420
421 if span_contains(item.span, self.offset) {
422 visit::walk_item(self, item);
423 }
424 }
425
426 fn visit_callable_decl(&mut self, decl: &'_ qsc::ast::CallableDecl) {
427 if span_contains(decl.span, self.offset) {
428 // This span covers the body too, but the
429 // context will get overwritten by visit_block
430 // if the offset is inside the actual body
431 self.context = Context::CallableSignature;
432 visit::walk_callable_decl(self, decl);
433 }
434 }
435
436 fn visit_block(&mut self, block: &'_ qsc::ast::Block) {
437 if span_contains(block.span, self.offset) {
438 self.context = Context::Block;
439 }
440 }
441}
442