aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2018-11-21 09:57:05 +0000
committerAleksey Kladov <[email protected]>2018-11-21 09:57:05 +0000
commit049f8df93cca05af395ce873738dc85d5a25f3fc (patch)
tree55614a697cc531a53f2836c994d3211c6cd756c2
parentb70b6bce19981df5d0cda6a0193fb9b07da6ea51 (diff)
switch completion to new scope
-rw-r--r--crates/ra_analysis/src/completion/mod.rs10
-rw-r--r--crates/ra_analysis/src/completion/reference_completion.rs26
-rw-r--r--crates/ra_analysis/src/db.rs3
-rw-r--r--crates/ra_analysis/src/descriptors/mod.rs6
-rw-r--r--crates/ra_analysis/src/descriptors/module/imp.rs21
-rw-r--r--crates/ra_analysis/src/descriptors/module/mod.rs9
-rw-r--r--crates/ra_analysis/src/descriptors/module/nameres.rs60
-rw-r--r--crates/ra_analysis/src/descriptors/module/scope.rs124
-rw-r--r--crates/ra_analysis/tests/tests.rs8
9 files changed, 69 insertions, 198 deletions
diff --git a/crates/ra_analysis/src/completion/mod.rs b/crates/ra_analysis/src/completion/mod.rs
index 5e3ee79dd..a8a752fc7 100644
--- a/crates/ra_analysis/src/completion/mod.rs
+++ b/crates/ra_analysis/src/completion/mod.rs
@@ -204,9 +204,9 @@ mod tests {
204 <|> 204 <|>
205 } 205 }
206 ", 206 ",
207 r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, 207 r#"[CompletionItem { label: "quux", lookup: None, snippet: None },
208 CompletionItem { label: "Baz", lookup: None, snippet: None }, 208 CompletionItem { label: "Foo", lookup: None, snippet: None },
209 CompletionItem { label: "quux", lookup: None, snippet: None }]"#, 209 CompletionItem { label: "Baz", lookup: None, snippet: None }]"#,
210 ); 210 );
211 } 211 }
212 212
@@ -230,8 +230,8 @@ mod tests {
230 fn quux() { <|> } 230 fn quux() { <|> }
231 } 231 }
232 ", 232 ",
233 r#"[CompletionItem { label: "Bar", lookup: None, snippet: None }, 233 r#"[CompletionItem { label: "quux", lookup: None, snippet: None },
234 CompletionItem { label: "quux", lookup: None, snippet: None }]"#, 234 CompletionItem { label: "Bar", lookup: None, snippet: None }]"#,
235 ); 235 );
236 } 236 }
237 237
diff --git a/crates/ra_analysis/src/completion/reference_completion.rs b/crates/ra_analysis/src/completion/reference_completion.rs
index c94d9af75..84383b547 100644
--- a/crates/ra_analysis/src/completion/reference_completion.rs
+++ b/crates/ra_analysis/src/completion/reference_completion.rs
@@ -39,14 +39,17 @@ pub(super) fn completions(
39 let module_scope = module.scope(db)?; 39 let module_scope = module.scope(db)?;
40 acc.extend( 40 acc.extend(
41 module_scope 41 module_scope
42 .entries() 42 .items
43 .iter() 43 .iter()
44 .filter(|entry| { 44 .filter(|(_name, res)| {
45 // Don't expose this item 45 // Don't expose this item
46 !entry.ptr().range().is_subrange(&name_ref.syntax().range()) 46 match res.import_name {
47 None => true,
48 Some(ptr) => !ptr.range().is_subrange(&name_ref.syntax().range()),
49 }
47 }) 50 })
48 .map(|entry| CompletionItem { 51 .map(|(name, _res)| CompletionItem {
49 label: entry.name().to_string(), 52 label: name.to_string(),
50 lookup: None, 53 lookup: None,
51 snippet: None, 54 snippet: None,
52 }), 55 }),
@@ -173,11 +176,14 @@ fn complete_path(
173 Some(it) => it, 176 Some(it) => it,
174 }; 177 };
175 let module_scope = target_module.scope(db)?; 178 let module_scope = target_module.scope(db)?;
176 let completions = module_scope.entries().iter().map(|entry| CompletionItem { 179 let completions = module_scope
177 label: entry.name().to_string(), 180 .items
178 lookup: None, 181 .iter()
179 snippet: None, 182 .map(|(name, _res)| CompletionItem {
180 }); 183 label: name.to_string(),
184 lookup: None,
185 snippet: None,
186 });
181 acc.extend(completions); 187 acc.extend(completions);
182 Ok(()) 188 Ok(())
183} 189}
diff --git a/crates/ra_analysis/src/db.rs b/crates/ra_analysis/src/db.rs
index 9a5cd4b24..887d687ea 100644
--- a/crates/ra_analysis/src/db.rs
+++ b/crates/ra_analysis/src/db.rs
@@ -7,7 +7,7 @@ use salsa::{self, Database};
7use crate::{ 7use crate::{
8 db, 8 db,
9 descriptors::{ 9 descriptors::{
10 DescriptorDatabase, FnScopesQuery, FnSyntaxQuery, ModuleScopeQuery, ModuleTreeQuery, 10 DescriptorDatabase, FnScopesQuery, FnSyntaxQuery, ModuleTreeQuery,
11 SubmodulesQuery, ItemMapQuery, InputModuleItemsQuery, 11 SubmodulesQuery, ItemMapQuery, InputModuleItemsQuery,
12 }, 12 },
13 symbol_index::SymbolIndex, 13 symbol_index::SymbolIndex,
@@ -88,7 +88,6 @@ salsa::database_storage! {
88 fn fn_scopes() for FnScopesQuery; 88 fn fn_scopes() for FnScopesQuery;
89 fn _input_module_items() for InputModuleItemsQuery; 89 fn _input_module_items() for InputModuleItemsQuery;
90 fn _item_map() for ItemMapQuery; 90 fn _item_map() for ItemMapQuery;
91 fn _module_scope() for ModuleScopeQuery;
92 fn _fn_syntax() for FnSyntaxQuery; 91 fn _fn_syntax() for FnSyntaxQuery;
93 fn _submodules() for SubmodulesQuery; 92 fn _submodules() for SubmodulesQuery;
94 } 93 }
diff --git a/crates/ra_analysis/src/descriptors/mod.rs b/crates/ra_analysis/src/descriptors/mod.rs
index a32042b84..6b56d92e1 100644
--- a/crates/ra_analysis/src/descriptors/mod.rs
+++ b/crates/ra_analysis/src/descriptors/mod.rs
@@ -11,7 +11,7 @@ use ra_syntax::{
11use crate::{ 11use crate::{
12 db::SyntaxDatabase, 12 db::SyntaxDatabase,
13 descriptors::function::{resolve_local_name, FnId, FnScopes}, 13 descriptors::function::{resolve_local_name, FnId, FnScopes},
14 descriptors::module::{ModuleId, ModuleScope, ModuleTree, ModuleSource, nameres::{ItemMap, InputModuleItems}}, 14 descriptors::module::{ModuleId, ModuleTree, ModuleSource, nameres::{ItemMap, InputModuleItems}},
15 input::SourceRootId, 15 input::SourceRootId,
16 loc2id::IdDatabase, 16 loc2id::IdDatabase,
17 syntax_ptr::LocalSyntaxPtr, 17 syntax_ptr::LocalSyntaxPtr,
@@ -37,10 +37,6 @@ salsa::query_group! {
37 type ModuleTreeQuery; 37 type ModuleTreeQuery;
38 use fn module::imp::module_tree; 38 use fn module::imp::module_tree;
39 } 39 }
40 fn _module_scope(source_root_id: SourceRootId, module_id: ModuleId) -> Cancelable<Arc<ModuleScope>> {
41 type ModuleScopeQuery;
42 use fn module::imp::module_scope;
43 }
44 fn _fn_syntax(fn_id: FnId) -> FnDefNode { 40 fn _fn_syntax(fn_id: FnId) -> FnDefNode {
45 type FnSyntaxQuery; 41 type FnSyntaxQuery;
46 // Don't retain syntax trees in memory 42 // Don't retain syntax trees in memory
diff --git a/crates/ra_analysis/src/descriptors/module/imp.rs b/crates/ra_analysis/src/descriptors/module/imp.rs
index defe87216..d4dce861f 100644
--- a/crates/ra_analysis/src/descriptors/module/imp.rs
+++ b/crates/ra_analysis/src/descriptors/module/imp.rs
@@ -1,7 +1,7 @@
1use std::sync::Arc; 1use std::sync::Arc;
2 2
3use ra_syntax::{ 3use ra_syntax::{
4 ast::{self, ModuleItemOwner, NameOwner}, 4 ast::{self, NameOwner},
5 SmolStr, 5 SmolStr,
6}; 6};
7use relative_path::RelativePathBuf; 7use relative_path::RelativePathBuf;
@@ -15,7 +15,7 @@ use crate::{
15}; 15};
16 16
17use super::{ 17use super::{
18 LinkData, LinkId, ModuleData, ModuleId, ModuleScope, ModuleSource, ModuleSourceNode, 18 LinkData, LinkId, ModuleData, ModuleId, ModuleSource, ModuleSourceNode,
19 ModuleTree, Problem, 19 ModuleTree, Problem,
20}; 20};
21 21
@@ -81,23 +81,6 @@ pub(crate) fn modules<'a>(
81 }) 81 })
82} 82}
83 83
84pub(crate) fn module_scope(
85 db: &impl DescriptorDatabase,
86 source_root_id: SourceRootId,
87 module_id: ModuleId,
88) -> Cancelable<Arc<ModuleScope>> {
89 let tree = db._module_tree(source_root_id)?;
90 let source = module_id.source(&tree).resolve(db);
91 let res = match source {
92 ModuleSourceNode::SourceFile(it) => ModuleScope::new(it.borrowed().items()),
93 ModuleSourceNode::Module(it) => match it.borrowed().item_list() {
94 Some(items) => ModuleScope::new(items.items()),
95 None => ModuleScope::new(std::iter::empty()),
96 },
97 };
98 Ok(Arc::new(res))
99}
100
101pub(crate) fn module_tree( 84pub(crate) fn module_tree(
102 db: &impl DescriptorDatabase, 85 db: &impl DescriptorDatabase,
103 source_root: SourceRootId, 86 source_root: SourceRootId,
diff --git a/crates/ra_analysis/src/descriptors/module/mod.rs b/crates/ra_analysis/src/descriptors/module/mod.rs
index 95d9bcc27..cfdffcdbc 100644
--- a/crates/ra_analysis/src/descriptors/module/mod.rs
+++ b/crates/ra_analysis/src/descriptors/module/mod.rs
@@ -1,5 +1,4 @@
1pub(super) mod imp; 1pub(super) mod imp;
2mod scope;
3pub(super) mod nameres; 2pub(super) mod nameres;
4 3
5use std::sync::Arc; 4use std::sync::Arc;
@@ -19,7 +18,7 @@ use crate::{
19 input::SourceRootId 18 input::SourceRootId
20}; 19};
21 20
22pub(crate) use self::scope::ModuleScope; 21pub(crate) use self::{nameres::ModuleScope};
23 22
24/// `ModuleDescriptor` is API entry point to get all the information 23/// `ModuleDescriptor` is API entry point to get all the information
25/// about a particular module. 24/// about a particular module.
@@ -126,8 +125,10 @@ impl ModuleDescriptor {
126 } 125 }
127 126
128 /// Returns a `ModuleScope`: a set of items, visible in this module. 127 /// Returns a `ModuleScope`: a set of items, visible in this module.
129 pub fn scope(&self, db: &impl DescriptorDatabase) -> Cancelable<Arc<ModuleScope>> { 128 pub(crate) fn scope(&self, db: &impl DescriptorDatabase) -> Cancelable<ModuleScope> {
130 db._module_scope(self.source_root_id, self.module_id) 129 let item_map = db._item_map(self.source_root_id)?;
130 let res = item_map.per_module[&self.module_id].clone();
131 Ok(res)
131 } 132 }
132 133
133 pub fn problems(&self, db: &impl DescriptorDatabase) -> Vec<(SyntaxNode, Problem)> { 134 pub fn problems(&self, db: &impl DescriptorDatabase) -> Vec<(SyntaxNode, Problem)> {
diff --git a/crates/ra_analysis/src/descriptors/module/nameres.rs b/crates/ra_analysis/src/descriptors/module/nameres.rs
index 34127e78f..c5bf467ca 100644
--- a/crates/ra_analysis/src/descriptors/module/nameres.rs
+++ b/crates/ra_analysis/src/descriptors/module/nameres.rs
@@ -8,7 +8,7 @@ use rustc_hash::FxHashMap;
8 8
9use ra_syntax::{ 9use ra_syntax::{
10 SmolStr, SyntaxKind::{self, *}, 10 SmolStr, SyntaxKind::{self, *},
11 ast::{self, NameOwner, AstNode, ModuleItemOwner} 11 ast::{self, AstNode, ModuleItemOwner}
12}; 12};
13 13
14use crate::{ 14use crate::{
@@ -26,13 +26,13 @@ use crate::{
26/// module, the set of visible items. 26/// module, the set of visible items.
27#[derive(Default, Debug, PartialEq, Eq)] 27#[derive(Default, Debug, PartialEq, Eq)]
28pub(crate) struct ItemMap { 28pub(crate) struct ItemMap {
29 per_module: FxHashMap<ModuleId, ModuleItems>, 29 pub(crate) per_module: FxHashMap<ModuleId, ModuleScope>,
30} 30}
31 31
32#[derive(Debug, Default, PartialEq, Eq)] 32#[derive(Debug, Default, PartialEq, Eq, Clone)]
33struct ModuleItems { 33pub(crate) struct ModuleScope {
34 items: FxHashMap<SmolStr, Resolution>, 34 pub(crate) items: FxHashMap<SmolStr, Resolution>,
35 import_resolutions: FxHashMap<LocalSyntaxPtr, DefId>, 35 pub(crate) import_resolutions: FxHashMap<LocalSyntaxPtr, DefId>,
36} 36}
37 37
38/// A set of items and imports declared inside a module, without relation to 38/// A set of items and imports declared inside a module, without relation to
@@ -117,22 +117,25 @@ pub(crate) fn item_map(
117/// Resolution is basically `DefId` atm, but it should account for stuff like 117/// Resolution is basically `DefId` atm, but it should account for stuff like
118/// multiple namespaces, ambiguity and errors. 118/// multiple namespaces, ambiguity and errors.
119#[derive(Debug, Clone, PartialEq, Eq)] 119#[derive(Debug, Clone, PartialEq, Eq)]
120struct Resolution { 120pub(crate) struct Resolution {
121 /// None for unresolved 121 /// None for unresolved
122 def_id: Option<DefId>, 122 pub(crate) def_id: Option<DefId>,
123 /// ident by whitch this is imported into local scope.
124 /// TODO: make this offset-independent.
125 pub(crate) import_name: Option<LocalSyntaxPtr>,
123} 126}
124 127
125#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 128// #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
126enum Namespace { 129// enum Namespace {
127 Types, 130// Types,
128 Values, 131// Values,
129} 132// }
130 133
131#[derive(Debug)] 134// #[derive(Debug)]
132struct PerNs<T> { 135// struct PerNs<T> {
133 types: Option<T>, 136// types: Option<T>,
134 values: Option<T>, 137// values: Option<T>,
135} 138// }
136 139
137#[derive(Debug, PartialEq, Eq)] 140#[derive(Debug, PartialEq, Eq)]
138struct ModuleItem { 141struct ModuleItem {
@@ -144,7 +147,7 @@ struct ModuleItem {
144 147
145#[derive(Debug, PartialEq, Eq)] 148#[derive(Debug, PartialEq, Eq)]
146enum Vis { 149enum Vis {
147 Priv, 150 // Priv,
148 Other, 151 Other,
149} 152}
150 153
@@ -302,13 +305,17 @@ where
302 fn populate_module(&mut self, module_id: ModuleId, input: &InputModuleItems) { 305 fn populate_module(&mut self, module_id: ModuleId, input: &InputModuleItems) {
303 let file_id = module_id.source(&self.module_tree).file_id(); 306 let file_id = module_id.source(&self.module_tree).file_id();
304 307
305 let mut module_items = ModuleItems::default(); 308 let mut module_items = ModuleScope::default();
306 309
307 for import in input.imports.iter() { 310 for import in input.imports.iter() {
308 if let Some((_, name)) = import.segments.last() { 311 if let Some((ptr, name)) = import.segments.last() {
309 module_items 312 module_items.items.insert(
310 .items 313 name.clone(),
311 .insert(name.clone(), Resolution { def_id: None }); 314 Resolution {
315 def_id: None,
316 import_name: Some(*ptr),
317 },
318 );
312 } 319 }
313 } 320 }
314 321
@@ -322,6 +329,7 @@ where
322 let def_id = self.db.id_maps().def_id(def_loc); 329 let def_id = self.db.id_maps().def_id(def_loc);
323 let resolution = Resolution { 330 let resolution = Resolution {
324 def_id: Some(def_id), 331 def_id: Some(def_id),
332 import_name: None,
325 }; 333 };
326 module_items.items.insert(item.name.clone(), resolution); 334 module_items.items.insert(item.name.clone(), resolution);
327 } 335 }
@@ -334,6 +342,7 @@ where
334 let def_id = self.db.id_maps().def_id(def_loc); 342 let def_id = self.db.id_maps().def_id(def_loc);
335 let resolution = Resolution { 343 let resolution = Resolution {
336 def_id: Some(def_id), 344 def_id: Some(def_id),
345 import_name: None,
337 }; 346 };
338 module_items.items.insert(name, resolution); 347 module_items.items.insert(name, resolution);
339 } 348 }
@@ -386,6 +395,7 @@ where
386 self.update(module_id, |items| { 395 self.update(module_id, |items| {
387 let res = Resolution { 396 let res = Resolution {
388 def_id: Some(def_id), 397 def_id: Some(def_id),
398 import_name: Some(*ptr),
389 }; 399 };
390 items.items.insert(name.clone(), res); 400 items.items.insert(name.clone(), res);
391 }) 401 })
@@ -393,7 +403,7 @@ where
393 } 403 }
394 } 404 }
395 405
396 fn update(&mut self, module_id: ModuleId, f: impl FnOnce(&mut ModuleItems)) { 406 fn update(&mut self, module_id: ModuleId, f: impl FnOnce(&mut ModuleScope)) {
397 let module_items = self.result.per_module.get_mut(&module_id).unwrap(); 407 let module_items = self.result.per_module.get_mut(&module_id).unwrap();
398 f(module_items) 408 f(module_items)
399 } 409 }
diff --git a/crates/ra_analysis/src/descriptors/module/scope.rs b/crates/ra_analysis/src/descriptors/module/scope.rs
deleted file mode 100644
index 4490228e4..000000000
--- a/crates/ra_analysis/src/descriptors/module/scope.rs
+++ /dev/null
@@ -1,124 +0,0 @@
1//! Backend for module-level scope resolution & completion
2
3use ra_syntax::{ast, AstNode, SmolStr};
4
5use crate::syntax_ptr::LocalSyntaxPtr;
6
7/// `ModuleScope` contains all named items declared in the scope.
8#[derive(Debug, PartialEq, Eq)]
9pub(crate) struct ModuleScope {
10 entries: Vec<Entry>,
11}
12
13/// `Entry` is a single named declaration iside a module.
14#[derive(Debug, PartialEq, Eq)]
15pub(crate) struct Entry {
16 ptr: LocalSyntaxPtr,
17 kind: EntryKind,
18 name: SmolStr,
19}
20
21#[derive(Debug, PartialEq, Eq)]
22enum EntryKind {
23 Item,
24 Import,
25}
26
27impl ModuleScope {
28 pub(super) fn new<'a>(items: impl Iterator<Item = ast::ModuleItem<'a>>) -> ModuleScope {
29 let mut entries = Vec::new();
30 for item in items {
31 let entry = match item {
32 ast::ModuleItem::StructDef(item) => Entry::new(item),
33 ast::ModuleItem::EnumDef(item) => Entry::new(item),
34 ast::ModuleItem::FnDef(item) => Entry::new(item),
35 ast::ModuleItem::ConstDef(item) => Entry::new(item),
36 ast::ModuleItem::StaticDef(item) => Entry::new(item),
37 ast::ModuleItem::TraitDef(item) => Entry::new(item),
38 ast::ModuleItem::TypeDef(item) => Entry::new(item),
39 ast::ModuleItem::Module(item) => Entry::new(item),
40 ast::ModuleItem::UseItem(item) => {
41 if let Some(tree) = item.use_tree() {
42 collect_imports(tree, &mut entries);
43 }
44 continue;
45 }
46 ast::ModuleItem::ExternCrateItem(_) | ast::ModuleItem::ImplItem(_) => continue,
47 };
48 entries.extend(entry)
49 }
50
51 ModuleScope { entries }
52 }
53
54 pub fn entries(&self) -> &[Entry] {
55 self.entries.as_slice()
56 }
57}
58
59impl Entry {
60 fn new<'a>(item: impl ast::NameOwner<'a>) -> Option<Entry> {
61 let name = item.name()?;
62 Some(Entry {
63 name: name.text(),
64 ptr: LocalSyntaxPtr::new(name.syntax()),
65 kind: EntryKind::Item,
66 })
67 }
68 fn new_import(path: ast::Path) -> Option<Entry> {
69 let name_ref = path.segment()?.name_ref()?;
70 Some(Entry {
71 name: name_ref.text(),
72 ptr: LocalSyntaxPtr::new(name_ref.syntax()),
73 kind: EntryKind::Import,
74 })
75 }
76 pub fn name(&self) -> &SmolStr {
77 &self.name
78 }
79 pub fn ptr(&self) -> LocalSyntaxPtr {
80 self.ptr
81 }
82}
83
84fn collect_imports(tree: ast::UseTree, acc: &mut Vec<Entry>) {
85 if let Some(use_tree_list) = tree.use_tree_list() {
86 return use_tree_list
87 .use_trees()
88 .for_each(|it| collect_imports(it, acc));
89 }
90 if let Some(path) = tree.path() {
91 acc.extend(Entry::new_import(path));
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use ra_syntax::{ast::ModuleItemOwner, SourceFileNode};
99
100 fn do_check(code: &str, expected: &[&str]) {
101 let file = SourceFileNode::parse(&code);
102 let scope = ModuleScope::new(file.ast().items());
103 let actual = scope.entries.iter().map(|it| it.name()).collect::<Vec<_>>();
104 assert_eq!(expected, actual.as_slice());
105 }
106
107 #[test]
108 fn test_module_scope() {
109 do_check(
110 "
111 struct Foo;
112 enum Bar {}
113 mod baz {}
114 fn quux() {}
115 use x::{
116 y::z,
117 t,
118 };
119 type T = ();
120 ",
121 &["Foo", "Bar", "baz", "quux", "z", "t", "T"],
122 )
123 }
124}
diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs
index 719c166b5..72b2ebf97 100644
--- a/crates/ra_analysis/tests/tests.rs
+++ b/crates/ra_analysis/tests/tests.rs
@@ -447,8 +447,8 @@ fn test_complete_crate_path() {
447 ); 447 );
448 let completions = analysis.completions(position).unwrap().unwrap(); 448 let completions = analysis.completions(position).unwrap().unwrap();
449 assert_eq_dbg( 449 assert_eq_dbg(
450 r#"[CompletionItem { label: "foo", lookup: None, snippet: None }, 450 r#"[CompletionItem { label: "Spam", lookup: None, snippet: None },
451 CompletionItem { label: "Spam", lookup: None, snippet: None }]"#, 451 CompletionItem { label: "foo", lookup: None, snippet: None }]"#,
452 &completions, 452 &completions,
453 ); 453 );
454} 454}
@@ -466,8 +466,8 @@ fn test_complete_crate_path_with_braces() {
466 ); 466 );
467 let completions = analysis.completions(position).unwrap().unwrap(); 467 let completions = analysis.completions(position).unwrap().unwrap();
468 assert_eq_dbg( 468 assert_eq_dbg(
469 r#"[CompletionItem { label: "foo", lookup: None, snippet: None }, 469 r#"[CompletionItem { label: "Spam", lookup: None, snippet: None },
470 CompletionItem { label: "Spam", lookup: None, snippet: None }]"#, 470 CompletionItem { label: "foo", lookup: None, snippet: None }]"#,
471 &completions, 471 &completions,
472 ); 472 );
473} 473}