aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_expand
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-11-11 10:53:24 +0000
committerGitHub <[email protected]>2019-11-11 10:53:24 +0000
commitef2a9aedb6ac7f0b79e636cff7947935fecb909d (patch)
treea603361f9e6c02d90c0ae8cdfd0902370677f71d /crates/ra_hir_expand
parent5ac4ffbc121c8231fe3ea5c2bb918f7aae60f197 (diff)
parent4f7df2aac107c0de2cab851f2a4f1ab369511fc8 (diff)
Merge #2205
2205: Implement bulitin line! macro r=matklad a=edwin0cheng This PR implements bulitin macro `line!` and add basic infra-structure for other bulitin macros: 1. Extend `MacroDefId` to support builtin macros 2. Add a `quote!` macro for simple quasi quoting. Note that for support others builtin macros, eager macro expansion have to be supported first, this PR not try to handle it. :) Co-authored-by: Edwin Cheng <[email protected]>
Diffstat (limited to 'crates/ra_hir_expand')
-rw-r--r--crates/ra_hir_expand/src/builtin_macro.rs80
-rw-r--r--crates/ra_hir_expand/src/db.rs66
-rw-r--r--crates/ra_hir_expand/src/hygiene.rs7
-rw-r--r--crates/ra_hir_expand/src/lib.rs12
-rw-r--r--crates/ra_hir_expand/src/name.rs3
-rw-r--r--crates/ra_hir_expand/src/quote.rs261
6 files changed, 410 insertions, 19 deletions
diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs
new file mode 100644
index 000000000..97fb0cb55
--- /dev/null
+++ b/crates/ra_hir_expand/src/builtin_macro.rs
@@ -0,0 +1,80 @@
1//! Builtin macro
2use crate::db::AstDatabase;
3use crate::{
4 ast::{self, AstNode},
5 name, AstId, CrateId, HirFileId, MacroCallId, MacroDefId, MacroDefKind, MacroFileKind,
6 TextUnit,
7};
8
9use crate::quote;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum BuiltinExpander {
13 Line,
14}
15
16impl BuiltinExpander {
17 pub fn expand(
18 &self,
19 db: &dyn AstDatabase,
20 id: MacroCallId,
21 tt: &tt::Subtree,
22 ) -> Result<tt::Subtree, mbe::ExpandError> {
23 match self {
24 BuiltinExpander::Line => line_expand(db, id, tt),
25 }
26 }
27}
28
29pub fn find_builtin_macro(
30 ident: &name::Name,
31 krate: CrateId,
32 ast_id: AstId<ast::MacroCall>,
33) -> Option<MacroDefId> {
34 // FIXME: Better registering method
35 if ident == &name::LINE_MACRO {
36 Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(BuiltinExpander::Line) })
37 } else {
38 None
39 }
40}
41
42fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize {
43 // FIXME: Use expansion info
44 let file_id = file.original_file(db);
45 let text = db.file_text(file_id);
46 let mut line_num = 1;
47
48 // Count line end
49 for (i, c) in text.chars().enumerate() {
50 if i == pos.to_usize() {
51 break;
52 }
53 if c == '\n' {
54 line_num += 1;
55 }
56 }
57
58 line_num
59}
60
61fn line_expand(
62 db: &dyn AstDatabase,
63 id: MacroCallId,
64 _tt: &tt::Subtree,
65) -> Result<tt::Subtree, mbe::ExpandError> {
66 let loc = db.lookup_intern_macro(id);
67 let macro_call = loc.ast_id.to_node(db);
68
69 let arg = macro_call.token_tree().ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;
70 let arg_start = arg.syntax().text_range().start();
71
72 let file = id.as_file(MacroFileKind::Expr);
73 let line_num = to_line_number(db, file, arg_start);
74
75 let expanded = quote! {
76 #line_num
77 };
78
79 Ok(expanded)
80}
diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs
index b4dafe1d8..5eadee9c2 100644
--- a/crates/ra_hir_expand/src/db.rs
+++ b/crates/ra_hir_expand/src/db.rs
@@ -9,10 +9,37 @@ 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, HirFileId, HirFileIdRepr, MacroCallId, MacroCallLoc, MacroDefId, 12 ast_id_map::AstIdMap, BuiltinExpander, HirFileId, HirFileIdRepr, MacroCallId, MacroCallLoc,
13 MacroFile, MacroFileKind, 13 MacroDefId, MacroDefKind, MacroFile, MacroFileKind,
14}; 14};
15 15
16#[derive(Debug, Clone, Eq, PartialEq)]
17pub enum TokenExpander {
18 MacroRules(mbe::MacroRules),
19 Builtin(BuiltinExpander),
20}
21
22impl TokenExpander {
23 pub fn expand(
24 &self,
25 db: &dyn AstDatabase,
26 id: MacroCallId,
27 tt: &tt::Subtree,
28 ) -> Result<tt::Subtree, mbe::ExpandError> {
29 match self {
30 TokenExpander::MacroRules(it) => it.expand(tt),
31 TokenExpander::Builtin(it) => it.expand(db, id, tt),
32 }
33 }
34
35 pub fn shift(&self) -> u32 {
36 match self {
37 TokenExpander::MacroRules(it) => it.shift(),
38 TokenExpander::Builtin(_) => 0,
39 }
40 }
41}
42
16// FIXME: rename to ExpandDatabase 43// FIXME: rename to ExpandDatabase
17#[salsa::query_group(AstDatabaseStorage)] 44#[salsa::query_group(AstDatabaseStorage)]
18pub trait AstDatabase: SourceDatabase { 45pub trait AstDatabase: SourceDatabase {
@@ -24,7 +51,7 @@ pub trait AstDatabase: SourceDatabase {
24 #[salsa::interned] 51 #[salsa::interned]
25 fn intern_macro(&self, macro_call: MacroCallLoc) -> MacroCallId; 52 fn intern_macro(&self, macro_call: MacroCallLoc) -> MacroCallId;
26 fn macro_arg(&self, id: MacroCallId) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>>; 53 fn macro_arg(&self, id: MacroCallId) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>>;
27 fn macro_def(&self, id: MacroDefId) -> Option<Arc<(mbe::MacroRules, mbe::TokenMap)>>; 54 fn macro_def(&self, id: MacroDefId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>>;
28 fn parse_macro( 55 fn parse_macro(
29 &self, 56 &self,
30 macro_file: MacroFile, 57 macro_file: MacroFile,
@@ -41,18 +68,25 @@ pub(crate) fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdM
41pub(crate) fn macro_def( 68pub(crate) fn macro_def(
42 db: &dyn AstDatabase, 69 db: &dyn AstDatabase,
43 id: MacroDefId, 70 id: MacroDefId,
44) -> Option<Arc<(mbe::MacroRules, mbe::TokenMap)>> { 71) -> Option<Arc<(TokenExpander, mbe::TokenMap)>> {
45 let macro_call = id.ast_id.to_node(db); 72 match id.kind {
46 let arg = macro_call.token_tree()?; 73 MacroDefKind::Declarative => {
47 let (tt, tmap) = mbe::ast_to_token_tree(&arg).or_else(|| { 74 let macro_call = id.ast_id.to_node(db);
48 log::warn!("fail on macro_def to token tree: {:#?}", arg); 75 let arg = macro_call.token_tree()?;
49 None 76 let (tt, tmap) = mbe::ast_to_token_tree(&arg).or_else(|| {
50 })?; 77 log::warn!("fail on macro_def to token tree: {:#?}", arg);
51 let rules = MacroRules::parse(&tt).ok().or_else(|| { 78 None
52 log::warn!("fail on macro_def parse: {:#?}", tt); 79 })?;
53 None 80 let rules = MacroRules::parse(&tt).ok().or_else(|| {
54 })?; 81 log::warn!("fail on macro_def parse: {:#?}", tt);
55 Some(Arc::new((rules, tmap))) 82 None
83 })?;
84 Some(Arc::new((TokenExpander::MacroRules(rules), tmap)))
85 }
86 MacroDefKind::BuiltIn(expander) => {
87 Some(Arc::new((TokenExpander::Builtin(expander.clone()), mbe::TokenMap::default())))
88 }
89 }
56} 90}
57 91
58pub(crate) fn macro_arg( 92pub(crate) fn macro_arg(
@@ -74,7 +108,7 @@ pub(crate) fn macro_expand(
74 let macro_arg = db.macro_arg(id).ok_or("Fail to args in to tt::TokenTree")?; 108 let macro_arg = db.macro_arg(id).ok_or("Fail to args in to tt::TokenTree")?;
75 109
76 let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?; 110 let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?;
77 let tt = macro_rules.0.expand(&macro_arg.0).map_err(|err| format!("{:?}", err))?; 111 let tt = macro_rules.0.expand(db, id, &macro_arg.0).map_err(|err| format!("{:?}", err))?;
78 // Set a hard limit for the expanded tt 112 // Set a hard limit for the expanded tt
79 let count = tt.count(); 113 let count = tt.count();
80 if count > 65536 { 114 if count > 65536 {
diff --git a/crates/ra_hir_expand/src/hygiene.rs b/crates/ra_hir_expand/src/hygiene.rs
index 77428ec99..379562a2c 100644
--- a/crates/ra_hir_expand/src/hygiene.rs
+++ b/crates/ra_hir_expand/src/hygiene.rs
@@ -9,7 +9,7 @@ use crate::{
9 db::AstDatabase, 9 db::AstDatabase,
10 either::Either, 10 either::Either,
11 name::{AsName, Name}, 11 name::{AsName, Name},
12 HirFileId, HirFileIdRepr, 12 HirFileId, HirFileIdRepr, MacroDefKind,
13}; 13};
14 14
15#[derive(Debug)] 15#[derive(Debug)]
@@ -24,7 +24,10 @@ impl Hygiene {
24 HirFileIdRepr::FileId(_) => None, 24 HirFileIdRepr::FileId(_) => None,
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 Some(loc.def.krate) 27 match loc.def.kind {
28 MacroDefKind::Declarative => Some(loc.def.krate),
29 MacroDefKind::BuiltIn(_) => None,
30 }
28 } 31 }
29 }; 32 };
30 Hygiene { def_crate } 33 Hygiene { def_crate }
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs
index 151d1d785..c6ffa2c6f 100644
--- a/crates/ra_hir_expand/src/lib.rs
+++ b/crates/ra_hir_expand/src/lib.rs
@@ -10,6 +10,8 @@ pub mod either;
10pub mod name; 10pub mod name;
11pub mod hygiene; 11pub mod hygiene;
12pub mod diagnostics; 12pub mod diagnostics;
13pub mod builtin_macro;
14pub mod quote;
13 15
14use std::hash::{Hash, Hasher}; 16use std::hash::{Hash, Hasher};
15use std::sync::Arc; 17use std::sync::Arc;
@@ -21,6 +23,7 @@ use ra_syntax::{
21}; 23};
22 24
23use crate::ast_id_map::FileAstId; 25use crate::ast_id_map::FileAstId;
26use crate::builtin_macro::BuiltinExpander;
24 27
25/// Input to the analyzer is a set of files, where each file is identified by 28/// Input to the analyzer is a set of files, where each file is identified by
26/// `FileId` and contains source code. However, another source of source code in 29/// `FileId` and contains source code. However, another source of source code in
@@ -122,6 +125,13 @@ impl salsa::InternKey for MacroCallId {
122pub struct MacroDefId { 125pub struct MacroDefId {
123 pub krate: CrateId, 126 pub krate: CrateId,
124 pub ast_id: AstId<ast::MacroCall>, 127 pub ast_id: AstId<ast::MacroCall>,
128 pub kind: MacroDefKind,
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
132pub enum MacroDefKind {
133 Declarative,
134 BuiltIn(BuiltinExpander),
125} 135}
126 136
127#[derive(Debug, Clone, PartialEq, Eq, Hash)] 137#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -144,7 +154,7 @@ pub struct ExpansionInfo {
144 pub(crate) def_start: (HirFileId, TextUnit), 154 pub(crate) def_start: (HirFileId, TextUnit),
145 pub(crate) shift: u32, 155 pub(crate) shift: u32,
146 156
147 pub(crate) macro_def: Arc<(mbe::MacroRules, mbe::TokenMap)>, 157 pub(crate) macro_def: Arc<(db::TokenExpander, mbe::TokenMap)>,
148 pub(crate) macro_arg: Arc<(tt::Subtree, mbe::TokenMap)>, 158 pub(crate) macro_arg: Arc<(tt::Subtree, mbe::TokenMap)>,
149 pub(crate) exp_map: Arc<mbe::RevTokenMap>, 159 pub(crate) exp_map: Arc<mbe::RevTokenMap>,
150} 160}
diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs
index 720896ee8..1bf17d12b 100644
--- a/crates/ra_hir_expand/src/name.rs
+++ b/crates/ra_hir_expand/src/name.rs
@@ -140,3 +140,6 @@ pub const RESULT_TYPE: Name = Name::new_inline_ascii(6, b"Result");
140pub const OUTPUT_TYPE: Name = Name::new_inline_ascii(6, b"Output"); 140pub const OUTPUT_TYPE: Name = Name::new_inline_ascii(6, b"Output");
141pub const TARGET_TYPE: Name = Name::new_inline_ascii(6, b"Target"); 141pub const TARGET_TYPE: Name = Name::new_inline_ascii(6, b"Target");
142pub const BOX_TYPE: Name = Name::new_inline_ascii(3, b"Box"); 142pub const BOX_TYPE: Name = Name::new_inline_ascii(3, b"Box");
143
144// Builtin Macros
145pub const LINE_MACRO: Name = Name::new_inline_ascii(4, b"line");
diff --git a/crates/ra_hir_expand/src/quote.rs b/crates/ra_hir_expand/src/quote.rs
new file mode 100644
index 000000000..9cd17f0e3
--- /dev/null
+++ b/crates/ra_hir_expand/src/quote.rs
@@ -0,0 +1,261 @@
1//! A simplified version of quote-crate like quasi quote macro
2
3// A helper macro quote macro
4// FIXME:
5// 1. Not all puncts are handled
6// 2. #()* pattern repetition not supported now
7// * But we can do it manually, see `test_quote_derive_copy_hack`
8#[doc(hidden)]
9#[macro_export]
10macro_rules! __quote {
11 () => {
12 Vec::<tt::TokenTree>::new()
13 };
14
15 ( @SUBTREE $delim:ident $($tt:tt)* ) => {
16 {
17 let children = $crate::__quote!($($tt)*);
18 let subtree = tt::Subtree {
19 delimiter: tt::Delimiter::$delim,
20 token_trees: $crate::quote::IntoTt::to_tokens(children),
21 };
22 subtree
23 }
24 };
25
26 ( @PUNCT $first:literal ) => {
27 {
28 vec![
29 tt::Leaf::Punct(tt::Punct {
30 char: $first,
31 spacing: tt::Spacing::Alone,
32 }).into()
33 ]
34 }
35 };
36
37 ( @PUNCT $first:literal, $sec:literal ) => {
38 {
39 vec![
40 tt::Leaf::Punct(tt::Punct {
41 char: $first,
42 spacing: tt::Spacing::Joint,
43 }).into(),
44 tt::Leaf::Punct(tt::Punct {
45 char: $sec,
46 spacing: tt::Spacing::Alone,
47 }).into()
48 ]
49 }
50 };
51
52 // hash variable
53 ( # $first:ident $($tail:tt)* ) => {
54 {
55 let token = $crate::quote::ToTokenTree::to_token($first);
56 let mut tokens = vec![token.into()];
57 let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*));
58 tokens.append(&mut tail_tokens);
59 tokens
60 }
61 };
62
63 // Brace
64 ( { $($tt:tt)* } ) => { $crate::__quote!(@SUBTREE Brace $($tt)*) };
65 // Bracket
66 ( [ $($tt:tt)* ] ) => { $crate::__quote!(@SUBTREE Bracket $($tt)*) };
67 // Parenthesis
68 ( ( $($tt:tt)* ) ) => { $crate::__quote!(@SUBTREE Parenthesis $($tt)*) };
69
70 // Literal
71 ( $tt:literal ) => { vec![$crate::quote::ToTokenTree::to_token($tt).into()] };
72 // Ident
73 ( $tt:ident ) => {
74 vec![ {
75 tt::Leaf::Ident(tt::Ident {
76 text: stringify!($tt).into(),
77 id: tt::TokenId::unspecified(),
78 }).into()
79 }]
80 };
81
82 // Puncts
83 // FIXME: Not all puncts are handled
84 ( -> ) => {$crate::__quote!(@PUNCT '-', '>')};
85 ( & ) => {$crate::__quote!(@PUNCT '&')};
86 ( , ) => {$crate::__quote!(@PUNCT ',')};
87 ( : ) => {$crate::__quote!(@PUNCT ':')};
88 ( . ) => {$crate::__quote!(@PUNCT '.')};
89
90 ( $first:tt $($tail:tt)+ ) => {
91 {
92 let mut tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($first));
93 let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*));
94
95 tokens.append(&mut tail_tokens);
96 tokens
97 }
98 };
99}
100
101/// FIXME:
102/// It probably should implement in proc-macro
103#[macro_export]
104macro_rules! quote {
105 ( $($tt:tt)* ) => {
106 $crate::quote::IntoTt::to_subtree($crate::__quote!($($tt)*))
107 }
108}
109
110pub(crate) trait IntoTt {
111 fn to_subtree(self) -> tt::Subtree;
112 fn to_tokens(self) -> Vec<tt::TokenTree>;
113}
114
115impl IntoTt for Vec<tt::TokenTree> {
116 fn to_subtree(self) -> tt::Subtree {
117 tt::Subtree { delimiter: tt::Delimiter::None, token_trees: self }
118 }
119
120 fn to_tokens(self) -> Vec<tt::TokenTree> {
121 self
122 }
123}
124
125impl IntoTt for tt::Subtree {
126 fn to_subtree(self) -> tt::Subtree {
127 self
128 }
129
130 fn to_tokens(self) -> Vec<tt::TokenTree> {
131 vec![tt::TokenTree::Subtree(self)]
132 }
133}
134
135pub(crate) trait ToTokenTree {
136 fn to_token(self) -> tt::TokenTree;
137}
138
139impl ToTokenTree for tt::TokenTree {
140 fn to_token(self) -> tt::TokenTree {
141 self
142 }
143}
144
145impl ToTokenTree for tt::Subtree {
146 fn to_token(self) -> tt::TokenTree {
147 self.into()
148 }
149}
150
151macro_rules! impl_to_to_tokentrees {
152 ($($ty:ty => $this:ident $im:block);*) => {
153 $(
154 impl ToTokenTree for $ty {
155 fn to_token($this) -> tt::TokenTree {
156 let leaf: tt::Leaf = $im.into();
157 leaf.into()
158 }
159 }
160
161 impl ToTokenTree for &$ty {
162 fn to_token($this) -> tt::TokenTree {
163 let leaf: tt::Leaf = $im.clone().into();
164 leaf.into()
165 }
166 }
167 )*
168 }
169}
170
171impl_to_to_tokentrees! {
172 u32 => self { tt::Literal{text: self.to_string().into()} };
173 usize => self { tt::Literal{text: self.to_string().into()}};
174 i32 => self { tt::Literal{text: self.to_string().into()}};
175 &str => self { tt::Literal{text: self.to_string().into()}};
176 String => self { tt::Literal{text: self.into()}};
177 tt::Leaf => self { self };
178 tt::Literal => self { self };
179 tt::Ident => self { self };
180 tt::Punct => self { self }
181}
182
183#[cfg(test)]
184mod tests {
185 #[test]
186 fn test_quote_delimiters() {
187 assert_eq!(quote!({}).to_string(), "{}");
188 assert_eq!(quote!(()).to_string(), "()");
189 assert_eq!(quote!([]).to_string(), "[]");
190 }
191
192 #[test]
193 fn test_quote_idents() {
194 assert_eq!(quote!(32).to_string(), "32");
195 assert_eq!(quote!(struct).to_string(), "struct");
196 }
197
198 #[test]
199 fn test_quote_hash_simple_literal() {
200 let a = 20;
201 assert_eq!(quote!(#a).to_string(), "20");
202 let s: String = "hello".into();
203 assert_eq!(quote!(#s).to_string(), "hello");
204 }
205
206 fn mk_ident(name: &str) -> tt::Ident {
207 tt::Ident { text: name.into(), id: tt::TokenId::unspecified() }
208 }
209
210 #[test]
211 fn test_quote_hash_token_tree() {
212 let a = mk_ident("hello");
213
214 let quoted = quote!(#a);
215 assert_eq!(quoted.to_string(), "hello");
216 let t = format!("{:?}", quoted);
217 assert_eq!(t, "Subtree { delimiter: None, token_trees: [Leaf(Ident(Ident { text: \"hello\", id: TokenId(4294967295) }))] }");
218 }
219
220 #[test]
221 fn test_quote_simple_derive_copy() {
222 let name = mk_ident("Foo");
223
224 let quoted = quote! {
225 impl Clone for #name {
226 fn clone(&self) -> Self {
227 Self {}
228 }
229 }
230 };
231
232 assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {}}}");
233 }
234
235 #[test]
236 fn test_quote_derive_copy_hack() {
237 // Assume the given struct is:
238 // struct Foo {
239 // name: String,
240 // id: u32,
241 // }
242 let struct_name = mk_ident("Foo");
243 let fields = [mk_ident("name"), mk_ident("id")];
244 let fields = fields
245 .into_iter()
246 .map(|it| quote!(#it: self.#it.clone(), ).token_trees.clone())
247 .flatten();
248
249 let list = tt::Subtree { delimiter: tt::Delimiter::Brace, token_trees: fields.collect() };
250
251 let quoted = quote! {
252 impl Clone for #struct_name {
253 fn clone(&self) -> Self {
254 Self #list
255 }
256 }
257 };
258
259 assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {name : self . name . clone () , id : self . id . clone () ,}}}");
260 }
261}