diff options
Diffstat (limited to 'crates/ra_hir_expand')
-rw-r--r-- | crates/ra_hir_expand/src/builtin_macro.rs | 186 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/db.rs | 71 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/test_db.rs | 9 |
3 files changed, 203 insertions, 63 deletions
diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs index 9fc33e4b1..a90007f26 100644 --- a/crates/ra_hir_expand/src/builtin_macro.rs +++ b/crates/ra_hir_expand/src/builtin_macro.rs | |||
@@ -7,6 +7,7 @@ use crate::{ | |||
7 | 7 | ||
8 | use crate::{quote, EagerMacroId, LazyMacroId, MacroCallId}; | 8 | use crate::{quote, EagerMacroId, LazyMacroId, MacroCallId}; |
9 | use either::Either; | 9 | use either::Either; |
10 | use mbe::parse_to_token_tree; | ||
10 | use ra_db::{FileId, RelativePath}; | 11 | use ra_db::{FileId, RelativePath}; |
11 | use ra_parser::FragmentKind; | 12 | use ra_parser::FragmentKind; |
12 | 13 | ||
@@ -89,15 +90,15 @@ register_builtin! { | |||
89 | (line, Line) => line_expand, | 90 | (line, Line) => line_expand, |
90 | (stringify, Stringify) => stringify_expand, | 91 | (stringify, Stringify) => stringify_expand, |
91 | (format_args, FormatArgs) => format_args_expand, | 92 | (format_args, FormatArgs) => format_args_expand, |
92 | (env, Env) => env_expand, | ||
93 | (option_env, OptionEnv) => option_env_expand, | ||
94 | // format_args_nl only differs in that it adds a newline in the end, | 93 | // format_args_nl only differs in that it adds a newline in the end, |
95 | // so we use the same stub expansion for now | 94 | // so we use the same stub expansion for now |
96 | (format_args_nl, FormatArgsNl) => format_args_expand, | 95 | (format_args_nl, FormatArgsNl) => format_args_expand, |
97 | 96 | ||
98 | EAGER: | 97 | EAGER: |
99 | (concat, Concat) => concat_expand, | 98 | (concat, Concat) => concat_expand, |
100 | (include, Include) => include_expand | 99 | (include, Include) => include_expand, |
100 | (env, Env) => env_expand, | ||
101 | (option_env, OptionEnv) => option_env_expand | ||
101 | } | 102 | } |
102 | 103 | ||
103 | fn line_expand( | 104 | fn line_expand( |
@@ -136,28 +137,6 @@ fn stringify_expand( | |||
136 | Ok(expanded) | 137 | Ok(expanded) |
137 | } | 138 | } |
138 | 139 | ||
139 | fn env_expand( | ||
140 | _db: &dyn AstDatabase, | ||
141 | _id: LazyMacroId, | ||
142 | _tt: &tt::Subtree, | ||
143 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
144 | // dummy implementation for type-checking purposes | ||
145 | let expanded = quote! { "" }; | ||
146 | |||
147 | Ok(expanded) | ||
148 | } | ||
149 | |||
150 | fn option_env_expand( | ||
151 | _db: &dyn AstDatabase, | ||
152 | _id: LazyMacroId, | ||
153 | _tt: &tt::Subtree, | ||
154 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
155 | // dummy implementation for type-checking purposes | ||
156 | let expanded = quote! { std::option::Option::None::<&str> }; | ||
157 | |||
158 | Ok(expanded) | ||
159 | } | ||
160 | |||
161 | fn column_expand( | 140 | fn column_expand( |
162 | _db: &dyn AstDatabase, | 141 | _db: &dyn AstDatabase, |
163 | _id: LazyMacroId, | 142 | _id: LazyMacroId, |
@@ -274,44 +253,101 @@ fn concat_expand( | |||
274 | 253 | ||
275 | fn relative_file(db: &dyn AstDatabase, call_id: MacroCallId, path: &str) -> Option<FileId> { | 254 | fn relative_file(db: &dyn AstDatabase, call_id: MacroCallId, path: &str) -> Option<FileId> { |
276 | let call_site = call_id.as_file().original_file(db); | 255 | let call_site = call_id.as_file().original_file(db); |
277 | let path = RelativePath::new(&path); | ||
278 | 256 | ||
279 | db.resolve_relative_path(call_site, &path) | 257 | // Handle trivial case |
258 | if let Some(res) = db.resolve_relative_path(call_site, &RelativePath::new(&path)) { | ||
259 | // Prevent include itself | ||
260 | return if res == call_site { None } else { Some(res) }; | ||
261 | } | ||
262 | |||
263 | // Extern paths ? | ||
264 | let krate = db.relevant_crates(call_site).get(0)?.clone(); | ||
265 | let (extern_source_id, relative_file) = | ||
266 | db.crate_graph()[krate].extern_source.extern_path(path)?; | ||
267 | |||
268 | db.resolve_extern_path(extern_source_id, &relative_file) | ||
280 | } | 269 | } |
281 | 270 | ||
282 | fn include_expand( | 271 | fn parse_string(tt: &tt::Subtree) -> Result<String, mbe::ExpandError> { |
283 | db: &dyn AstDatabase, | 272 | tt.token_trees |
284 | arg_id: EagerMacroId, | ||
285 | tt: &tt::Subtree, | ||
286 | ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> { | ||
287 | let path = tt | ||
288 | .token_trees | ||
289 | .get(0) | 273 | .get(0) |
290 | .and_then(|tt| match tt { | 274 | .and_then(|tt| match tt { |
291 | tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(&it), | 275 | tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(&it), |
292 | _ => None, | 276 | _ => None, |
293 | }) | 277 | }) |
294 | .ok_or_else(|| mbe::ExpandError::ConversionError)?; | 278 | .ok_or_else(|| mbe::ExpandError::ConversionError) |
279 | } | ||
295 | 280 | ||
281 | fn include_expand( | ||
282 | db: &dyn AstDatabase, | ||
283 | arg_id: EagerMacroId, | ||
284 | tt: &tt::Subtree, | ||
285 | ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> { | ||
286 | let path = parse_string(tt)?; | ||
296 | let file_id = | 287 | let file_id = |
297 | relative_file(db, arg_id.into(), &path).ok_or_else(|| mbe::ExpandError::ConversionError)?; | 288 | relative_file(db, arg_id.into(), &path).ok_or_else(|| mbe::ExpandError::ConversionError)?; |
298 | 289 | ||
299 | // FIXME: | 290 | // FIXME: |
300 | // Handle include as expression | 291 | // Handle include as expression |
301 | let node = | 292 | let res = parse_to_token_tree(&db.file_text(file_id.into())) |
302 | db.parse_or_expand(file_id.into()).ok_or_else(|| mbe::ExpandError::ConversionError)?; | 293 | .ok_or_else(|| mbe::ExpandError::ConversionError)? |
303 | let res = | 294 | .0; |
304 | mbe::syntax_node_to_token_tree(&node).ok_or_else(|| mbe::ExpandError::ConversionError)?.0; | ||
305 | 295 | ||
306 | Ok((res, FragmentKind::Items)) | 296 | Ok((res, FragmentKind::Items)) |
307 | } | 297 | } |
308 | 298 | ||
299 | fn get_env_inner(db: &dyn AstDatabase, arg_id: EagerMacroId, key: &str) -> Option<String> { | ||
300 | let call_id: MacroCallId = arg_id.into(); | ||
301 | let original_file = call_id.as_file().original_file(db); | ||
302 | |||
303 | let krate = db.relevant_crates(original_file).get(0)?.clone(); | ||
304 | db.crate_graph()[krate].env.get(key) | ||
305 | } | ||
306 | |||
307 | fn env_expand( | ||
308 | db: &dyn AstDatabase, | ||
309 | arg_id: EagerMacroId, | ||
310 | tt: &tt::Subtree, | ||
311 | ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> { | ||
312 | let key = parse_string(tt)?; | ||
313 | |||
314 | // FIXME: | ||
315 | // If the environment variable is not defined int rustc, then a compilation error will be emitted. | ||
316 | // We might do the same if we fully support all other stuffs. | ||
317 | // But for now on, we should return some dummy string for better type infer purpose. | ||
318 | // However, we cannot use an empty string here, because for | ||
319 | // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become | ||
320 | // `include!("foo.rs"), which might go to infinite loop | ||
321 | let s = get_env_inner(db, arg_id, &key).unwrap_or("__RA_UNIMPLEMENTATED__".to_string()); | ||
322 | let expanded = quote! { #s }; | ||
323 | |||
324 | Ok((expanded, FragmentKind::Expr)) | ||
325 | } | ||
326 | |||
327 | fn option_env_expand( | ||
328 | db: &dyn AstDatabase, | ||
329 | arg_id: EagerMacroId, | ||
330 | tt: &tt::Subtree, | ||
331 | ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> { | ||
332 | let key = parse_string(tt)?; | ||
333 | let expanded = match get_env_inner(db, arg_id, &key) { | ||
334 | None => quote! { std::option::Option::None::<&str> }, | ||
335 | Some(s) => quote! { std::option::Some(#s) }, | ||
336 | }; | ||
337 | |||
338 | Ok((expanded, FragmentKind::Expr)) | ||
339 | } | ||
340 | |||
309 | #[cfg(test)] | 341 | #[cfg(test)] |
310 | mod tests { | 342 | mod tests { |
311 | use super::*; | 343 | use super::*; |
312 | use crate::{name::AsName, test_db::TestDB, AstNode, MacroCallId, MacroCallKind, MacroCallLoc}; | 344 | use crate::{ |
345 | name::AsName, test_db::TestDB, AstNode, EagerCallLoc, MacroCallId, MacroCallKind, | ||
346 | MacroCallLoc, | ||
347 | }; | ||
313 | use ra_db::{fixture::WithFixture, SourceDatabase}; | 348 | use ra_db::{fixture::WithFixture, SourceDatabase}; |
314 | use ra_syntax::ast::NameOwner; | 349 | use ra_syntax::ast::NameOwner; |
350 | use std::sync::Arc; | ||
315 | 351 | ||
316 | fn expand_builtin_macro(ra_fixture: &str) -> String { | 352 | fn expand_builtin_macro(ra_fixture: &str) -> String { |
317 | let (db, file_id) = TestDB::with_single_file(&ra_fixture); | 353 | let (db, file_id) = TestDB::with_single_file(&ra_fixture); |
@@ -322,27 +358,61 @@ mod tests { | |||
322 | let ast_id_map = db.ast_id_map(file_id.into()); | 358 | let ast_id_map = db.ast_id_map(file_id.into()); |
323 | 359 | ||
324 | let expander = find_by_name(¯o_calls[0].name().unwrap().as_name()).unwrap(); | 360 | let expander = find_by_name(¯o_calls[0].name().unwrap().as_name()).unwrap(); |
325 | let expander = expander.left().unwrap(); | ||
326 | 361 | ||
327 | // the first one should be a macro_rules | 362 | let file_id = match expander { |
328 | let def = MacroDefId { | 363 | Either::Left(expander) => { |
329 | krate: Some(CrateId(0)), | 364 | // the first one should be a macro_rules |
330 | ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), | 365 | let def = MacroDefId { |
331 | kind: MacroDefKind::BuiltIn(expander), | 366 | krate: Some(CrateId(0)), |
332 | }; | 367 | ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), |
368 | kind: MacroDefKind::BuiltIn(expander), | ||
369 | }; | ||
333 | 370 | ||
334 | let loc = MacroCallLoc { | 371 | let loc = MacroCallLoc { |
335 | def, | 372 | def, |
336 | kind: MacroCallKind::FnLike(AstId::new( | 373 | kind: MacroCallKind::FnLike(AstId::new( |
337 | file_id.into(), | 374 | file_id.into(), |
338 | ast_id_map.ast_id(¯o_calls[1]), | 375 | ast_id_map.ast_id(¯o_calls[1]), |
339 | )), | 376 | )), |
340 | }; | 377 | }; |
341 | 378 | ||
342 | let id: MacroCallId = db.intern_macro(loc).into(); | 379 | let id: MacroCallId = db.intern_macro(loc).into(); |
343 | let parsed = db.parse_or_expand(id.as_file()).unwrap(); | 380 | id.as_file() |
381 | } | ||
382 | Either::Right(expander) => { | ||
383 | // the first one should be a macro_rules | ||
384 | let def = MacroDefId { | ||
385 | krate: Some(CrateId(0)), | ||
386 | ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), | ||
387 | kind: MacroDefKind::BuiltInEager(expander), | ||
388 | }; | ||
389 | |||
390 | let args = macro_calls[1].token_tree().unwrap(); | ||
391 | let parsed_args = mbe::ast_to_token_tree(&args).unwrap().0; | ||
392 | |||
393 | let arg_id = db.intern_eager_expansion({ | ||
394 | EagerCallLoc { | ||
395 | def, | ||
396 | fragment: FragmentKind::Expr, | ||
397 | subtree: Arc::new(parsed_args.clone()), | ||
398 | file_id: file_id.into(), | ||
399 | } | ||
400 | }); | ||
401 | |||
402 | let (subtree, fragment) = expander.expand(&db, arg_id, &parsed_args).unwrap(); | ||
403 | let eager = EagerCallLoc { | ||
404 | def, | ||
405 | fragment, | ||
406 | subtree: Arc::new(subtree), | ||
407 | file_id: file_id.into(), | ||
408 | }; | ||
344 | 409 | ||
345 | parsed.text().to_string() | 410 | let id: MacroCallId = db.intern_eager_expansion(eager.into()).into(); |
411 | id.as_file() | ||
412 | } | ||
413 | }; | ||
414 | |||
415 | db.parse_or_expand(file_id).unwrap().to_string() | ||
346 | } | 416 | } |
347 | 417 | ||
348 | #[test] | 418 | #[test] |
@@ -394,7 +464,7 @@ mod tests { | |||
394 | "#, | 464 | "#, |
395 | ); | 465 | ); |
396 | 466 | ||
397 | assert_eq!(expanded, "\"\""); | 467 | assert_eq!(expanded, "\"__RA_UNIMPLEMENTATED__\""); |
398 | } | 468 | } |
399 | 469 | ||
400 | #[test] | 470 | #[test] |
diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs index f3a84cacc..29dde3d80 100644 --- a/crates/ra_hir_expand/src/db.rs +++ b/crates/ra_hir_expand/src/db.rs | |||
@@ -72,6 +72,30 @@ pub trait AstDatabase: SourceDatabase { | |||
72 | fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId; | 72 | fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId; |
73 | } | 73 | } |
74 | 74 | ||
75 | /// This expands the given macro call, but with different arguments. This is | ||
76 | /// used for completion, where we want to see what 'would happen' if we insert a | ||
77 | /// token. The `token_to_map` mapped down into the expansion, with the mapped | ||
78 | /// token returned. | ||
79 | pub fn expand_hypothetical( | ||
80 | db: &impl AstDatabase, | ||
81 | actual_macro_call: MacroCallId, | ||
82 | hypothetical_args: &ra_syntax::ast::TokenTree, | ||
83 | token_to_map: ra_syntax::SyntaxToken, | ||
84 | ) -> Option<(SyntaxNode, ra_syntax::SyntaxToken)> { | ||
85 | let macro_file = MacroFile { macro_call_id: actual_macro_call }; | ||
86 | let (tt, tmap_1) = mbe::syntax_node_to_token_tree(hypothetical_args.syntax()).unwrap(); | ||
87 | let range = | ||
88 | token_to_map.text_range().checked_sub(hypothetical_args.syntax().text_range().start())?; | ||
89 | let token_id = tmap_1.token_by_range(range)?; | ||
90 | let macro_def = expander(db, actual_macro_call)?; | ||
91 | let (node, tmap_2) = | ||
92 | parse_macro_with_arg(db, macro_file, Some(std::sync::Arc::new((tt, tmap_1))))?; | ||
93 | let token_id = macro_def.0.map_id_down(token_id); | ||
94 | let range = tmap_2.range_by_token(token_id)?.by_kind(token_to_map.kind())?; | ||
95 | let token = ra_syntax::algo::find_covering_element(&node.syntax_node(), range).into_token()?; | ||
96 | Some((node.syntax_node(), token)) | ||
97 | } | ||
98 | |||
75 | pub(crate) fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdMap> { | 99 | pub(crate) fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdMap> { |
76 | let map = | 100 | let map = |
77 | db.parse_or_expand(file_id).map_or_else(AstIdMap::default, |it| AstIdMap::from_source(&it)); | 101 | db.parse_or_expand(file_id).map_or_else(AstIdMap::default, |it| AstIdMap::from_source(&it)); |
@@ -130,15 +154,42 @@ pub(crate) fn macro_expand( | |||
130 | db: &dyn AstDatabase, | 154 | db: &dyn AstDatabase, |
131 | id: MacroCallId, | 155 | id: MacroCallId, |
132 | ) -> Result<Arc<tt::Subtree>, String> { | 156 | ) -> Result<Arc<tt::Subtree>, String> { |
157 | macro_expand_with_arg(db, id, None) | ||
158 | } | ||
159 | |||
160 | fn expander(db: &dyn AstDatabase, id: MacroCallId) -> Option<Arc<(TokenExpander, mbe::TokenMap)>> { | ||
161 | let lazy_id = match id { | ||
162 | MacroCallId::LazyMacro(id) => id, | ||
163 | MacroCallId::EagerMacro(_id) => { | ||
164 | return None; | ||
165 | } | ||
166 | }; | ||
167 | |||
168 | let loc = db.lookup_intern_macro(lazy_id); | ||
169 | let macro_rules = db.macro_def(loc.def)?; | ||
170 | Some(macro_rules) | ||
171 | } | ||
172 | |||
173 | fn macro_expand_with_arg( | ||
174 | db: &dyn AstDatabase, | ||
175 | id: MacroCallId, | ||
176 | arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>, | ||
177 | ) -> Result<Arc<tt::Subtree>, String> { | ||
133 | let lazy_id = match id { | 178 | let lazy_id = match id { |
134 | MacroCallId::LazyMacro(id) => id, | 179 | MacroCallId::LazyMacro(id) => id, |
135 | MacroCallId::EagerMacro(id) => { | 180 | MacroCallId::EagerMacro(id) => { |
136 | return Ok(db.lookup_intern_eager_expansion(id).subtree); | 181 | if arg.is_some() { |
182 | return Err( | ||
183 | "hypothetical macro expansion not implemented for eager macro".to_owned() | ||
184 | ); | ||
185 | } else { | ||
186 | return Ok(db.lookup_intern_eager_expansion(id).subtree); | ||
187 | } | ||
137 | } | 188 | } |
138 | }; | 189 | }; |
139 | 190 | ||
140 | let loc = db.lookup_intern_macro(lazy_id); | 191 | let loc = db.lookup_intern_macro(lazy_id); |
141 | let macro_arg = db.macro_arg(id).ok_or("Fail to args in to tt::TokenTree")?; | 192 | let macro_arg = arg.or_else(|| db.macro_arg(id)).ok_or("Fail to args in to tt::TokenTree")?; |
142 | 193 | ||
143 | let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?; | 194 | let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?; |
144 | let tt = macro_rules.0.expand(db, lazy_id, ¯o_arg.0).map_err(|err| format!("{:?}", err))?; | 195 | let tt = macro_rules.0.expand(db, lazy_id, ¯o_arg.0).map_err(|err| format!("{:?}", err))?; |
@@ -163,11 +214,23 @@ pub(crate) fn parse_macro( | |||
163 | db: &dyn AstDatabase, | 214 | db: &dyn AstDatabase, |
164 | macro_file: MacroFile, | 215 | macro_file: MacroFile, |
165 | ) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> { | 216 | ) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> { |
217 | parse_macro_with_arg(db, macro_file, None) | ||
218 | } | ||
219 | |||
220 | pub fn parse_macro_with_arg( | ||
221 | db: &dyn AstDatabase, | ||
222 | macro_file: MacroFile, | ||
223 | arg: Option<Arc<(tt::Subtree, mbe::TokenMap)>>, | ||
224 | ) -> Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)> { | ||
166 | let _p = profile("parse_macro_query"); | 225 | let _p = profile("parse_macro_query"); |
167 | 226 | ||
168 | let macro_call_id = macro_file.macro_call_id; | 227 | let macro_call_id = macro_file.macro_call_id; |
169 | let tt = db | 228 | let expansion = if let Some(arg) = arg { |
170 | .macro_expand(macro_call_id) | 229 | macro_expand_with_arg(db, macro_call_id, Some(arg)) |
230 | } else { | ||
231 | db.macro_expand(macro_call_id) | ||
232 | }; | ||
233 | let tt = expansion | ||
171 | .map_err(|err| { | 234 | .map_err(|err| { |
172 | // Note: | 235 | // Note: |
173 | // The final goal we would like to make all parse_macro success, | 236 | // The final goal we would like to make all parse_macro success, |
diff --git a/crates/ra_hir_expand/src/test_db.rs b/crates/ra_hir_expand/src/test_db.rs index 918736e2a..c1fb762de 100644 --- a/crates/ra_hir_expand/src/test_db.rs +++ b/crates/ra_hir_expand/src/test_db.rs | |||
@@ -5,7 +5,7 @@ use std::{ | |||
5 | sync::{Arc, Mutex}, | 5 | sync::{Arc, Mutex}, |
6 | }; | 6 | }; |
7 | 7 | ||
8 | use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath}; | 8 | use ra_db::{salsa, CrateId, ExternSourceId, FileId, FileLoader, FileLoaderDelegate, RelativePath}; |
9 | 9 | ||
10 | #[salsa::database( | 10 | #[salsa::database( |
11 | ra_db::SourceDatabaseExtStorage, | 11 | ra_db::SourceDatabaseExtStorage, |
@@ -51,4 +51,11 @@ impl FileLoader for TestDB { | |||
51 | fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> { | 51 | fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> { |
52 | FileLoaderDelegate(self).relevant_crates(file_id) | 52 | FileLoaderDelegate(self).relevant_crates(file_id) |
53 | } | 53 | } |
54 | fn resolve_extern_path( | ||
55 | &self, | ||
56 | anchor: ExternSourceId, | ||
57 | relative_path: &RelativePath, | ||
58 | ) -> Option<FileId> { | ||
59 | FileLoaderDelegate(self).resolve_extern_path(anchor, relative_path) | ||
60 | } | ||
54 | } | 61 | } |