diff options
Diffstat (limited to 'crates/syntax/src/ast/node_ext.rs')
-rw-r--r-- | crates/syntax/src/ast/node_ext.rs | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs new file mode 100644 index 000000000..50c1c157d --- /dev/null +++ b/crates/syntax/src/ast/node_ext.rs | |||
@@ -0,0 +1,485 @@ | |||
1 | //! Various extension methods to ast Nodes, which are hard to code-generate. | ||
2 | //! Extensions for various expressions live in a sibling `expr_extensions` module. | ||
3 | |||
4 | use std::fmt; | ||
5 | |||
6 | use itertools::Itertools; | ||
7 | use parser::SyntaxKind; | ||
8 | |||
9 | use crate::{ | ||
10 | ast::{self, support, AstNode, NameOwner, SyntaxNode}, | ||
11 | SmolStr, SyntaxElement, SyntaxToken, T, | ||
12 | }; | ||
13 | |||
14 | impl ast::Name { | ||
15 | pub fn text(&self) -> &SmolStr { | ||
16 | text_of_first_token(self.syntax()) | ||
17 | } | ||
18 | } | ||
19 | |||
20 | impl ast::NameRef { | ||
21 | pub fn text(&self) -> &SmolStr { | ||
22 | text_of_first_token(self.syntax()) | ||
23 | } | ||
24 | |||
25 | pub fn as_tuple_field(&self) -> Option<usize> { | ||
26 | self.text().parse().ok() | ||
27 | } | ||
28 | } | ||
29 | |||
30 | fn text_of_first_token(node: &SyntaxNode) -> &SmolStr { | ||
31 | node.green().children().next().and_then(|it| it.into_token()).unwrap().text() | ||
32 | } | ||
33 | |||
34 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
35 | pub enum AttrKind { | ||
36 | Inner, | ||
37 | Outer, | ||
38 | } | ||
39 | |||
40 | impl ast::Attr { | ||
41 | pub fn as_simple_atom(&self) -> Option<SmolStr> { | ||
42 | if self.eq_token().is_some() || self.token_tree().is_some() { | ||
43 | return None; | ||
44 | } | ||
45 | self.simple_name() | ||
46 | } | ||
47 | |||
48 | pub fn as_simple_call(&self) -> Option<(SmolStr, ast::TokenTree)> { | ||
49 | let tt = self.token_tree()?; | ||
50 | Some((self.simple_name()?, tt)) | ||
51 | } | ||
52 | |||
53 | pub fn as_simple_key_value(&self) -> Option<(SmolStr, SmolStr)> { | ||
54 | let lit = self.literal()?; | ||
55 | let key = self.simple_name()?; | ||
56 | // FIXME: escape? raw string? | ||
57 | let value = lit.syntax().first_token()?.text().trim_matches('"').into(); | ||
58 | Some((key, value)) | ||
59 | } | ||
60 | |||
61 | pub fn simple_name(&self) -> Option<SmolStr> { | ||
62 | let path = self.path()?; | ||
63 | match (path.segment(), path.qualifier()) { | ||
64 | (Some(segment), None) => Some(segment.syntax().first_token()?.text().clone()), | ||
65 | _ => None, | ||
66 | } | ||
67 | } | ||
68 | |||
69 | pub fn kind(&self) -> AttrKind { | ||
70 | let first_token = self.syntax().first_token(); | ||
71 | let first_token_kind = first_token.as_ref().map(SyntaxToken::kind); | ||
72 | let second_token_kind = | ||
73 | first_token.and_then(|token| token.next_token()).as_ref().map(SyntaxToken::kind); | ||
74 | |||
75 | match (first_token_kind, second_token_kind) { | ||
76 | (Some(SyntaxKind::POUND), Some(T![!])) => AttrKind::Inner, | ||
77 | _ => AttrKind::Outer, | ||
78 | } | ||
79 | } | ||
80 | } | ||
81 | |||
82 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
83 | pub enum PathSegmentKind { | ||
84 | Name(ast::NameRef), | ||
85 | Type { type_ref: Option<ast::Type>, trait_ref: Option<ast::PathType> }, | ||
86 | SelfKw, | ||
87 | SuperKw, | ||
88 | CrateKw, | ||
89 | } | ||
90 | |||
91 | impl ast::PathSegment { | ||
92 | pub fn parent_path(&self) -> ast::Path { | ||
93 | self.syntax() | ||
94 | .parent() | ||
95 | .and_then(ast::Path::cast) | ||
96 | .expect("segments are always nested in paths") | ||
97 | } | ||
98 | |||
99 | pub fn kind(&self) -> Option<PathSegmentKind> { | ||
100 | let res = if let Some(name_ref) = self.name_ref() { | ||
101 | PathSegmentKind::Name(name_ref) | ||
102 | } else { | ||
103 | match self.syntax().first_child_or_token()?.kind() { | ||
104 | T![self] => PathSegmentKind::SelfKw, | ||
105 | T![super] => PathSegmentKind::SuperKw, | ||
106 | T![crate] => PathSegmentKind::CrateKw, | ||
107 | T![<] => { | ||
108 | // <T> or <T as Trait> | ||
109 | // T is any TypeRef, Trait has to be a PathType | ||
110 | let mut type_refs = | ||
111 | self.syntax().children().filter(|node| ast::Type::can_cast(node.kind())); | ||
112 | let type_ref = type_refs.next().and_then(ast::Type::cast); | ||
113 | let trait_ref = type_refs.next().and_then(ast::PathType::cast); | ||
114 | PathSegmentKind::Type { type_ref, trait_ref } | ||
115 | } | ||
116 | _ => return None, | ||
117 | } | ||
118 | }; | ||
119 | Some(res) | ||
120 | } | ||
121 | } | ||
122 | |||
123 | impl ast::Path { | ||
124 | pub fn parent_path(&self) -> Option<ast::Path> { | ||
125 | self.syntax().parent().and_then(ast::Path::cast) | ||
126 | } | ||
127 | } | ||
128 | |||
129 | impl ast::UseTreeList { | ||
130 | pub fn parent_use_tree(&self) -> ast::UseTree { | ||
131 | self.syntax() | ||
132 | .parent() | ||
133 | .and_then(ast::UseTree::cast) | ||
134 | .expect("UseTreeLists are always nested in UseTrees") | ||
135 | } | ||
136 | } | ||
137 | |||
138 | impl ast::Impl { | ||
139 | pub fn self_ty(&self) -> Option<ast::Type> { | ||
140 | match self.target() { | ||
141 | (Some(t), None) | (_, Some(t)) => Some(t), | ||
142 | _ => None, | ||
143 | } | ||
144 | } | ||
145 | |||
146 | pub fn trait_(&self) -> Option<ast::Type> { | ||
147 | match self.target() { | ||
148 | (Some(t), Some(_)) => Some(t), | ||
149 | _ => None, | ||
150 | } | ||
151 | } | ||
152 | |||
153 | fn target(&self) -> (Option<ast::Type>, Option<ast::Type>) { | ||
154 | let mut types = support::children(self.syntax()); | ||
155 | let first = types.next(); | ||
156 | let second = types.next(); | ||
157 | (first, second) | ||
158 | } | ||
159 | } | ||
160 | |||
161 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
162 | pub enum StructKind { | ||
163 | Record(ast::RecordFieldList), | ||
164 | Tuple(ast::TupleFieldList), | ||
165 | Unit, | ||
166 | } | ||
167 | |||
168 | impl StructKind { | ||
169 | fn from_node<N: AstNode>(node: &N) -> StructKind { | ||
170 | if let Some(nfdl) = support::child::<ast::RecordFieldList>(node.syntax()) { | ||
171 | StructKind::Record(nfdl) | ||
172 | } else if let Some(pfl) = support::child::<ast::TupleFieldList>(node.syntax()) { | ||
173 | StructKind::Tuple(pfl) | ||
174 | } else { | ||
175 | StructKind::Unit | ||
176 | } | ||
177 | } | ||
178 | } | ||
179 | |||
180 | impl ast::Struct { | ||
181 | pub fn kind(&self) -> StructKind { | ||
182 | StructKind::from_node(self) | ||
183 | } | ||
184 | } | ||
185 | |||
186 | impl ast::RecordExprField { | ||
187 | pub fn for_field_name(field_name: &ast::NameRef) -> Option<ast::RecordExprField> { | ||
188 | let candidate = | ||
189 | field_name.syntax().parent().and_then(ast::RecordExprField::cast).or_else(|| { | ||
190 | field_name.syntax().ancestors().nth(4).and_then(ast::RecordExprField::cast) | ||
191 | })?; | ||
192 | if candidate.field_name().as_ref() == Some(field_name) { | ||
193 | Some(candidate) | ||
194 | } else { | ||
195 | None | ||
196 | } | ||
197 | } | ||
198 | |||
199 | /// Deals with field init shorthand | ||
200 | pub fn field_name(&self) -> Option<ast::NameRef> { | ||
201 | if let Some(name_ref) = self.name_ref() { | ||
202 | return Some(name_ref); | ||
203 | } | ||
204 | if let Some(ast::Expr::PathExpr(expr)) = self.expr() { | ||
205 | let path = expr.path()?; | ||
206 | let segment = path.segment()?; | ||
207 | let name_ref = segment.name_ref()?; | ||
208 | if path.qualifier().is_none() { | ||
209 | return Some(name_ref); | ||
210 | } | ||
211 | } | ||
212 | None | ||
213 | } | ||
214 | } | ||
215 | |||
216 | pub enum NameOrNameRef { | ||
217 | Name(ast::Name), | ||
218 | NameRef(ast::NameRef), | ||
219 | } | ||
220 | |||
221 | impl fmt::Display for NameOrNameRef { | ||
222 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
223 | match self { | ||
224 | NameOrNameRef::Name(it) => fmt::Display::fmt(it, f), | ||
225 | NameOrNameRef::NameRef(it) => fmt::Display::fmt(it, f), | ||
226 | } | ||
227 | } | ||
228 | } | ||
229 | |||
230 | impl ast::RecordPatField { | ||
231 | /// Deals with field init shorthand | ||
232 | pub fn field_name(&self) -> Option<NameOrNameRef> { | ||
233 | if let Some(name_ref) = self.name_ref() { | ||
234 | return Some(NameOrNameRef::NameRef(name_ref)); | ||
235 | } | ||
236 | if let Some(ast::Pat::IdentPat(pat)) = self.pat() { | ||
237 | let name = pat.name()?; | ||
238 | return Some(NameOrNameRef::Name(name)); | ||
239 | } | ||
240 | None | ||
241 | } | ||
242 | } | ||
243 | |||
244 | impl ast::Variant { | ||
245 | pub fn parent_enum(&self) -> ast::Enum { | ||
246 | self.syntax() | ||
247 | .parent() | ||
248 | .and_then(|it| it.parent()) | ||
249 | .and_then(ast::Enum::cast) | ||
250 | .expect("EnumVariants are always nested in Enums") | ||
251 | } | ||
252 | pub fn kind(&self) -> StructKind { | ||
253 | StructKind::from_node(self) | ||
254 | } | ||
255 | } | ||
256 | |||
257 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
258 | pub enum FieldKind { | ||
259 | Name(ast::NameRef), | ||
260 | Index(SyntaxToken), | ||
261 | } | ||
262 | |||
263 | impl ast::FieldExpr { | ||
264 | pub fn index_token(&self) -> Option<SyntaxToken> { | ||
265 | self.syntax | ||
266 | .children_with_tokens() | ||
267 | // FIXME: Accepting floats here to reject them in validation later | ||
268 | .find(|c| c.kind() == SyntaxKind::INT_NUMBER || c.kind() == SyntaxKind::FLOAT_NUMBER) | ||
269 | .as_ref() | ||
270 | .and_then(SyntaxElement::as_token) | ||
271 | .cloned() | ||
272 | } | ||
273 | |||
274 | pub fn field_access(&self) -> Option<FieldKind> { | ||
275 | if let Some(nr) = self.name_ref() { | ||
276 | Some(FieldKind::Name(nr)) | ||
277 | } else if let Some(tok) = self.index_token() { | ||
278 | Some(FieldKind::Index(tok)) | ||
279 | } else { | ||
280 | None | ||
281 | } | ||
282 | } | ||
283 | } | ||
284 | |||
285 | pub struct SlicePatComponents { | ||
286 | pub prefix: Vec<ast::Pat>, | ||
287 | pub slice: Option<ast::Pat>, | ||
288 | pub suffix: Vec<ast::Pat>, | ||
289 | } | ||
290 | |||
291 | impl ast::SlicePat { | ||
292 | pub fn components(&self) -> SlicePatComponents { | ||
293 | let mut args = self.pats().peekable(); | ||
294 | let prefix = args | ||
295 | .peeking_take_while(|p| match p { | ||
296 | ast::Pat::RestPat(_) => false, | ||
297 | ast::Pat::IdentPat(bp) => match bp.pat() { | ||
298 | Some(ast::Pat::RestPat(_)) => false, | ||
299 | _ => true, | ||
300 | }, | ||
301 | ast::Pat::RefPat(rp) => match rp.pat() { | ||
302 | Some(ast::Pat::RestPat(_)) => false, | ||
303 | Some(ast::Pat::IdentPat(bp)) => match bp.pat() { | ||
304 | Some(ast::Pat::RestPat(_)) => false, | ||
305 | _ => true, | ||
306 | }, | ||
307 | _ => true, | ||
308 | }, | ||
309 | _ => true, | ||
310 | }) | ||
311 | .collect(); | ||
312 | let slice = args.next(); | ||
313 | let suffix = args.collect(); | ||
314 | |||
315 | SlicePatComponents { prefix, slice, suffix } | ||
316 | } | ||
317 | } | ||
318 | |||
319 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] | ||
320 | pub enum SelfParamKind { | ||
321 | /// self | ||
322 | Owned, | ||
323 | /// &self | ||
324 | Ref, | ||
325 | /// &mut self | ||
326 | MutRef, | ||
327 | } | ||
328 | |||
329 | impl ast::SelfParam { | ||
330 | pub fn kind(&self) -> SelfParamKind { | ||
331 | if self.amp_token().is_some() { | ||
332 | if self.mut_token().is_some() { | ||
333 | SelfParamKind::MutRef | ||
334 | } else { | ||
335 | SelfParamKind::Ref | ||
336 | } | ||
337 | } else { | ||
338 | SelfParamKind::Owned | ||
339 | } | ||
340 | } | ||
341 | } | ||
342 | |||
343 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] | ||
344 | pub enum TypeBoundKind { | ||
345 | /// Trait | ||
346 | PathType(ast::PathType), | ||
347 | /// for<'a> ... | ||
348 | ForType(ast::ForType), | ||
349 | /// 'a | ||
350 | Lifetime(SyntaxToken), | ||
351 | } | ||
352 | |||
353 | impl ast::TypeBound { | ||
354 | pub fn kind(&self) -> TypeBoundKind { | ||
355 | if let Some(path_type) = support::children(self.syntax()).next() { | ||
356 | TypeBoundKind::PathType(path_type) | ||
357 | } else if let Some(for_type) = support::children(self.syntax()).next() { | ||
358 | TypeBoundKind::ForType(for_type) | ||
359 | } else if let Some(lifetime) = self.lifetime_token() { | ||
360 | TypeBoundKind::Lifetime(lifetime) | ||
361 | } else { | ||
362 | unreachable!() | ||
363 | } | ||
364 | } | ||
365 | } | ||
366 | |||
367 | pub enum VisibilityKind { | ||
368 | In(ast::Path), | ||
369 | PubCrate, | ||
370 | PubSuper, | ||
371 | PubSelf, | ||
372 | Pub, | ||
373 | } | ||
374 | |||
375 | impl ast::Visibility { | ||
376 | pub fn kind(&self) -> VisibilityKind { | ||
377 | if let Some(path) = support::children(self.syntax()).next() { | ||
378 | VisibilityKind::In(path) | ||
379 | } else if self.crate_token().is_some() { | ||
380 | VisibilityKind::PubCrate | ||
381 | } else if self.super_token().is_some() { | ||
382 | VisibilityKind::PubSuper | ||
383 | } else if self.self_token().is_some() { | ||
384 | VisibilityKind::PubSelf | ||
385 | } else { | ||
386 | VisibilityKind::Pub | ||
387 | } | ||
388 | } | ||
389 | } | ||
390 | |||
391 | impl ast::MacroCall { | ||
392 | pub fn is_macro_rules(&self) -> Option<ast::Name> { | ||
393 | let name_ref = self.path()?.segment()?.name_ref()?; | ||
394 | if name_ref.text() == "macro_rules" { | ||
395 | self.name() | ||
396 | } else { | ||
397 | None | ||
398 | } | ||
399 | } | ||
400 | |||
401 | pub fn is_bang(&self) -> bool { | ||
402 | self.is_macro_rules().is_none() | ||
403 | } | ||
404 | } | ||
405 | |||
406 | impl ast::LifetimeParam { | ||
407 | pub fn lifetime_bounds(&self) -> impl Iterator<Item = SyntaxToken> { | ||
408 | self.syntax() | ||
409 | .children_with_tokens() | ||
410 | .filter_map(|it| it.into_token()) | ||
411 | .skip_while(|x| x.kind() != T![:]) | ||
412 | .filter(|it| it.kind() == T![lifetime]) | ||
413 | } | ||
414 | } | ||
415 | |||
416 | impl ast::RangePat { | ||
417 | pub fn start(&self) -> Option<ast::Pat> { | ||
418 | self.syntax() | ||
419 | .children_with_tokens() | ||
420 | .take_while(|it| !(it.kind() == T![..] || it.kind() == T![..=])) | ||
421 | .filter_map(|it| it.into_node()) | ||
422 | .find_map(ast::Pat::cast) | ||
423 | } | ||
424 | |||
425 | pub fn end(&self) -> Option<ast::Pat> { | ||
426 | self.syntax() | ||
427 | .children_with_tokens() | ||
428 | .skip_while(|it| !(it.kind() == T![..] || it.kind() == T![..=])) | ||
429 | .filter_map(|it| it.into_node()) | ||
430 | .find_map(ast::Pat::cast) | ||
431 | } | ||
432 | } | ||
433 | |||
434 | impl ast::TokenTree { | ||
435 | pub fn left_delimiter_token(&self) -> Option<SyntaxToken> { | ||
436 | self.syntax() | ||
437 | .first_child_or_token()? | ||
438 | .into_token() | ||
439 | .filter(|it| matches!(it.kind(), T!['{'] | T!['('] | T!['['])) | ||
440 | } | ||
441 | |||
442 | pub fn right_delimiter_token(&self) -> Option<SyntaxToken> { | ||
443 | self.syntax() | ||
444 | .last_child_or_token()? | ||
445 | .into_token() | ||
446 | .filter(|it| matches!(it.kind(), T!['}'] | T![')'] | T![']'])) | ||
447 | } | ||
448 | } | ||
449 | |||
450 | impl ast::GenericParamList { | ||
451 | pub fn lifetime_params(&self) -> impl Iterator<Item = ast::LifetimeParam> { | ||
452 | self.generic_params().filter_map(|param| match param { | ||
453 | ast::GenericParam::LifetimeParam(it) => Some(it), | ||
454 | ast::GenericParam::TypeParam(_) | ast::GenericParam::ConstParam(_) => None, | ||
455 | }) | ||
456 | } | ||
457 | pub fn type_params(&self) -> impl Iterator<Item = ast::TypeParam> { | ||
458 | self.generic_params().filter_map(|param| match param { | ||
459 | ast::GenericParam::TypeParam(it) => Some(it), | ||
460 | ast::GenericParam::LifetimeParam(_) | ast::GenericParam::ConstParam(_) => None, | ||
461 | }) | ||
462 | } | ||
463 | pub fn const_params(&self) -> impl Iterator<Item = ast::ConstParam> { | ||
464 | self.generic_params().filter_map(|param| match param { | ||
465 | ast::GenericParam::ConstParam(it) => Some(it), | ||
466 | ast::GenericParam::TypeParam(_) | ast::GenericParam::LifetimeParam(_) => None, | ||
467 | }) | ||
468 | } | ||
469 | } | ||
470 | |||
471 | impl ast::DocCommentsOwner for ast::SourceFile {} | ||
472 | impl ast::DocCommentsOwner for ast::Fn {} | ||
473 | impl ast::DocCommentsOwner for ast::Struct {} | ||
474 | impl ast::DocCommentsOwner for ast::Union {} | ||
475 | impl ast::DocCommentsOwner for ast::RecordField {} | ||
476 | impl ast::DocCommentsOwner for ast::TupleField {} | ||
477 | impl ast::DocCommentsOwner for ast::Enum {} | ||
478 | impl ast::DocCommentsOwner for ast::Variant {} | ||
479 | impl ast::DocCommentsOwner for ast::Trait {} | ||
480 | impl ast::DocCommentsOwner for ast::Module {} | ||
481 | impl ast::DocCommentsOwner for ast::Static {} | ||
482 | impl ast::DocCommentsOwner for ast::Const {} | ||
483 | impl ast::DocCommentsOwner for ast::TypeAlias {} | ||
484 | impl ast::DocCommentsOwner for ast::Impl {} | ||
485 | impl ast::DocCommentsOwner for ast::MacroCall {} | ||