diff options
-rw-r--r-- | crates/ra_hir_expand/src/builtin_macro.rs | 181 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/tests/macros.rs | 20 |
2 files changed, 141 insertions, 60 deletions
diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs index 3f60b1cca..298ec22ff 100644 --- a/crates/ra_hir_expand/src/builtin_macro.rs +++ b/crates/ra_hir_expand/src/builtin_macro.rs | |||
@@ -90,15 +90,15 @@ register_builtin! { | |||
90 | (line, Line) => line_expand, | 90 | (line, Line) => line_expand, |
91 | (stringify, Stringify) => stringify_expand, | 91 | (stringify, Stringify) => stringify_expand, |
92 | (format_args, FormatArgs) => format_args_expand, | 92 | (format_args, FormatArgs) => format_args_expand, |
93 | (env, Env) => env_expand, | ||
94 | (option_env, OptionEnv) => option_env_expand, | ||
95 | // 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, |
96 | // so we use the same stub expansion for now | 94 | // so we use the same stub expansion for now |
97 | (format_args_nl, FormatArgsNl) => format_args_expand, | 95 | (format_args_nl, FormatArgsNl) => format_args_expand, |
98 | 96 | ||
99 | EAGER: | 97 | EAGER: |
100 | (concat, Concat) => concat_expand, | 98 | (concat, Concat) => concat_expand, |
101 | (include, Include) => include_expand | 99 | (include, Include) => include_expand, |
100 | (env, Env) => env_expand, | ||
101 | (option_env, OptionEnv) => option_env_expand | ||
102 | } | 102 | } |
103 | 103 | ||
104 | fn line_expand( | 104 | fn line_expand( |
@@ -137,31 +137,6 @@ fn stringify_expand( | |||
137 | Ok(expanded) | 137 | Ok(expanded) |
138 | } | 138 | } |
139 | 139 | ||
140 | fn env_expand( | ||
141 | _db: &dyn AstDatabase, | ||
142 | _id: LazyMacroId, | ||
143 | _tt: &tt::Subtree, | ||
144 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
145 | // dummy implementation for type-checking purposes | ||
146 | // we cannot use an empty string here, because for | ||
147 | // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become | ||
148 | // `include!("foo.rs"), which maybe infinite loop | ||
149 | let expanded = quote! { "__RA_UNIMPLEMENTATED__" }; | ||
150 | |||
151 | Ok(expanded) | ||
152 | } | ||
153 | |||
154 | fn option_env_expand( | ||
155 | _db: &dyn AstDatabase, | ||
156 | _id: LazyMacroId, | ||
157 | _tt: &tt::Subtree, | ||
158 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
159 | // dummy implementation for type-checking purposes | ||
160 | let expanded = quote! { std::option::Option::None::<&str> }; | ||
161 | |||
162 | Ok(expanded) | ||
163 | } | ||
164 | |||
165 | fn column_expand( | 140 | fn column_expand( |
166 | _db: &dyn AstDatabase, | 141 | _db: &dyn AstDatabase, |
167 | _id: LazyMacroId, | 142 | _id: LazyMacroId, |
@@ -278,30 +253,36 @@ fn concat_expand( | |||
278 | 253 | ||
279 | 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> { |
280 | let call_site = call_id.as_file().original_file(db); | 255 | let call_site = call_id.as_file().original_file(db); |
281 | let path = RelativePath::new(&path); | ||
282 | 256 | ||
283 | let res = db.resolve_relative_path(call_site, &path)?; | 257 | // Handle trivial case |
284 | // Prevent include itself | 258 | if let Some(res) = db.resolve_relative_path(call_site, &RelativePath::new(&path)) { |
285 | if res == call_site { | 259 | // Prevent include itself |
286 | return None; | 260 | return if res == call_site { None } else { Some(res) }; |
287 | } | 261 | } |
288 | Some(res) | 262 | |
263 | // Extern paths ? | ||
264 | let krate = db.relevant_crates(call_site).get(0)?.clone(); | ||
265 | let (extern_source_id, relative_file) = db.crate_graph()[krate].env.extern_path(path)?; | ||
266 | |||
267 | db.resolve_extern_path(extern_source_id, &relative_file) | ||
289 | } | 268 | } |
290 | 269 | ||
291 | fn include_expand( | 270 | fn parse_string(tt: &tt::Subtree) -> Result<String, mbe::ExpandError> { |
292 | db: &dyn AstDatabase, | 271 | tt.token_trees |
293 | arg_id: EagerMacroId, | ||
294 | tt: &tt::Subtree, | ||
295 | ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> { | ||
296 | let path = tt | ||
297 | .token_trees | ||
298 | .get(0) | 272 | .get(0) |
299 | .and_then(|tt| match tt { | 273 | .and_then(|tt| match tt { |
300 | tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(&it), | 274 | tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(&it), |
301 | _ => None, | 275 | _ => None, |
302 | }) | 276 | }) |
303 | .ok_or_else(|| mbe::ExpandError::ConversionError)?; | 277 | .ok_or_else(|| mbe::ExpandError::ConversionError) |
278 | } | ||
304 | 279 | ||
280 | fn include_expand( | ||
281 | db: &dyn AstDatabase, | ||
282 | arg_id: EagerMacroId, | ||
283 | tt: &tt::Subtree, | ||
284 | ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> { | ||
285 | let path = parse_string(tt)?; | ||
305 | let file_id = | 286 | let file_id = |
306 | relative_file(db, arg_id.into(), &path).ok_or_else(|| mbe::ExpandError::ConversionError)?; | 287 | relative_file(db, arg_id.into(), &path).ok_or_else(|| mbe::ExpandError::ConversionError)?; |
307 | 288 | ||
@@ -314,12 +295,58 @@ fn include_expand( | |||
314 | Ok((res, FragmentKind::Items)) | 295 | Ok((res, FragmentKind::Items)) |
315 | } | 296 | } |
316 | 297 | ||
298 | fn get_env_inner(db: &dyn AstDatabase, arg_id: EagerMacroId, key: &str) -> Option<String> { | ||
299 | let call_id: MacroCallId = arg_id.into(); | ||
300 | let original_file = call_id.as_file().original_file(db); | ||
301 | |||
302 | let krate = db.relevant_crates(original_file).get(0)?.clone(); | ||
303 | db.crate_graph()[krate].env.get(key) | ||
304 | } | ||
305 | |||
306 | fn env_expand( | ||
307 | db: &dyn AstDatabase, | ||
308 | arg_id: EagerMacroId, | ||
309 | tt: &tt::Subtree, | ||
310 | ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> { | ||
311 | let key = parse_string(tt)?; | ||
312 | |||
313 | // FIXME: | ||
314 | // If the environment variable is not defined int rustc, then a compilation error will be emitted. | ||
315 | // We might do the same if we fully support all other stuffs. | ||
316 | // But for now on, we should return some dummy string for better type infer purpose. | ||
317 | // However, we cannot use an empty string here, because for | ||
318 | // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become | ||
319 | // `include!("foo.rs"), which might go to infinite loop | ||
320 | let s = get_env_inner(db, arg_id, &key).unwrap_or("__RA_UNIMPLEMENTATED__".to_string()); | ||
321 | let expanded = quote! { #s }; | ||
322 | |||
323 | Ok((expanded, FragmentKind::Expr)) | ||
324 | } | ||
325 | |||
326 | fn option_env_expand( | ||
327 | db: &dyn AstDatabase, | ||
328 | arg_id: EagerMacroId, | ||
329 | tt: &tt::Subtree, | ||
330 | ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> { | ||
331 | let key = parse_string(tt)?; | ||
332 | let expanded = match get_env_inner(db, arg_id, &key) { | ||
333 | None => quote! { std::option::Option::None::<&str> }, | ||
334 | Some(s) => quote! { std::option::Some(#s) }, | ||
335 | }; | ||
336 | |||
337 | Ok((expanded, FragmentKind::Expr)) | ||
338 | } | ||
339 | |||
317 | #[cfg(test)] | 340 | #[cfg(test)] |
318 | mod tests { | 341 | mod tests { |
319 | use super::*; | 342 | use super::*; |
320 | use crate::{name::AsName, test_db::TestDB, AstNode, MacroCallId, MacroCallKind, MacroCallLoc}; | 343 | use crate::{ |
344 | name::AsName, test_db::TestDB, AstNode, EagerCallLoc, MacroCallId, MacroCallKind, | ||
345 | MacroCallLoc, | ||
346 | }; | ||
321 | use ra_db::{fixture::WithFixture, SourceDatabase}; | 347 | use ra_db::{fixture::WithFixture, SourceDatabase}; |
322 | use ra_syntax::ast::NameOwner; | 348 | use ra_syntax::ast::NameOwner; |
349 | use std::sync::Arc; | ||
323 | 350 | ||
324 | fn expand_builtin_macro(ra_fixture: &str) -> String { | 351 | fn expand_builtin_macro(ra_fixture: &str) -> String { |
325 | let (db, file_id) = TestDB::with_single_file(&ra_fixture); | 352 | let (db, file_id) = TestDB::with_single_file(&ra_fixture); |
@@ -330,27 +357,61 @@ mod tests { | |||
330 | let ast_id_map = db.ast_id_map(file_id.into()); | 357 | let ast_id_map = db.ast_id_map(file_id.into()); |
331 | 358 | ||
332 | let expander = find_by_name(¯o_calls[0].name().unwrap().as_name()).unwrap(); | 359 | let expander = find_by_name(¯o_calls[0].name().unwrap().as_name()).unwrap(); |
333 | let expander = expander.left().unwrap(); | ||
334 | 360 | ||
335 | // the first one should be a macro_rules | 361 | let file_id = match expander { |
336 | let def = MacroDefId { | 362 | Either::Left(expander) => { |
337 | krate: Some(CrateId(0)), | 363 | // the first one should be a macro_rules |
338 | ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), | 364 | let def = MacroDefId { |
339 | kind: MacroDefKind::BuiltIn(expander), | 365 | krate: Some(CrateId(0)), |
340 | }; | 366 | ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), |
367 | kind: MacroDefKind::BuiltIn(expander), | ||
368 | }; | ||
341 | 369 | ||
342 | let loc = MacroCallLoc { | 370 | let loc = MacroCallLoc { |
343 | def, | 371 | def, |
344 | kind: MacroCallKind::FnLike(AstId::new( | 372 | kind: MacroCallKind::FnLike(AstId::new( |
345 | file_id.into(), | 373 | file_id.into(), |
346 | ast_id_map.ast_id(¯o_calls[1]), | 374 | ast_id_map.ast_id(¯o_calls[1]), |
347 | )), | 375 | )), |
348 | }; | 376 | }; |
377 | |||
378 | let id: MacroCallId = db.intern_macro(loc).into(); | ||
379 | id.as_file() | ||
380 | } | ||
381 | Either::Right(expander) => { | ||
382 | // the first one should be a macro_rules | ||
383 | let def = MacroDefId { | ||
384 | krate: Some(CrateId(0)), | ||
385 | ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), | ||
386 | kind: MacroDefKind::BuiltInEager(expander), | ||
387 | }; | ||
349 | 388 | ||
350 | let id: MacroCallId = db.intern_macro(loc).into(); | 389 | let args = macro_calls[1].token_tree().unwrap(); |
351 | let parsed = db.parse_or_expand(id.as_file()).unwrap(); | 390 | let parsed_args = mbe::ast_to_token_tree(&args).unwrap().0; |
391 | |||
392 | let arg_id = db.intern_eager_expansion({ | ||
393 | EagerCallLoc { | ||
394 | def, | ||
395 | fragment: FragmentKind::Expr, | ||
396 | subtree: Arc::new(parsed_args.clone()), | ||
397 | file_id: file_id.into(), | ||
398 | } | ||
399 | }); | ||
400 | |||
401 | let (subtree, fragment) = expander.expand(&db, arg_id, &parsed_args).unwrap(); | ||
402 | let eager = EagerCallLoc { | ||
403 | def, | ||
404 | fragment, | ||
405 | subtree: Arc::new(subtree), | ||
406 | file_id: file_id.into(), | ||
407 | }; | ||
408 | |||
409 | let id: MacroCallId = db.intern_eager_expansion(eager.into()).into(); | ||
410 | id.as_file() | ||
411 | } | ||
412 | }; | ||
352 | 413 | ||
353 | parsed.text().to_string() | 414 | db.parse_or_expand(file_id).unwrap().to_string() |
354 | } | 415 | } |
355 | 416 | ||
356 | #[test] | 417 | #[test] |
diff --git a/crates/ra_hir_ty/src/tests/macros.rs b/crates/ra_hir_ty/src/tests/macros.rs index ffa78b046..32457bbf7 100644 --- a/crates/ra_hir_ty/src/tests/macros.rs +++ b/crates/ra_hir_ty/src/tests/macros.rs | |||
@@ -550,6 +550,26 @@ fn main() { | |||
550 | } | 550 | } |
551 | 551 | ||
552 | #[test] | 552 | #[test] |
553 | fn infer_builtin_macros_env() { | ||
554 | assert_snapshot!( | ||
555 | infer(r#" | ||
556 | //- /main.rs env:foo=bar | ||
557 | #[rustc_builtin_macro] | ||
558 | macro_rules! env {() => {}} | ||
559 | |||
560 | fn main() { | ||
561 | let x = env!("foo"); | ||
562 | } | ||
563 | "#), | ||
564 | @r###" | ||
565 | ![0; 5) '"bar"': &str | ||
566 | [88; 116) '{ ...o"); }': () | ||
567 | [98; 99) 'x': &str | ||
568 | "### | ||
569 | ); | ||
570 | } | ||
571 | |||
572 | #[test] | ||
553 | fn infer_derive_clone_simple() { | 573 | fn infer_derive_clone_simple() { |
554 | let (db, pos) = TestDB::with_position( | 574 | let (db, pos) = TestDB::with_position( |
555 | r#" | 575 | r#" |