microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.23.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/language_service/src/completion/qsharp.rs

307lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use std::{iter::once, sync::Arc};
5
6use qsc::{
7 LanguageFeatures,
8 line_column::Encoding,
9 parse::completion::{
10 HardcodedIdentKind, NameKind, PathKind, WordKinds, possible_words_at_offset_in_fragments,
11 possible_words_at_offset_in_source,
12 },
13};
14use qsc_project::ProjectType;
15
16use crate::{
17 Compilation,
18 compilation::CompilationKind,
19 completion::{AstContext, Fields, Globals},
20 protocol::{CompletionItem, CompletionItemKind, CompletionList},
21};
22
23use super::{Completion, Locals, TextEditRange, collect_path_segments, into_completion_list};
24
25pub(super) fn completions(
26 compilation: &Compilation,
27 position_encoding: Encoding,
28 package_offset: u32,
29 contents: &Arc<str>,
30 source_offset: u32,
31 source_name_relative: &str,
32) -> CompletionList {
33 // Special case: no completions in attribute arguments, even when the
34 // parser expects an expression.
35 let ast_context = AstContext::init(source_offset, &compilation.user_unit().ast.package);
36 if let Some(name) = ast_context.get_name_of_attr_for_attr_arg() {
37 if name.as_ref() == "EntryPoint" {
38 return CompletionList {
39 items: vec![
40 CompletionItem::new("Unrestricted".to_string(), CompletionItemKind::Keyword),
41 CompletionItem::new("Base".to_string(), CompletionItemKind::Keyword),
42 CompletionItem::new("Adaptive_RI".to_string(), CompletionItemKind::Keyword),
43 CompletionItem::new("Adaptive_RIF".to_string(), CompletionItemKind::Keyword),
44 ],
45 };
46 }
47 // No completions in attribute expressions, they're misleading.
48 return CompletionList::default();
49 }
50
51 // What kinds of words are expected at the cursor location?
52 let expected_words_at_cursor =
53 expected_word_kinds(compilation, source_name_relative, contents, source_offset);
54
55 // Now that we have the information from the parser about what kinds of
56 // words are expected, gather the actual words (identifiers, keywords, etc) for each kind.
57
58 // Keywords and other hardcoded words
59 let hardcoded_completions = collect_hardcoded_words(expected_words_at_cursor);
60
61 // The tricky bit: globals, locals, names we need to gather from the compilation.
62 let name_completions = collect_names(
63 expected_words_at_cursor,
64 package_offset,
65 compilation,
66 position_encoding,
67 );
68
69 // We have all the data, put everything into a completion list.
70 into_completion_list(once(hardcoded_completions).chain(name_completions))
71}
72
73/// Invokes the parser to determine what kinds of words are expected at the cursor location.
74fn expected_word_kinds(
75 compilation: &Compilation,
76 source_name_relative: &str,
77 source_contents: &str,
78 cursor_offset: u32,
79) -> WordKinds {
80 // We should not return any completions in comments.
81 // This compensates for a bug in [`possible_words_at_offset_in_source`] .
82 // Ideally, that function would be aware of the comment context and not
83 // return any completions, however this is difficult to do today because
84 // of the parser's unawareness of comment tokens.
85 // So we do a simple check here where we have access to the full source contents.
86 if in_comment(source_contents, cursor_offset) {
87 return WordKinds::empty();
88 }
89
90 match &compilation.kind {
91 CompilationKind::OpenProject {
92 package_graph_sources,
93 ..
94 } => possible_words_at_offset_in_source(
95 source_contents,
96 Some(source_name_relative),
97 package_graph_sources.root.language_features,
98 cursor_offset,
99 ),
100 CompilationKind::Notebook { project } => possible_words_at_offset_in_fragments(
101 source_contents,
102 project.as_ref().map_or(LanguageFeatures::default(), |p| {
103 let ProjectType::QSharp(sources) = &p.project_type else {
104 unreachable!("Project type should be Q#")
105 };
106 sources.root.language_features
107 }),
108 cursor_offset,
109 ),
110 CompilationKind::OpenQASM { .. } => {
111 unreachable!("OpenQASM compilations shouldn't request Q# completions")
112 }
113 }
114}
115
116fn in_comment(source_contents: &str, cursor_offset: u32) -> bool {
117 // find the last newline before the cursor
118 let last_line_start = source_contents[..cursor_offset as usize]
119 .rfind('\n')
120 .unwrap_or(0);
121 // find the last comment start before the cursor
122 let last_comment_start = source_contents[last_line_start..cursor_offset as usize].rfind("//");
123 last_comment_start.is_some()
124}
125
126/// Collects hardcoded completions from the given set of parser predictions.
127///
128/// Hardcoded words are actual keywords (`let`, etc) as well as other words that are
129/// hardcoded into the language (`Qubit`, `EntryPoint`, etc)
130fn collect_hardcoded_words(expected: WordKinds) -> Vec<Completion> {
131 let mut completions = Vec::new();
132 for word_kind in expected.iter_hardcoded_ident_kinds() {
133 match word_kind {
134 HardcodedIdentKind::Qubit => {
135 completions.push(Completion::new(
136 "Qubit".to_string(),
137 CompletionItemKind::Interface,
138 ));
139 }
140 HardcodedIdentKind::Attr => {
141 completions.extend([
142 Completion::new("EntryPoint".to_string(), CompletionItemKind::Interface),
143 Completion::new("Config".to_string(), CompletionItemKind::Interface),
144 Completion::new(
145 "SimulatableIntrinsic".to_string(),
146 CompletionItemKind::Interface,
147 ),
148 Completion::new("Measurement".to_string(), CompletionItemKind::Interface),
149 Completion::new("Reset".to_string(), CompletionItemKind::Interface),
150 Completion::new("Test".to_string(), CompletionItemKind::Interface),
151 ]);
152 }
153 HardcodedIdentKind::Size => {
154 completions.push(Completion::new(
155 "size".to_string(),
156 CompletionItemKind::Keyword,
157 ));
158 }
159 }
160 }
161
162 for keyword in expected.iter_keywords() {
163 let keyword = keyword.to_string();
164 // Skip adding the underscore keyword to the list, it's more confusing than helpful.
165 if keyword != "_" {
166 completions.push(Completion::new(keyword, CompletionItemKind::Keyword));
167 }
168 }
169
170 completions
171}
172
173/// Collects names from the compilation that match the expected word kinds.
174fn collect_names(
175 expected: WordKinds,
176 cursor_offset: u32,
177 compilation: &Compilation,
178 position_encoding: Encoding,
179) -> Vec<Vec<Completion>> {
180 let mut groups = Vec::new();
181
182 for name_kind in expected.iter_name_kinds() {
183 match name_kind {
184 NameKind::Path(path_kind) => {
185 let globals = Globals::init(cursor_offset, compilation);
186 let edit_range = TextEditRange::init(cursor_offset, compilation, position_encoding);
187 let locals = Locals::new(cursor_offset, compilation);
188
189 groups.extend(collect_paths(path_kind, &globals, &locals, &edit_range));
190 }
191 NameKind::PathSegment => {
192 let globals = Globals::init(cursor_offset, compilation);
193 let ast_context =
194 AstContext::init(cursor_offset, &compilation.user_unit().ast.package);
195 let fields = Fields::new(compilation, &ast_context);
196
197 groups.extend(collect_path_segments(&ast_context, &globals, &fields));
198 }
199 NameKind::TyParam => {
200 let locals = Locals::new(cursor_offset, compilation);
201 groups.push(locals.type_names());
202 }
203 NameKind::Field => {
204 let ast_context =
205 AstContext::init(cursor_offset, &compilation.user_unit().ast.package);
206 let fields = Fields::new(compilation, &ast_context);
207
208 groups.push(fields.fields());
209 }
210 NameKind::PrimitiveClass => {
211 // we know the types of the primitive classes, so we can just return them
212 // hard coded here.
213 // If we ever support user-defined primitive classes, we'll need to change this.
214
215 // this is here to force us to update completions if a new primitive class
216 // constraint is supported
217 use qsc::hir::ty::ClassConstraint::*;
218 match Add {
219 Add
220 | Eq
221 | Exp { .. }
222 | Iterable { .. }
223 | NonNativeClass(_)
224 | Integral
225 | Mod
226 | Sub
227 | Mul
228 | Div
229 | Signed
230 | Ord
231 | Show => (),
232 }
233
234 groups.push(vec![
235 Completion::new("Add".to_string(), CompletionItemKind::Class),
236 Completion::new("Eq".to_string(), CompletionItemKind::Class),
237 Completion::with_detail(
238 "Exp".to_string(),
239 CompletionItemKind::Class,
240 Some("Exp['Power]".into()),
241 ),
242 Completion::new("Integral".to_string(), CompletionItemKind::Class),
243 Completion::new("Show".to_string(), CompletionItemKind::Class),
244 Completion::new("Signed".to_string(), CompletionItemKind::Class),
245 Completion::new("Ord".to_string(), CompletionItemKind::Class),
246 Completion::new("Mod".to_string(), CompletionItemKind::Class),
247 Completion::new("Sub".to_string(), CompletionItemKind::Class),
248 Completion::new("Mul".to_string(), CompletionItemKind::Class),
249 Completion::new("Div".to_string(), CompletionItemKind::Class),
250 ]);
251 }
252 }
253 }
254 groups
255}
256
257/// Collects paths that are applicable at the current cursor offset,
258/// taking into account all the relevant name resolution context,
259/// such as scopes and visibility at the cursor location.
260///
261/// Note that the list will not contain full paths to items. It will typically
262/// only include leading qualifier, or the item name along with an auto-import edit.
263/// For example, the item `Microsoft.Quantum.Diagnostics.DumpMachine` will contribute
264/// two completion items: the leading qualifier (namespace) `Microsoft` and the
265/// callable name `DumpMachine` with an auto-import edit to add `Microsoft.Quantum.Diagnostics`.
266fn collect_paths(
267 expected: PathKind,
268 globals: &Globals,
269 locals_at_cursor: &Locals,
270 text_edit_range: &TextEditRange,
271) -> Vec<Vec<Completion>> {
272 let mut global_names = Vec::new();
273 let mut locals_and_builtins = Vec::new();
274 match expected {
275 PathKind::Expr => {
276 locals_and_builtins.push(locals_at_cursor.expr_names());
277 global_names.extend(globals.expr_names(text_edit_range));
278 }
279 PathKind::Ty => {
280 locals_and_builtins.push(locals_at_cursor.type_names());
281 locals_and_builtins.push(
282 [
283 "Qubit", "Int", "Unit", "Result", "Bool", "BigInt", "Double", "Pauli", "Range",
284 "String",
285 ]
286 .map(|s| Completion::new(s.to_string(), CompletionItemKind::Interface))
287 .into(),
288 );
289
290 global_names.extend(globals.type_names(text_edit_range));
291 }
292 PathKind::Import => {
293 global_names.extend(globals.importable_names());
294 }
295 PathKind::Struct => {
296 global_names.extend(globals.type_names(text_edit_range));
297 }
298 PathKind::Namespace => {
299 global_names.push(globals.namespaces());
300 }
301 }
302
303 // This order ensures that locals and builtins come before globals
304 // in the eventual completion list
305 locals_and_builtins.extend(global_names);
306 locals_and_builtins
307}
308