diff options
-rw-r--r-- | crates/ra_assists/src/assists/fill_match_arms.rs | 67 | ||||
-rw-r--r-- | crates/ra_assists/src/ast_transform.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir/src/code_model.rs | 24 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/lib.rs | 15 | ||||
-rw-r--r-- | crates/ra_ide/src/display/navigation_target.rs | 6 | ||||
-rw-r--r-- | crates/ra_ide/src/extend_selection.rs | 168 | ||||
-rw-r--r-- | crates/ra_ide/src/impls.rs | 12 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 5 |
8 files changed, 263 insertions, 36 deletions
diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/assists/fill_match_arms.rs index b75bd44eb..99d80998c 100644 --- a/crates/ra_assists/src/assists/fill_match_arms.rs +++ b/crates/ra_assists/src/assists/fill_match_arms.rs | |||
@@ -46,19 +46,24 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist | |||
46 | }; | 46 | }; |
47 | 47 | ||
48 | let expr = match_expr.expr()?; | 48 | let expr = match_expr.expr()?; |
49 | let enum_def = { | 49 | let (enum_def, module) = { |
50 | let analyzer = ctx.source_analyzer(expr.syntax(), None); | 50 | let analyzer = ctx.source_analyzer(expr.syntax(), None); |
51 | resolve_enum_def(ctx.db, &analyzer, &expr)? | 51 | (resolve_enum_def(ctx.db, &analyzer, &expr)?, analyzer.module()?) |
52 | }; | 52 | }; |
53 | let variant_list = enum_def.variant_list()?; | 53 | let variants = enum_def.variants(ctx.db); |
54 | if variants.is_empty() { | ||
55 | return None; | ||
56 | } | ||
57 | |||
58 | let db = ctx.db; | ||
54 | 59 | ||
55 | ctx.add_assist(AssistId("fill_match_arms"), "fill match arms", |edit| { | 60 | ctx.add_assist(AssistId("fill_match_arms"), "fill match arms", |edit| { |
56 | let indent_level = IndentLevel::from_node(match_arm_list.syntax()); | 61 | let indent_level = IndentLevel::from_node(match_arm_list.syntax()); |
57 | 62 | ||
58 | let new_arm_list = { | 63 | let new_arm_list = { |
59 | let variants = variant_list.variants(); | ||
60 | let arms = variants | 64 | let arms = variants |
61 | .filter_map(build_pat) | 65 | .into_iter() |
66 | .filter_map(|variant| build_pat(db, module, variant)) | ||
62 | .map(|pat| make::match_arm(iter::once(pat), make::expr_unit())); | 67 | .map(|pat| make::match_arm(iter::once(pat), make::expr_unit())); |
63 | indent_level.increase_indent(make::match_arm_list(arms)) | 68 | indent_level.increase_indent(make::match_arm_list(arms)) |
64 | }; | 69 | }; |
@@ -80,23 +85,25 @@ fn resolve_enum_def( | |||
80 | db: &impl HirDatabase, | 85 | db: &impl HirDatabase, |
81 | analyzer: &hir::SourceAnalyzer, | 86 | analyzer: &hir::SourceAnalyzer, |
82 | expr: &ast::Expr, | 87 | expr: &ast::Expr, |
83 | ) -> Option<ast::EnumDef> { | 88 | ) -> Option<hir::Enum> { |
84 | let expr_ty = analyzer.type_of(db, &expr)?; | 89 | let expr_ty = analyzer.type_of(db, &expr)?; |
85 | 90 | ||
86 | let res = expr_ty.autoderef(db).find_map(|ty| match ty.as_adt() { | 91 | let result = expr_ty.autoderef(db).find_map(|ty| match ty.as_adt() { |
87 | Some(Adt::Enum(e)) => Some(e.source(db).value), | 92 | Some(Adt::Enum(e)) => Some(e), |
88 | _ => None, | 93 | _ => None, |
89 | }); | 94 | }); |
90 | res | 95 | result |
91 | } | 96 | } |
92 | 97 | ||
93 | fn build_pat(var: ast::EnumVariant) -> Option<ast::Pat> { | 98 | fn build_pat( |
94 | let path = make::path_qualified( | 99 | db: &impl HirDatabase, |
95 | make::path_from_name_ref(make::name_ref(&var.parent_enum().name()?.syntax().to_string())), | 100 | module: hir::Module, |
96 | make::name_ref(&var.name()?.syntax().to_string()), | 101 | var: hir::EnumVariant, |
97 | ); | 102 | ) -> Option<ast::Pat> { |
103 | let path = crate::ast_transform::path_to_ast(module.find_use_path(db, var.into())?); | ||
98 | 104 | ||
99 | let pat: ast::Pat = match var.kind() { | 105 | // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though |
106 | let pat: ast::Pat = match var.source(db).value.kind() { | ||
100 | ast::StructKind::Tuple(field_list) => { | 107 | ast::StructKind::Tuple(field_list) => { |
101 | let pats = | 108 | let pats = |
102 | iter::repeat(make::placeholder_pat().into()).take(field_list.fields().count()); | 109 | iter::repeat(make::placeholder_pat().into()).take(field_list.fields().count()); |
@@ -106,7 +113,7 @@ fn build_pat(var: ast::EnumVariant) -> Option<ast::Pat> { | |||
106 | let pats = field_list.fields().map(|f| make::bind_pat(f.name().unwrap()).into()); | 113 | let pats = field_list.fields().map(|f| make::bind_pat(f.name().unwrap()).into()); |
107 | make::record_pat(path, pats).into() | 114 | make::record_pat(path, pats).into() |
108 | } | 115 | } |
109 | ast::StructKind::Unit => make::path_pat(path).into(), | 116 | ast::StructKind::Unit => make::path_pat(path), |
110 | }; | 117 | }; |
111 | 118 | ||
112 | Some(pat) | 119 | Some(pat) |
@@ -252,4 +259,32 @@ mod tests { | |||
252 | "#, | 259 | "#, |
253 | ); | 260 | ); |
254 | } | 261 | } |
262 | |||
263 | #[test] | ||
264 | fn fill_match_arms_qualifies_path() { | ||
265 | check_assist( | ||
266 | fill_match_arms, | ||
267 | r#" | ||
268 | mod foo { pub enum E { X, Y } } | ||
269 | use foo::E::X; | ||
270 | |||
271 | fn main() { | ||
272 | match X { | ||
273 | <|> | ||
274 | } | ||
275 | } | ||
276 | "#, | ||
277 | r#" | ||
278 | mod foo { pub enum E { X, Y } } | ||
279 | use foo::E::X; | ||
280 | |||
281 | fn main() { | ||
282 | match <|>X { | ||
283 | X => (), | ||
284 | foo::E::Y => (), | ||
285 | } | ||
286 | } | ||
287 | "#, | ||
288 | ); | ||
289 | } | ||
255 | } | 290 | } |
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs index cbddc50ac..eac2903d1 100644 --- a/crates/ra_assists/src/ast_transform.rs +++ b/crates/ra_assists/src/ast_transform.rs | |||
@@ -173,7 +173,7 @@ impl<'a, DB: HirDatabase> AstTransform<'a> for QualifyPaths<'a, DB> { | |||
173 | } | 173 | } |
174 | } | 174 | } |
175 | 175 | ||
176 | fn path_to_ast(path: hir::ModPath) -> ast::Path { | 176 | pub(crate) fn path_to_ast(path: hir::ModPath) -> ast::Path { |
177 | let parse = ast::SourceFile::parse(&path.to_string()); | 177 | let parse = ast::SourceFile::parse(&path.to_string()); |
178 | parse.tree().syntax().descendants().find_map(ast::Path::cast).unwrap() | 178 | parse.tree().syntax().descendants().find_map(ast::Path::cast).unwrap() |
179 | } | 179 | } |
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index df9c151e5..a177cebca 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs | |||
@@ -26,10 +26,11 @@ use hir_ty::{ | |||
26 | }; | 26 | }; |
27 | use ra_db::{CrateId, Edition, FileId}; | 27 | use ra_db::{CrateId, Edition, FileId}; |
28 | use ra_prof::profile; | 28 | use ra_prof::profile; |
29 | use ra_syntax::ast; | 29 | use ra_syntax::ast::{self, AttrsOwner}; |
30 | 30 | ||
31 | use crate::{ | 31 | use crate::{ |
32 | db::{DefDatabase, HirDatabase}, | 32 | db::{DefDatabase, HirDatabase}, |
33 | has_source::HasSource, | ||
33 | CallableDef, HirDisplay, InFile, Name, | 34 | CallableDef, HirDisplay, InFile, Name, |
34 | }; | 35 | }; |
35 | 36 | ||
@@ -805,6 +806,27 @@ impl ImplBlock { | |||
805 | pub fn krate(&self, db: &impl DefDatabase) -> Crate { | 806 | pub fn krate(&self, db: &impl DefDatabase) -> Crate { |
806 | Crate { id: self.module(db).id.krate } | 807 | Crate { id: self.module(db).id.krate } |
807 | } | 808 | } |
809 | |||
810 | pub fn is_builtin_derive(&self, db: &impl DefDatabase) -> Option<InFile<ast::Attr>> { | ||
811 | let src = self.source(db); | ||
812 | let item = src.file_id.is_builtin_derive(db)?; | ||
813 | let hygenic = hir_expand::hygiene::Hygiene::new(db, item.file_id); | ||
814 | |||
815 | let attr = item | ||
816 | .value | ||
817 | .attrs() | ||
818 | .filter_map(|it| { | ||
819 | let path = hir_def::path::ModPath::from_src(it.path()?, &hygenic)?; | ||
820 | if path.as_ident()?.to_string() == "derive" { | ||
821 | Some(it) | ||
822 | } else { | ||
823 | None | ||
824 | } | ||
825 | }) | ||
826 | .last()?; | ||
827 | |||
828 | Some(item.with_value(attr)) | ||
829 | } | ||
808 | } | 830 | } |
809 | 831 | ||
810 | #[derive(Clone, PartialEq, Eq, Debug)] | 832 | #[derive(Clone, PartialEq, Eq, Debug)] |
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs index 51c5f9623..7cf3b59a7 100644 --- a/crates/ra_hir_expand/src/lib.rs +++ b/crates/ra_hir_expand/src/lib.rs | |||
@@ -112,6 +112,21 @@ impl HirFileId { | |||
112 | } | 112 | } |
113 | } | 113 | } |
114 | } | 114 | } |
115 | |||
116 | /// Indicate it is macro file generated for builtin derive | ||
117 | pub fn is_builtin_derive(&self, db: &dyn db::AstDatabase) -> Option<InFile<ast::ModuleItem>> { | ||
118 | match self.0 { | ||
119 | HirFileIdRepr::FileId(_) => None, | ||
120 | HirFileIdRepr::MacroFile(macro_file) => { | ||
121 | let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id); | ||
122 | let item = match loc.def.kind { | ||
123 | MacroDefKind::BuiltInDerive(_) => loc.kind.node(db), | ||
124 | _ => return None, | ||
125 | }; | ||
126 | Some(item.with_value(ast::ModuleItem::cast(item.value.clone())?)) | ||
127 | } | ||
128 | } | ||
129 | } | ||
115 | } | 130 | } |
116 | 131 | ||
117 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | 132 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs index f2e45fa31..b2af3479c 100644 --- a/crates/ra_ide/src/display/navigation_target.rs +++ b/crates/ra_ide/src/display/navigation_target.rs | |||
@@ -251,7 +251,11 @@ impl ToNav for hir::Module { | |||
251 | impl ToNav for hir::ImplBlock { | 251 | impl ToNav for hir::ImplBlock { |
252 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { | 252 | fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { |
253 | let src = self.source(db); | 253 | let src = self.source(db); |
254 | let frange = original_range(db, src.as_ref().map(|it| it.syntax())); | 254 | let frange = if let Some(item) = self.is_builtin_derive(db) { |
255 | original_range(db, item.syntax()) | ||
256 | } else { | ||
257 | original_range(db, src.as_ref().map(|it| it.syntax())) | ||
258 | }; | ||
255 | 259 | ||
256 | NavigationTarget::from_syntax( | 260 | NavigationTarget::from_syntax( |
257 | frange.file_id, | 261 | frange.file_id, |
diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs index 1ec41a117..70b6fde82 100644 --- a/crates/ra_ide/src/extend_selection.rs +++ b/crates/ra_ide/src/extend_selection.rs | |||
@@ -4,20 +4,27 @@ use ra_db::SourceDatabase; | |||
4 | use ra_syntax::{ | 4 | use ra_syntax::{ |
5 | algo::find_covering_element, | 5 | algo::find_covering_element, |
6 | ast::{self, AstNode, AstToken}, | 6 | ast::{self, AstNode, AstToken}, |
7 | Direction, NodeOrToken, | 7 | Direction, NodeOrToken, SyntaxElement, |
8 | SyntaxKind::{self, *}, | 8 | SyntaxKind::{self, *}, |
9 | SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, | 9 | SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, |
10 | }; | 10 | }; |
11 | 11 | ||
12 | use crate::{db::RootDatabase, FileRange}; | 12 | use crate::{db::RootDatabase, expand::descend_into_macros, FileId, FileRange}; |
13 | use hir::db::AstDatabase; | ||
14 | use std::iter::successors; | ||
13 | 15 | ||
14 | // FIXME: restore macro support | ||
15 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { | 16 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { |
16 | let parse = db.parse(frange.file_id); | 17 | let src = db.parse(frange.file_id).tree(); |
17 | try_extend_selection(parse.tree().syntax(), frange.range).unwrap_or(frange.range) | 18 | try_extend_selection(db, src.syntax(), frange).unwrap_or(frange.range) |
18 | } | 19 | } |
19 | 20 | ||
20 | fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> { | 21 | fn try_extend_selection( |
22 | db: &RootDatabase, | ||
23 | root: &SyntaxNode, | ||
24 | frange: FileRange, | ||
25 | ) -> Option<TextRange> { | ||
26 | let range = frange.range; | ||
27 | |||
21 | let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING]; | 28 | let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING]; |
22 | let list_kinds = [ | 29 | let list_kinds = [ |
23 | RECORD_FIELD_PAT_LIST, | 30 | RECORD_FIELD_PAT_LIST, |
@@ -72,12 +79,21 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange | |||
72 | } | 79 | } |
73 | NodeOrToken::Node(node) => node, | 80 | NodeOrToken::Node(node) => node, |
74 | }; | 81 | }; |
82 | |||
83 | // if we are in single token_tree, we maybe live in macro or attr | ||
84 | if node.kind() == TOKEN_TREE { | ||
85 | if let Some(macro_call) = node.ancestors().find_map(ast::MacroCall::cast) { | ||
86 | if let Some(range) = extend_tokens_from_range(db, frange.file_id, macro_call, range) { | ||
87 | return Some(range); | ||
88 | } | ||
89 | } | ||
90 | } | ||
91 | |||
75 | if node.text_range() != range { | 92 | if node.text_range() != range { |
76 | return Some(node.text_range()); | 93 | return Some(node.text_range()); |
77 | } | 94 | } |
78 | 95 | ||
79 | // Using shallowest node with same range allows us to traverse siblings. | 96 | let node = shallowest_node(&node.into()).unwrap(); |
80 | let node = node.ancestors().take_while(|n| n.text_range() == node.text_range()).last().unwrap(); | ||
81 | 97 | ||
82 | if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) { | 98 | if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) { |
83 | if let Some(range) = extend_list_item(&node) { | 99 | if let Some(range) = extend_list_item(&node) { |
@@ -88,6 +104,94 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange | |||
88 | node.parent().map(|it| it.text_range()) | 104 | node.parent().map(|it| it.text_range()) |
89 | } | 105 | } |
90 | 106 | ||
107 | fn extend_tokens_from_range( | ||
108 | db: &RootDatabase, | ||
109 | file_id: FileId, | ||
110 | macro_call: ast::MacroCall, | ||
111 | original_range: TextRange, | ||
112 | ) -> Option<TextRange> { | ||
113 | let src = find_covering_element(¯o_call.syntax(), original_range); | ||
114 | let (first_token, last_token) = match src { | ||
115 | NodeOrToken::Node(it) => (it.first_token()?, it.last_token()?), | ||
116 | NodeOrToken::Token(it) => (it.clone(), it), | ||
117 | }; | ||
118 | |||
119 | let mut first_token = skip_whitespace(first_token, Direction::Next)?; | ||
120 | let mut last_token = skip_whitespace(last_token, Direction::Prev)?; | ||
121 | |||
122 | while !first_token.text_range().is_subrange(&original_range) { | ||
123 | first_token = skip_whitespace(first_token.next_token()?, Direction::Next)?; | ||
124 | } | ||
125 | while !last_token.text_range().is_subrange(&original_range) { | ||
126 | last_token = skip_whitespace(last_token.prev_token()?, Direction::Prev)?; | ||
127 | } | ||
128 | |||
129 | // compute original mapped token range | ||
130 | let expanded = { | ||
131 | let first_node = descend_into_macros(db, file_id, first_token.clone()); | ||
132 | let first_node = first_node.map(|it| it.text_range()); | ||
133 | |||
134 | let last_node = descend_into_macros(db, file_id, last_token.clone()); | ||
135 | if last_node.file_id == file_id.into() || first_node.file_id != last_node.file_id { | ||
136 | return None; | ||
137 | } | ||
138 | first_node.map(|it| union_range(it, last_node.value.text_range())) | ||
139 | }; | ||
140 | |||
141 | // Compute parent node range | ||
142 | let src = db.parse_or_expand(expanded.file_id)?; | ||
143 | let parent = shallowest_node(&find_covering_element(&src, expanded.value))?.parent()?; | ||
144 | |||
145 | let validate = |token: SyntaxToken| { | ||
146 | let node = descend_into_macros(db, file_id, token.clone()); | ||
147 | if node.file_id == expanded.file_id | ||
148 | && node.value.text_range().is_subrange(&parent.text_range()) | ||
149 | { | ||
150 | Some(token) | ||
151 | } else { | ||
152 | None | ||
153 | } | ||
154 | }; | ||
155 | |||
156 | // Find the first and last text range under expanded parent | ||
157 | let first = successors(Some(first_token), |token| { | ||
158 | validate(skip_whitespace(token.prev_token()?, Direction::Prev)?) | ||
159 | }) | ||
160 | .last()?; | ||
161 | let last = successors(Some(last_token), |token| { | ||
162 | validate(skip_whitespace(token.next_token()?, Direction::Next)?) | ||
163 | }) | ||
164 | .last()?; | ||
165 | |||
166 | let range = union_range(first.text_range(), last.text_range()); | ||
167 | if original_range.is_subrange(&range) && original_range != range { | ||
168 | Some(range) | ||
169 | } else { | ||
170 | None | ||
171 | } | ||
172 | } | ||
173 | |||
174 | fn skip_whitespace(mut token: SyntaxToken, direction: Direction) -> Option<SyntaxToken> { | ||
175 | while token.kind() == WHITESPACE { | ||
176 | token = match direction { | ||
177 | Direction::Next => token.next_token()?, | ||
178 | Direction::Prev => token.prev_token()?, | ||
179 | } | ||
180 | } | ||
181 | Some(token) | ||
182 | } | ||
183 | |||
184 | fn union_range(range: TextRange, r: TextRange) -> TextRange { | ||
185 | let start = range.start().min(r.start()); | ||
186 | let end = range.end().max(r.end()); | ||
187 | TextRange::from_to(start, end) | ||
188 | } | ||
189 | |||
190 | /// Find the shallowest node with same range, which allows us to traverse siblings. | ||
191 | fn shallowest_node(node: &SyntaxElement) -> Option<SyntaxNode> { | ||
192 | node.ancestors().take_while(|n| n.text_range() == node.text_range()).last() | ||
193 | } | ||
194 | |||
91 | fn extend_single_word_in_comment_or_string( | 195 | fn extend_single_word_in_comment_or_string( |
92 | leaf: &SyntaxToken, | 196 | leaf: &SyntaxToken, |
93 | offset: TextUnit, | 197 | offset: TextUnit, |
@@ -227,18 +331,19 @@ fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment { | |||
227 | 331 | ||
228 | #[cfg(test)] | 332 | #[cfg(test)] |
229 | mod tests { | 333 | mod tests { |
230 | use ra_syntax::{AstNode, SourceFile}; | ||
231 | use test_utils::extract_offset; | ||
232 | |||
233 | use super::*; | 334 | use super::*; |
335 | use crate::mock_analysis::single_file; | ||
336 | use test_utils::extract_offset; | ||
234 | 337 | ||
235 | fn do_check(before: &str, afters: &[&str]) { | 338 | fn do_check(before: &str, afters: &[&str]) { |
236 | let (cursor, before) = extract_offset(before); | 339 | let (cursor, before) = extract_offset(before); |
237 | let parse = SourceFile::parse(&before); | 340 | let (analysis, file_id) = single_file(&before); |
238 | let mut range = TextRange::offset_len(cursor, 0.into()); | 341 | let range = TextRange::offset_len(cursor, 0.into()); |
342 | let mut frange = FileRange { file_id: file_id, range }; | ||
343 | |||
239 | for &after in afters { | 344 | for &after in afters { |
240 | range = try_extend_selection(parse.tree().syntax(), range).unwrap(); | 345 | frange.range = analysis.extend_selection(frange).unwrap(); |
241 | let actual = &before[range]; | 346 | let actual = &before[frange.range]; |
242 | assert_eq!(after, actual); | 347 | assert_eq!(after, actual); |
243 | } | 348 | } |
244 | } | 349 | } |
@@ -503,4 +608,37 @@ fn main() { let var = ( | |||
503 | ], | 608 | ], |
504 | ); | 609 | ); |
505 | } | 610 | } |
611 | |||
612 | #[test] | ||
613 | fn extend_selection_inside_macros() { | ||
614 | do_check( | ||
615 | r#"macro_rules! foo { ($item:item) => {$item} } | ||
616 | foo!{fn hello(na<|>me:usize){}}"#, | ||
617 | &[ | ||
618 | "name", | ||
619 | "name:usize", | ||
620 | "(name:usize)", | ||
621 | "fn hello(name:usize){}", | ||
622 | "{fn hello(name:usize){}}", | ||
623 | "foo!{fn hello(name:usize){}}", | ||
624 | ], | ||
625 | ); | ||
626 | } | ||
627 | |||
628 | #[test] | ||
629 | fn extend_selection_inside_recur_macros() { | ||
630 | do_check( | ||
631 | r#" macro_rules! foo2 { ($item:item) => {$item} } | ||
632 | macro_rules! foo { ($item:item) => {foo2!($item);} } | ||
633 | foo!{fn hello(na<|>me:usize){}}"#, | ||
634 | &[ | ||
635 | "name", | ||
636 | "name:usize", | ||
637 | "(name:usize)", | ||
638 | "fn hello(name:usize){}", | ||
639 | "{fn hello(name:usize){}}", | ||
640 | "foo!{fn hello(name:usize){}}", | ||
641 | ], | ||
642 | ); | ||
643 | } | ||
506 | } | 644 | } |
diff --git a/crates/ra_ide/src/impls.rs b/crates/ra_ide/src/impls.rs index 9b165ee2a..31195036e 100644 --- a/crates/ra_ide/src/impls.rs +++ b/crates/ra_ide/src/impls.rs | |||
@@ -203,4 +203,16 @@ mod tests { | |||
203 | ], | 203 | ], |
204 | ); | 204 | ); |
205 | } | 205 | } |
206 | |||
207 | #[test] | ||
208 | fn goto_implementation_to_builtin_derive() { | ||
209 | check_goto( | ||
210 | " | ||
211 | //- /lib.rs | ||
212 | #[derive(Copy)] | ||
213 | struct Foo<|>; | ||
214 | ", | ||
215 | &["impl IMPL_BLOCK FileId(1) [0; 15)"], | ||
216 | ); | ||
217 | } | ||
206 | } | 218 | } |
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 9781b748f..eef45090d 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -117,10 +117,11 @@ pub fn record_pat(path: ast::Path, pats: impl IntoIterator<Item = ast::Pat>) -> | |||
117 | } | 117 | } |
118 | } | 118 | } |
119 | 119 | ||
120 | pub fn path_pat(path: ast::Path) -> ast::PathPat { | 120 | /// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise. |
121 | pub fn path_pat(path: ast::Path) -> ast::Pat { | ||
121 | let path_str = path.syntax().text().to_string(); | 122 | let path_str = path.syntax().text().to_string(); |
122 | return from_text(path_str.as_str()); | 123 | return from_text(path_str.as_str()); |
123 | fn from_text(text: &str) -> ast::PathPat { | 124 | fn from_text(text: &str) -> ast::Pat { |
124 | ast_from_text(&format!("fn f({}: ())", text)) | 125 | ast_from_text(&format!("fn f({}: ())", text)) |
125 | } | 126 | } |
126 | } | 127 | } |