aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_syntax/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_syntax/src')
-rw-r--r--crates/ra_syntax/src/algo.rs19
-rw-r--r--crates/ra_syntax/src/algo/visit.rs110
-rw-r--r--crates/ra_syntax/src/ast.rs1
-rw-r--r--crates/ra_syntax/src/ast/edit.rs271
-rw-r--r--crates/ra_syntax/src/ast/extensions.rs93
-rw-r--r--crates/ra_syntax/src/ast/generated.rs46
-rw-r--r--crates/ra_syntax/src/ast/make.rs48
-rw-r--r--crates/ra_syntax/src/ast/traits.rs2
-rw-r--r--crates/ra_syntax/src/fuzz.rs2
-rw-r--r--crates/ra_syntax/src/grammar.ron8
-rw-r--r--crates/ra_syntax/src/lib.rs31
-rw-r--r--crates/ra_syntax/src/parsing/lexer.rs2
-rw-r--r--crates/ra_syntax/src/parsing/text_token_source.rs2
-rw-r--r--crates/ra_syntax/src/parsing/text_tree_sink.rs2
-rw-r--r--crates/ra_syntax/src/ptr.rs2
-rw-r--r--crates/ra_syntax/src/syntax_error.rs2
-rw-r--r--crates/ra_syntax/src/validation.rs20
-rw-r--r--crates/ra_syntax/src/validation/block.rs2
18 files changed, 476 insertions, 187 deletions
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs
index 46680a08f..7cfea70f9 100644
--- a/crates/ra_syntax/src/algo.rs
+++ b/crates/ra_syntax/src/algo.rs
@@ -1,8 +1,9 @@
1pub mod visit; 1//! FIXME: write short doc here
2 2
3use std::ops::RangeInclusive; 3use std::ops::RangeInclusive;
4 4
5use itertools::Itertools; 5use itertools::Itertools;
6use ra_text_edit::TextEditBuilder;
6use rustc_hash::FxHashMap; 7use rustc_hash::FxHashMap;
7 8
8use crate::{ 9use crate::{
@@ -63,6 +64,18 @@ pub enum InsertPosition<T> {
63 After(T), 64 After(T),
64} 65}
65 66
67pub struct TreeDiff {
68 replacements: FxHashMap<SyntaxElement, SyntaxElement>,
69}
70
71impl TreeDiff {
72 pub fn into_text_edit(&self, builder: &mut TextEditBuilder) {
73 for (from, to) in self.replacements.iter() {
74 builder.replace(from.text_range(), to.to_string())
75 }
76 }
77}
78
66/// Finds minimal the diff, which, applied to `from`, will result in `to`. 79/// Finds minimal the diff, which, applied to `from`, will result in `to`.
67/// 80///
68/// Specifically, returns a map whose keys are descendants of `from` and values 81/// Specifically, returns a map whose keys are descendants of `from` and values
@@ -70,12 +83,12 @@ pub enum InsertPosition<T> {
70/// 83///
71/// A trivial solution is a singletom map `{ from: to }`, but this function 84/// A trivial solution is a singletom map `{ from: to }`, but this function
72/// tries to find a more fine-grained diff. 85/// tries to find a more fine-grained diff.
73pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> FxHashMap<SyntaxElement, SyntaxElement> { 86pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
74 let mut buf = FxHashMap::default(); 87 let mut buf = FxHashMap::default();
75 // FIXME: this is both horrible inefficient and gives larger than 88 // FIXME: this is both horrible inefficient and gives larger than
76 // necessary diff. I bet there's a cool algorithm to diff trees properly. 89 // necessary diff. I bet there's a cool algorithm to diff trees properly.
77 go(&mut buf, from.clone().into(), to.clone().into()); 90 go(&mut buf, from.clone().into(), to.clone().into());
78 return buf; 91 return TreeDiff { replacements: buf };
79 92
80 fn go( 93 fn go(
81 buf: &mut FxHashMap<SyntaxElement, SyntaxElement>, 94 buf: &mut FxHashMap<SyntaxElement, SyntaxElement>,
diff --git a/crates/ra_syntax/src/algo/visit.rs b/crates/ra_syntax/src/algo/visit.rs
deleted file mode 100644
index 87bd15cc0..000000000
--- a/crates/ra_syntax/src/algo/visit.rs
+++ /dev/null
@@ -1,110 +0,0 @@
1use crate::{AstNode, SyntaxNode};
2
3use std::marker::PhantomData;
4
5pub fn visitor<'a, T>() -> impl Visitor<'a, Output = T> {
6 EmptyVisitor { ph: PhantomData }
7}
8
9pub fn visitor_ctx<'a, T, C>(ctx: C) -> impl VisitorCtx<'a, Output = T, Ctx = C> {
10 EmptyVisitorCtx { ph: PhantomData, ctx }
11}
12
13pub trait Visitor<'a>: Sized {
14 type Output;
15 fn accept(self, node: &'a SyntaxNode) -> Option<Self::Output>;
16 fn visit<N, F>(self, f: F) -> Vis<Self, N, F>
17 where
18 N: AstNode + 'a,
19 F: FnOnce(N) -> Self::Output,
20 {
21 Vis { inner: self, f, ph: PhantomData }
22 }
23}
24
25pub trait VisitorCtx<'a>: Sized {
26 type Output;
27 type Ctx;
28 fn accept(self, node: &'a SyntaxNode) -> Result<Self::Output, Self::Ctx>;
29 fn visit<N, F>(self, f: F) -> VisCtx<Self, N, F>
30 where
31 N: AstNode + 'a,
32 F: FnOnce(N, Self::Ctx) -> Self::Output,
33 {
34 VisCtx { inner: self, f, ph: PhantomData }
35 }
36}
37
38#[derive(Debug)]
39struct EmptyVisitor<T> {
40 ph: PhantomData<fn() -> T>,
41}
42
43impl<'a, T> Visitor<'a> for EmptyVisitor<T> {
44 type Output = T;
45
46 fn accept(self, _node: &'a SyntaxNode) -> Option<T> {
47 None
48 }
49}
50
51#[derive(Debug)]
52struct EmptyVisitorCtx<T, C> {
53 ctx: C,
54 ph: PhantomData<fn() -> T>,
55}
56
57impl<'a, T, C> VisitorCtx<'a> for EmptyVisitorCtx<T, C> {
58 type Output = T;
59 type Ctx = C;
60
61 fn accept(self, _node: &'a SyntaxNode) -> Result<T, C> {
62 Err(self.ctx)
63 }
64}
65
66#[derive(Debug)]
67pub struct Vis<V, N, F> {
68 inner: V,
69 f: F,
70 ph: PhantomData<fn(N)>,
71}
72
73impl<'a, V, N, F> Visitor<'a> for Vis<V, N, F>
74where
75 V: Visitor<'a>,
76 N: AstNode + 'a,
77 F: FnOnce(N) -> <V as Visitor<'a>>::Output,
78{
79 type Output = <V as Visitor<'a>>::Output;
80
81 fn accept(self, node: &'a SyntaxNode) -> Option<Self::Output> {
82 let Vis { inner, f, .. } = self;
83 inner.accept(node).or_else(|| N::cast(node.clone()).map(f))
84 }
85}
86
87#[derive(Debug)]
88pub struct VisCtx<V, N, F> {
89 inner: V,
90 f: F,
91 ph: PhantomData<fn(N)>,
92}
93
94impl<'a, V, N, F> VisitorCtx<'a> for VisCtx<V, N, F>
95where
96 V: VisitorCtx<'a>,
97 N: AstNode + 'a,
98 F: FnOnce(N, <V as VisitorCtx<'a>>::Ctx) -> <V as VisitorCtx<'a>>::Output,
99{
100 type Output = <V as VisitorCtx<'a>>::Output;
101 type Ctx = <V as VisitorCtx<'a>>::Ctx;
102
103 fn accept(self, node: &'a SyntaxNode) -> Result<Self::Output, Self::Ctx> {
104 let VisCtx { inner, f, .. } = self;
105 inner.accept(node).or_else(|ctx| match N::cast(node.clone()) {
106 None => Err(ctx),
107 Some(node) => Ok(f(node, ctx)),
108 })
109 }
110}
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs
index f464d6534..1b2ce921a 100644
--- a/crates/ra_syntax/src/ast.rs
+++ b/crates/ra_syntax/src/ast.rs
@@ -5,6 +5,7 @@ mod traits;
5mod tokens; 5mod tokens;
6mod extensions; 6mod extensions;
7mod expr_extensions; 7mod expr_extensions;
8pub mod edit;
8pub mod make; 9pub mod make;
9 10
10use std::marker::PhantomData; 11use std::marker::PhantomData;
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs
new file mode 100644
index 000000000..03f3b5fbb
--- /dev/null
+++ b/crates/ra_syntax/src/ast/edit.rs
@@ -0,0 +1,271 @@
1//! This module contains functions for editing syntax trees. As the trees are
2//! immutable, all function here return a fresh copy of the tree, instead of
3//! doing an in-place modification.
4use std::{iter, ops::RangeInclusive};
5
6use arrayvec::ArrayVec;
7use rustc_hash::FxHashMap;
8
9use crate::{
10 algo,
11 ast::{
12 self,
13 make::{self, tokens},
14 AstNode, TypeBoundsOwner,
15 },
16 AstToken, Direction, InsertPosition, SmolStr, SyntaxElement,
17 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
18 SyntaxNode, T,
19};
20
21impl ast::FnDef {
22 #[must_use]
23 pub fn with_body(&self, body: ast::Block) -> ast::FnDef {
24 let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new();
25 let old_body_or_semi: SyntaxElement = if let Some(old_body) = self.body() {
26 old_body.syntax().clone().into()
27 } else if let Some(semi) = self.semicolon_token() {
28 to_insert.push(make::tokens::single_space().into());
29 semi.into()
30 } else {
31 to_insert.push(make::tokens::single_space().into());
32 to_insert.push(body.syntax().clone().into());
33 return insert_children(self, InsertPosition::Last, to_insert.into_iter());
34 };
35 to_insert.push(body.syntax().clone().into());
36 let replace_range = RangeInclusive::new(old_body_or_semi.clone(), old_body_or_semi);
37 replace_children(self, replace_range, to_insert.into_iter())
38 }
39}
40
41impl ast::ItemList {
42 #[must_use]
43 pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList {
44 let mut res = self.clone();
45 if !self.syntax().text().contains_char('\n') {
46 res = res.make_multiline();
47 }
48 items.for_each(|it| res = res.append_item(it));
49 res
50 }
51
52 #[must_use]
53 pub fn append_item(&self, item: ast::ImplItem) -> ast::ItemList {
54 let (indent, position) = match self.impl_items().last() {
55 Some(it) => (
56 leading_indent(it.syntax()).unwrap_or_default().to_string(),
57 InsertPosition::After(it.syntax().clone().into()),
58 ),
59 None => match self.l_curly() {
60 Some(it) => (
61 " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(),
62 InsertPosition::After(it),
63 ),
64 None => return self.clone(),
65 },
66 };
67 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
68 let to_insert: ArrayVec<[SyntaxElement; 2]> =
69 [ws.ws().into(), item.syntax().clone().into()].into();
70 insert_children(self, position, to_insert.into_iter())
71 }
72
73 fn l_curly(&self) -> Option<SyntaxElement> {
74 self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
75 }
76
77 fn make_multiline(&self) -> ast::ItemList {
78 let l_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
79 Some(it) => it,
80 None => return self.clone(),
81 };
82 let sibling = match l_curly.next_sibling_or_token() {
83 Some(it) => it,
84 None => return self.clone(),
85 };
86 let existing_ws = match sibling.as_token() {
87 None => None,
88 Some(tok) if tok.kind() != WHITESPACE => None,
89 Some(ws) => {
90 if ws.text().contains('\n') {
91 return self.clone();
92 }
93 Some(ws.clone())
94 }
95 };
96
97 let indent = leading_indent(self.syntax()).unwrap_or("".into());
98 let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
99 let to_insert = iter::once(ws.ws().into());
100 match existing_ws {
101 None => insert_children(self, InsertPosition::After(l_curly), to_insert),
102 Some(ws) => {
103 replace_children(self, RangeInclusive::new(ws.clone().into(), ws.into()), to_insert)
104 }
105 }
106 }
107}
108
109impl ast::RecordFieldList {
110 #[must_use]
111 pub fn append_field(&self, field: &ast::RecordField) -> ast::RecordFieldList {
112 self.insert_field(InsertPosition::Last, field)
113 }
114
115 #[must_use]
116 pub fn insert_field(
117 &self,
118 position: InsertPosition<&'_ ast::RecordField>,
119 field: &ast::RecordField,
120 ) -> ast::RecordFieldList {
121 let is_multiline = self.syntax().text().contains_char('\n');
122 let ws;
123 let space = if is_multiline {
124 ws = tokens::WsBuilder::new(&format!(
125 "\n{} ",
126 leading_indent(self.syntax()).unwrap_or("".into())
127 ));
128 ws.ws()
129 } else {
130 tokens::single_space()
131 };
132
133 let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
134 to_insert.push(space.into());
135 to_insert.push(field.syntax().clone().into());
136 to_insert.push(tokens::comma().into());
137
138 macro_rules! after_l_curly {
139 () => {{
140 let anchor = match self.l_curly() {
141 Some(it) => it,
142 None => return self.clone(),
143 };
144 InsertPosition::After(anchor)
145 }};
146 }
147
148 macro_rules! after_field {
149 ($anchor:expr) => {
150 if let Some(comma) = $anchor
151 .syntax()
152 .siblings_with_tokens(Direction::Next)
153 .find(|it| it.kind() == T![,])
154 {
155 InsertPosition::After(comma)
156 } else {
157 to_insert.insert(0, tokens::comma().into());
158 InsertPosition::After($anchor.syntax().clone().into())
159 }
160 };
161 };
162
163 let position = match position {
164 InsertPosition::First => after_l_curly!(),
165 InsertPosition::Last => {
166 if !is_multiline {
167 // don't insert comma before curly
168 to_insert.pop();
169 }
170 match self.fields().last() {
171 Some(it) => after_field!(it),
172 None => after_l_curly!(),
173 }
174 }
175 InsertPosition::Before(anchor) => {
176 InsertPosition::Before(anchor.syntax().clone().into())
177 }
178 InsertPosition::After(anchor) => after_field!(anchor),
179 };
180
181 insert_children(self, position, to_insert.iter().cloned())
182 }
183
184 fn l_curly(&self) -> Option<SyntaxElement> {
185 self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
186 }
187}
188
189impl ast::TypeParam {
190 #[must_use]
191 pub fn remove_bounds(&self) -> ast::TypeParam {
192 let colon = match self.colon_token() {
193 Some(it) => it,
194 None => return self.clone(),
195 };
196 let end = match self.type_bound_list() {
197 Some(it) => it.syntax().clone().into(),
198 None => colon.clone().into(),
199 };
200 replace_children(self, RangeInclusive::new(colon.into(), end), iter::empty())
201 }
202}
203
204#[must_use]
205pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
206 N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap()
207}
208
209fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode {
210 while let Some(start) =
211 node.children_with_tokens().find(|it| it.kind() == ATTR || it.kind() == COMMENT)
212 {
213 let end = match &start.next_sibling_or_token() {
214 Some(el) if el.kind() == WHITESPACE => el.clone(),
215 Some(_) | None => start.clone(),
216 };
217 node = algo::replace_children(&node, RangeInclusive::new(start, end), &mut iter::empty());
218 }
219 node
220}
221
222#[must_use]
223pub fn replace_descendants<N: AstNode, D: AstNode>(
224 parent: &N,
225 replacement_map: impl Iterator<Item = (D, D)>,
226) -> N {
227 let map = replacement_map
228 .map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into()))
229 .collect::<FxHashMap<_, _>>();
230 let new_syntax = algo::replace_descendants(parent.syntax(), &map);
231 N::cast(new_syntax).unwrap()
232}
233
234// Note this is copy-pasted from fmt. It seems like fmt should be a separate
235// crate, but basic tree building should be this crate. However, tree building
236// might want to call into fmt...
237fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> {
238 let prev_tokens = std::iter::successors(node.first_token(), |token| token.prev_token());
239 for token in prev_tokens {
240 if let Some(ws) = ast::Whitespace::cast(token.clone()) {
241 let ws_text = ws.text();
242 if let Some(pos) = ws_text.rfind('\n') {
243 return Some(ws_text[pos + 1..].into());
244 }
245 }
246 if token.text().contains('\n') {
247 break;
248 }
249 }
250 None
251}
252
253#[must_use]
254fn insert_children<N: AstNode>(
255 parent: &N,
256 position: InsertPosition<SyntaxElement>,
257 mut to_insert: impl Iterator<Item = SyntaxElement>,
258) -> N {
259 let new_syntax = algo::insert_children(parent.syntax(), position, &mut to_insert);
260 N::cast(new_syntax).unwrap()
261}
262
263#[must_use]
264fn replace_children<N: AstNode>(
265 parent: &N,
266 to_replace: RangeInclusive<SyntaxElement>,
267 mut to_insert: impl Iterator<Item = SyntaxElement>,
268) -> N {
269 let new_syntax = algo::replace_children(parent.syntax(), to_replace, &mut to_insert);
270 N::cast(new_syntax).unwrap()
271}
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs
index 5f7e9f5b1..cefc00402 100644
--- a/crates/ra_syntax/src/ast/extensions.rs
+++ b/crates/ra_syntax/src/ast/extensions.rs
@@ -1,10 +1,8 @@
1//! Various extension methods to ast Nodes, which are hard to code-generate. 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. 2//! Extensions for various expressions live in a sibling `expr_extensions` module.
3 3
4use itertools::Itertools;
5
6use crate::{ 4use crate::{
7 ast::{self, child_opt, children, AstNode, SyntaxNode}, 5 ast::{self, child_opt, children, AstChildren, AstNode, AttrInput, SyntaxNode},
8 SmolStr, SyntaxElement, 6 SmolStr, SyntaxElement,
9 SyntaxKind::*, 7 SyntaxKind::*,
10 SyntaxToken, T, 8 SyntaxToken, T,
@@ -21,6 +19,16 @@ impl ast::NameRef {
21 pub fn text(&self) -> &SmolStr { 19 pub fn text(&self) -> &SmolStr {
22 text_of_first_token(self.syntax()) 20 text_of_first_token(self.syntax())
23 } 21 }
22
23 pub fn as_tuple_field(&self) -> Option<usize> {
24 self.syntax().children_with_tokens().find_map(|c| {
25 if c.kind() == SyntaxKind::INT_NUMBER {
26 c.as_token().and_then(|tok| tok.text().as_str().parse().ok())
27 } else {
28 None
29 }
30 })
31 }
24} 32}
25 33
26fn text_of_first_token(node: &SyntaxNode) -> &SmolStr { 34fn text_of_first_token(node: &SyntaxNode) -> &SmolStr {
@@ -28,62 +36,37 @@ fn text_of_first_token(node: &SyntaxNode) -> &SmolStr {
28} 36}
29 37
30impl ast::Attr { 38impl ast::Attr {
31 pub fn is_inner(&self) -> bool { 39 pub fn as_simple_atom(&self) -> Option<SmolStr> {
32 let tt = match self.value() { 40 match self.input() {
33 None => return false, 41 None => self.simple_name(),
34 Some(tt) => tt, 42 Some(_) => None,
35 };
36
37 let prev = match tt.syntax().prev_sibling() {
38 None => return false,
39 Some(prev) => prev,
40 };
41
42 prev.kind() == T![!]
43 }
44
45 pub fn as_atom(&self) -> Option<SmolStr> {
46 let tt = self.value()?;
47 let (_bra, attr, _ket) = tt.syntax().children_with_tokens().collect_tuple()?;
48 if attr.kind() == IDENT {
49 Some(attr.as_token()?.text().clone())
50 } else {
51 None
52 } 43 }
53 } 44 }
54 45
55 pub fn as_call(&self) -> Option<(SmolStr, ast::TokenTree)> { 46 pub fn as_simple_call(&self) -> Option<(SmolStr, ast::TokenTree)> {
56 let tt = self.value()?; 47 match self.input() {
57 let (_bra, attr, args, _ket) = tt.syntax().children_with_tokens().collect_tuple()?; 48 Some(AttrInput::TokenTree(tt)) => Some((self.simple_name()?, tt)),
58 let args = ast::TokenTree::cast(args.as_node()?.clone())?; 49 _ => None,
59 if attr.kind() == IDENT {
60 Some((attr.as_token()?.text().clone(), args))
61 } else {
62 None
63 } 50 }
64 } 51 }
65 52
66 pub fn as_named(&self) -> Option<SmolStr> { 53 pub fn as_simple_key_value(&self) -> Option<(SmolStr, SmolStr)> {
67 let tt = self.value()?; 54 match self.input() {
68 let attr = tt.syntax().children_with_tokens().nth(1)?; 55 Some(AttrInput::Literal(lit)) => {
69 if attr.kind() == IDENT { 56 let key = self.simple_name()?;
70 Some(attr.as_token()?.text().clone()) 57 // FIXME: escape? raw string?
71 } else { 58 let value = lit.syntax().first_token()?.text().trim_matches('"').into();
72 None 59 Some((key, value))
60 }
61 _ => None,
73 } 62 }
74 } 63 }
75 64
76 pub fn as_key_value(&self) -> Option<(SmolStr, SmolStr)> { 65 pub fn simple_name(&self) -> Option<SmolStr> {
77 let tt = self.value()?; 66 let path = self.path()?;
78 let tt_node = tt.syntax(); 67 match (path.segment(), path.qualifier()) {
79 let attr = tt_node.children_with_tokens().nth(1)?; 68 (Some(segment), None) => Some(segment.syntax().first_token()?.text().clone()),
80 if attr.kind() == IDENT { 69 _ => None,
81 let key = attr.as_token()?.text().clone();
82 let val_node = tt_node.children_with_tokens().find(|t| t.kind() == STRING)?;
83 let val = val_node.as_token()?.text().trim_start_matches('"').trim_end_matches('"');
84 Some((key, SmolStr::new(val)))
85 } else {
86 None
87 } 70 }
88 } 71 }
89} 72}
@@ -193,6 +176,16 @@ impl ast::ImplBlock {
193 } 176 }
194} 177}
195 178
179impl ast::AttrsOwner for ast::ImplItem {
180 fn attrs(&self) -> AstChildren<ast::Attr> {
181 match self {
182 ast::ImplItem::FnDef(it) => it.attrs(),
183 ast::ImplItem::TypeAliasDef(it) => it.attrs(),
184 ast::ImplItem::ConstDef(it) => it.attrs(),
185 }
186 }
187}
188
196#[derive(Debug, Clone, PartialEq, Eq)] 189#[derive(Debug, Clone, PartialEq, Eq)]
197pub enum StructKind { 190pub enum StructKind {
198 Tuple(ast::TupleFieldDefList), 191 Tuple(ast::TupleFieldDefList),
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index dc1f8c82c..34b22c3e2 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -1,4 +1,4 @@
1// Generated file, do not edit by hand, see `crate/ra_tools/src/codegen` 1//! Generated file, do not edit by hand, see `crate/ra_tools/src/codegen`
2 2
3use crate::{ 3use crate::{
4 ast::{self, AstChildren, AstNode}, 4 ast::{self, AstChildren, AstNode},
@@ -166,11 +166,52 @@ impl AstNode for Attr {
166 } 166 }
167} 167}
168impl Attr { 168impl Attr {
169 pub fn value(&self) -> Option<TokenTree> { 169 pub fn path(&self) -> Option<Path> {
170 AstChildren::new(&self.syntax).next()
171 }
172 pub fn input(&self) -> Option<AttrInput> {
170 AstChildren::new(&self.syntax).next() 173 AstChildren::new(&self.syntax).next()
171 } 174 }
172} 175}
173#[derive(Debug, Clone, PartialEq, Eq, Hash)] 176#[derive(Debug, Clone, PartialEq, Eq, Hash)]
177pub enum AttrInput {
178 Literal(Literal),
179 TokenTree(TokenTree),
180}
181impl From<Literal> for AttrInput {
182 fn from(node: Literal) -> AttrInput {
183 AttrInput::Literal(node)
184 }
185}
186impl From<TokenTree> for AttrInput {
187 fn from(node: TokenTree) -> AttrInput {
188 AttrInput::TokenTree(node)
189 }
190}
191impl AstNode for AttrInput {
192 fn can_cast(kind: SyntaxKind) -> bool {
193 match kind {
194 LITERAL | TOKEN_TREE => true,
195 _ => false,
196 }
197 }
198 fn cast(syntax: SyntaxNode) -> Option<Self> {
199 let res = match syntax.kind() {
200 LITERAL => AttrInput::Literal(Literal { syntax }),
201 TOKEN_TREE => AttrInput::TokenTree(TokenTree { syntax }),
202 _ => return None,
203 };
204 Some(res)
205 }
206 fn syntax(&self) -> &SyntaxNode {
207 match self {
208 AttrInput::Literal(it) => &it.syntax,
209 AttrInput::TokenTree(it) => &it.syntax,
210 }
211 }
212}
213impl AttrInput {}
214#[derive(Debug, Clone, PartialEq, Eq, Hash)]
174pub struct AwaitExpr { 215pub struct AwaitExpr {
175 pub(crate) syntax: SyntaxNode, 216 pub(crate) syntax: SyntaxNode,
176} 217}
@@ -1921,6 +1962,7 @@ impl AstNode for ModuleItem {
1921 } 1962 }
1922 } 1963 }
1923} 1964}
1965impl ast::AttrsOwner for ModuleItem {}
1924impl ModuleItem {} 1966impl ModuleItem {}
1925#[derive(Debug, Clone, PartialEq, Eq, Hash)] 1967#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1926pub struct Name { 1968pub struct Name {
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index c06c62b3b..287a40bee 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -133,3 +133,51 @@ fn ast_from_text<N: AstNode>(text: &str) -> N {
133 let res = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); 133 let res = parse.tree().syntax().descendants().find_map(N::cast).unwrap();
134 res 134 res
135} 135}
136
137pub mod tokens {
138 use crate::{AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken, T};
139 use once_cell::sync::Lazy;
140
141 static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| SourceFile::parse(",\n; ;"));
142
143 pub fn comma() -> SyntaxToken {
144 SOURCE_FILE
145 .tree()
146 .syntax()
147 .descendants_with_tokens()
148 .filter_map(|it| it.into_token())
149 .find(|it| it.kind() == T![,])
150 .unwrap()
151 }
152
153 pub fn single_space() -> SyntaxToken {
154 SOURCE_FILE
155 .tree()
156 .syntax()
157 .descendants_with_tokens()
158 .filter_map(|it| it.into_token())
159 .find(|it| it.kind() == WHITESPACE && it.text().as_str() == " ")
160 .unwrap()
161 }
162
163 pub fn single_newline() -> SyntaxToken {
164 SOURCE_FILE
165 .tree()
166 .syntax()
167 .descendants_with_tokens()
168 .filter_map(|it| it.into_token())
169 .find(|it| it.kind() == WHITESPACE && it.text().as_str() == "\n")
170 .unwrap()
171 }
172
173 pub struct WsBuilder(SourceFile);
174
175 impl WsBuilder {
176 pub fn new(text: &str) -> WsBuilder {
177 WsBuilder(SourceFile::parse(text).ok().unwrap())
178 }
179 pub fn ws(&self) -> SyntaxToken {
180 self.0.syntax().first_child_or_token().unwrap().into_token().unwrap()
181 }
182 }
183}
diff --git a/crates/ra_syntax/src/ast/traits.rs b/crates/ra_syntax/src/ast/traits.rs
index c3e676d4c..f275a4955 100644
--- a/crates/ra_syntax/src/ast/traits.rs
+++ b/crates/ra_syntax/src/ast/traits.rs
@@ -99,7 +99,7 @@ pub trait AttrsOwner: AstNode {
99 children(self) 99 children(self)
100 } 100 }
101 fn has_atom_attr(&self, atom: &str) -> bool { 101 fn has_atom_attr(&self, atom: &str) -> bool {
102 self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom) 102 self.attrs().filter_map(|x| x.as_simple_atom()).any(|x| x == atom)
103 } 103 }
104} 104}
105 105
diff --git a/crates/ra_syntax/src/fuzz.rs b/crates/ra_syntax/src/fuzz.rs
index 698a624ec..7012df7f0 100644
--- a/crates/ra_syntax/src/fuzz.rs
+++ b/crates/ra_syntax/src/fuzz.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use crate::{validation, AstNode, SourceFile, TextRange, TextUnit}; 3use crate::{validation, AstNode, SourceFile, TextRange, TextUnit};
2use ra_text_edit::AtomTextEdit; 4use ra_text_edit::AtomTextEdit;
3use std::str::{self, FromStr}; 5use std::str::{self, FromStr};
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index 5f395501a..25e6f64ce 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -1,5 +1,5 @@
1// Stores definitions which must be used in multiple places 1// Stores definitions which must be used in multiple places
2// See `cargo gen-syntax` (defined in crates/tools/src/main.rs) 2// See `cargo gen-syntax` (defined in crates/ra_tools/src/main.rs)
3Grammar( 3Grammar(
4 punct: [ 4 punct: [
5 (";", "SEMI"), 5 (";", "SEMI"),
@@ -397,7 +397,8 @@ Grammar(
397 ), 397 ),
398 "ModuleItem": ( 398 "ModuleItem": (
399 enum: ["StructDef", "EnumDef", "FnDef", "TraitDef", "TypeAliasDef", "ImplBlock", 399 enum: ["StructDef", "EnumDef", "FnDef", "TraitDef", "TypeAliasDef", "ImplBlock",
400 "UseItem", "ExternCrateItem", "ConstDef", "StaticDef", "Module" ] 400 "UseItem", "ExternCrateItem", "ConstDef", "StaticDef", "Module" ],
401 traits: ["AttrsOwner"]
401 ), 402 ),
402 "ImplItem": ( 403 "ImplItem": (
403 enum: ["FnDef", "TypeAliasDef", "ConstDef"] 404 enum: ["FnDef", "TypeAliasDef", "ConstDef"]
@@ -576,7 +577,8 @@ Grammar(
576 traits: [ "NameOwner", "AttrsOwner","DocCommentsOwner" ], 577 traits: [ "NameOwner", "AttrsOwner","DocCommentsOwner" ],
577 options: [ "TokenTree", "Path" ], 578 options: [ "TokenTree", "Path" ],
578 ), 579 ),
579 "Attr": ( options: [ ["value", "TokenTree"] ] ), 580 "AttrInput": ( enum: [ "Literal", "TokenTree" ] ),
581 "Attr": ( options: [ "Path", [ "input", "AttrInput" ] ] ),
580 "TokenTree": (), 582 "TokenTree": (),
581 "TypeParamList": ( 583 "TypeParamList": (
582 collections: [ 584 collections: [
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]
164macro_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/parsing/lexer.rs b/crates/ra_syntax/src/parsing/lexer.rs
index bdb01d40b..6d839208d 100644
--- a/crates/ra_syntax/src/parsing/lexer.rs
+++ b/crates/ra_syntax/src/parsing/lexer.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use crate::{ 3use crate::{
2 SyntaxKind::{self, *}, 4 SyntaxKind::{self, *},
3 TextUnit, 5 TextUnit,
diff --git a/crates/ra_syntax/src/parsing/text_token_source.rs b/crates/ra_syntax/src/parsing/text_token_source.rs
index 64cb20ae8..e793f93a4 100644
--- a/crates/ra_syntax/src/parsing/text_token_source.rs
+++ b/crates/ra_syntax/src/parsing/text_token_source.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use ra_parser::Token as PToken; 3use ra_parser::Token as PToken;
2use ra_parser::TokenSource; 4use ra_parser::TokenSource;
3 5
diff --git a/crates/ra_syntax/src/parsing/text_tree_sink.rs b/crates/ra_syntax/src/parsing/text_tree_sink.rs
index be6e51780..142164316 100644
--- a/crates/ra_syntax/src/parsing/text_tree_sink.rs
+++ b/crates/ra_syntax/src/parsing/text_tree_sink.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use std::mem; 3use std::mem;
2 4
3use ra_parser::{ParseError, TreeSink}; 5use ra_parser::{ParseError, TreeSink};
diff --git a/crates/ra_syntax/src/ptr.rs b/crates/ra_syntax/src/ptr.rs
index 992034ef0..31167cada 100644
--- a/crates/ra_syntax/src/ptr.rs
+++ b/crates/ra_syntax/src/ptr.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use std::{iter::successors, marker::PhantomData}; 3use std::{iter::successors, marker::PhantomData};
2 4
3use crate::{AstNode, SyntaxKind, SyntaxNode, TextRange}; 5use crate::{AstNode, SyntaxKind, SyntaxNode, TextRange};
diff --git a/crates/ra_syntax/src/syntax_error.rs b/crates/ra_syntax/src/syntax_error.rs
index 5aefec768..d6eca2ad7 100644
--- a/crates/ra_syntax/src/syntax_error.rs
+++ b/crates/ra_syntax/src/syntax_error.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use std::fmt; 3use std::fmt;
2 4
3use ra_parser::ParseError; 5use ra_parser::ParseError;
diff --git a/crates/ra_syntax/src/validation.rs b/crates/ra_syntax/src/validation.rs
index 16824f3c4..ab4f15908 100644
--- a/crates/ra_syntax/src/validation.rs
+++ b/crates/ra_syntax/src/validation.rs
@@ -1,10 +1,11 @@
1//! FIXME: write short doc here
2
1mod block; 3mod block;
2 4
3use rustc_lexer::unescape; 5use rustc_lexer::unescape;
4 6
5use crate::{ 7use crate::{
6 algo::visit::{visitor_ctx, VisitorCtx}, 8 ast, match_ast, AstNode, SyntaxError, SyntaxErrorKind,
7 ast, AstNode, SyntaxError, SyntaxErrorKind,
8 SyntaxKind::{BYTE, BYTE_STRING, CHAR, INT_NUMBER, STRING}, 9 SyntaxKind::{BYTE, BYTE_STRING, CHAR, INT_NUMBER, STRING},
9 SyntaxNode, SyntaxToken, TextUnit, T, 10 SyntaxNode, SyntaxToken, TextUnit, T,
10}; 11};
@@ -95,12 +96,15 @@ impl From<rustc_lexer::unescape::EscapeError> for SyntaxErrorKind {
95pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> { 96pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> {
96 let mut errors = Vec::new(); 97 let mut errors = Vec::new();
97 for node in root.descendants() { 98 for node in root.descendants() {
98 let _ = visitor_ctx(&mut errors) 99 match_ast! {
99 .visit::<ast::Literal, _>(validate_literal) 100 match node {
100 .visit::<ast::BlockExpr, _>(block::validate_block_expr) 101 ast::Literal(it) => { validate_literal(it, &mut errors) },
101 .visit::<ast::FieldExpr, _>(|it, errors| validate_numeric_name(it.name_ref(), errors)) 102 ast::BlockExpr(it) => { block::validate_block_expr(it, &mut errors) },
102 .visit::<ast::RecordField, _>(|it, errors| validate_numeric_name(it.name_ref(), errors)) 103 ast::FieldExpr(it) => { validate_numeric_name(it.name_ref(), &mut errors) },
103 .accept(&node); 104 ast::RecordField(it) => { validate_numeric_name(it.name_ref(), &mut errors) },
105 _ => (),
106 }
107 }
104 } 108 }
105 errors 109 errors
106} 110}
diff --git a/crates/ra_syntax/src/validation/block.rs b/crates/ra_syntax/src/validation/block.rs
index 3c9e96eb3..c85bbc1f4 100644
--- a/crates/ra_syntax/src/validation/block.rs
+++ b/crates/ra_syntax/src/validation/block.rs
@@ -1,3 +1,5 @@
1//! FIXME: write short doc here
2
1use crate::{ 3use crate::{
2 ast::{self, AstNode, AttrsOwner}, 4 ast::{self, AstNode, AttrsOwner},
3 SyntaxError, 5 SyntaxError,