diff options
author | Aleksey Kladov <[email protected]> | 2018-10-30 23:08:54 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2018-10-30 23:08:54 +0000 |
commit | fbbee537228538f448a335bb0b2dabec2b3d443e (patch) | |
tree | db676e2c84585c13179e0116b018a3e67b550190 | |
parent | d10214581e18a3c98760ecf8ebbd27aaf48ec9ae (diff) |
Add ModuleScope as a query
This is a first step towards queryifing completion and resolve.
Some code currently duplicates ra_editor: the plan is to move all
completion from ra_editor, but it'll take more than one commit.
-rw-r--r-- | crates/ra_analysis/src/completion.rs | 35 | ||||
-rw-r--r-- | crates/ra_analysis/src/db.rs | 7 | ||||
-rw-r--r-- | crates/ra_analysis/src/descriptors/module/imp.rs | 14 | ||||
-rw-r--r-- | crates/ra_analysis/src/descriptors/module/mod.rs | 8 | ||||
-rw-r--r-- | crates/ra_analysis/src/descriptors/module/scope.rs | 128 | ||||
-rw-r--r-- | crates/ra_analysis/src/syntax_ptr.rs | 45 | ||||
-rw-r--r-- | crates/ra_editor/src/completion.rs | 2 | ||||
-rw-r--r-- | crates/ra_editor/src/scope/mod_scope.rs | 6 |
8 files changed, 223 insertions, 22 deletions
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index 0a2f99575..0141d132e 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs | |||
@@ -1,40 +1,51 @@ | |||
1 | use ra_editor::{CompletionItem, find_node_at_offset, complete_module_items}; | 1 | use ra_editor::{CompletionItem, find_node_at_offset}; |
2 | use ra_syntax::{ | 2 | use ra_syntax::{ |
3 | AtomEdit, File, TextUnit, AstNode, | 3 | AtomEdit, File, TextUnit, AstNode, |
4 | ast::{self, ModuleItemOwner}, | 4 | ast::{self, ModuleItemOwner, AstChildren}, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use crate::{ | 7 | use crate::{ |
8 | FileId, Cancelable, | 8 | FileId, Cancelable, |
9 | input::FilesDatabase, | 9 | input::FilesDatabase, |
10 | db::{self, SyntaxDatabase}, | 10 | db::{self, SyntaxDatabase}, |
11 | descriptors::module::{ModulesDatabase, ModuleTree, ModuleId}, | 11 | descriptors::module::{ModulesDatabase, ModuleTree, ModuleId, scope::ModuleScope}, |
12 | }; | 12 | }; |
13 | 13 | ||
14 | pub(crate) fn resolve_based_completion(db: &db::RootDatabase, file_id: FileId, offset: TextUnit) -> Cancelable<Option<Vec<CompletionItem>>> { | 14 | pub(crate) fn resolve_based_completion(db: &db::RootDatabase, file_id: FileId, offset: TextUnit) -> Cancelable<Option<Vec<CompletionItem>>> { |
15 | let source_root_id = db.file_source_root(file_id); | 15 | let source_root_id = db.file_source_root(file_id); |
16 | let file = db.file_syntax(file_id); | 16 | let file = db.file_syntax(file_id); |
17 | let module_tree = db.module_tree(source_root_id)?; | 17 | let module_tree = db.module_tree(source_root_id)?; |
18 | let module_id = match module_tree.any_module_for_file(file_id) { | ||
19 | None => return Ok(None), | ||
20 | Some(it) => it, | ||
21 | }; | ||
18 | let file = { | 22 | let file = { |
19 | let edit = AtomEdit::insert(offset, "intellijRulezz".to_string()); | 23 | let edit = AtomEdit::insert(offset, "intellijRulezz".to_string()); |
20 | file.reparse(&edit) | 24 | file.reparse(&edit) |
21 | }; | 25 | }; |
22 | let target_file = match find_target_module(&module_tree, file_id, &file, offset) { | 26 | let target_module_id = match find_target_module(&module_tree, module_id, &file, offset) { |
23 | None => return Ok(None), | 27 | None => return Ok(None), |
24 | Some(target_module) => { | 28 | Some(it) => it, |
25 | let file_id = target_module.file_id(&module_tree); | ||
26 | db.file_syntax(file_id) | ||
27 | } | ||
28 | }; | 29 | }; |
29 | let mut res = Vec::new(); | 30 | let module_scope = db.module_scope(source_root_id, target_module_id)?; |
30 | complete_module_items(target_file.ast().items(), None, &mut res); | 31 | let res: Vec<_> = module_scope |
32 | .entries() | ||
33 | .iter() | ||
34 | .map(|entry| CompletionItem { | ||
35 | label: entry.name().to_string(), | ||
36 | lookup: None, | ||
37 | snippet: None, | ||
38 | }) | ||
39 | .collect(); | ||
31 | Ok(Some(res)) | 40 | Ok(Some(res)) |
32 | } | 41 | } |
33 | 42 | ||
34 | pub(crate) fn find_target_module(module_tree: &ModuleTree, file_id: FileId, file: &File, offset: TextUnit) -> Option<ModuleId> { | 43 | |
44 | |||
45 | pub(crate) fn find_target_module(module_tree: &ModuleTree, module_id: ModuleId, file: &File, offset: TextUnit) -> Option<ModuleId> { | ||
35 | let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), offset)?; | 46 | let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), offset)?; |
36 | let mut crate_path = crate_path(name_ref)?; | 47 | let mut crate_path = crate_path(name_ref)?; |
37 | let module_id = module_tree.any_module_for_file(file_id)?; | 48 | |
38 | crate_path.pop(); | 49 | crate_path.pop(); |
39 | let mut target_module = module_id.root(&module_tree); | 50 | let mut target_module = module_id.root(&module_tree); |
40 | for name in crate_path { | 51 | for name in crate_path { |
diff --git a/crates/ra_analysis/src/db.rs b/crates/ra_analysis/src/db.rs index 3ca14af79..e7a5d5e2f 100644 --- a/crates/ra_analysis/src/db.rs +++ b/crates/ra_analysis/src/db.rs | |||
@@ -9,8 +9,9 @@ use salsa; | |||
9 | use crate::{ | 9 | use crate::{ |
10 | db, | 10 | db, |
11 | Cancelable, Canceled, | 11 | Cancelable, Canceled, |
12 | descriptors::module::{SubmodulesQuery, ModuleTreeQuery, ModulesDatabase}, | 12 | descriptors::module::{SubmodulesQuery, ModuleTreeQuery, ModulesDatabase, ModuleScopeQuery}, |
13 | symbol_index::SymbolIndex, | 13 | symbol_index::SymbolIndex, |
14 | syntax_ptr::{SyntaxPtrDatabase, ResolveSyntaxPtrQuery}, | ||
14 | FileId, | 15 | FileId, |
15 | }; | 16 | }; |
16 | 17 | ||
@@ -65,6 +66,10 @@ salsa::database_storage! { | |||
65 | impl ModulesDatabase { | 66 | impl ModulesDatabase { |
66 | fn module_tree() for ModuleTreeQuery; | 67 | fn module_tree() for ModuleTreeQuery; |
67 | fn module_descriptor() for SubmodulesQuery; | 68 | fn module_descriptor() for SubmodulesQuery; |
69 | fn module_scope() for ModuleScopeQuery; | ||
70 | } | ||
71 | impl SyntaxPtrDatabase { | ||
72 | fn resolve_syntax_ptr() for ResolveSyntaxPtrQuery; | ||
68 | } | 73 | } |
69 | } | 74 | } |
70 | } | 75 | } |
diff --git a/crates/ra_analysis/src/descriptors/module/imp.rs b/crates/ra_analysis/src/descriptors/module/imp.rs index aecf6e29a..5fdaad137 100644 --- a/crates/ra_analysis/src/descriptors/module/imp.rs +++ b/crates/ra_analysis/src/descriptors/module/imp.rs | |||
@@ -13,7 +13,7 @@ use crate::{ | |||
13 | }; | 13 | }; |
14 | 14 | ||
15 | use super::{ | 15 | use super::{ |
16 | ModuleData, ModuleTree, ModuleId, LinkId, LinkData, Problem, ModulesDatabase | 16 | ModuleData, ModuleTree, ModuleId, LinkId, LinkData, Problem, ModulesDatabase, ModuleScope |
17 | }; | 17 | }; |
18 | 18 | ||
19 | 19 | ||
@@ -35,6 +35,18 @@ pub(super) fn modules(root: ast::Root<'_>) -> impl Iterator<Item = (SmolStr, ast | |||
35 | }) | 35 | }) |
36 | } | 36 | } |
37 | 37 | ||
38 | pub(super) fn module_scope( | ||
39 | db: &impl ModulesDatabase, | ||
40 | source_root_id: SourceRootId, | ||
41 | module_id: ModuleId, | ||
42 | ) -> Cancelable<Arc<ModuleScope>> { | ||
43 | let tree = db.module_tree(source_root_id)?; | ||
44 | let file_id = module_id.file_id(&tree); | ||
45 | let syntax = db.file_syntax(file_id); | ||
46 | let res = ModuleScope::new(&syntax); | ||
47 | Ok(Arc::new(res)) | ||
48 | } | ||
49 | |||
38 | pub(super) fn module_tree( | 50 | pub(super) fn module_tree( |
39 | db: &impl ModulesDatabase, | 51 | db: &impl ModulesDatabase, |
40 | source_root: SourceRootId, | 52 | source_root: SourceRootId, |
diff --git a/crates/ra_analysis/src/descriptors/module/mod.rs b/crates/ra_analysis/src/descriptors/module/mod.rs index 8968c4afd..9e5d73f94 100644 --- a/crates/ra_analysis/src/descriptors/module/mod.rs +++ b/crates/ra_analysis/src/descriptors/module/mod.rs | |||
@@ -1,4 +1,5 @@ | |||
1 | mod imp; | 1 | mod imp; |
2 | pub(crate) mod scope; | ||
2 | 3 | ||
3 | use std::sync::Arc; | 4 | use std::sync::Arc; |
4 | 5 | ||
@@ -11,6 +12,8 @@ use crate::{ | |||
11 | input::SourceRootId, | 12 | input::SourceRootId, |
12 | }; | 13 | }; |
13 | 14 | ||
15 | pub(crate) use self::scope::ModuleScope; | ||
16 | |||
14 | salsa::query_group! { | 17 | salsa::query_group! { |
15 | pub(crate) trait ModulesDatabase: SyntaxDatabase { | 18 | pub(crate) trait ModulesDatabase: SyntaxDatabase { |
16 | fn module_tree(source_root_id: SourceRootId) -> Cancelable<Arc<ModuleTree>> { | 19 | fn module_tree(source_root_id: SourceRootId) -> Cancelable<Arc<ModuleTree>> { |
@@ -21,6 +24,10 @@ salsa::query_group! { | |||
21 | type SubmodulesQuery; | 24 | type SubmodulesQuery; |
22 | use fn imp::submodules; | 25 | use fn imp::submodules; |
23 | } | 26 | } |
27 | fn module_scope(source_root_id: SourceRootId, module_id: ModuleId) -> Cancelable<Arc<ModuleScope>> { | ||
28 | type ModuleScopeQuery; | ||
29 | use fn imp::module_scope; | ||
30 | } | ||
24 | } | 31 | } |
25 | } | 32 | } |
26 | 33 | ||
@@ -78,6 +85,7 @@ impl ModuleId { | |||
78 | while let Some(next) = curr.parent(tree) { | 85 | while let Some(next) = curr.parent(tree) { |
79 | curr = next; | 86 | curr = next; |
80 | i += 1; | 87 | i += 1; |
88 | // simplistic cycle detection | ||
81 | if i > 100 { | 89 | if i > 100 { |
82 | return self; | 90 | return self; |
83 | } | 91 | } |
diff --git a/crates/ra_analysis/src/descriptors/module/scope.rs b/crates/ra_analysis/src/descriptors/module/scope.rs new file mode 100644 index 000000000..da58ddce0 --- /dev/null +++ b/crates/ra_analysis/src/descriptors/module/scope.rs | |||
@@ -0,0 +1,128 @@ | |||
1 | //! Backend for module-level scope resolution & completion | ||
2 | |||
3 | |||
4 | use ra_syntax::{ | ||
5 | ast::{self, AstChildren, ModuleItemOwner}, | ||
6 | File, AstNode, SmolStr, SyntaxNode, SyntaxNodeRef, | ||
7 | }; | ||
8 | |||
9 | use crate::syntax_ptr::LocalSyntaxPtr; | ||
10 | |||
11 | /// `ModuleScope` contains all named items declared in the scope. | ||
12 | #[derive(Debug, PartialEq, Eq)] | ||
13 | pub(crate) struct ModuleScope { | ||
14 | entries: Vec<Entry>, | ||
15 | } | ||
16 | |||
17 | /// `Entry` is a single named declaration iside a module. | ||
18 | #[derive(Debug, PartialEq, Eq)] | ||
19 | pub(crate) struct Entry { | ||
20 | ptr: LocalSyntaxPtr, | ||
21 | kind: EntryKind, | ||
22 | name: SmolStr, | ||
23 | } | ||
24 | |||
25 | #[derive(Debug, PartialEq, Eq)] | ||
26 | enum EntryKind { | ||
27 | Item, | ||
28 | Import, | ||
29 | } | ||
30 | |||
31 | impl ModuleScope { | ||
32 | pub fn new(file: &File) -> ModuleScope { | ||
33 | let mut entries = Vec::new(); | ||
34 | for item in file.ast().items() { | ||
35 | let entry = match item { | ||
36 | ast::ModuleItem::StructDef(item) => Entry::new(item), | ||
37 | ast::ModuleItem::EnumDef(item) => Entry::new(item), | ||
38 | ast::ModuleItem::FnDef(item) => Entry::new(item), | ||
39 | ast::ModuleItem::ConstDef(item) => Entry::new(item), | ||
40 | ast::ModuleItem::StaticDef(item) => Entry::new(item), | ||
41 | ast::ModuleItem::TraitDef(item) => Entry::new(item), | ||
42 | ast::ModuleItem::TypeDef(item) => Entry::new(item), | ||
43 | ast::ModuleItem::Module(item) => Entry::new(item), | ||
44 | ast::ModuleItem::UseItem(item) => { | ||
45 | if let Some(tree) = item.use_tree() { | ||
46 | collect_imports(tree, &mut entries); | ||
47 | } | ||
48 | continue; | ||
49 | } | ||
50 | ast::ModuleItem::ExternCrateItem(_) | ast::ModuleItem::ImplItem(_) => continue, | ||
51 | }; | ||
52 | entries.extend(entry) | ||
53 | } | ||
54 | |||
55 | ModuleScope { entries } | ||
56 | } | ||
57 | |||
58 | pub fn entries(&self) -> &[Entry] { | ||
59 | self.entries.as_slice() | ||
60 | } | ||
61 | } | ||
62 | |||
63 | impl Entry { | ||
64 | fn new<'a>(item: impl ast::NameOwner<'a>) -> Option<Entry> { | ||
65 | let name = item.name()?; | ||
66 | Some(Entry { | ||
67 | name: name.text(), | ||
68 | ptr: LocalSyntaxPtr::new(name.syntax()), | ||
69 | kind: EntryKind::Item, | ||
70 | }) | ||
71 | } | ||
72 | fn new_import(path: ast::Path) -> Option<Entry> { | ||
73 | let name_ref = path.segment()?.name_ref()?; | ||
74 | Some(Entry { | ||
75 | name: name_ref.text(), | ||
76 | ptr: LocalSyntaxPtr::new(name_ref.syntax()), | ||
77 | kind: EntryKind::Import, | ||
78 | }) | ||
79 | } | ||
80 | pub fn name(&self) -> &SmolStr { | ||
81 | &self.name | ||
82 | } | ||
83 | pub fn ptr(&self) -> LocalSyntaxPtr { | ||
84 | self.ptr | ||
85 | } | ||
86 | } | ||
87 | |||
88 | fn collect_imports(tree: ast::UseTree, acc: &mut Vec<Entry>) { | ||
89 | if let Some(use_tree_list) = tree.use_tree_list() { | ||
90 | return use_tree_list | ||
91 | .use_trees() | ||
92 | .for_each(|it| collect_imports(it, acc)); | ||
93 | } | ||
94 | if let Some(path) = tree.path() { | ||
95 | acc.extend(Entry::new_import(path)); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | #[cfg(test)] | ||
100 | mod tests { | ||
101 | use super::*; | ||
102 | use ra_syntax::{ast::ModuleItemOwner, File}; | ||
103 | |||
104 | fn do_check(code: &str, expected: &[&str]) { | ||
105 | let file = File::parse(&code); | ||
106 | let scope = ModuleScope::new(&file); | ||
107 | let actual = scope.entries.iter().map(|it| it.name()).collect::<Vec<_>>(); | ||
108 | assert_eq!(expected, actual.as_slice()); | ||
109 | } | ||
110 | |||
111 | #[test] | ||
112 | fn test_module_scope() { | ||
113 | do_check( | ||
114 | " | ||
115 | struct Foo; | ||
116 | enum Bar {} | ||
117 | mod baz {} | ||
118 | fn quux() {} | ||
119 | use x::{ | ||
120 | y::z, | ||
121 | t, | ||
122 | }; | ||
123 | type T = (); | ||
124 | ", | ||
125 | &["Foo", "Bar", "baz", "quux", "z", "t", "T"], | ||
126 | ) | ||
127 | } | ||
128 | } | ||
diff --git a/crates/ra_analysis/src/syntax_ptr.rs b/crates/ra_analysis/src/syntax_ptr.rs index 863ad2672..adbff4806 100644 --- a/crates/ra_analysis/src/syntax_ptr.rs +++ b/crates/ra_analysis/src/syntax_ptr.rs | |||
@@ -1,3 +1,5 @@ | |||
1 | use std::marker::PhantomData; | ||
2 | |||
1 | use ra_syntax::{ | 3 | use ra_syntax::{ |
2 | File, TextRange, SyntaxKind, SyntaxNode, SyntaxNodeRef, | 4 | File, TextRange, SyntaxKind, SyntaxNode, SyntaxNodeRef, |
3 | ast::{self, AstNode}, | 5 | ast::{self, AstNode}, |
@@ -6,10 +8,24 @@ use ra_syntax::{ | |||
6 | use crate::FileId; | 8 | use crate::FileId; |
7 | use crate::db::SyntaxDatabase; | 9 | use crate::db::SyntaxDatabase; |
8 | 10 | ||
11 | salsa::query_group! { | ||
12 | pub(crate) trait SyntaxPtrDatabase: SyntaxDatabase { | ||
13 | fn resolve_syntax_ptr(ptr: SyntaxPtr) -> SyntaxNode { | ||
14 | type ResolveSyntaxPtrQuery; | ||
15 | storage volatile; | ||
16 | } | ||
17 | } | ||
18 | } | ||
19 | |||
20 | fn resolve_syntax_ptr(db: &impl SyntaxDatabase, ptr: SyntaxPtr) -> SyntaxNode { | ||
21 | let syntax = db.file_syntax(ptr.file_id); | ||
22 | ptr.local.resolve(&syntax) | ||
23 | } | ||
24 | |||
9 | /// SyntaxPtr is a cheap `Copy` id which identifies a particular syntax node, | 25 | /// SyntaxPtr is a cheap `Copy` id which identifies a particular syntax node, |
10 | /// without retainig syntax tree in memory. You need to explicitelly `resovle` | 26 | /// without retainig syntax tree in memory. You need to explicitelly `resovle` |
11 | /// `SyntaxPtr` to get a `SyntaxNode` | 27 | /// `SyntaxPtr` to get a `SyntaxNode` |
12 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 28 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
13 | pub(crate) struct SyntaxPtr { | 29 | pub(crate) struct SyntaxPtr { |
14 | file_id: FileId, | 30 | file_id: FileId, |
15 | local: LocalSyntaxPtr, | 31 | local: LocalSyntaxPtr, |
@@ -20,30 +36,43 @@ impl SyntaxPtr { | |||
20 | let local = LocalSyntaxPtr::new(node); | 36 | let local = LocalSyntaxPtr::new(node); |
21 | SyntaxPtr { file_id, local } | 37 | SyntaxPtr { file_id, local } |
22 | } | 38 | } |
39 | } | ||
40 | |||
41 | struct OwnedAst<T> { | ||
42 | syntax: SyntaxNode, | ||
43 | phantom: PhantomData<T>, | ||
44 | } | ||
45 | |||
46 | trait ToAst { | ||
47 | type Ast; | ||
48 | fn to_ast(self) -> Self::Ast; | ||
49 | } | ||
23 | 50 | ||
24 | pub(crate) fn resolve(self, db: &impl SyntaxDatabase) -> SyntaxNode { | 51 | impl<'a> ToAst for &'a OwnedAst<ast::FnDef<'static>> { |
25 | let syntax = db.file_syntax(self.file_id); | 52 | type Ast = ast::FnDef<'a>; |
26 | self.local.resolve(&syntax) | 53 | fn to_ast(self) -> ast::FnDef<'a> { |
54 | ast::FnDef::cast(self.syntax.borrowed()) | ||
55 | .unwrap() | ||
27 | } | 56 | } |
28 | } | 57 | } |
29 | 58 | ||
30 | 59 | ||
31 | /// A pionter to a syntax node inside a file. | 60 | /// A pionter to a syntax node inside a file. |
32 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 61 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
33 | struct LocalSyntaxPtr { | 62 | pub(crate) struct LocalSyntaxPtr { |
34 | range: TextRange, | 63 | range: TextRange, |
35 | kind: SyntaxKind, | 64 | kind: SyntaxKind, |
36 | } | 65 | } |
37 | 66 | ||
38 | impl LocalSyntaxPtr { | 67 | impl LocalSyntaxPtr { |
39 | fn new(node: SyntaxNodeRef) -> LocalSyntaxPtr { | 68 | pub(crate) fn new(node: SyntaxNodeRef) -> LocalSyntaxPtr { |
40 | LocalSyntaxPtr { | 69 | LocalSyntaxPtr { |
41 | range: node.range(), | 70 | range: node.range(), |
42 | kind: node.kind(), | 71 | kind: node.kind(), |
43 | } | 72 | } |
44 | } | 73 | } |
45 | 74 | ||
46 | fn resolve(self, file: &File) -> SyntaxNode { | 75 | pub(crate) fn resolve(self, file: &File) -> SyntaxNode { |
47 | let mut curr = file.syntax(); | 76 | let mut curr = file.syntax(); |
48 | loop { | 77 | loop { |
49 | if curr.range() == self.range && curr.kind() == self.kind { | 78 | if curr.range() == self.range && curr.kind() == self.kind { |
diff --git a/crates/ra_editor/src/completion.rs b/crates/ra_editor/src/completion.rs index a0b168bc6..20c8546a4 100644 --- a/crates/ra_editor/src/completion.rs +++ b/crates/ra_editor/src/completion.rs | |||
@@ -1,3 +1,5 @@ | |||
1 | /// FIXME: move completion from ra_editor to ra_analysis | ||
2 | |||
1 | use rustc_hash::{FxHashMap, FxHashSet}; | 3 | use rustc_hash::{FxHashMap, FxHashSet}; |
2 | 4 | ||
3 | use ra_syntax::{ | 5 | use ra_syntax::{ |
diff --git a/crates/ra_editor/src/scope/mod_scope.rs b/crates/ra_editor/src/scope/mod_scope.rs index 956ee0e2f..818749a12 100644 --- a/crates/ra_editor/src/scope/mod_scope.rs +++ b/crates/ra_editor/src/scope/mod_scope.rs | |||
@@ -1,3 +1,9 @@ | |||
1 | /// FIXME: this is now moved to ra_analysis::descriptors::module::scope. | ||
2 | /// | ||
3 | /// Current copy will be deleted as soon as we move the rest of the completion | ||
4 | /// to the analyezer. | ||
5 | |||
6 | |||
1 | use ra_syntax::{ | 7 | use ra_syntax::{ |
2 | ast::{self, AstChildren}, | 8 | ast::{self, AstChildren}, |
3 | AstNode, SmolStr, SyntaxNode, SyntaxNodeRef, | 9 | AstNode, SmolStr, SyntaxNode, SyntaxNodeRef, |