diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-04-21 11:34:07 +0100 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-04-21 11:34:07 +0100 |
commit | fa15c4d75e87bd3bf761c91f030c76aec59308ae (patch) | |
tree | ba7a3495b13e41231076d5a0e491132bbdb0670e | |
parent | 493bf20b3d1a0a890514d5252901f13d2878ff34 (diff) | |
parent | 9e35bf91b827900b089a7ea937cb73707bebc420 (diff) |
Merge #1175
1175: Fix bugs and add error log about macro expansion r=matklad a=edwin0cheng
This PR fixed / add following things:
* Add a fused count which stop recursion of macro expansion in name resolution.
* Add some logs when macro expansion fails
* Add `$crate` meta variable support in mbe, which create a `$crate` ident token in token tree.
* Fixed matching a `$REPEAT` pattern inside a subtree, e.g. `(fn $name:ident {$($i:ident)*} ) => {...}`
* Remove composite-able punct token in syntax node to token conversion.
Co-authored-by: Edwin Cheng <[email protected]>
-rw-r--r-- | crates/ra_hir/src/ids.rs | 24 | ||||
-rw-r--r-- | crates/ra_hir/src/nameres/collector.rs | 21 | ||||
-rw-r--r-- | crates/ra_mbe/src/lib.rs | 110 | ||||
-rw-r--r-- | crates/ra_mbe/src/mbe_expander.rs | 30 | ||||
-rw-r--r-- | crates/ra_mbe/src/subtree_source.rs | 19 |
5 files changed, 154 insertions, 50 deletions
diff --git a/crates/ra_hir/src/ids.rs b/crates/ra_hir/src/ids.rs index 141c9072f..2a1ed9b81 100644 --- a/crates/ra_hir/src/ids.rs +++ b/crates/ra_hir/src/ids.rs | |||
@@ -63,8 +63,15 @@ impl HirFileId { | |||
63 | match file_id.0 { | 63 | match file_id.0 { |
64 | HirFileIdRepr::File(file_id) => db.parse(file_id), | 64 | HirFileIdRepr::File(file_id) => db.parse(file_id), |
65 | HirFileIdRepr::Macro(macro_call_id) => { | 65 | HirFileIdRepr::Macro(macro_call_id) => { |
66 | // returning an empty string looks fishy... | 66 | parse_macro(db, macro_call_id).unwrap_or_else(|| { |
67 | parse_macro(db, macro_call_id).unwrap_or_else(|| SourceFile::parse("")) | 67 | // Note: |
68 | // The final goal we would like to make all parse_macro success, | ||
69 | // such that the following log will not call anyway. | ||
70 | log::warn!("fail on macro_parse: {}", macro_call_id.debug_dump(db)); | ||
71 | |||
72 | // returning an empty string looks fishy... | ||
73 | SourceFile::parse("") | ||
74 | }) | ||
68 | } | 75 | } |
69 | } | 76 | } |
70 | } | 77 | } |
@@ -299,3 +306,16 @@ impl AstItemDef<ast::TypeAliasDef> for TypeAliasId { | |||
299 | db.lookup_intern_type_alias(self) | 306 | db.lookup_intern_type_alias(self) |
300 | } | 307 | } |
301 | } | 308 | } |
309 | |||
310 | impl MacroCallId { | ||
311 | pub fn debug_dump(&self, db: &impl DefDatabase) -> String { | ||
312 | let loc = self.clone().loc(db); | ||
313 | let node = loc.ast_id.to_node(db); | ||
314 | let syntax_str = node.syntax().to_string(); | ||
315 | |||
316 | // dump the file name | ||
317 | let file_id: HirFileId = self.clone().into(); | ||
318 | let original = file_id.original_file(db); | ||
319 | format!("macro call [file: {:#?}] : {}", db.file_relative_path(original), syntax_str) | ||
320 | } | ||
321 | } | ||
diff --git a/crates/ra_hir/src/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs index 39cadc94a..6147b3219 100644 --- a/crates/ra_hir/src/nameres/collector.rs +++ b/crates/ra_hir/src/nameres/collector.rs | |||
@@ -42,6 +42,7 @@ pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> C | |||
42 | unresolved_imports: Vec::new(), | 42 | unresolved_imports: Vec::new(), |
43 | unexpanded_macros: Vec::new(), | 43 | unexpanded_macros: Vec::new(), |
44 | global_macro_scope: FxHashMap::default(), | 44 | global_macro_scope: FxHashMap::default(), |
45 | marco_stack_count: 0, | ||
45 | }; | 46 | }; |
46 | collector.collect(); | 47 | collector.collect(); |
47 | collector.finish() | 48 | collector.finish() |
@@ -55,6 +56,10 @@ struct DefCollector<DB> { | |||
55 | unresolved_imports: Vec<(CrateModuleId, raw::ImportId, raw::ImportData)>, | 56 | unresolved_imports: Vec<(CrateModuleId, raw::ImportId, raw::ImportData)>, |
56 | unexpanded_macros: Vec<(CrateModuleId, AstId<ast::MacroCall>, Path)>, | 57 | unexpanded_macros: Vec<(CrateModuleId, AstId<ast::MacroCall>, Path)>, |
57 | global_macro_scope: FxHashMap<Name, MacroDefId>, | 58 | global_macro_scope: FxHashMap<Name, MacroDefId>, |
59 | |||
60 | /// Some macro use `$tt:tt which mean we have to handle the macro perfectly | ||
61 | /// To prevent stackoverflow, we add a deep counter here for prevent that. | ||
62 | marco_stack_count: u32, | ||
58 | } | 63 | } |
59 | 64 | ||
60 | impl<'a, DB> DefCollector<&'a DB> | 65 | impl<'a, DB> DefCollector<&'a DB> |
@@ -324,10 +329,18 @@ where | |||
324 | } | 329 | } |
325 | 330 | ||
326 | fn collect_macro_expansion(&mut self, module_id: CrateModuleId, macro_call_id: MacroCallId) { | 331 | fn collect_macro_expansion(&mut self, module_id: CrateModuleId, macro_call_id: MacroCallId) { |
327 | let file_id: HirFileId = macro_call_id.into(); | 332 | self.marco_stack_count += 1; |
328 | let raw_items = self.db.raw_items(file_id); | 333 | |
329 | ModCollector { def_collector: &mut *self, file_id, module_id, raw_items: &raw_items } | 334 | if self.marco_stack_count < 300 { |
330 | .collect(raw_items.items()) | 335 | let file_id: HirFileId = macro_call_id.into(); |
336 | let raw_items = self.db.raw_items(file_id); | ||
337 | ModCollector { def_collector: &mut *self, file_id, module_id, raw_items: &raw_items } | ||
338 | .collect(raw_items.items()) | ||
339 | } else { | ||
340 | log::error!("Too deep macro expansion: {}", macro_call_id.debug_dump(self.db)); | ||
341 | } | ||
342 | |||
343 | self.marco_stack_count -= 1; | ||
331 | } | 344 | } |
332 | 345 | ||
333 | fn finish(self) -> CrateDefMap { | 346 | fn finish(self) -> CrateDefMap { |
diff --git a/crates/ra_mbe/src/lib.rs b/crates/ra_mbe/src/lib.rs index d2115bd67..9aad08db9 100644 --- a/crates/ra_mbe/src/lib.rs +++ b/crates/ra_mbe/src/lib.rs | |||
@@ -214,14 +214,15 @@ impl_froms!(TokenTree: Leaf, Subtree); | |||
214 | let tree = token_tree_to_macro_items(&expanded); | 214 | let tree = token_tree_to_macro_items(&expanded); |
215 | 215 | ||
216 | // Eat all white space by parse it back and forth | 216 | // Eat all white space by parse it back and forth |
217 | let expansion = ast::SourceFile::parse(expansion); | 217 | // Because $crate will seperate in two token , will do some special treatment here |
218 | let expansion = expansion.replace("$crate", "C_C__C"); | ||
219 | let expansion = ast::SourceFile::parse(&expansion); | ||
218 | let expansion = syntax_node_to_token_tree(expansion.syntax()).unwrap().0; | 220 | let expansion = syntax_node_to_token_tree(expansion.syntax()).unwrap().0; |
219 | let file = token_tree_to_macro_items(&expansion); | 221 | let file = token_tree_to_macro_items(&expansion); |
222 | let file = file.unwrap().syntax().debug_dump().trim().to_string(); | ||
223 | let file = file.replace("C_C__C", "$crate"); | ||
220 | 224 | ||
221 | assert_eq!( | 225 | assert_eq!(tree.unwrap().syntax().debug_dump().trim(), file,); |
222 | tree.unwrap().syntax().debug_dump().trim(), | ||
223 | file.unwrap().syntax().debug_dump().trim() | ||
224 | ); | ||
225 | } | 226 | } |
226 | 227 | ||
227 | #[test] | 228 | #[test] |
@@ -348,7 +349,36 @@ impl_froms!(TokenTree: Leaf, Subtree); | |||
348 | } | 349 | } |
349 | 350 | ||
350 | #[test] | 351 | #[test] |
351 | fn expand_to_item_list() { | 352 | fn test_match_group_empty_fixed_token() { |
353 | let rules = create_rules( | ||
354 | r#" | ||
355 | macro_rules! foo { | ||
356 | ($ ($ i:ident)* #abc) => ( fn baz { $ ( | ||
357 | $ i (); | ||
358 | )*} ); | ||
359 | } | ||
360 | "#, | ||
361 | ); | ||
362 | |||
363 | assert_expansion(&rules, "foo! {#abc}", "fn baz {}"); | ||
364 | } | ||
365 | |||
366 | #[test] | ||
367 | fn test_match_group_in_subtree() { | ||
368 | let rules = create_rules( | ||
369 | r#" | ||
370 | macro_rules! foo { | ||
371 | (fn $name:ident {$($i:ident)*} ) => ( fn $name() { $ ( | ||
372 | $ i (); | ||
373 | )*} ); | ||
374 | }"#, | ||
375 | ); | ||
376 | |||
377 | assert_expansion(&rules, "foo! {fn baz {a b} }", "fn baz () {a () ; b () ;}"); | ||
378 | } | ||
379 | |||
380 | #[test] | ||
381 | fn test_expand_to_item_list() { | ||
352 | let rules = create_rules( | 382 | let rules = create_rules( |
353 | " | 383 | " |
354 | macro_rules! structs { | 384 | macro_rules! structs { |
@@ -401,7 +431,7 @@ MACRO_ITEMS@[0; 40) | |||
401 | } | 431 | } |
402 | 432 | ||
403 | #[test] | 433 | #[test] |
404 | fn expand_literals_to_token_tree() { | 434 | fn test_expand_literals_to_token_tree() { |
405 | fn to_subtree(tt: &tt::TokenTree) -> &tt::Subtree { | 435 | fn to_subtree(tt: &tt::TokenTree) -> &tt::Subtree { |
406 | if let tt::TokenTree::Subtree(subtree) = tt { | 436 | if let tt::TokenTree::Subtree(subtree) = tt { |
407 | return &subtree; | 437 | return &subtree; |
@@ -763,30 +793,29 @@ MACRO_ITEMS@[0; 40) | |||
763 | ); | 793 | ); |
764 | } | 794 | } |
765 | 795 | ||
766 | // #[test] | 796 | #[test] |
767 | // fn test_tt_block() { | 797 | // fn test_tt_block() { |
768 | // let rules = create_rules( | 798 | // let rules = create_rules( |
769 | // r#" | 799 | // r#" |
770 | // macro_rules! foo { | 800 | // macro_rules! foo { |
771 | // ($ i:tt) => { fn foo() $ i } | 801 | // ($ i:tt) => { fn foo() $ i } |
772 | // } | 802 | // } |
773 | // "#, | 803 | // "#, |
774 | // ); | 804 | // ); |
775 | // assert_expansion(&rules, r#"foo! { { 1; } }"#, r#"fn foo () {1 ;}"#); | 805 | // assert_expansion(&rules, r#"foo! { { 1; } }"#, r#"fn foo () {1 ;}"#); |
776 | // } | 806 | // } |
777 | 807 | ||
778 | // #[test] | 808 | // #[test] |
779 | // fn test_tt_group() { | 809 | // fn test_tt_group() { |
780 | // let rules = create_rules( | 810 | // let rules = create_rules( |
781 | // r#" | 811 | // r#" |
782 | // macro_rules! foo { | 812 | // macro_rules! foo { |
783 | // ($($ i:tt)*) => { $($ i)* } | 813 | // ($($ i:tt)*) => { $($ i)* } |
784 | // } | 814 | // } |
785 | // "#, | 815 | // "#, |
786 | // ); | 816 | // ); |
787 | // assert_expansion(&rules, r#"foo! { fn foo() {} }"#, r#"fn foo () {}"#); | 817 | // assert_expansion(&rules, r#"foo! { fn foo() {} }"#, r#"fn foo () {}"#); |
788 | // } | 818 | // } |
789 | |||
790 | #[test] | 819 | #[test] |
791 | fn test_lifetime() { | 820 | fn test_lifetime() { |
792 | let rules = create_rules( | 821 | let rules = create_rules( |
@@ -822,4 +851,39 @@ MACRO_ITEMS@[0; 40) | |||
822 | ); | 851 | ); |
823 | assert_expansion(&rules, r#"foo!(pub foo);"#, r#"pub fn foo () {}"#); | 852 | assert_expansion(&rules, r#"foo!(pub foo);"#, r#"pub fn foo () {}"#); |
824 | } | 853 | } |
854 | |||
855 | // The following tests are based on real world situations | ||
856 | #[test] | ||
857 | fn test_winapi_struct() { | ||
858 | // from https://github.com/retep998/winapi-rs/blob/a7ef2bca086aae76cf6c4ce4c2552988ed9798ad/src/macros.rs#L366 | ||
859 | |||
860 | let rules = create_rules( | ||
861 | r#" | ||
862 | macro_rules! STRUCT { | ||
863 | ($(#[$attrs:meta])* struct $name:ident { | ||
864 | $($field:ident: $ftype:ty,)+ | ||
865 | }) => ( | ||
866 | #[repr(C)] #[derive(Copy)] $(#[$attrs])* | ||
867 | pub struct $name { | ||
868 | $(pub $field: $ftype,)+ | ||
869 | } | ||
870 | impl Clone for $name { | ||
871 | #[inline] | ||
872 | fn clone(&self) -> $name { *self } | ||
873 | } | ||
874 | #[cfg(feature = "impl-default")] | ||
875 | impl Default for $name { | ||
876 | #[inline] | ||
877 | fn default() -> $name { unsafe { $crate::_core::mem::zeroed() } } | ||
878 | } | ||
879 | ); | ||
880 | } | ||
881 | "#, | ||
882 | ); | ||
883 | // from https://github.com/retep998/winapi-rs/blob/a7ef2bca086aae76cf6c4ce4c2552988ed9798ad/src/shared/d3d9caps.rs | ||
884 | assert_expansion(&rules, r#"STRUCT!{struct D3DVSHADERCAPS2_0 {Caps: u8,}}"#, | ||
885 | "# [repr (C)] # [derive (Copy)] pub struct D3DVSHADERCAPS2_0 {pub Caps : u8 ,} impl Clone for D3DVSHADERCAPS2_0 {# [inline] fn clone (& self) -> D3DVSHADERCAPS2_0 {* self}} # [cfg (feature = \"impl-default\")] impl Default for D3DVSHADERCAPS2_0 {# [inline] fn default () -> D3DVSHADERCAPS2_0 {unsafe {$crate :: _core :: mem :: zeroed ()}}}"); | ||
886 | assert_expansion(&rules, r#"STRUCT!{#[cfg_attr(target_arch = "x86", repr(packed))] struct D3DCONTENTPROTECTIONCAPS {Caps : u8 ,}}"#, | ||
887 | "# [repr (C)] # [derive (Copy)] # [cfg_attr (target_arch = \"x86\" , repr (packed))] pub struct D3DCONTENTPROTECTIONCAPS {pub Caps : u8 ,} impl Clone for D3DCONTENTPROTECTIONCAPS {# [inline] fn clone (& self) -> D3DCONTENTPROTECTIONCAPS {* self}} # [cfg (feature = \"impl-default\")] impl Default for D3DCONTENTPROTECTIONCAPS {# [inline] fn default () -> D3DCONTENTPROTECTIONCAPS {unsafe {$crate :: _core :: mem :: zeroed ()}}}"); | ||
888 | } | ||
825 | } | 889 | } |
diff --git a/crates/ra_mbe/src/mbe_expander.rs b/crates/ra_mbe/src/mbe_expander.rs index 86867111f..66ea76698 100644 --- a/crates/ra_mbe/src/mbe_expander.rs +++ b/crates/ra_mbe/src/mbe_expander.rs | |||
@@ -121,6 +121,10 @@ impl Bindings { | |||
121 | } | 121 | } |
122 | Ok(()) | 122 | Ok(()) |
123 | } | 123 | } |
124 | |||
125 | fn merge(&mut self, nested: Bindings) { | ||
126 | self.inner.extend(nested.inner); | ||
127 | } | ||
124 | } | 128 | } |
125 | 129 | ||
126 | fn match_lhs(pattern: &crate::Subtree, input: &mut TtCursor) -> Result<Bindings, ExpandError> { | 130 | fn match_lhs(pattern: &crate::Subtree, input: &mut TtCursor) -> Result<Bindings, ExpandError> { |
@@ -236,7 +240,21 @@ fn match_lhs(pattern: &crate::Subtree, input: &mut TtCursor) -> Result<Bindings, | |||
236 | } | 240 | } |
237 | } | 241 | } |
238 | } | 242 | } |
239 | _ => {} | 243 | crate::TokenTree::Subtree(subtree) => { |
244 | let input_subtree = | ||
245 | input.eat_subtree().map_err(|_| ExpandError::UnexpectedToken)?; | ||
246 | if subtree.delimiter != input_subtree.delimiter { | ||
247 | return Err(ExpandError::UnexpectedToken); | ||
248 | } | ||
249 | |||
250 | let mut input = TtCursor::new(input_subtree); | ||
251 | let bindings = match_lhs(&subtree, &mut input)?; | ||
252 | if !input.is_eof() { | ||
253 | return Err(ExpandError::UnexpectedToken); | ||
254 | } | ||
255 | |||
256 | res.merge(bindings); | ||
257 | } | ||
240 | } | 258 | } |
241 | } | 259 | } |
242 | Ok(res) | 260 | Ok(res) |
@@ -287,7 +305,15 @@ fn expand_tt( | |||
287 | .into() | 305 | .into() |
288 | } | 306 | } |
289 | crate::Leaf::Punct(punct) => tt::Leaf::from(punct.clone()).into(), | 307 | crate::Leaf::Punct(punct) => tt::Leaf::from(punct.clone()).into(), |
290 | crate::Leaf::Var(v) => bindings.get(&v.text, nesting)?.clone(), | 308 | crate::Leaf::Var(v) => { |
309 | if v.text == "crate" { | ||
310 | // FIXME: Properly handle $crate token | ||
311 | tt::Leaf::from(tt::Ident { text: "$crate".into(), id: TokenId::unspecified() }) | ||
312 | .into() | ||
313 | } else { | ||
314 | bindings.get(&v.text, nesting)?.clone() | ||
315 | } | ||
316 | } | ||
291 | crate::Leaf::Literal(l) => tt::Leaf::from(tt::Literal { text: l.text.clone() }).into(), | 317 | crate::Leaf::Literal(l) => tt::Leaf::from(tt::Literal { text: l.text.clone() }).into(), |
292 | }, | 318 | }, |
293 | }; | 319 | }; |
diff --git a/crates/ra_mbe/src/subtree_source.rs b/crates/ra_mbe/src/subtree_source.rs index 0a070b46a..16a053b49 100644 --- a/crates/ra_mbe/src/subtree_source.rs +++ b/crates/ra_mbe/src/subtree_source.rs | |||
@@ -379,8 +379,6 @@ where | |||
379 | { | 379 | { |
380 | if let Some((m, is_joint_to_next)) = iter.current_punct3(p) { | 380 | if let Some((m, is_joint_to_next)) = iter.current_punct3(p) { |
381 | if let Some((kind, text)) = match m { | 381 | if let Some((kind, text)) = match m { |
382 | ('<', '<', '=') => Some((SHLEQ, "<<=")), | ||
383 | ('>', '>', '=') => Some((SHREQ, ">>=")), | ||
384 | ('.', '.', '.') => Some((DOTDOTDOT, "...")), | 382 | ('.', '.', '.') => Some((DOTDOTDOT, "...")), |
385 | ('.', '.', '=') => Some((DOTDOTEQ, "..=")), | 383 | ('.', '.', '=') => Some((DOTDOTEQ, "..=")), |
386 | _ => None, | 384 | _ => None, |
@@ -391,23 +389,6 @@ where | |||
391 | 389 | ||
392 | if let Some((m, is_joint_to_next)) = iter.current_punct2(p) { | 390 | if let Some((m, is_joint_to_next)) = iter.current_punct2(p) { |
393 | if let Some((kind, text)) = match m { | 391 | if let Some((kind, text)) = match m { |
394 | ('<', '<') => Some((SHL, "<<")), | ||
395 | ('>', '>') => Some((SHR, ">>")), | ||
396 | |||
397 | ('|', '|') => Some((PIPEPIPE, "||")), | ||
398 | ('&', '&') => Some((AMPAMP, "&&")), | ||
399 | ('%', '=') => Some((PERCENTEQ, "%=")), | ||
400 | ('*', '=') => Some((STAREQ, "*=")), | ||
401 | ('/', '=') => Some((SLASHEQ, "/=")), | ||
402 | ('^', '=') => Some((CARETEQ, "^=")), | ||
403 | |||
404 | ('&', '=') => Some((AMPEQ, "&=")), | ||
405 | ('|', '=') => Some((PIPEEQ, "|=")), | ||
406 | ('-', '=') => Some((MINUSEQ, "-=")), | ||
407 | ('+', '=') => Some((PLUSEQ, "+=")), | ||
408 | ('>', '=') => Some((GTEQ, ">=")), | ||
409 | ('<', '=') => Some((LTEQ, "<=")), | ||
410 | |||
411 | ('-', '>') => Some((THIN_ARROW, "->")), | 392 | ('-', '>') => Some((THIN_ARROW, "->")), |
412 | ('!', '=') => Some((NEQ, "!=")), | 393 | ('!', '=') => Some((NEQ, "!=")), |
413 | ('=', '>') => Some((FAT_ARROW, "=>")), | 394 | ('=', '>') => Some((FAT_ARROW, "=>")), |