aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-12-05 20:00:20 +0000
committerGitHub <[email protected]>2019-12-05 20:00:20 +0000
commit6e10a9f57815ad865a570816436adfdf0de1cdf0 (patch)
tree410c416bfe9daa05743ce8c49418c20df28dd625 /crates
parent217a6fa4a387dbfe6ac725b6dba2f15d6532679f (diff)
parent10697041c1c72ddbe27c41912e691656be6ccce4 (diff)
Merge #2479
2479: Add expansion infrastructure for derive macros r=matklad a=flodiebold I thought I'd experiment a bit with attribute macro/derive expansion, and here's what I've got so far. It has dummy implementations of the Copy / Clone derives, to show that the approach works; it doesn't add any attribute macro support, but I think that fits into the architecture. Basically, during raw item collection, we look at the attributes and generate macro calls for them if necessary. Currently I only do this for derives, and just add the derive macro calls as separate calls next to the item. I think for derives, it's important that they don't obscure the actual item, since they can't actually change it (e.g. sending the item token tree through macro expansion unnecessarily might make completion within it more complicated). Attribute macros would have to be recognized at that stage and replace the item (i.e., the raw item collector will just emit an attribute macro call, and not the item). I think when we implement this, we should try to recognize known inert attributes, so that we don't do macro expansion unnecessarily; anything that isn't known needs to be treated as a possible attribute macro call (since the raw item collector can't resolve the macro yet). There's basically no name resolution for attribute macros implemented, I just hardcoded the built-in derives. In the future, the built-ins should work within the normal name resolution infrastructure; the problem there is that the builtin stubs in `std` use macros 2.0, which we don't support yet (and adding support is outside the scope of this). One aspect that I don't really have a solution for, but I don't know how important it is, is removing the attribute itself from its input. I'm pretty sure rustc leaves out the attribute macro from the input, but to do that, we'd have to create a completely new syntax node. I guess we could do it when / after converting to a token tree. Co-authored-by: Florian Diebold <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_hir/src/code_model/src.rs5
-rw-r--r--crates/ra_hir/src/from_source.rs4
-rw-r--r--crates/ra_hir/src/source_binder.rs5
-rw-r--r--crates/ra_hir_def/src/attr.rs4
-rw-r--r--crates/ra_hir_def/src/body.rs6
-rw-r--r--crates/ra_hir_def/src/docs.rs2
-rw-r--r--crates/ra_hir_def/src/nameres/collector.rs73
-rw-r--r--crates/ra_hir_def/src/nameres/raw.rs15
-rw-r--r--crates/ra_hir_def/src/nameres/tests/macros.rs24
-rw-r--r--crates/ra_hir_def/src/path.rs5
-rw-r--r--crates/ra_hir_expand/src/ast_id_map.rs12
-rw-r--r--crates/ra_hir_expand/src/builtin_derive.rs301
-rw-r--r--crates/ra_hir_expand/src/builtin_macro.rs37
-rw-r--r--crates/ra_hir_expand/src/db.rs19
-rw-r--r--crates/ra_hir_expand/src/hygiene.rs3
-rw-r--r--crates/ra_hir_expand/src/lib.rs76
-rw-r--r--crates/ra_hir_expand/src/name.rs17
-rw-r--r--crates/ra_hir_expand/src/quote.rs10
-rw-r--r--crates/ra_hir_ty/src/tests/macros.rs51
-rw-r--r--crates/ra_mbe/src/syntax_bridge.rs44
20 files changed, 632 insertions, 81 deletions
diff --git a/crates/ra_hir/src/code_model/src.rs b/crates/ra_hir/src/code_model/src.rs
index d9bccd902..78a454082 100644
--- a/crates/ra_hir/src/code_model/src.rs
+++ b/crates/ra_hir/src/code_model/src.rs
@@ -105,7 +105,10 @@ impl HasSource for TypeAlias {
105impl HasSource for MacroDef { 105impl HasSource for MacroDef {
106 type Ast = ast::MacroCall; 106 type Ast = ast::MacroCall;
107 fn source(self, db: &impl DefDatabase) -> InFile<ast::MacroCall> { 107 fn source(self, db: &impl DefDatabase) -> InFile<ast::MacroCall> {
108 InFile { file_id: self.id.ast_id.file_id, value: self.id.ast_id.to_node(db) } 108 InFile {
109 file_id: self.id.ast_id.expect("MacroDef without ast_id").file_id,
110 value: self.id.ast_id.expect("MacroDef without ast_id").to_node(db),
111 }
109 } 112 }
110} 113}
111impl HasSource for ImplBlock { 114impl HasSource for ImplBlock {
diff --git a/crates/ra_hir/src/from_source.rs b/crates/ra_hir/src/from_source.rs
index 6fa947759..5cb222bd3 100644
--- a/crates/ra_hir/src/from_source.rs
+++ b/crates/ra_hir/src/from_source.rs
@@ -93,9 +93,9 @@ impl FromSource for MacroDef {
93 93
94 let module_src = ModuleSource::from_child_node(db, src.as_ref().map(|it| it.syntax())); 94 let module_src = ModuleSource::from_child_node(db, src.as_ref().map(|it| it.syntax()));
95 let module = Module::from_definition(db, InFile::new(src.file_id, module_src))?; 95 let module = Module::from_definition(db, InFile::new(src.file_id, module_src))?;
96 let krate = module.krate().crate_id(); 96 let krate = Some(module.krate().crate_id());
97 97
98 let ast_id = AstId::new(src.file_id, db.ast_id_map(src.file_id).ast_id(&src.value)); 98 let ast_id = Some(AstId::new(src.file_id, db.ast_id_map(src.file_id).ast_id(&src.value)));
99 99
100 let id: MacroDefId = MacroDefId { krate, ast_id, kind }; 100 let id: MacroDefId = MacroDefId { krate, ast_id, kind };
101 Some(MacroDef { id }) 101 Some(MacroDef { id })
diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs
index db0451059..42c392513 100644
--- a/crates/ra_hir/src/source_binder.rs
+++ b/crates/ra_hir/src/source_binder.rs
@@ -20,7 +20,8 @@ use hir_def::{
20 AssocItemId, DefWithBodyId, 20 AssocItemId, DefWithBodyId,
21}; 21};
22use hir_expand::{ 22use hir_expand::{
23 hygiene::Hygiene, name::AsName, AstId, HirFileId, InFile, MacroCallId, MacroFileKind, 23 hygiene::Hygiene, name::AsName, AstId, HirFileId, InFile, MacroCallId, MacroCallKind,
24 MacroFileKind,
24}; 25};
25use ra_syntax::{ 26use ra_syntax::{
26 ast::{self, AstNode}, 27 ast::{self, AstNode},
@@ -456,7 +457,7 @@ impl SourceAnalyzer {
456 db.ast_id_map(macro_call.file_id).ast_id(macro_call.value), 457 db.ast_id_map(macro_call.file_id).ast_id(macro_call.value),
457 ); 458 );
458 Some(Expansion { 459 Some(Expansion {
459 macro_call_id: def.as_call_id(db, ast_id), 460 macro_call_id: def.as_call_id(db, MacroCallKind::FnLike(ast_id)),
460 macro_file_kind: to_macro_file_kind(macro_call.value), 461 macro_file_kind: to_macro_file_kind(macro_call.value),
461 }) 462 })
462 } 463 }
diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs
index 7f9a6e7ca..2f8f02d82 100644
--- a/crates/ra_hir_def/src/attr.rs
+++ b/crates/ra_hir_def/src/attr.rs
@@ -61,7 +61,9 @@ impl Attrs {
61 AdtId::UnionId(it) => attrs_from_ast(it.lookup_intern(db).ast_id, db), 61 AdtId::UnionId(it) => attrs_from_ast(it.lookup_intern(db).ast_id, db),
62 }, 62 },
63 AttrDefId::TraitId(it) => attrs_from_ast(it.lookup_intern(db).ast_id, db), 63 AttrDefId::TraitId(it) => attrs_from_ast(it.lookup_intern(db).ast_id, db),
64 AttrDefId::MacroDefId(it) => attrs_from_ast(it.ast_id, db), 64 AttrDefId::MacroDefId(it) => {
65 it.ast_id.map_or_else(Default::default, |ast_id| attrs_from_ast(ast_id, db))
66 }
65 AttrDefId::ImplId(it) => attrs_from_ast(it.lookup_intern(db).ast_id, db), 67 AttrDefId::ImplId(it) => attrs_from_ast(it.lookup_intern(db).ast_id, db),
66 AttrDefId::ConstId(it) => attrs_from_loc(it.lookup(db), db), 68 AttrDefId::ConstId(it) => attrs_from_loc(it.lookup(db), db),
67 AttrDefId::StaticId(it) => attrs_from_loc(it.lookup(db), db), 69 AttrDefId::StaticId(it) => attrs_from_loc(it.lookup(db), db),
diff --git a/crates/ra_hir_def/src/body.rs b/crates/ra_hir_def/src/body.rs
index ef1816836..7b385f3fd 100644
--- a/crates/ra_hir_def/src/body.rs
+++ b/crates/ra_hir_def/src/body.rs
@@ -6,7 +6,9 @@ pub mod scope;
6use std::{ops::Index, sync::Arc}; 6use std::{ops::Index, sync::Arc};
7 7
8use either::Either; 8use either::Either;
9use hir_expand::{hygiene::Hygiene, AstId, HirFileId, InFile, MacroDefId, MacroFileKind}; 9use hir_expand::{
10 hygiene::Hygiene, AstId, HirFileId, InFile, MacroCallKind, MacroDefId, MacroFileKind,
11};
10use ra_arena::{map::ArenaMap, Arena}; 12use ra_arena::{map::ArenaMap, Arena};
11use ra_syntax::{ast, AstNode, AstPtr}; 13use ra_syntax::{ast, AstNode, AstPtr};
12use rustc_hash::FxHashMap; 14use rustc_hash::FxHashMap;
@@ -46,7 +48,7 @@ impl Expander {
46 48
47 if let Some(path) = macro_call.path().and_then(|path| self.parse_path(path)) { 49 if let Some(path) = macro_call.path().and_then(|path| self.parse_path(path)) {
48 if let Some(def) = self.resolve_path_as_macro(db, &path) { 50 if let Some(def) = self.resolve_path_as_macro(db, &path) {
49 let call_id = def.as_call_id(db, ast_id); 51 let call_id = def.as_call_id(db, MacroCallKind::FnLike(ast_id));
50 let file_id = call_id.as_file(MacroFileKind::Expr); 52 let file_id = call_id.as_file(MacroFileKind::Expr);
51 if let Some(node) = db.parse_or_expand(file_id) { 53 if let Some(node) = db.parse_or_expand(file_id) {
52 if let Some(expr) = ast::Expr::cast(node) { 54 if let Some(expr) = ast::Expr::cast(node) {
diff --git a/crates/ra_hir_def/src/docs.rs b/crates/ra_hir_def/src/docs.rs
index 3fc6d6934..61727bd26 100644
--- a/crates/ra_hir_def/src/docs.rs
+++ b/crates/ra_hir_def/src/docs.rs
@@ -60,7 +60,7 @@ impl Documentation {
60 docs_from_ast(&src.value[it.local_id]) 60 docs_from_ast(&src.value[it.local_id])
61 } 61 }
62 AttrDefId::TraitId(it) => docs_from_ast(&it.source(db).value), 62 AttrDefId::TraitId(it) => docs_from_ast(&it.source(db).value),
63 AttrDefId::MacroDefId(it) => docs_from_ast(&it.ast_id.to_node(db)), 63 AttrDefId::MacroDefId(it) => docs_from_ast(&it.ast_id?.to_node(db)),
64 AttrDefId::ConstId(it) => docs_from_ast(&it.lookup(db).source(db).value), 64 AttrDefId::ConstId(it) => docs_from_ast(&it.lookup(db).source(db).value),
65 AttrDefId::StaticId(it) => docs_from_ast(&it.lookup(db).source(db).value), 65 AttrDefId::StaticId(it) => docs_from_ast(&it.lookup(db).source(db).value),
66 AttrDefId::FunctionId(it) => docs_from_ast(&it.lookup(db).source(db).value), 66 AttrDefId::FunctionId(it) => docs_from_ast(&it.lookup(db).source(db).value),
diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs
index 9d948d4f4..08693cb13 100644
--- a/crates/ra_hir_def/src/nameres/collector.rs
+++ b/crates/ra_hir_def/src/nameres/collector.rs
@@ -4,9 +4,10 @@
4//! resolves imports and expands macros. 4//! resolves imports and expands macros.
5 5
6use hir_expand::{ 6use hir_expand::{
7 builtin_derive::find_builtin_derive,
7 builtin_macro::find_builtin_macro, 8 builtin_macro::find_builtin_macro,
8 name::{self, AsName, Name}, 9 name::{self, AsName, Name},
9 HirFileId, MacroCallId, MacroDefId, MacroDefKind, MacroFileKind, 10 HirFileId, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, MacroFileKind,
10}; 11};
11use ra_cfg::CfgOptions; 12use ra_cfg::CfgOptions;
12use ra_db::{CrateId, FileId}; 13use ra_db::{CrateId, FileId};
@@ -58,6 +59,7 @@ pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> C
58 glob_imports: FxHashMap::default(), 59 glob_imports: FxHashMap::default(),
59 unresolved_imports: Vec::new(), 60 unresolved_imports: Vec::new(),
60 unexpanded_macros: Vec::new(), 61 unexpanded_macros: Vec::new(),
62 unexpanded_attribute_macros: Vec::new(),
61 mod_dirs: FxHashMap::default(), 63 mod_dirs: FxHashMap::default(),
62 macro_stack_monitor: MacroStackMonitor::default(), 64 macro_stack_monitor: MacroStackMonitor::default(),
63 poison_macros: FxHashSet::default(), 65 poison_macros: FxHashSet::default(),
@@ -102,6 +104,7 @@ struct DefCollector<'a, DB> {
102 glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, LocalImportId)>>, 104 glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, LocalImportId)>>,
103 unresolved_imports: Vec<(LocalModuleId, LocalImportId, raw::ImportData)>, 105 unresolved_imports: Vec<(LocalModuleId, LocalImportId, raw::ImportData)>,
104 unexpanded_macros: Vec<(LocalModuleId, AstId<ast::MacroCall>, Path)>, 106 unexpanded_macros: Vec<(LocalModuleId, AstId<ast::MacroCall>, Path)>,
107 unexpanded_attribute_macros: Vec<(LocalModuleId, AstId<ast::ModuleItem>, Path)>,
105 mod_dirs: FxHashMap<LocalModuleId, ModDir>, 108 mod_dirs: FxHashMap<LocalModuleId, ModDir>,
106 109
107 /// Some macro use `$tt:tt which mean we have to handle the macro perfectly 110 /// Some macro use `$tt:tt which mean we have to handle the macro perfectly
@@ -470,6 +473,8 @@ where
470 473
471 fn resolve_macros(&mut self) -> ReachedFixedPoint { 474 fn resolve_macros(&mut self) -> ReachedFixedPoint {
472 let mut macros = std::mem::replace(&mut self.unexpanded_macros, Vec::new()); 475 let mut macros = std::mem::replace(&mut self.unexpanded_macros, Vec::new());
476 let mut attribute_macros =
477 std::mem::replace(&mut self.unexpanded_attribute_macros, Vec::new());
473 let mut resolved = Vec::new(); 478 let mut resolved = Vec::new();
474 let mut res = ReachedFixedPoint::Yes; 479 let mut res = ReachedFixedPoint::Yes;
475 macros.retain(|(module_id, ast_id, path)| { 480 macros.retain(|(module_id, ast_id, path)| {
@@ -482,7 +487,19 @@ where
482 ); 487 );
483 488
484 if let Some(def) = resolved_res.resolved_def.take_macros() { 489 if let Some(def) = resolved_res.resolved_def.take_macros() {
485 let call_id = def.as_call_id(self.db, *ast_id); 490 let call_id = def.as_call_id(self.db, MacroCallKind::FnLike(*ast_id));
491 resolved.push((*module_id, call_id, def));
492 res = ReachedFixedPoint::No;
493 return false;
494 }
495
496 true
497 });
498 attribute_macros.retain(|(module_id, ast_id, path)| {
499 let resolved_res = self.resolve_attribute_macro(path);
500
501 if let Some(def) = resolved_res {
502 let call_id = def.as_call_id(self.db, MacroCallKind::Attr(*ast_id));
486 resolved.push((*module_id, call_id, def)); 503 resolved.push((*module_id, call_id, def));
487 res = ReachedFixedPoint::No; 504 res = ReachedFixedPoint::No;
488 return false; 505 return false;
@@ -492,6 +509,7 @@ where
492 }); 509 });
493 510
494 self.unexpanded_macros = macros; 511 self.unexpanded_macros = macros;
512 self.unexpanded_attribute_macros = attribute_macros;
495 513
496 for (module_id, macro_call_id, macro_def_id) in resolved { 514 for (module_id, macro_call_id, macro_def_id) in resolved {
497 self.collect_macro_expansion(module_id, macro_call_id, macro_def_id); 515 self.collect_macro_expansion(module_id, macro_call_id, macro_def_id);
@@ -500,6 +518,20 @@ where
500 res 518 res
501 } 519 }
502 520
521 fn resolve_attribute_macro(&self, path: &Path) -> Option<MacroDefId> {
522 // FIXME this is currently super hacky, just enough to support the
523 // built-in derives
524 if let Some(name) = path.as_ident() {
525 // FIXME this should actually be handled with the normal name
526 // resolution; the std lib defines built-in stubs for the derives,
527 // but these are new-style `macro`s, which we don't support yet
528 if let Some(def_id) = find_builtin_derive(name) {
529 return Some(def_id);
530 }
531 }
532 None
533 }
534
503 fn collect_macro_expansion( 535 fn collect_macro_expansion(
504 &mut self, 536 &mut self,
505 module_id: LocalModuleId, 537 module_id: LocalModuleId,
@@ -587,7 +619,9 @@ where
587 .def_collector 619 .def_collector
588 .unresolved_imports 620 .unresolved_imports
589 .push((self.module_id, import_id, self.raw_items[import_id].clone())), 621 .push((self.module_id, import_id, self.raw_items[import_id].clone())),
590 raw::RawItemKind::Def(def) => self.define_def(&self.raw_items[def]), 622 raw::RawItemKind::Def(def) => {
623 self.define_def(&self.raw_items[def], &item.attrs)
624 }
591 raw::RawItemKind::Macro(mac) => self.collect_macro(&self.raw_items[mac]), 625 raw::RawItemKind::Macro(mac) => self.collect_macro(&self.raw_items[mac]),
592 raw::RawItemKind::Impl(imp) => { 626 raw::RawItemKind::Impl(imp) => {
593 let module = ModuleId { 627 let module = ModuleId {
@@ -682,10 +716,16 @@ where
682 res 716 res
683 } 717 }
684 718
685 fn define_def(&mut self, def: &raw::DefData) { 719 fn define_def(&mut self, def: &raw::DefData, attrs: &Attrs) {
686 let module = ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id }; 720 let module = ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id };
687 let ctx = LocationCtx::new(self.def_collector.db, module, self.file_id); 721 let ctx = LocationCtx::new(self.def_collector.db, module, self.file_id);
688 722
723 // FIXME: check attrs to see if this is an attribute macro invocation;
724 // in which case we don't add the invocation, just a single attribute
725 // macro invocation
726
727 self.collect_derives(attrs, def);
728
689 let name = def.name.clone(); 729 let name = def.name.clone();
690 let def: PerNs = match def.kind { 730 let def: PerNs = match def.kind {
691 raw::DefKind::Function(ast_id) => { 731 raw::DefKind::Function(ast_id) => {
@@ -736,6 +776,23 @@ where
736 self.def_collector.update(self.module_id, None, &[(name, resolution)]) 776 self.def_collector.update(self.module_id, None, &[(name, resolution)])
737 } 777 }
738 778
779 fn collect_derives(&mut self, attrs: &Attrs, def: &raw::DefData) {
780 for derive_subtree in attrs.by_key("derive").tt_values() {
781 // for #[derive(Copy, Clone)], `derive_subtree` is the `(Copy, Clone)` subtree
782 for tt in &derive_subtree.token_trees {
783 let ident = match &tt {
784 tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => ident,
785 tt::TokenTree::Leaf(tt::Leaf::Punct(_)) => continue, // , is ok
786 _ => continue, // anything else would be an error (which we currently ignore)
787 };
788 let path = Path::from_tt_ident(ident);
789
790 let ast_id = AstId::new(self.file_id, def.kind.ast_id());
791 self.def_collector.unexpanded_attribute_macros.push((self.module_id, ast_id, path));
792 }
793 }
794 }
795
739 fn collect_macro(&mut self, mac: &raw::MacroData) { 796 fn collect_macro(&mut self, mac: &raw::MacroData) {
740 let ast_id = AstId::new(self.file_id, mac.ast_id); 797 let ast_id = AstId::new(self.file_id, mac.ast_id);
741 798
@@ -759,8 +816,8 @@ where
759 if is_macro_rules(&mac.path) { 816 if is_macro_rules(&mac.path) {
760 if let Some(name) = &mac.name { 817 if let Some(name) = &mac.name {
761 let macro_id = MacroDefId { 818 let macro_id = MacroDefId {
762 ast_id, 819 ast_id: Some(ast_id),
763 krate: self.def_collector.def_map.krate, 820 krate: Some(self.def_collector.def_map.krate),
764 kind: MacroDefKind::Declarative, 821 kind: MacroDefKind::Declarative,
765 }; 822 };
766 self.def_collector.define_macro(self.module_id, name.clone(), macro_id, mac.export); 823 self.def_collector.define_macro(self.module_id, name.clone(), macro_id, mac.export);
@@ -773,7 +830,8 @@ where
773 if let Some(macro_def) = mac.path.as_ident().and_then(|name| { 830 if let Some(macro_def) = mac.path.as_ident().and_then(|name| {
774 self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name) 831 self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name)
775 }) { 832 }) {
776 let macro_call_id = macro_def.as_call_id(self.def_collector.db, ast_id); 833 let macro_call_id =
834 macro_def.as_call_id(self.def_collector.db, MacroCallKind::FnLike(ast_id));
777 835
778 self.def_collector.collect_macro_expansion(self.module_id, macro_call_id, macro_def); 836 self.def_collector.collect_macro_expansion(self.module_id, macro_call_id, macro_def);
779 return; 837 return;
@@ -829,6 +887,7 @@ mod tests {
829 glob_imports: FxHashMap::default(), 887 glob_imports: FxHashMap::default(),
830 unresolved_imports: Vec::new(), 888 unresolved_imports: Vec::new(),
831 unexpanded_macros: Vec::new(), 889 unexpanded_macros: Vec::new(),
890 unexpanded_attribute_macros: Vec::new(),
832 mod_dirs: FxHashMap::default(), 891 mod_dirs: FxHashMap::default(),
833 macro_stack_monitor: monitor, 892 macro_stack_monitor: monitor,
834 poison_macros: FxHashSet::default(), 893 poison_macros: FxHashSet::default(),
diff --git a/crates/ra_hir_def/src/nameres/raw.rs b/crates/ra_hir_def/src/nameres/raw.rs
index de4e706c2..a2821e1c3 100644
--- a/crates/ra_hir_def/src/nameres/raw.rs
+++ b/crates/ra_hir_def/src/nameres/raw.rs
@@ -184,6 +184,21 @@ pub(super) enum DefKind {
184 TypeAlias(FileAstId<ast::TypeAliasDef>), 184 TypeAlias(FileAstId<ast::TypeAliasDef>),
185} 185}
186 186
187impl DefKind {
188 pub fn ast_id(&self) -> FileAstId<ast::ModuleItem> {
189 match self {
190 DefKind::Function(it) => it.upcast(),
191 DefKind::Struct(it) => it.upcast(),
192 DefKind::Union(it) => it.upcast(),
193 DefKind::Enum(it) => it.upcast(),
194 DefKind::Const(it) => it.upcast(),
195 DefKind::Static(it) => it.upcast(),
196 DefKind::Trait(it) => it.upcast(),
197 DefKind::TypeAlias(it) => it.upcast(),
198 }
199 }
200}
201
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 202#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
188pub(super) struct Macro(RawId); 203pub(super) struct Macro(RawId);
189impl_arena_id!(Macro); 204impl_arena_id!(Macro);
diff --git a/crates/ra_hir_def/src/nameres/tests/macros.rs b/crates/ra_hir_def/src/nameres/tests/macros.rs
index 704065633..cfa4ecb1a 100644
--- a/crates/ra_hir_def/src/nameres/tests/macros.rs
+++ b/crates/ra_hir_def/src/nameres/tests/macros.rs
@@ -600,3 +600,27 @@ fn macro_dollar_crate_is_correct_in_indirect_deps() {
600 â‹®bar: t v 600 â‹®bar: t v
601 "###); 601 "###);
602} 602}
603
604#[test]
605fn expand_derive() {
606 let map = compute_crate_def_map(
607 "
608 //- /main.rs
609 #[derive(Clone)]
610 struct Foo;
611 ",
612 );
613 assert_eq!(map.modules[map.root].impls.len(), 1);
614}
615
616#[test]
617fn expand_multiple_derive() {
618 let map = compute_crate_def_map(
619 "
620 //- /main.rs
621 #[derive(Copy, Clone)]
622 struct Foo;
623 ",
624 );
625 assert_eq!(map.modules[map.root].impls.len(), 2);
626}
diff --git a/crates/ra_hir_def/src/path.rs b/crates/ra_hir_def/src/path.rs
index 3030dcdf6..e547b2f03 100644
--- a/crates/ra_hir_def/src/path.rs
+++ b/crates/ra_hir_def/src/path.rs
@@ -199,6 +199,11 @@ impl Path {
199 name_ref.as_name().into() 199 name_ref.as_name().into()
200 } 200 }
201 201
202 /// Converts an `tt::Ident` into a single-identifier `Path`.
203 pub(crate) fn from_tt_ident(ident: &tt::Ident) -> Path {
204 ident.as_name().into()
205 }
206
202 /// `true` is this path is a single identifier, like `foo` 207 /// `true` is this path is a single identifier, like `foo`
203 pub fn is_ident(&self) -> bool { 208 pub fn is_ident(&self) -> bool {
204 self.kind == PathKind::Plain && self.segments.len() == 1 209 self.kind == PathKind::Plain && self.segments.len() == 1
diff --git a/crates/ra_hir_expand/src/ast_id_map.rs b/crates/ra_hir_expand/src/ast_id_map.rs
index cb464c3ff..a764bdf24 100644
--- a/crates/ra_hir_expand/src/ast_id_map.rs
+++ b/crates/ra_hir_expand/src/ast_id_map.rs
@@ -39,6 +39,16 @@ impl<N: AstNode> Hash for FileAstId<N> {
39 } 39 }
40} 40}
41 41
42impl<N: AstNode> FileAstId<N> {
43 // Can't make this a From implementation because of coherence
44 pub fn upcast<M: AstNode>(self) -> FileAstId<M>
45 where
46 M: From<N>,
47 {
48 FileAstId { raw: self.raw, _ty: PhantomData }
49 }
50}
51
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 52#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
43struct ErasedFileAstId(RawId); 53struct ErasedFileAstId(RawId);
44impl_arena_id!(ErasedFileAstId); 54impl_arena_id!(ErasedFileAstId);
@@ -53,7 +63,7 @@ impl AstIdMap {
53 pub(crate) fn from_source(node: &SyntaxNode) -> AstIdMap { 63 pub(crate) fn from_source(node: &SyntaxNode) -> AstIdMap {
54 assert!(node.parent().is_none()); 64 assert!(node.parent().is_none());
55 let mut res = AstIdMap { arena: Arena::default() }; 65 let mut res = AstIdMap { arena: Arena::default() };
56 // By walking the tree in bread-first order we make sure that parents 66 // By walking the tree in breadth-first order we make sure that parents
57 // get lower ids then children. That is, adding a new child does not 67 // get lower ids then children. That is, adding a new child does not
58 // change parent's id. This means that, say, adding a new function to a 68 // change parent's id. This means that, say, adding a new function to a
59 // trait does not change ids of top-level items, which helps caching. 69 // trait does not change ids of top-level items, which helps caching.
diff --git a/crates/ra_hir_expand/src/builtin_derive.rs b/crates/ra_hir_expand/src/builtin_derive.rs
new file mode 100644
index 000000000..78fa9b09a
--- /dev/null
+++ b/crates/ra_hir_expand/src/builtin_derive.rs
@@ -0,0 +1,301 @@
1//! Builtin derives.
2
3use log::debug;
4
5use ra_parser::FragmentKind;
6use ra_syntax::{
7 ast::{self, AstNode, ModuleItemOwner, NameOwner, TypeParamsOwner},
8 match_ast,
9};
10
11use crate::db::AstDatabase;
12use crate::{name, quote, MacroCallId, MacroDefId, MacroDefKind};
13
14macro_rules! register_builtin {
15 ( $(($name:ident, $kind: ident) => $expand:ident),* ) => {
16 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17 pub enum BuiltinDeriveExpander {
18 $($kind),*
19 }
20
21 impl BuiltinDeriveExpander {
22 pub fn expand(
23 &self,
24 db: &dyn AstDatabase,
25 id: MacroCallId,
26 tt: &tt::Subtree,
27 ) -> Result<tt::Subtree, mbe::ExpandError> {
28 let expander = match *self {
29 $( BuiltinDeriveExpander::$kind => $expand, )*
30 };
31 expander(db, id, tt)
32 }
33 }
34
35 pub fn find_builtin_derive(ident: &name::Name) -> Option<MacroDefId> {
36 let kind = match ident {
37 $( id if id == &name::$name => BuiltinDeriveExpander::$kind, )*
38 _ => return None,
39 };
40
41 Some(MacroDefId { krate: None, ast_id: None, kind: MacroDefKind::BuiltInDerive(kind) })
42 }
43 };
44}
45
46register_builtin! {
47 (COPY_TRAIT, Copy) => copy_expand,
48 (CLONE_TRAIT, Clone) => clone_expand,
49 (DEFAULT_TRAIT, Default) => default_expand,
50 (DEBUG_TRAIT, Debug) => debug_expand,
51 (HASH_TRAIT, Hash) => hash_expand,
52 (ORD_TRAIT, Ord) => ord_expand,
53 (PARTIAL_ORD_TRAIT, PartialOrd) => partial_ord_expand,
54 (EQ_TRAIT, Eq) => eq_expand,
55 (PARTIAL_EQ_TRAIT, PartialEq) => partial_eq_expand
56}
57
58struct BasicAdtInfo {
59 name: tt::Ident,
60 type_params: usize,
61}
62
63fn parse_adt(tt: &tt::Subtree) -> Result<BasicAdtInfo, mbe::ExpandError> {
64 let (parsed, token_map) = mbe::token_tree_to_syntax_node(tt, FragmentKind::Items)?; // FragmentKind::Items doesn't parse attrs?
65 let macro_items = ast::MacroItems::cast(parsed.syntax_node()).ok_or_else(|| {
66 debug!("derive node didn't parse");
67 mbe::ExpandError::UnexpectedToken
68 })?;
69 let item = macro_items.items().next().ok_or_else(|| {
70 debug!("no module item parsed");
71 mbe::ExpandError::NoMatchingRule
72 })?;
73 let node = item.syntax();
74 let (name, params) = match_ast! {
75 match node {
76 ast::StructDef(it) => { (it.name(), it.type_param_list()) },
77 ast::EnumDef(it) => { (it.name(), it.type_param_list()) },
78 ast::UnionDef(it) => { (it.name(), it.type_param_list()) },
79 _ => {
80 debug!("unexpected node is {:?}", node);
81 return Err(mbe::ExpandError::ConversionError)
82 },
83 }
84 };
85 let name = name.ok_or_else(|| {
86 debug!("parsed item has no name");
87 mbe::ExpandError::NoMatchingRule
88 })?;
89 let name_token_id = token_map.token_by_range(name.syntax().text_range()).ok_or_else(|| {
90 debug!("name token not found");
91 mbe::ExpandError::ConversionError
92 })?;
93 let name_token = tt::Ident { id: name_token_id, text: name.text().clone() };
94 let type_params = params.map_or(0, |type_param_list| type_param_list.type_params().count());
95 Ok(BasicAdtInfo { name: name_token, type_params })
96}
97
98fn make_type_args(n: usize, bound: Vec<tt::TokenTree>) -> Vec<tt::TokenTree> {
99 let mut result = Vec::<tt::TokenTree>::new();
100 result.push(tt::Leaf::Punct(tt::Punct { char: '<', spacing: tt::Spacing::Alone }).into());
101 for i in 0..n {
102 if i > 0 {
103 result
104 .push(tt::Leaf::Punct(tt::Punct { char: ',', spacing: tt::Spacing::Alone }).into());
105 }
106 result.push(
107 tt::Leaf::Ident(tt::Ident {
108 id: tt::TokenId::unspecified(),
109 text: format!("T{}", i).into(),
110 })
111 .into(),
112 );
113 result.extend(bound.iter().cloned());
114 }
115 result.push(tt::Leaf::Punct(tt::Punct { char: '>', spacing: tt::Spacing::Alone }).into());
116 result
117}
118
119fn expand_simple_derive(
120 tt: &tt::Subtree,
121 trait_path: tt::Subtree,
122) -> Result<tt::Subtree, mbe::ExpandError> {
123 let info = parse_adt(tt)?;
124 let name = info.name;
125 let trait_path_clone = trait_path.token_trees.clone();
126 let bound = (quote! { : ##trait_path_clone }).token_trees;
127 let type_params = make_type_args(info.type_params, bound);
128 let type_args = make_type_args(info.type_params, Vec::new());
129 let trait_path = trait_path.token_trees;
130 let expanded = quote! {
131 impl ##type_params ##trait_path for #name ##type_args {}
132 };
133 Ok(expanded)
134}
135
136fn copy_expand(
137 _db: &dyn AstDatabase,
138 _id: MacroCallId,
139 tt: &tt::Subtree,
140) -> Result<tt::Subtree, mbe::ExpandError> {
141 expand_simple_derive(tt, quote! { std::marker::Copy })
142}
143
144fn clone_expand(
145 _db: &dyn AstDatabase,
146 _id: MacroCallId,
147 tt: &tt::Subtree,
148) -> Result<tt::Subtree, mbe::ExpandError> {
149 expand_simple_derive(tt, quote! { std::clone::Clone })
150}
151
152fn default_expand(
153 _db: &dyn AstDatabase,
154 _id: MacroCallId,
155 tt: &tt::Subtree,
156) -> Result<tt::Subtree, mbe::ExpandError> {
157 expand_simple_derive(tt, quote! { std::default::Default })
158}
159
160fn debug_expand(
161 _db: &dyn AstDatabase,
162 _id: MacroCallId,
163 tt: &tt::Subtree,
164) -> Result<tt::Subtree, mbe::ExpandError> {
165 expand_simple_derive(tt, quote! { std::fmt::Debug })
166}
167
168fn hash_expand(
169 _db: &dyn AstDatabase,
170 _id: MacroCallId,
171 tt: &tt::Subtree,
172) -> Result<tt::Subtree, mbe::ExpandError> {
173 expand_simple_derive(tt, quote! { std::hash::Hash })
174}
175
176fn eq_expand(
177 _db: &dyn AstDatabase,
178 _id: MacroCallId,
179 tt: &tt::Subtree,
180) -> Result<tt::Subtree, mbe::ExpandError> {
181 expand_simple_derive(tt, quote! { std::cmp::Eq })
182}
183
184fn partial_eq_expand(
185 _db: &dyn AstDatabase,
186 _id: MacroCallId,
187 tt: &tt::Subtree,
188) -> Result<tt::Subtree, mbe::ExpandError> {
189 expand_simple_derive(tt, quote! { std::cmp::PartialEq })
190}
191
192fn ord_expand(
193 _db: &dyn AstDatabase,
194 _id: MacroCallId,
195 tt: &tt::Subtree,
196) -> Result<tt::Subtree, mbe::ExpandError> {
197 expand_simple_derive(tt, quote! { std::cmp::Ord })
198}
199
200fn partial_ord_expand(
201 _db: &dyn AstDatabase,
202 _id: MacroCallId,
203 tt: &tt::Subtree,
204) -> Result<tt::Subtree, mbe::ExpandError> {
205 expand_simple_derive(tt, quote! { std::cmp::PartialOrd })
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use crate::{test_db::TestDB, AstId, MacroCallKind, MacroCallLoc, MacroFileKind};
212 use ra_db::{fixture::WithFixture, SourceDatabase};
213
214 fn expand_builtin_derive(s: &str, expander: BuiltinDeriveExpander) -> String {
215 let (db, file_id) = TestDB::with_single_file(&s);
216 let parsed = db.parse(file_id);
217 let items: Vec<_> =
218 parsed.syntax_node().descendants().filter_map(|it| ast::ModuleItem::cast(it)).collect();
219
220 let ast_id_map = db.ast_id_map(file_id.into());
221
222 // the first one should be a macro_rules
223 let def =
224 MacroDefId { krate: None, ast_id: None, kind: MacroDefKind::BuiltInDerive(expander) };
225
226 let loc = MacroCallLoc {
227 def,
228 kind: MacroCallKind::Attr(AstId::new(file_id.into(), ast_id_map.ast_id(&items[0]))),
229 };
230
231 let id = db.intern_macro(loc);
232 let parsed = db.parse_or_expand(id.as_file(MacroFileKind::Items)).unwrap();
233
234 // FIXME text() for syntax nodes parsed from token tree looks weird
235 // because there's no whitespace, see below
236 parsed.text().to_string()
237 }
238
239 #[test]
240 fn test_copy_expand_simple() {
241 let expanded = expand_builtin_derive(
242 r#"
243 #[derive(Copy)]
244 struct Foo;
245"#,
246 BuiltinDeriveExpander::Copy,
247 );
248
249 assert_eq!(expanded, "impl <>std::marker::CopyforFoo <>{}");
250 }
251
252 #[test]
253 fn test_copy_expand_with_type_params() {
254 let expanded = expand_builtin_derive(
255 r#"
256 #[derive(Copy)]
257 struct Foo<A, B>;
258"#,
259 BuiltinDeriveExpander::Copy,
260 );
261
262 assert_eq!(
263 expanded,
264 "impl<T0:std::marker::Copy,T1:std::marker::Copy>std::marker::CopyforFoo<T0,T1>{}"
265 );
266 }
267
268 #[test]
269 fn test_copy_expand_with_lifetimes() {
270 let expanded = expand_builtin_derive(
271 r#"
272 #[derive(Copy)]
273 struct Foo<A, B, 'a, 'b>;
274"#,
275 BuiltinDeriveExpander::Copy,
276 );
277
278 // We currently just ignore lifetimes
279
280 assert_eq!(
281 expanded,
282 "impl<T0:std::marker::Copy,T1:std::marker::Copy>std::marker::CopyforFoo<T0,T1>{}"
283 );
284 }
285
286 #[test]
287 fn test_clone_expand() {
288 let expanded = expand_builtin_derive(
289 r#"
290 #[derive(Clone)]
291 struct Foo<A, B>;
292"#,
293 BuiltinDeriveExpander::Clone,
294 );
295
296 assert_eq!(
297 expanded,
298 "impl<T0:std::clone::Clone,T1:std::clone::Clone>std::clone::CloneforFoo<T0,T1>{}"
299 );
300 }
301}
diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs
index d370dfb34..35f99b2bc 100644
--- a/crates/ra_hir_expand/src/builtin_macro.rs
+++ b/crates/ra_hir_expand/src/builtin_macro.rs
@@ -39,7 +39,7 @@ macro_rules! register_builtin {
39 _ => return None, 39 _ => return None,
40 }; 40 };
41 41
42 Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(kind) }) 42 Some(MacroDefId { krate: Some(krate), ast_id: Some(ast_id), kind: MacroDefKind::BuiltIn(kind) })
43 } 43 }
44 }; 44 };
45} 45}
@@ -82,10 +82,9 @@ fn line_expand(
82 _tt: &tt::Subtree, 82 _tt: &tt::Subtree,
83) -> Result<tt::Subtree, mbe::ExpandError> { 83) -> Result<tt::Subtree, mbe::ExpandError> {
84 let loc = db.lookup_intern_macro(id); 84 let loc = db.lookup_intern_macro(id);
85 let macro_call = loc.ast_id.to_node(db);
86 85
87 let arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; 86 let arg = loc.kind.arg(db).ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;
88 let arg_start = arg.syntax().text_range().start(); 87 let arg_start = arg.text_range().start();
89 88
90 let file = id.as_file(MacroFileKind::Expr); 89 let file = id.as_file(MacroFileKind::Expr);
91 let line_num = to_line_number(db, file, arg_start); 90 let line_num = to_line_number(db, file, arg_start);
@@ -103,11 +102,10 @@ fn stringify_expand(
103 _tt: &tt::Subtree, 102 _tt: &tt::Subtree,
104) -> Result<tt::Subtree, mbe::ExpandError> { 103) -> Result<tt::Subtree, mbe::ExpandError> {
105 let loc = db.lookup_intern_macro(id); 104 let loc = db.lookup_intern_macro(id);
106 let macro_call = loc.ast_id.to_node(db);
107 105
108 let macro_content = { 106 let macro_content = {
109 let arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; 107 let arg = loc.kind.arg(db).ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;
110 let macro_args = arg.syntax().clone(); 108 let macro_args = arg.clone();
111 let text = macro_args.text(); 109 let text = macro_args.text();
112 let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')'); 110 let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')');
113 text.slice(without_parens).to_string() 111 text.slice(without_parens).to_string()
@@ -148,7 +146,10 @@ fn column_expand(
148 _tt: &tt::Subtree, 146 _tt: &tt::Subtree,
149) -> Result<tt::Subtree, mbe::ExpandError> { 147) -> Result<tt::Subtree, mbe::ExpandError> {
150 let loc = db.lookup_intern_macro(id); 148 let loc = db.lookup_intern_macro(id);
151 let macro_call = loc.ast_id.to_node(db); 149 let macro_call = match loc.kind {
150 crate::MacroCallKind::FnLike(ast_id) => ast_id.to_node(db),
151 _ => panic!("column macro called as attr"),
152 };
152 153
153 let _arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?; 154 let _arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;
154 let col_start = macro_call.syntax().text_range().start(); 155 let col_start = macro_call.syntax().text_range().start();
@@ -164,15 +165,10 @@ fn column_expand(
164} 165}
165 166
166fn file_expand( 167fn file_expand(
167 db: &dyn AstDatabase, 168 _db: &dyn AstDatabase,
168 id: MacroCallId, 169 _id: MacroCallId,
169 _tt: &tt::Subtree, 170 _tt: &tt::Subtree,
170) -> Result<tt::Subtree, mbe::ExpandError> { 171) -> Result<tt::Subtree, mbe::ExpandError> {
171 let loc = db.lookup_intern_macro(id);
172 let macro_call = loc.ast_id.to_node(db);
173
174 let _ = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;
175
176 // FIXME: RA purposefully lacks knowledge of absolute file names 172 // FIXME: RA purposefully lacks knowledge of absolute file names
177 // so just return "". 173 // so just return "".
178 let file_name = ""; 174 let file_name = "";
@@ -207,7 +203,7 @@ fn compile_error_expand(
207#[cfg(test)] 203#[cfg(test)]
208mod tests { 204mod tests {
209 use super::*; 205 use super::*;
210 use crate::{test_db::TestDB, MacroCallLoc}; 206 use crate::{test_db::TestDB, MacroCallKind, MacroCallLoc};
211 use ra_db::{fixture::WithFixture, SourceDatabase}; 207 use ra_db::{fixture::WithFixture, SourceDatabase};
212 208
213 fn expand_builtin_macro(s: &str, expander: BuiltinFnLikeExpander) -> String { 209 fn expand_builtin_macro(s: &str, expander: BuiltinFnLikeExpander) -> String {
@@ -220,14 +216,17 @@ mod tests {
220 216
221 // the first one should be a macro_rules 217 // the first one should be a macro_rules
222 let def = MacroDefId { 218 let def = MacroDefId {
223 krate: CrateId(0), 219 krate: Some(CrateId(0)),
224 ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(&macro_calls[0])), 220 ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(&macro_calls[0]))),
225 kind: MacroDefKind::BuiltIn(expander), 221 kind: MacroDefKind::BuiltIn(expander),
226 }; 222 };
227 223
228 let loc = MacroCallLoc { 224 let loc = MacroCallLoc {
229 def, 225 def,
230 ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(&macro_calls[1])), 226 kind: MacroCallKind::FnLike(AstId::new(
227 file_id.into(),
228 ast_id_map.ast_id(&macro_calls[1]),
229 )),
231 }; 230 };
232 231
233 let id = db.intern_macro(loc); 232 let id = db.intern_macro(loc);
diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs
index 8e46fa177..99dabf3fb 100644
--- a/crates/ra_hir_expand/src/db.rs
+++ b/crates/ra_hir_expand/src/db.rs
@@ -9,14 +9,15 @@ use ra_prof::profile;
9use ra_syntax::{AstNode, Parse, SyntaxNode}; 9use ra_syntax::{AstNode, Parse, SyntaxNode};
10 10
11use crate::{ 11use crate::{
12 ast_id_map::AstIdMap, BuiltinFnLikeExpander, HirFileId, HirFileIdRepr, MacroCallId, 12 ast_id_map::AstIdMap, BuiltinDeriveExpander, BuiltinFnLikeExpander, HirFileId, HirFileIdRepr,
13 MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, MacroFileKind, 13 MacroCallId, MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, MacroFileKind,
14}; 14};
15 15
16#[derive(Debug, Clone, Eq, PartialEq)] 16#[derive(Debug, Clone, Eq, PartialEq)]
17pub enum TokenExpander { 17pub enum TokenExpander {
18 MacroRules(mbe::MacroRules), 18 MacroRules(mbe::MacroRules),
19 Builtin(BuiltinFnLikeExpander), 19 Builtin(BuiltinFnLikeExpander),
20 BuiltinDerive(BuiltinDeriveExpander),
20} 21}
21 22
22impl TokenExpander { 23impl TokenExpander {
@@ -29,6 +30,7 @@ impl TokenExpander {
29 match self { 30 match self {
30 TokenExpander::MacroRules(it) => it.expand(tt), 31 TokenExpander::MacroRules(it) => it.expand(tt),
31 TokenExpander::Builtin(it) => it.expand(db, id, tt), 32 TokenExpander::Builtin(it) => it.expand(db, id, tt),
33 TokenExpander::BuiltinDerive(it) => it.expand(db, id, tt),
32 } 34 }
33 } 35 }
34 36
@@ -36,6 +38,7 @@ impl TokenExpander {
36 match self { 38 match self {
37 TokenExpander::MacroRules(it) => it.map_id_down(id), 39 TokenExpander::MacroRules(it) => it.map_id_down(id),
38 TokenExpander::Builtin(..) => id, 40 TokenExpander::Builtin(..) => id,
41 TokenExpander::BuiltinDerive(..) => id,
39 } 42 }
40 } 43 }
41 44
@@ -43,6 +46,7 @@ impl TokenExpander {
43 match self { 46 match self {
44 TokenExpander::MacroRules(it) => it.map_id_up(id), 47 TokenExpander::MacroRules(it) => it.map_id_up(id),
45 TokenExpander::Builtin(..) => (id, mbe::Origin::Def), 48 TokenExpander::Builtin(..) => (id, mbe::Origin::Def),
49 TokenExpander::BuiltinDerive(..) => (id, mbe::Origin::Def),
46 } 50 }
47 } 51 }
48} 52}
@@ -76,7 +80,7 @@ pub(crate) fn macro_def(
76) -> Option<Arc<(TokenExpander, mbe::TokenMap)>> { 80) -> Option<Arc<(TokenExpander, mbe::TokenMap)>> {
77 match id.kind { 81 match id.kind {
78 MacroDefKind::Declarative => { 82 MacroDefKind::Declarative => {
79 let macro_call = id.ast_id.to_node(db); 83 let macro_call = id.ast_id?.to_node(db);
80 let arg = macro_call.token_tree()?; 84 let arg = macro_call.token_tree()?;
81 let (tt, tmap) = mbe::ast_to_token_tree(&arg).or_else(|| { 85 let (tt, tmap) = mbe::ast_to_token_tree(&arg).or_else(|| {
82 log::warn!("fail on macro_def to token tree: {:#?}", arg); 86 log::warn!("fail on macro_def to token tree: {:#?}", arg);
@@ -91,6 +95,10 @@ pub(crate) fn macro_def(
91 MacroDefKind::BuiltIn(expander) => { 95 MacroDefKind::BuiltIn(expander) => {
92 Some(Arc::new((TokenExpander::Builtin(expander.clone()), mbe::TokenMap::default()))) 96 Some(Arc::new((TokenExpander::Builtin(expander.clone()), mbe::TokenMap::default())))
93 } 97 }
98 MacroDefKind::BuiltInDerive(expander) => Some(Arc::new((
99 TokenExpander::BuiltinDerive(expander.clone()),
100 mbe::TokenMap::default(),
101 ))),
94 } 102 }
95} 103}
96 104
@@ -99,9 +107,8 @@ pub(crate) fn macro_arg(
99 id: MacroCallId, 107 id: MacroCallId,
100) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>> { 108) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>> {
101 let loc = db.lookup_intern_macro(id); 109 let loc = db.lookup_intern_macro(id);
102 let macro_call = loc.ast_id.to_node(db); 110 let arg = loc.kind.arg(db)?;
103 let arg = macro_call.token_tree()?; 111 let (tt, tmap) = mbe::syntax_node_to_token_tree(&arg)?;
104 let (tt, tmap) = mbe::ast_to_token_tree(&arg)?;
105 Some(Arc::new((tt, tmap))) 112 Some(Arc::new((tt, tmap)))
106} 113}
107 114
diff --git a/crates/ra_hir_expand/src/hygiene.rs b/crates/ra_hir_expand/src/hygiene.rs
index 64c8b06c6..2e8a533f7 100644
--- a/crates/ra_hir_expand/src/hygiene.rs
+++ b/crates/ra_hir_expand/src/hygiene.rs
@@ -25,8 +25,9 @@ impl Hygiene {
25 HirFileIdRepr::MacroFile(macro_file) => { 25 HirFileIdRepr::MacroFile(macro_file) => {
26 let loc = db.lookup_intern_macro(macro_file.macro_call_id); 26 let loc = db.lookup_intern_macro(macro_file.macro_call_id);
27 match loc.def.kind { 27 match loc.def.kind {
28 MacroDefKind::Declarative => Some(loc.def.krate), 28 MacroDefKind::Declarative => loc.def.krate,
29 MacroDefKind::BuiltIn(_) => None, 29 MacroDefKind::BuiltIn(_) => None,
30 MacroDefKind::BuiltInDerive(_) => None,
30 } 31 }
31 } 32 }
32 }; 33 };
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs
index 3be9bdf86..59c69b91b 100644
--- a/crates/ra_hir_expand/src/lib.rs
+++ b/crates/ra_hir_expand/src/lib.rs
@@ -9,6 +9,7 @@ pub mod ast_id_map;
9pub mod name; 9pub mod name;
10pub mod hygiene; 10pub mod hygiene;
11pub mod diagnostics; 11pub mod diagnostics;
12pub mod builtin_derive;
12pub mod builtin_macro; 13pub mod builtin_macro;
13pub mod quote; 14pub mod quote;
14 15
@@ -23,6 +24,7 @@ use ra_syntax::{
23}; 24};
24 25
25use crate::ast_id_map::FileAstId; 26use crate::ast_id_map::FileAstId;
27use crate::builtin_derive::BuiltinDeriveExpander;
26use crate::builtin_macro::BuiltinFnLikeExpander; 28use crate::builtin_macro::BuiltinFnLikeExpander;
27 29
28#[cfg(test)] 30#[cfg(test)]
@@ -69,7 +71,7 @@ impl HirFileId {
69 HirFileIdRepr::FileId(file_id) => file_id, 71 HirFileIdRepr::FileId(file_id) => file_id,
70 HirFileIdRepr::MacroFile(macro_file) => { 72 HirFileIdRepr::MacroFile(macro_file) => {
71 let loc = db.lookup_intern_macro(macro_file.macro_call_id); 73 let loc = db.lookup_intern_macro(macro_file.macro_call_id);
72 loc.ast_id.file_id.original_file(db) 74 loc.kind.file_id().original_file(db)
73 } 75 }
74 } 76 }
75 } 77 }
@@ -81,8 +83,8 @@ impl HirFileId {
81 HirFileIdRepr::MacroFile(macro_file) => { 83 HirFileIdRepr::MacroFile(macro_file) => {
82 let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id); 84 let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
83 85
84 let arg_tt = loc.ast_id.to_node(db).token_tree()?; 86 let arg_tt = loc.kind.arg(db)?;
85 let def_tt = loc.def.ast_id.to_node(db).token_tree()?; 87 let def_tt = loc.def.ast_id?.to_node(db).token_tree()?;
86 88
87 let macro_def = db.macro_def(loc.def)?; 89 let macro_def = db.macro_def(loc.def)?;
88 let (parse, exp_map) = db.parse_macro(macro_file)?; 90 let (parse, exp_map) = db.parse_macro(macro_file)?;
@@ -90,8 +92,8 @@ impl HirFileId {
90 92
91 Some(ExpansionInfo { 93 Some(ExpansionInfo {
92 expanded: InFile::new(self, parse.syntax_node()), 94 expanded: InFile::new(self, parse.syntax_node()),
93 arg: InFile::new(loc.ast_id.file_id, arg_tt), 95 arg: InFile::new(loc.kind.file_id(), arg_tt),
94 def: InFile::new(loc.ast_id.file_id, def_tt), 96 def: InFile::new(loc.def.ast_id?.file_id, def_tt),
95 macro_arg, 97 macro_arg,
96 macro_def, 98 macro_def,
97 exp_map, 99 exp_map,
@@ -129,18 +131,20 @@ impl salsa::InternKey for MacroCallId {
129 131
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 132#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
131pub struct MacroDefId { 133pub struct MacroDefId {
132 pub krate: CrateId, 134 // FIXME: krate and ast_id are currently optional because we don't have a
133 pub ast_id: AstId<ast::MacroCall>, 135 // definition location for built-in derives. There is one, though: the
136 // standard library defines them. The problem is that it uses the new
137 // `macro` syntax for this, which we don't support yet. As soon as we do
138 // (which will probably require touching this code), we can instead use
139 // that (and also remove the hacks for resolving built-in derives).
140 pub krate: Option<CrateId>,
141 pub ast_id: Option<AstId<ast::MacroCall>>,
134 pub kind: MacroDefKind, 142 pub kind: MacroDefKind,
135} 143}
136 144
137impl MacroDefId { 145impl MacroDefId {
138 pub fn as_call_id( 146 pub fn as_call_id(self, db: &dyn db::AstDatabase, kind: MacroCallKind) -> MacroCallId {
139 self, 147 db.intern_macro(MacroCallLoc { def: self, kind })
140 db: &dyn db::AstDatabase,
141 ast_id: AstId<ast::MacroCall>,
142 ) -> MacroCallId {
143 db.intern_macro(MacroCallLoc { def: self, ast_id })
144 } 148 }
145} 149}
146 150
@@ -148,12 +152,38 @@ impl MacroDefId {
148pub enum MacroDefKind { 152pub enum MacroDefKind {
149 Declarative, 153 Declarative,
150 BuiltIn(BuiltinFnLikeExpander), 154 BuiltIn(BuiltinFnLikeExpander),
155 // FIXME: maybe just Builtin and rename BuiltinFnLikeExpander to BuiltinExpander
156 BuiltInDerive(BuiltinDeriveExpander),
151} 157}
152 158
153#[derive(Debug, Clone, PartialEq, Eq, Hash)] 159#[derive(Debug, Clone, PartialEq, Eq, Hash)]
154pub struct MacroCallLoc { 160pub struct MacroCallLoc {
155 pub(crate) def: MacroDefId, 161 pub(crate) def: MacroDefId,
156 pub(crate) ast_id: AstId<ast::MacroCall>, 162 pub(crate) kind: MacroCallKind,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Hash)]
166pub enum MacroCallKind {
167 FnLike(AstId<ast::MacroCall>),
168 Attr(AstId<ast::ModuleItem>),
169}
170
171impl MacroCallKind {
172 pub fn file_id(&self) -> HirFileId {
173 match self {
174 MacroCallKind::FnLike(ast_id) => ast_id.file_id,
175 MacroCallKind::Attr(ast_id) => ast_id.file_id,
176 }
177 }
178
179 pub fn arg(&self, db: &dyn db::AstDatabase) -> Option<SyntaxNode> {
180 match self {
181 MacroCallKind::FnLike(ast_id) => {
182 Some(ast_id.to_node(db).token_tree()?.syntax().clone())
183 }
184 MacroCallKind::Attr(ast_id) => Some(ast_id.to_node(db).syntax().clone()),
185 }
186 }
157} 187}
158 188
159impl MacroCallId { 189impl MacroCallId {
@@ -167,7 +197,7 @@ impl MacroCallId {
167#[derive(Debug, Clone, PartialEq, Eq)] 197#[derive(Debug, Clone, PartialEq, Eq)]
168pub struct ExpansionInfo { 198pub struct ExpansionInfo {
169 expanded: InFile<SyntaxNode>, 199 expanded: InFile<SyntaxNode>,
170 arg: InFile<ast::TokenTree>, 200 arg: InFile<SyntaxNode>,
171 def: InFile<ast::TokenTree>, 201 def: InFile<ast::TokenTree>,
172 202
173 macro_def: Arc<(db::TokenExpander, mbe::TokenMap)>, 203 macro_def: Arc<(db::TokenExpander, mbe::TokenMap)>,
@@ -178,8 +208,7 @@ pub struct ExpansionInfo {
178impl ExpansionInfo { 208impl ExpansionInfo {
179 pub fn map_token_down(&self, token: InFile<&SyntaxToken>) -> Option<InFile<SyntaxToken>> { 209 pub fn map_token_down(&self, token: InFile<&SyntaxToken>) -> Option<InFile<SyntaxToken>> {
180 assert_eq!(token.file_id, self.arg.file_id); 210 assert_eq!(token.file_id, self.arg.file_id);
181 let range = 211 let range = token.value.text_range().checked_sub(self.arg.value.text_range().start())?;
182 token.value.text_range().checked_sub(self.arg.value.syntax().text_range().start())?;
183 let token_id = self.macro_arg.1.token_by_range(range)?; 212 let token_id = self.macro_arg.1.token_by_range(range)?;
184 let token_id = self.macro_def.0.map_id_down(token_id); 213 let token_id = self.macro_def.0.map_id_down(token_id);
185 214
@@ -195,16 +224,15 @@ impl ExpansionInfo {
195 224
196 let (token_id, origin) = self.macro_def.0.map_id_up(token_id); 225 let (token_id, origin) = self.macro_def.0.map_id_up(token_id);
197 let (token_map, tt) = match origin { 226 let (token_map, tt) = match origin {
198 mbe::Origin::Call => (&self.macro_arg.1, &self.arg), 227 mbe::Origin::Call => (&self.macro_arg.1, self.arg.clone()),
199 mbe::Origin::Def => (&self.macro_def.1, &self.def), 228 mbe::Origin::Def => {
229 (&self.macro_def.1, self.def.as_ref().map(|tt| tt.syntax().clone()))
230 }
200 }; 231 };
201 232
202 let range = token_map.range_by_token(token_id)?; 233 let range = token_map.range_by_token(token_id)?;
203 let token = algo::find_covering_element( 234 let token = algo::find_covering_element(&tt.value, range + tt.value.text_range().start())
204 tt.value.syntax(), 235 .into_token()?;
205 range + tt.value.syntax().text_range().start(),
206 )
207 .into_token()?;
208 Some(tt.with_value(token)) 236 Some(tt.with_value(token))
209 } 237 }
210} 238}
diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs
index 05ba37070..c5a191160 100644
--- a/crates/ra_hir_expand/src/name.rs
+++ b/crates/ra_hir_expand/src/name.rs
@@ -83,6 +83,12 @@ impl AsName for ast::Name {
83 } 83 }
84} 84}
85 85
86impl AsName for tt::Ident {
87 fn as_name(&self) -> Name {
88 Name::resolve(&self.text)
89 }
90}
91
86impl AsName for ast::FieldKind { 92impl AsName for ast::FieldKind {
87 fn as_name(&self) -> Name { 93 fn as_name(&self) -> Name {
88 match self { 94 match self {
@@ -153,3 +159,14 @@ pub const COLUMN_MACRO: Name = Name::new_inline_ascii(6, b"column");
153pub const COMPILE_ERROR_MACRO: Name = Name::new_inline_ascii(13, b"compile_error"); 159pub const COMPILE_ERROR_MACRO: Name = Name::new_inline_ascii(13, b"compile_error");
154pub const LINE_MACRO: Name = Name::new_inline_ascii(4, b"line"); 160pub const LINE_MACRO: Name = Name::new_inline_ascii(4, b"line");
155pub const STRINGIFY_MACRO: Name = Name::new_inline_ascii(9, b"stringify"); 161pub const STRINGIFY_MACRO: Name = Name::new_inline_ascii(9, b"stringify");
162
163// Builtin derives
164pub const COPY_TRAIT: Name = Name::new_inline_ascii(4, b"Copy");
165pub const CLONE_TRAIT: Name = Name::new_inline_ascii(5, b"Clone");
166pub const DEFAULT_TRAIT: Name = Name::new_inline_ascii(7, b"Default");
167pub const DEBUG_TRAIT: Name = Name::new_inline_ascii(5, b"Debug");
168pub const HASH_TRAIT: Name = Name::new_inline_ascii(4, b"Hash");
169pub const ORD_TRAIT: Name = Name::new_inline_ascii(3, b"Ord");
170pub const PARTIAL_ORD_TRAIT: Name = Name::new_inline_ascii(10, b"PartialOrd");
171pub const EQ_TRAIT: Name = Name::new_inline_ascii(2, b"Eq");
172pub const PARTIAL_EQ_TRAIT: Name = Name::new_inline_ascii(9, b"PartialEq");
diff --git a/crates/ra_hir_expand/src/quote.rs b/crates/ra_hir_expand/src/quote.rs
index 65a35e52f..4f698ff13 100644
--- a/crates/ra_hir_expand/src/quote.rs
+++ b/crates/ra_hir_expand/src/quote.rs
@@ -60,6 +60,15 @@ macro_rules! __quote {
60 } 60 }
61 }; 61 };
62 62
63 ( ## $first:ident $($tail:tt)* ) => {
64 {
65 let mut tokens = $first.into_iter().map($crate::quote::ToTokenTree::to_token).collect::<Vec<tt::TokenTree>>();
66 let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*));
67 tokens.append(&mut tail_tokens);
68 tokens
69 }
70 };
71
63 // Brace 72 // Brace
64 ( { $($tt:tt)* } ) => { $crate::__quote!(@SUBTREE Brace $($tt)*) }; 73 ( { $($tt:tt)* } ) => { $crate::__quote!(@SUBTREE Brace $($tt)*) };
65 // Bracket 74 // Bracket
@@ -85,6 +94,7 @@ macro_rules! __quote {
85 ( & ) => {$crate::__quote!(@PUNCT '&')}; 94 ( & ) => {$crate::__quote!(@PUNCT '&')};
86 ( , ) => {$crate::__quote!(@PUNCT ',')}; 95 ( , ) => {$crate::__quote!(@PUNCT ',')};
87 ( : ) => {$crate::__quote!(@PUNCT ':')}; 96 ( : ) => {$crate::__quote!(@PUNCT ':')};
97 ( :: ) => {$crate::__quote!(@PUNCT ':', ':')};
88 ( . ) => {$crate::__quote!(@PUNCT '.')}; 98 ( . ) => {$crate::__quote!(@PUNCT '.')};
89 99
90 ( $first:tt $($tail:tt)+ ) => { 100 ( $first:tt $($tail:tt)+ ) => {
diff --git a/crates/ra_hir_ty/src/tests/macros.rs b/crates/ra_hir_ty/src/tests/macros.rs
index 0d9a35ce0..9c29a054e 100644
--- a/crates/ra_hir_ty/src/tests/macros.rs
+++ b/crates/ra_hir_ty/src/tests/macros.rs
@@ -266,3 +266,54 @@ fn main() {
266 "### 266 "###
267 ); 267 );
268} 268}
269
270#[test]
271fn infer_derive_clone_simple() {
272 let (db, pos) = TestDB::with_position(
273 r#"
274//- /main.rs crate:main deps:std
275#[derive(Clone)]
276struct S;
277fn test() {
278 S.clone()<|>;
279}
280
281//- /lib.rs crate:std
282#[prelude_import]
283use clone::*;
284mod clone {
285 trait Clone {
286 fn clone(&self) -> Self;
287 }
288}
289"#,
290 );
291 assert_eq!("S", type_at_pos(&db, pos));
292}
293
294#[test]
295fn infer_derive_clone_with_params() {
296 let (db, pos) = TestDB::with_position(
297 r#"
298//- /main.rs crate:main deps:std
299#[derive(Clone)]
300struct S;
301#[derive(Clone)]
302struct Wrapper<T>(T);
303struct NonClone;
304fn test() {
305 (Wrapper(S).clone(), Wrapper(NonClone).clone())<|>;
306}
307
308//- /lib.rs crate:std
309#[prelude_import]
310use clone::*;
311mod clone {
312 trait Clone {
313 fn clone(&self) -> Self;
314 }
315}
316"#,
317 );
318 assert_eq!("(Wrapper<S>, {unknown})", type_at_pos(&db, pos));
319}
diff --git a/crates/ra_mbe/src/syntax_bridge.rs b/crates/ra_mbe/src/syntax_bridge.rs
index 1de399fee..66c1f0337 100644
--- a/crates/ra_mbe/src/syntax_bridge.rs
+++ b/crates/ra_mbe/src/syntax_bridge.rs
@@ -2,7 +2,7 @@
2 2
3use ra_parser::{FragmentKind, ParseError, TreeSink}; 3use ra_parser::{FragmentKind, ParseError, TreeSink};
4use ra_syntax::{ 4use ra_syntax::{
5 ast, AstNode, AstToken, NodeOrToken, Parse, SmolStr, SyntaxKind, SyntaxKind::*, SyntaxNode, 5 ast, AstToken, NodeOrToken, Parse, SmolStr, SyntaxKind, SyntaxKind::*, SyntaxNode,
6 SyntaxTreeBuilder, TextRange, TextUnit, T, 6 SyntaxTreeBuilder, TextRange, TextUnit, T,
7}; 7};
8use std::iter::successors; 8use std::iter::successors;
@@ -20,7 +20,7 @@ pub struct TokenMap {
20 20
21/// Convert the syntax tree (what user has written) to a `TokenTree` (what macro 21/// Convert the syntax tree (what user has written) to a `TokenTree` (what macro
22/// will consume). 22/// will consume).
23pub fn ast_to_token_tree(ast: &ast::TokenTree) -> Option<(tt::Subtree, TokenMap)> { 23pub fn ast_to_token_tree(ast: &impl ast::AstNode) -> Option<(tt::Subtree, TokenMap)> {
24 syntax_node_to_token_tree(ast.syntax()) 24 syntax_node_to_token_tree(ast.syntax())
25} 25}
26 26
@@ -208,13 +208,8 @@ impl Convertor {
208 } else if token.kind().is_trivia() { 208 } else if token.kind().is_trivia() {
209 continue; 209 continue;
210 } else if token.kind().is_punct() { 210 } else if token.kind().is_punct() {
211 assert!( 211 // we need to pull apart joined punctuation tokens
212 token.text().len() == 1, 212 let last_spacing = match child_iter.peek() {
213 "Input ast::token punct must be single char."
214 );
215 let char = token.text().chars().next().unwrap();
216
217 let spacing = match child_iter.peek() {
218 Some(NodeOrToken::Token(token)) => { 213 Some(NodeOrToken::Token(token)) => {
219 if token.kind().is_punct() { 214 if token.kind().is_punct() {
220 tt::Spacing::Joint 215 tt::Spacing::Joint
@@ -224,8 +219,12 @@ impl Convertor {
224 } 219 }
225 _ => tt::Spacing::Alone, 220 _ => tt::Spacing::Alone,
226 }; 221 };
227 222 let spacing_iter = std::iter::repeat(tt::Spacing::Joint)
228 token_trees.push(tt::Leaf::from(tt::Punct { char, spacing }).into()); 223 .take(token.text().len() - 1)
224 .chain(std::iter::once(last_spacing));
225 for (char, spacing) in token.text().chars().zip(spacing_iter) {
226 token_trees.push(tt::Leaf::from(tt::Punct { char, spacing }).into());
227 }
229 } else { 228 } else {
230 let child: tt::TokenTree = 229 let child: tt::TokenTree =
231 if token.kind() == T![true] || token.kind() == T![false] { 230 if token.kind() == T![true] || token.kind() == T![false] {
@@ -246,8 +245,14 @@ impl Convertor {
246 } 245 }
247 } 246 }
248 NodeOrToken::Node(node) => { 247 NodeOrToken::Node(node) => {
249 let child = self.go(&node)?.into(); 248 let child_subtree = self.go(&node)?;
250 token_trees.push(child); 249 if child_subtree.delimiter == tt::Delimiter::None
250 && node.kind() != SyntaxKind::TOKEN_TREE
251 {
252 token_trees.extend(child_subtree.token_trees);
253 } else {
254 token_trees.push(child_subtree.into());
255 }
251 } 256 }
252 }; 257 };
253 } 258 }
@@ -389,7 +394,10 @@ mod tests {
389 use super::*; 394 use super::*;
390 use crate::tests::{create_rules, expand}; 395 use crate::tests::{create_rules, expand};
391 use ra_parser::TokenSource; 396 use ra_parser::TokenSource;
392 use ra_syntax::algo::{insert_children, InsertPosition}; 397 use ra_syntax::{
398 algo::{insert_children, InsertPosition},
399 ast::AstNode,
400 };
393 401
394 #[test] 402 #[test]
395 fn convert_tt_token_source() { 403 fn convert_tt_token_source() {
@@ -491,4 +499,12 @@ mod tests {
491 499
492 assert_eq!(tt.delimiter, tt::Delimiter::Brace); 500 assert_eq!(tt.delimiter, tt::Delimiter::Brace);
493 } 501 }
502
503 #[test]
504 fn test_token_tree_multi_char_punct() {
505 let source_file = ast::SourceFile::parse("struct Foo { a: x::Y }").ok().unwrap();
506 let struct_def = source_file.syntax().descendants().find_map(ast::StructDef::cast).unwrap();
507 let tt = ast_to_token_tree(&struct_def).unwrap().0;
508 token_tree_to_syntax_node(&tt, FragmentKind::Item).unwrap();
509 }
494} 510}