aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide_api/src/completion/complete_fn_param.rs16
-rw-r--r--crates/ra_ide_api/src/completion/complete_keyword.rs18
-rw-r--r--crates/ra_ide_api/src/display/navigation_target.rs59
-rw-r--r--crates/ra_ide_api/src/display/structure.rs116
-rw-r--r--crates/ra_ide_api/src/goto_definition.rs185
-rw-r--r--crates/ra_ide_api/src/hover.rs77
-rw-r--r--crates/ra_ide_api/src/inlay_hints.rs104
-rw-r--r--crates/ra_ide_api/src/symbol_index.rs26
-rw-r--r--crates/ra_syntax/src/lib.rs12
-rw-r--r--crates/ra_syntax/src/validation.rs18
10 files changed, 334 insertions, 297 deletions
diff --git a/crates/ra_ide_api/src/completion/complete_fn_param.rs b/crates/ra_ide_api/src/completion/complete_fn_param.rs
index 844a63f6c..3e936e3ec 100644
--- a/crates/ra_ide_api/src/completion/complete_fn_param.rs
+++ b/crates/ra_ide_api/src/completion/complete_fn_param.rs
@@ -1,9 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use ra_syntax::{ 3use ra_syntax::{ast, match_ast, AstNode};
4 algo::visit::{visitor_ctx, VisitorCtx},
5 ast, AstNode,
6};
7use rustc_hash::FxHashMap; 4use rustc_hash::FxHashMap;
8 5
9use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions}; 6use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions};
@@ -19,10 +16,13 @@ pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext)
19 16
20 let mut params = FxHashMap::default(); 17 let mut params = FxHashMap::default();
21 for node in ctx.token.parent().ancestors() { 18 for node in ctx.token.parent().ancestors() {
22 let _ = visitor_ctx(&mut params) 19 match_ast! {
23 .visit::<ast::SourceFile, _>(process) 20 match node {
24 .visit::<ast::ItemList, _>(process) 21 ast::SourceFile(it) => { process(it, &mut params) },
25 .accept(&node); 22 ast::ItemList(it) => { process(it, &mut params) },
23 _ => (),
24 }
25 }
26 } 26 }
27 params 27 params
28 .into_iter() 28 .into_iter()
diff --git a/crates/ra_ide_api/src/completion/complete_keyword.rs b/crates/ra_ide_api/src/completion/complete_keyword.rs
index 3f121d45c..48c688a08 100644
--- a/crates/ra_ide_api/src/completion/complete_keyword.rs
+++ b/crates/ra_ide_api/src/completion/complete_keyword.rs
@@ -1,9 +1,8 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use ra_syntax::{ 3use ra_syntax::{
4 algo::visit::{visitor, Visitor},
5 ast::{self, LoopBodyOwner}, 4 ast::{self, LoopBodyOwner},
6 AstNode, 5 match_ast, AstNode,
7 SyntaxKind::*, 6 SyntaxKind::*,
8 SyntaxToken, 7 SyntaxToken,
9}; 8};
@@ -84,12 +83,15 @@ fn is_in_loop_body(leaf: &SyntaxToken) -> bool {
84 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { 83 if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
85 break; 84 break;
86 } 85 }
87 let loop_body = visitor() 86 let loop_body = match_ast! {
88 .visit::<ast::ForExpr, _>(|it| it.loop_body()) 87 match node {
89 .visit::<ast::WhileExpr, _>(|it| it.loop_body()) 88 ast::ForExpr(it) => { it.loop_body() },
90 .visit::<ast::LoopExpr, _>(|it| it.loop_body()) 89 ast::WhileExpr(it) => { it.loop_body() },
91 .accept(&node); 90 ast::LoopExpr(it) => { it.loop_body() },
92 if let Some(Some(body)) = loop_body { 91 _ => None,
92 }
93 };
94 if let Some(body) = loop_body {
93 if leaf.text_range().is_subrange(&body.syntax().text_range()) { 95 if leaf.text_range().is_subrange(&body.syntax().text_range()) {
94 return true; 96 return true;
95 } 97 }
diff --git a/crates/ra_ide_api/src/display/navigation_target.rs b/crates/ra_ide_api/src/display/navigation_target.rs
index 60ae802c0..d0b1a8a2a 100644
--- a/crates/ra_ide_api/src/display/navigation_target.rs
+++ b/crates/ra_ide_api/src/display/navigation_target.rs
@@ -3,9 +3,8 @@
3use hir::{AssocItem, FieldSource, HasSource, ModuleSource}; 3use hir::{AssocItem, FieldSource, HasSource, ModuleSource};
4use ra_db::{FileId, SourceDatabase}; 4use ra_db::{FileId, SourceDatabase};
5use ra_syntax::{ 5use ra_syntax::{
6 algo::visit::{visitor, Visitor},
7 ast::{self, DocCommentsOwner}, 6 ast::{self, DocCommentsOwner},
8 AstNode, AstPtr, SmolStr, 7 match_ast, AstNode, AstPtr, SmolStr,
9 SyntaxKind::{self, NAME}, 8 SyntaxKind::{self, NAME},
10 SyntaxNode, TextRange, 9 SyntaxNode, TextRange,
11}; 10};
@@ -308,19 +307,22 @@ pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option
308 let parse = db.parse(symbol.file_id); 307 let parse = db.parse(symbol.file_id);
309 let node = symbol.ptr.to_node(parse.tree().syntax()); 308 let node = symbol.ptr.to_node(parse.tree().syntax());
310 309
311 visitor() 310 match_ast! {
312 .visit(|it: ast::FnDef| it.doc_comment_text()) 311 match node {
313 .visit(|it: ast::StructDef| it.doc_comment_text()) 312 ast::FnDef(it) => { it.doc_comment_text() },
314 .visit(|it: ast::EnumDef| it.doc_comment_text()) 313 ast::StructDef(it) => { it.doc_comment_text() },
315 .visit(|it: ast::TraitDef| it.doc_comment_text()) 314 ast::EnumDef(it) => { it.doc_comment_text() },
316 .visit(|it: ast::Module| it.doc_comment_text()) 315 ast::TraitDef(it) => { it.doc_comment_text() },
317 .visit(|it: ast::TypeAliasDef| it.doc_comment_text()) 316 ast::Module(it) => { it.doc_comment_text() },
318 .visit(|it: ast::ConstDef| it.doc_comment_text()) 317 ast::TypeAliasDef(it) => { it.doc_comment_text() },
319 .visit(|it: ast::StaticDef| it.doc_comment_text()) 318 ast::ConstDef(it) => { it.doc_comment_text() },
320 .visit(|it: ast::RecordFieldDef| it.doc_comment_text()) 319 ast::StaticDef(it) => { it.doc_comment_text() },
321 .visit(|it: ast::EnumVariant| it.doc_comment_text()) 320 ast::RecordFieldDef(it) => { it.doc_comment_text() },
322 .visit(|it: ast::MacroCall| it.doc_comment_text()) 321 ast::EnumVariant(it) => { it.doc_comment_text() },
323 .accept(&node)? 322 ast::MacroCall(it) => { it.doc_comment_text() },
323 _ => None,
324 }
325 }
324} 326}
325 327
326/// Get a description of a symbol. 328/// Get a description of a symbol.
@@ -330,16 +332,19 @@ pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) ->
330 let parse = db.parse(symbol.file_id); 332 let parse = db.parse(symbol.file_id);
331 let node = symbol.ptr.to_node(parse.tree().syntax()); 333 let node = symbol.ptr.to_node(parse.tree().syntax());
332 334
333 visitor() 335 match_ast! {
334 .visit(|node: ast::FnDef| node.short_label()) 336 match node {
335 .visit(|node: ast::StructDef| node.short_label()) 337 ast::FnDef(it) => { it.short_label() },
336 .visit(|node: ast::EnumDef| node.short_label()) 338 ast::StructDef(it) => { it.short_label() },
337 .visit(|node: ast::TraitDef| node.short_label()) 339 ast::EnumDef(it) => { it.short_label() },
338 .visit(|node: ast::Module| node.short_label()) 340 ast::TraitDef(it) => { it.short_label() },
339 .visit(|node: ast::TypeAliasDef| node.short_label()) 341 ast::Module(it) => { it.short_label() },
340 .visit(|node: ast::ConstDef| node.short_label()) 342 ast::TypeAliasDef(it) => { it.short_label() },
341 .visit(|node: ast::StaticDef| node.short_label()) 343 ast::ConstDef(it) => { it.short_label() },
342 .visit(|node: ast::RecordFieldDef| node.short_label()) 344 ast::StaticDef(it) => { it.short_label() },
343 .visit(|node: ast::EnumVariant| node.short_label()) 345 ast::RecordFieldDef(it) => { it.short_label() },
344 .accept(&node)? 346 ast::EnumVariant(it) => { it.short_label() },
347 _ => None,
348 }
349 }
345} 350}
diff --git a/crates/ra_ide_api/src/display/structure.rs b/crates/ra_ide_api/src/display/structure.rs
index 8815df747..ddd8b7b20 100644
--- a/crates/ra_ide_api/src/display/structure.rs
+++ b/crates/ra_ide_api/src/display/structure.rs
@@ -3,9 +3,8 @@
3use crate::TextRange; 3use crate::TextRange;
4 4
5use ra_syntax::{ 5use ra_syntax::{
6 algo::visit::{visitor, Visitor},
7 ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, 6 ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner},
8 AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent, 7 match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent,
9}; 8};
10 9
11#[derive(Debug, Clone)] 10#[derive(Debug, Clone)]
@@ -101,63 +100,66 @@ fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
101 }) 100 })
102 } 101 }
103 102
104 visitor() 103 match_ast! {
105 .visit(|fn_def: ast::FnDef| { 104 match node {
106 let mut detail = String::from("fn"); 105 ast::FnDef(it) => {
107 if let Some(type_param_list) = fn_def.type_param_list() { 106 let mut detail = String::from("fn");
108 collapse_ws(type_param_list.syntax(), &mut detail); 107 if let Some(type_param_list) = it.type_param_list() {
109 } 108 collapse_ws(type_param_list.syntax(), &mut detail);
110 if let Some(param_list) = fn_def.param_list() { 109 }
111 collapse_ws(param_list.syntax(), &mut detail); 110 if let Some(param_list) = it.param_list() {
112 } 111 collapse_ws(param_list.syntax(), &mut detail);
113 if let Some(ret_type) = fn_def.ret_type() { 112 }
114 detail.push_str(" "); 113 if let Some(ret_type) = it.ret_type() {
115 collapse_ws(ret_type.syntax(), &mut detail); 114 detail.push_str(" ");
116 } 115 collapse_ws(ret_type.syntax(), &mut detail);
117
118 decl_with_detail(fn_def, Some(detail))
119 })
120 .visit(decl::<ast::StructDef>)
121 .visit(decl::<ast::EnumDef>)
122 .visit(decl::<ast::EnumVariant>)
123 .visit(decl::<ast::TraitDef>)
124 .visit(decl::<ast::Module>)
125 .visit(|td: ast::TypeAliasDef| {
126 let ty = td.type_ref();
127 decl_with_type_ref(td, ty)
128 })
129 .visit(decl_with_ascription::<ast::RecordFieldDef>)
130 .visit(decl_with_ascription::<ast::ConstDef>)
131 .visit(decl_with_ascription::<ast::StaticDef>)
132 .visit(|im: ast::ImplBlock| {
133 let target_type = im.target_type()?;
134 let target_trait = im.target_trait();
135 let label = match target_trait {
136 None => format!("impl {}", target_type.syntax().text()),
137 Some(t) => {
138 format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),)
139 } 116 }
140 };
141 117
142 let node = StructureNode { 118 decl_with_detail(it, Some(detail))
143 parent: None, 119 },
144 label, 120 ast::StructDef(it) => { decl(it) },
145 navigation_range: target_type.syntax().text_range(), 121 ast::EnumDef(it) => { decl(it) },
146 node_range: im.syntax().text_range(), 122 ast::EnumVariant(it) => { decl(it) },
147 kind: im.syntax().kind(), 123 ast::TraitDef(it) => { decl(it) },
148 detail: None, 124 ast::Module(it) => { decl(it) },
149 deprecated: false, 125 ast::TypeAliasDef(it) => {
150 }; 126 let ty = it.type_ref();
151 Some(node) 127 decl_with_type_ref(it, ty)
152 }) 128 },
153 .visit(|mc: ast::MacroCall| { 129 ast::RecordFieldDef(it) => { decl_with_ascription(it) },
154 let first_token = mc.syntax().first_token().unwrap(); 130 ast::ConstDef(it) => { decl_with_ascription(it) },
155 if first_token.text().as_str() != "macro_rules" { 131 ast::StaticDef(it) => { decl_with_ascription(it) },
156 return None; 132 ast::ImplBlock(it) => {
157 } 133 let target_type = it.target_type()?;
158 decl(mc) 134 let target_trait = it.target_trait();
159 }) 135 let label = match target_trait {
160 .accept(&node)? 136 None => format!("impl {}", target_type.syntax().text()),
137 Some(t) => {
138 format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),)
139 }
140 };
141
142 let node = StructureNode {
143 parent: None,
144 label,
145 navigation_range: target_type.syntax().text_range(),
146 node_range: it.syntax().text_range(),
147 kind: it.syntax().kind(),
148 detail: None,
149 deprecated: false,
150 };
151 Some(node)
152 },
153 ast::MacroCall(it) => {
154 let first_token = it.syntax().first_token().unwrap();
155 if first_token.text().as_str() != "macro_rules" {
156 return None;
157 }
158 decl(it)
159 },
160 _ => None,
161 }
162 }
161} 163}
162 164
163#[cfg(test)] 165#[cfg(test)]
diff --git a/crates/ra_ide_api/src/goto_definition.rs b/crates/ra_ide_api/src/goto_definition.rs
index 567d4a674..41a88314f 100644
--- a/crates/ra_ide_api/src/goto_definition.rs
+++ b/crates/ra_ide_api/src/goto_definition.rs
@@ -2,12 +2,9 @@
2 2
3use ra_db::{FileId, SourceDatabase}; 3use ra_db::{FileId, SourceDatabase};
4use ra_syntax::{ 4use ra_syntax::{
5 algo::{ 5 algo::find_node_at_offset,
6 find_node_at_offset,
7 visit::{visitor, Visitor},
8 },
9 ast::{self, DocCommentsOwner}, 6 ast::{self, DocCommentsOwner},
10 AstNode, SyntaxNode, 7 match_ast, AstNode, SyntaxNode,
11}; 8};
12 9
13use crate::{ 10use crate::{
@@ -114,91 +111,99 @@ pub(crate) fn name_definition(
114} 111}
115 112
116fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget> { 113fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget> {
117 visitor() 114 match_ast! {
118 .visit(|node: ast::StructDef| { 115 match node {
119 NavigationTarget::from_named( 116 ast::StructDef(it) => {
120 file_id, 117 Some(NavigationTarget::from_named(
121 &node, 118 file_id,
122 node.doc_comment_text(), 119 &it,
123 node.short_label(), 120 it.doc_comment_text(),
124 ) 121 it.short_label(),
125 }) 122 ))
126 .visit(|node: ast::EnumDef| { 123 },
127 NavigationTarget::from_named( 124 ast::EnumDef(it) => {
128 file_id, 125 Some(NavigationTarget::from_named(
129 &node, 126 file_id,
130 node.doc_comment_text(), 127 &it,
131 node.short_label(), 128 it.doc_comment_text(),
132 ) 129 it.short_label(),
133 }) 130 ))
134 .visit(|node: ast::EnumVariant| { 131 },
135 NavigationTarget::from_named( 132 ast::EnumVariant(it) => {
136 file_id, 133 Some(NavigationTarget::from_named(
137 &node, 134 file_id,
138 node.doc_comment_text(), 135 &it,
139 node.short_label(), 136 it.doc_comment_text(),
140 ) 137 it.short_label(),
141 }) 138 ))
142 .visit(|node: ast::FnDef| { 139 },
143 NavigationTarget::from_named( 140 ast::FnDef(it) => {
144 file_id, 141 Some(NavigationTarget::from_named(
145 &node, 142 file_id,
146 node.doc_comment_text(), 143 &it,
147 node.short_label(), 144 it.doc_comment_text(),
148 ) 145 it.short_label(),
149 }) 146 ))
150 .visit(|node: ast::TypeAliasDef| { 147 },
151 NavigationTarget::from_named( 148 ast::TypeAliasDef(it) => {
152 file_id, 149 Some(NavigationTarget::from_named(
153 &node, 150 file_id,
154 node.doc_comment_text(), 151 &it,
155 node.short_label(), 152 it.doc_comment_text(),
156 ) 153 it.short_label(),
157 }) 154 ))
158 .visit(|node: ast::ConstDef| { 155 },
159 NavigationTarget::from_named( 156 ast::ConstDef(it) => {
160 file_id, 157 Some(NavigationTarget::from_named(
161 &node, 158 file_id,
162 node.doc_comment_text(), 159 &it,
163 node.short_label(), 160 it.doc_comment_text(),
164 ) 161 it.short_label(),
165 }) 162 ))
166 .visit(|node: ast::StaticDef| { 163 },
167 NavigationTarget::from_named( 164 ast::StaticDef(it) => {
168 file_id, 165 Some(NavigationTarget::from_named(
169 &node, 166 file_id,
170 node.doc_comment_text(), 167 &it,
171 node.short_label(), 168 it.doc_comment_text(),
172 ) 169 it.short_label(),
173 }) 170 ))
174 .visit(|node: ast::TraitDef| { 171 },
175 NavigationTarget::from_named( 172 ast::TraitDef(it) => {
176 file_id, 173 Some(NavigationTarget::from_named(
177 &node, 174 file_id,
178 node.doc_comment_text(), 175 &it,
179 node.short_label(), 176 it.doc_comment_text(),
180 ) 177 it.short_label(),
181 }) 178 ))
182 .visit(|node: ast::RecordFieldDef| { 179 },
183 NavigationTarget::from_named( 180 ast::RecordFieldDef(it) => {
184 file_id, 181 Some(NavigationTarget::from_named(
185 &node, 182 file_id,
186 node.doc_comment_text(), 183 &it,
187 node.short_label(), 184 it.doc_comment_text(),
188 ) 185 it.short_label(),
189 }) 186 ))
190 .visit(|node: ast::Module| { 187 },
191 NavigationTarget::from_named( 188 ast::Module(it) => {
192 file_id, 189 Some(NavigationTarget::from_named(
193 &node, 190 file_id,
194 node.doc_comment_text(), 191 &it,
195 node.short_label(), 192 it.doc_comment_text(),
196 ) 193 it.short_label(),
197 }) 194 ))
198 .visit(|node: ast::MacroCall| { 195 },
199 NavigationTarget::from_named(file_id, &node, node.doc_comment_text(), None) 196 ast::MacroCall(it) => {
200 }) 197 Some(NavigationTarget::from_named(
201 .accept(node) 198 file_id,
199 &it,
200 it.doc_comment_text(),
201 None,
202 ))
203 },
204 _ => None,
205 }
206 }
202} 207}
203 208
204#[cfg(test)] 209#[cfg(test)]
diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs
index 200b57679..24b161c5c 100644
--- a/crates/ra_ide_api/src/hover.rs
+++ b/crates/ra_ide_api/src/hover.rs
@@ -3,12 +3,9 @@
3use hir::{Adt, HasSource, HirDisplay}; 3use hir::{Adt, HasSource, HirDisplay};
4use ra_db::SourceDatabase; 4use ra_db::SourceDatabase;
5use ra_syntax::{ 5use ra_syntax::{
6 algo::{ 6 algo::{ancestors_at_offset, find_covering_element, find_node_at_offset},
7 ancestors_at_offset, find_covering_element, find_node_at_offset,
8 visit::{visitor, Visitor},
9 },
10 ast::{self, DocCommentsOwner}, 7 ast::{self, DocCommentsOwner},
11 AstNode, 8 match_ast, AstNode,
12}; 9};
13 10
14use crate::{ 11use crate::{
@@ -178,37 +175,45 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
178 } 175 }
179 } else if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) { 176 } else if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) {
180 if let Some(parent) = name.syntax().parent() { 177 if let Some(parent) = name.syntax().parent() {
181 let text = visitor() 178 let text = match_ast! {
182 .visit(|node: ast::StructDef| { 179 match parent {
183 hover_text(node.doc_comment_text(), node.short_label()) 180 ast::StructDef(it) => {
184 }) 181 hover_text(it.doc_comment_text(), it.short_label())
185 .visit(|node: ast::EnumDef| hover_text(node.doc_comment_text(), node.short_label())) 182 },
186 .visit(|node: ast::EnumVariant| { 183 ast::EnumDef(it) => {
187 hover_text(node.doc_comment_text(), node.short_label()) 184 hover_text(it.doc_comment_text(), it.short_label())
188 }) 185 },
189 .visit(|node: ast::FnDef| hover_text(node.doc_comment_text(), node.short_label())) 186 ast::EnumVariant(it) => {
190 .visit(|node: ast::TypeAliasDef| { 187 hover_text(it.doc_comment_text(), it.short_label())
191 hover_text(node.doc_comment_text(), node.short_label()) 188 },
192 }) 189 ast::FnDef(it) => {
193 .visit(|node: ast::ConstDef| { 190 hover_text(it.doc_comment_text(), it.short_label())
194 hover_text(node.doc_comment_text(), node.short_label()) 191 },
195 }) 192 ast::TypeAliasDef(it) => {
196 .visit(|node: ast::StaticDef| { 193 hover_text(it.doc_comment_text(), it.short_label())
197 hover_text(node.doc_comment_text(), node.short_label()) 194 },
198 }) 195 ast::ConstDef(it) => {
199 .visit(|node: ast::TraitDef| { 196 hover_text(it.doc_comment_text(), it.short_label())
200 hover_text(node.doc_comment_text(), node.short_label()) 197 },
201 }) 198 ast::StaticDef(it) => {
202 .visit(|node: ast::RecordFieldDef| { 199 hover_text(it.doc_comment_text(), it.short_label())
203 hover_text(node.doc_comment_text(), node.short_label()) 200 },
204 }) 201 ast::TraitDef(it) => {
205 .visit(|node: ast::Module| hover_text(node.doc_comment_text(), node.short_label())) 202 hover_text(it.doc_comment_text(), it.short_label())
206 .visit(|node: ast::MacroCall| hover_text(node.doc_comment_text(), None)) 203 },
207 .accept(&parent); 204 ast::RecordFieldDef(it) => {
208 205 hover_text(it.doc_comment_text(), it.short_label())
209 if let Some(text) = text { 206 },
210 res.extend(text); 207 ast::Module(it) => {
211 } 208 hover_text(it.doc_comment_text(), it.short_label())
209 },
210 ast::MacroCall(it) => {
211 hover_text(it.doc_comment_text(), None)
212 },
213 _ => None,
214 }
215 };
216 res.extend(text);
212 } 217 }
213 218
214 if !res.is_empty() && range.is_none() { 219 if !res.is_empty() && range.is_none() {
diff --git a/crates/ra_ide_api/src/inlay_hints.rs b/crates/ra_ide_api/src/inlay_hints.rs
index 9b45575f8..f1c0dc164 100644
--- a/crates/ra_ide_api/src/inlay_hints.rs
+++ b/crates/ra_ide_api/src/inlay_hints.rs
@@ -3,9 +3,8 @@
3use crate::{db::RootDatabase, FileId}; 3use crate::{db::RootDatabase, FileId};
4use hir::{HirDisplay, SourceAnalyzer, Ty}; 4use hir::{HirDisplay, SourceAnalyzer, Ty};
5use ra_syntax::{ 5use ra_syntax::{
6 algo::visit::{visitor, Visitor},
7 ast::{self, AstNode, TypeAscriptionOwner}, 6 ast::{self, AstNode, TypeAscriptionOwner},
8 SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange, 7 match_ast, SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange,
9}; 8};
10 9
11#[derive(Debug, PartialEq, Eq)] 10#[derive(Debug, PartialEq, Eq)]
@@ -33,55 +32,58 @@ fn get_inlay_hints(
33 file_id: FileId, 32 file_id: FileId,
34 node: &SyntaxNode, 33 node: &SyntaxNode,
35) -> Option<Vec<InlayHint>> { 34) -> Option<Vec<InlayHint>> {
36 visitor() 35 match_ast! {
37 .visit(|let_statement: ast::LetStmt| { 36 match node {
38 if let_statement.ascribed_type().is_some() { 37 ast::LetStmt(it) => {
39 return None; 38 if it.ascribed_type().is_some() {
40 } 39 return None;
41 let pat = let_statement.pat()?; 40 }
42 let analyzer = SourceAnalyzer::new(db, file_id, let_statement.syntax(), None); 41 let pat = it.pat()?;
43 Some(get_pat_type_hints(db, &analyzer, pat, false)) 42 let analyzer = SourceAnalyzer::new(db, file_id, it.syntax(), None);
44 }) 43 Some(get_pat_type_hints(db, &analyzer, pat, false))
45 .visit(|closure_parameter: ast::LambdaExpr| { 44 },
46 let analyzer = SourceAnalyzer::new(db, file_id, closure_parameter.syntax(), None); 45 ast::LambdaExpr(it) => {
47 closure_parameter.param_list().map(|param_list| { 46 let analyzer = SourceAnalyzer::new(db, file_id, it.syntax(), None);
48 param_list 47 it.param_list().map(|param_list| {
49 .params() 48 param_list
50 .filter(|closure_param| closure_param.ascribed_type().is_none()) 49 .params()
51 .filter_map(|closure_param| closure_param.pat()) 50 .filter(|closure_param| closure_param.ascribed_type().is_none())
52 .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, false)) 51 .filter_map(|closure_param| closure_param.pat())
53 .flatten() 52 .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, false))
54 .collect() 53 .flatten()
55 }) 54 .collect()
56 }) 55 })
57 .visit(|for_expression: ast::ForExpr| { 56 },
58 let pat = for_expression.pat()?; 57 ast::ForExpr(it) => {
59 let analyzer = SourceAnalyzer::new(db, file_id, for_expression.syntax(), None); 58 let pat = it.pat()?;
60 Some(get_pat_type_hints(db, &analyzer, pat, false)) 59 let analyzer = SourceAnalyzer::new(db, file_id, it.syntax(), None);
61 }) 60 Some(get_pat_type_hints(db, &analyzer, pat, false))
62 .visit(|if_expr: ast::IfExpr| { 61 },
63 let pat = if_expr.condition()?.pat()?; 62 ast::IfExpr(it) => {
64 let analyzer = SourceAnalyzer::new(db, file_id, if_expr.syntax(), None); 63 let pat = it.condition()?.pat()?;
65 Some(get_pat_type_hints(db, &analyzer, pat, true)) 64 let analyzer = SourceAnalyzer::new(db, file_id, it.syntax(), None);
66 }) 65 Some(get_pat_type_hints(db, &analyzer, pat, true))
67 .visit(|while_expr: ast::WhileExpr| { 66 },
68 let pat = while_expr.condition()?.pat()?; 67 ast::WhileExpr(it) => {
69 let analyzer = SourceAnalyzer::new(db, file_id, while_expr.syntax(), None); 68 let pat = it.condition()?.pat()?;
70 Some(get_pat_type_hints(db, &analyzer, pat, true)) 69 let analyzer = SourceAnalyzer::new(db, file_id, it.syntax(), None);
71 }) 70 Some(get_pat_type_hints(db, &analyzer, pat, true))
72 .visit(|match_arm_list: ast::MatchArmList| { 71 },
73 let analyzer = SourceAnalyzer::new(db, file_id, match_arm_list.syntax(), None); 72 ast::MatchArmList(it) => {
74 Some( 73 let analyzer = SourceAnalyzer::new(db, file_id, it.syntax(), None);
75 match_arm_list 74 Some(
76 .arms() 75 it
77 .map(|match_arm| match_arm.pats()) 76 .arms()
78 .flatten() 77 .map(|match_arm| match_arm.pats())
79 .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, true)) 78 .flatten()
80 .flatten() 79 .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, true))
81 .collect(), 80 .flatten()
82 ) 81 .collect(),
83 }) 82 )
84 .accept(&node)? 83 },
84 _ => None,
85 }
86 }
85} 87}
86 88
87fn get_pat_type_hints( 89fn get_pat_type_hints(
diff --git a/crates/ra_ide_api/src/symbol_index.rs b/crates/ra_ide_api/src/symbol_index.rs
index 02cdfbc60..797e9926f 100644
--- a/crates/ra_ide_api/src/symbol_index.rs
+++ b/crates/ra_ide_api/src/symbol_index.rs
@@ -32,9 +32,8 @@ use ra_db::{
32 SourceDatabase, SourceRootId, 32 SourceDatabase, SourceRootId,
33}; 33};
34use ra_syntax::{ 34use ra_syntax::{
35 algo::visit::{visitor, Visitor},
36 ast::{self, NameOwner}, 35 ast::{self, NameOwner},
37 AstNode, Parse, SmolStr, SourceFile, 36 match_ast, AstNode, Parse, SmolStr, SourceFile,
38 SyntaxKind::{self, *}, 37 SyntaxKind::{self, *},
39 SyntaxNode, SyntaxNodePtr, TextRange, WalkEvent, 38 SyntaxNode, SyntaxNodePtr, TextRange, WalkEvent,
40}; 39};
@@ -306,16 +305,19 @@ fn to_symbol(node: &SyntaxNode) -> Option<(SmolStr, SyntaxNodePtr, TextRange)> {
306 305
307 Some((name, ptr, name_range)) 306 Some((name, ptr, name_range))
308 } 307 }
309 visitor() 308 match_ast! {
310 .visit(decl::<ast::FnDef>) 309 match node {
311 .visit(decl::<ast::StructDef>) 310 ast::FnDef(it) => { decl(it) },
312 .visit(decl::<ast::EnumDef>) 311 ast::StructDef(it) => { decl(it) },
313 .visit(decl::<ast::TraitDef>) 312 ast::EnumDef(it) => { decl(it) },
314 .visit(decl::<ast::Module>) 313 ast::TraitDef(it) => { decl(it) },
315 .visit(decl::<ast::TypeAliasDef>) 314 ast::Module(it) => { decl(it) },
316 .visit(decl::<ast::ConstDef>) 315 ast::TypeAliasDef(it) => { decl(it) },
317 .visit(decl::<ast::StaticDef>) 316 ast::ConstDef(it) => { decl(it) },
318 .accept(node)? 317 ast::StaticDef(it) => { decl(it) },
318 _ => None,
319 }
320 }
319} 321}
320 322
321fn to_file_symbol(node: &SyntaxNode, file_id: FileId) -> Option<FileSymbol> { 323fn to_file_symbol(node: &SyntaxNode, file_id: FileId) -> Option<FileSymbol> {
diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs
index edb6076bb..09230ccb2 100644
--- a/crates/ra_syntax/src/lib.rs
+++ b/crates/ra_syntax/src/lib.rs
@@ -295,6 +295,7 @@ fn api_walkthrough() {
295 // 1. explicitly call getter methods on AST nodes. 295 // 1. explicitly call getter methods on AST nodes.
296 // 2. use descendants and `AstNode::cast`. 296 // 2. use descendants and `AstNode::cast`.
297 // 3. use descendants and the visitor. 297 // 3. use descendants and the visitor.
298 // 4. use descendants and `match_ast!`.
298 // 299 //
299 // Here's how the first one looks like: 300 // Here's how the first one looks like:
300 let exprs_cast: Vec<String> = file 301 let exprs_cast: Vec<String> = file
@@ -319,3 +320,14 @@ fn api_walkthrough() {
319 } 320 }
320 assert_eq!(exprs_cast, exprs_visit); 321 assert_eq!(exprs_cast, exprs_visit);
321} 322}
323
324#[macro_export]
325macro_rules! match_ast {
326 (match $node:ident {
327 $( ast::$ast:ident($it:ident) => $res:block, )*
328 _ => $catch_all:expr,
329 }) => {{
330 $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )*
331 { $catch_all }
332 }};
333}
diff --git a/crates/ra_syntax/src/validation.rs b/crates/ra_syntax/src/validation.rs
index 4f8935b2c..ab4f15908 100644
--- a/crates/ra_syntax/src/validation.rs
+++ b/crates/ra_syntax/src/validation.rs
@@ -5,8 +5,7 @@ mod block;
5use rustc_lexer::unescape; 5use rustc_lexer::unescape;
6 6
7use crate::{ 7use crate::{
8 algo::visit::{visitor_ctx, VisitorCtx}, 8 ast, match_ast, AstNode, SyntaxError, SyntaxErrorKind,
9 ast, AstNode, SyntaxError, SyntaxErrorKind,
10 SyntaxKind::{BYTE, BYTE_STRING, CHAR, INT_NUMBER, STRING}, 9 SyntaxKind::{BYTE, BYTE_STRING, CHAR, INT_NUMBER, STRING},
11 SyntaxNode, SyntaxToken, TextUnit, T, 10 SyntaxNode, SyntaxToken, TextUnit, T,
12}; 11};
@@ -97,12 +96,15 @@ impl From<rustc_lexer::unescape::EscapeError> for SyntaxErrorKind {
97pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> { 96pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> {
98 let mut errors = Vec::new(); 97 let mut errors = Vec::new();
99 for node in root.descendants() { 98 for node in root.descendants() {
100 let _ = visitor_ctx(&mut errors) 99 match_ast! {
101 .visit::<ast::Literal, _>(validate_literal) 100 match node {
102 .visit::<ast::BlockExpr, _>(block::validate_block_expr) 101 ast::Literal(it) => { validate_literal(it, &mut errors) },
103 .visit::<ast::FieldExpr, _>(|it, errors| validate_numeric_name(it.name_ref(), errors)) 102 ast::BlockExpr(it) => { block::validate_block_expr(it, &mut errors) },
104 .visit::<ast::RecordField, _>(|it, errors| validate_numeric_name(it.name_ref(), errors)) 103 ast::FieldExpr(it) => { validate_numeric_name(it.name_ref(), &mut errors) },
105 .accept(&node); 104 ast::RecordField(it) => { validate_numeric_name(it.name_ref(), &mut errors) },
105 _ => (),
106 }
107 }
106 } 108 }
107 errors 109 errors
108} 110}