diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2019-10-05 15:54:25 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2019-10-05 15:54:25 +0100 |
commit | d3872964f88a0d751c428c150bb40d8b4f4c89a9 (patch) | |
tree | 14b7045119a1918118c18857cc094c914284705c /crates/ra_syntax | |
parent | ae6305b90c80eb919cfde985cba66975b6222ed2 (diff) | |
parent | 311dbb854536dd526cdbcadc6d270f9a37e4b816 (diff) |
Merge #1960
1960: Replace AST visitors with macro r=viorina a=viorina
Fixes #1672.
Co-authored-by: Ekaterina Babshukova <[email protected]>
Diffstat (limited to 'crates/ra_syntax')
-rw-r--r-- | crates/ra_syntax/src/algo.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/algo/visit.rs | 112 | ||||
-rw-r--r-- | crates/ra_syntax/src/lib.rs | 31 | ||||
-rw-r--r-- | crates/ra_syntax/src/validation.rs | 18 |
4 files changed, 31 insertions, 132 deletions
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs index d55534ede..7cfea70f9 100644 --- a/crates/ra_syntax/src/algo.rs +++ b/crates/ra_syntax/src/algo.rs | |||
@@ -1,7 +1,5 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | pub mod visit; | ||
4 | |||
5 | use std::ops::RangeInclusive; | 3 | use std::ops::RangeInclusive; |
6 | 4 | ||
7 | use itertools::Itertools; | 5 | use itertools::Itertools; |
diff --git a/crates/ra_syntax/src/algo/visit.rs b/crates/ra_syntax/src/algo/visit.rs deleted file mode 100644 index 4df275ba4..000000000 --- a/crates/ra_syntax/src/algo/visit.rs +++ /dev/null | |||
@@ -1,112 +0,0 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::{AstNode, SyntaxNode}; | ||
4 | |||
5 | use std::marker::PhantomData; | ||
6 | |||
7 | pub fn visitor<'a, T>() -> impl Visitor<'a, Output = T> { | ||
8 | EmptyVisitor { ph: PhantomData } | ||
9 | } | ||
10 | |||
11 | pub fn visitor_ctx<'a, T, C>(ctx: C) -> impl VisitorCtx<'a, Output = T, Ctx = C> { | ||
12 | EmptyVisitorCtx { ph: PhantomData, ctx } | ||
13 | } | ||
14 | |||
15 | pub trait Visitor<'a>: Sized { | ||
16 | type Output; | ||
17 | fn accept(self, node: &'a SyntaxNode) -> Option<Self::Output>; | ||
18 | fn visit<N, F>(self, f: F) -> Vis<Self, N, F> | ||
19 | where | ||
20 | N: AstNode + 'a, | ||
21 | F: FnOnce(N) -> Self::Output, | ||
22 | { | ||
23 | Vis { inner: self, f, ph: PhantomData } | ||
24 | } | ||
25 | } | ||
26 | |||
27 | pub trait VisitorCtx<'a>: Sized { | ||
28 | type Output; | ||
29 | type Ctx; | ||
30 | fn accept(self, node: &'a SyntaxNode) -> Result<Self::Output, Self::Ctx>; | ||
31 | fn visit<N, F>(self, f: F) -> VisCtx<Self, N, F> | ||
32 | where | ||
33 | N: AstNode + 'a, | ||
34 | F: FnOnce(N, Self::Ctx) -> Self::Output, | ||
35 | { | ||
36 | VisCtx { inner: self, f, ph: PhantomData } | ||
37 | } | ||
38 | } | ||
39 | |||
40 | #[derive(Debug)] | ||
41 | struct EmptyVisitor<T> { | ||
42 | ph: PhantomData<fn() -> T>, | ||
43 | } | ||
44 | |||
45 | impl<'a, T> Visitor<'a> for EmptyVisitor<T> { | ||
46 | type Output = T; | ||
47 | |||
48 | fn accept(self, _node: &'a SyntaxNode) -> Option<T> { | ||
49 | None | ||
50 | } | ||
51 | } | ||
52 | |||
53 | #[derive(Debug)] | ||
54 | struct EmptyVisitorCtx<T, C> { | ||
55 | ctx: C, | ||
56 | ph: PhantomData<fn() -> T>, | ||
57 | } | ||
58 | |||
59 | impl<'a, T, C> VisitorCtx<'a> for EmptyVisitorCtx<T, C> { | ||
60 | type Output = T; | ||
61 | type Ctx = C; | ||
62 | |||
63 | fn accept(self, _node: &'a SyntaxNode) -> Result<T, C> { | ||
64 | Err(self.ctx) | ||
65 | } | ||
66 | } | ||
67 | |||
68 | #[derive(Debug)] | ||
69 | pub struct Vis<V, N, F> { | ||
70 | inner: V, | ||
71 | f: F, | ||
72 | ph: PhantomData<fn(N)>, | ||
73 | } | ||
74 | |||
75 | impl<'a, V, N, F> Visitor<'a> for Vis<V, N, F> | ||
76 | where | ||
77 | V: Visitor<'a>, | ||
78 | N: AstNode + 'a, | ||
79 | F: FnOnce(N) -> <V as Visitor<'a>>::Output, | ||
80 | { | ||
81 | type Output = <V as Visitor<'a>>::Output; | ||
82 | |||
83 | fn accept(self, node: &'a SyntaxNode) -> Option<Self::Output> { | ||
84 | let Vis { inner, f, .. } = self; | ||
85 | inner.accept(node).or_else(|| N::cast(node.clone()).map(f)) | ||
86 | } | ||
87 | } | ||
88 | |||
89 | #[derive(Debug)] | ||
90 | pub struct VisCtx<V, N, F> { | ||
91 | inner: V, | ||
92 | f: F, | ||
93 | ph: PhantomData<fn(N)>, | ||
94 | } | ||
95 | |||
96 | impl<'a, V, N, F> VisitorCtx<'a> for VisCtx<V, N, F> | ||
97 | where | ||
98 | V: VisitorCtx<'a>, | ||
99 | N: AstNode + 'a, | ||
100 | F: FnOnce(N, <V as VisitorCtx<'a>>::Ctx) -> <V as VisitorCtx<'a>>::Output, | ||
101 | { | ||
102 | type Output = <V as VisitorCtx<'a>>::Output; | ||
103 | type Ctx = <V as VisitorCtx<'a>>::Ctx; | ||
104 | |||
105 | fn accept(self, node: &'a SyntaxNode) -> Result<Self::Output, Self::Ctx> { | ||
106 | let VisCtx { inner, f, .. } = self; | ||
107 | inner.accept(node).or_else(|ctx| match N::cast(node.clone()) { | ||
108 | None => Err(ctx), | ||
109 | Some(node) => Ok(f(node, ctx)), | ||
110 | }) | ||
111 | } | ||
112 | } | ||
diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs index edb6076bb..c315ba552 100644 --- a/crates/ra_syntax/src/lib.rs +++ b/crates/ra_syntax/src/lib.rs | |||
@@ -160,6 +160,17 @@ impl SourceFile { | |||
160 | } | 160 | } |
161 | } | 161 | } |
162 | 162 | ||
163 | #[macro_export] | ||
164 | macro_rules! match_ast { | ||
165 | (match $node:ident { | ||
166 | $( ast::$ast:ident($it:ident) => $res:block, )* | ||
167 | _ => $catch_all:expr, | ||
168 | }) => {{ | ||
169 | $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )* | ||
170 | { $catch_all } | ||
171 | }}; | ||
172 | } | ||
173 | |||
163 | /// This test does not assert anything and instead just shows off the crate's | 174 | /// This test does not assert anything and instead just shows off the crate's |
164 | /// API. | 175 | /// API. |
165 | #[test] | 176 | #[test] |
@@ -294,7 +305,7 @@ fn api_walkthrough() { | |||
294 | // To recursively process the tree, there are three approaches: | 305 | // To recursively process the tree, there are three approaches: |
295 | // 1. explicitly call getter methods on AST nodes. | 306 | // 1. explicitly call getter methods on AST nodes. |
296 | // 2. use descendants and `AstNode::cast`. | 307 | // 2. use descendants and `AstNode::cast`. |
297 | // 3. use descendants and the visitor. | 308 | // 3. use descendants and `match_ast!`. |
298 | // | 309 | // |
299 | // Here's how the first one looks like: | 310 | // Here's how the first one looks like: |
300 | let exprs_cast: Vec<String> = file | 311 | let exprs_cast: Vec<String> = file |
@@ -304,17 +315,17 @@ fn api_walkthrough() { | |||
304 | .map(|expr| expr.syntax().text().to_string()) | 315 | .map(|expr| expr.syntax().text().to_string()) |
305 | .collect(); | 316 | .collect(); |
306 | 317 | ||
307 | // An alternative is to use a visitor. The visitor does not do traversal | 318 | // An alternative is to use a macro. |
308 | // automatically (so it's more akin to a generic lambda) and is constructed | ||
309 | // from closures. This seems more flexible than a single generated visitor | ||
310 | // trait. | ||
311 | use algo::visit::{visitor, Visitor}; | ||
312 | let mut exprs_visit = Vec::new(); | 319 | let mut exprs_visit = Vec::new(); |
313 | for node in file.syntax().descendants() { | 320 | for node in file.syntax().descendants() { |
314 | if let Some(result) = | 321 | match_ast! { |
315 | visitor().visit::<ast::Expr, _>(|expr| expr.syntax().text().to_string()).accept(&node) | 322 | match node { |
316 | { | 323 | ast::Expr(it) => { |
317 | exprs_visit.push(result); | 324 | let res = it.syntax().text().to_string(); |
325 | exprs_visit.push(res); | ||
326 | }, | ||
327 | _ => (), | ||
328 | } | ||
318 | } | 329 | } |
319 | } | 330 | } |
320 | assert_eq!(exprs_cast, exprs_visit); | 331 | assert_eq!(exprs_cast, exprs_visit); |
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; | |||
5 | use rustc_lexer::unescape; | 5 | use rustc_lexer::unescape; |
6 | 6 | ||
7 | use crate::{ | 7 | use 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 { | |||
97 | pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> { | 96 | pub(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 | } |