diff options
Diffstat (limited to 'crates/ra_hir_expand/src')
-rw-r--r-- | crates/ra_hir_expand/src/builtin_macro.rs | 228 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/name.rs | 1 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/quote.rs | 1 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/test_db.rs | 9 |
4 files changed, 183 insertions, 56 deletions
diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs index 3f60b1cca..f9d3787f6 100644 --- a/crates/ra_hir_expand/src/builtin_macro.rs +++ b/crates/ra_hir_expand/src/builtin_macro.rs | |||
@@ -88,17 +88,18 @@ register_builtin! { | |||
88 | (compile_error, CompileError) => compile_error_expand, | 88 | (compile_error, CompileError) => compile_error_expand, |
89 | (file, File) => file_expand, | 89 | (file, File) => file_expand, |
90 | (line, Line) => line_expand, | 90 | (line, Line) => line_expand, |
91 | (assert, Assert) => assert_expand, | ||
91 | (stringify, Stringify) => stringify_expand, | 92 | (stringify, Stringify) => stringify_expand, |
92 | (format_args, FormatArgs) => format_args_expand, | 93 | (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, | 94 | // format_args_nl only differs in that it adds a newline in the end, |
96 | // so we use the same stub expansion for now | 95 | // so we use the same stub expansion for now |
97 | (format_args_nl, FormatArgsNl) => format_args_expand, | 96 | (format_args_nl, FormatArgsNl) => format_args_expand, |
98 | 97 | ||
99 | EAGER: | 98 | EAGER: |
100 | (concat, Concat) => concat_expand, | 99 | (concat, Concat) => concat_expand, |
101 | (include, Include) => include_expand | 100 | (include, Include) => include_expand, |
101 | (env, Env) => env_expand, | ||
102 | (option_env, OptionEnv) => option_env_expand | ||
102 | } | 103 | } |
103 | 104 | ||
104 | fn line_expand( | 105 | fn line_expand( |
@@ -137,42 +138,56 @@ fn stringify_expand( | |||
137 | Ok(expanded) | 138 | Ok(expanded) |
138 | } | 139 | } |
139 | 140 | ||
140 | fn env_expand( | 141 | fn column_expand( |
141 | _db: &dyn AstDatabase, | 142 | _db: &dyn AstDatabase, |
142 | _id: LazyMacroId, | 143 | _id: LazyMacroId, |
143 | _tt: &tt::Subtree, | 144 | _tt: &tt::Subtree, |
144 | ) -> Result<tt::Subtree, mbe::ExpandError> { | 145 | ) -> Result<tt::Subtree, mbe::ExpandError> { |
145 | // dummy implementation for type-checking purposes | 146 | // dummy implementation for type-checking purposes |
146 | // we cannot use an empty string here, because for | 147 | let col_num = 0; |
147 | // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become | 148 | let expanded = quote! { |
148 | // `include!("foo.rs"), which maybe infinite loop | 149 | #col_num |
149 | let expanded = quote! { "__RA_UNIMPLEMENTATED__" }; | 150 | }; |
150 | 151 | ||
151 | Ok(expanded) | 152 | Ok(expanded) |
152 | } | 153 | } |
153 | 154 | ||
154 | fn option_env_expand( | 155 | fn assert_expand( |
155 | _db: &dyn AstDatabase, | 156 | _db: &dyn AstDatabase, |
156 | _id: LazyMacroId, | 157 | _id: LazyMacroId, |
157 | _tt: &tt::Subtree, | 158 | tt: &tt::Subtree, |
158 | ) -> Result<tt::Subtree, mbe::ExpandError> { | 159 | ) -> Result<tt::Subtree, mbe::ExpandError> { |
159 | // dummy implementation for type-checking purposes | 160 | // A hacky implementation for goto def and hover |
160 | let expanded = quote! { std::option::Option::None::<&str> }; | 161 | // We expand `assert!(cond, arg1, arg2)` to |
162 | // ``` | ||
163 | // {(cond, &(arg1), &(arg2));} | ||
164 | // ```, | ||
165 | // which is wrong but useful. | ||
161 | 166 | ||
162 | Ok(expanded) | 167 | let mut args = Vec::new(); |
163 | } | 168 | let mut current = Vec::new(); |
169 | for tt in tt.token_trees.iter().cloned() { | ||
170 | match tt { | ||
171 | tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => { | ||
172 | args.push(current); | ||
173 | current = Vec::new(); | ||
174 | } | ||
175 | _ => { | ||
176 | current.push(tt); | ||
177 | } | ||
178 | } | ||
179 | } | ||
180 | if !current.is_empty() { | ||
181 | args.push(current); | ||
182 | } | ||
183 | |||
184 | let arg_tts = args.into_iter().flat_map(|arg| { | ||
185 | quote! { &(##arg), } | ||
186 | }.token_trees).collect::<Vec<_>>(); | ||
164 | 187 | ||
165 | fn column_expand( | ||
166 | _db: &dyn AstDatabase, | ||
167 | _id: LazyMacroId, | ||
168 | _tt: &tt::Subtree, | ||
169 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
170 | // dummy implementation for type-checking purposes | ||
171 | let col_num = 0; | ||
172 | let expanded = quote! { | 188 | let expanded = quote! { |
173 | #col_num | 189 | { { (##arg_tts); } } |
174 | }; | 190 | }; |
175 | |||
176 | Ok(expanded) | 191 | Ok(expanded) |
177 | } | 192 | } |
178 | 193 | ||
@@ -278,30 +293,37 @@ fn concat_expand( | |||
278 | 293 | ||
279 | fn relative_file(db: &dyn AstDatabase, call_id: MacroCallId, path: &str) -> Option<FileId> { | 294 | fn relative_file(db: &dyn AstDatabase, call_id: MacroCallId, path: &str) -> Option<FileId> { |
280 | let call_site = call_id.as_file().original_file(db); | 295 | let call_site = call_id.as_file().original_file(db); |
281 | let path = RelativePath::new(&path); | ||
282 | 296 | ||
283 | let res = db.resolve_relative_path(call_site, &path)?; | 297 | // Handle trivial case |
284 | // Prevent include itself | 298 | if let Some(res) = db.resolve_relative_path(call_site, &RelativePath::new(&path)) { |
285 | if res == call_site { | 299 | // Prevent include itself |
286 | return None; | 300 | return if res == call_site { None } else { Some(res) }; |
287 | } | 301 | } |
288 | Some(res) | 302 | |
303 | // Extern paths ? | ||
304 | let krate = db.relevant_crates(call_site).get(0)?.clone(); | ||
305 | let (extern_source_id, relative_file) = | ||
306 | db.crate_graph()[krate].extern_source.extern_path(path)?; | ||
307 | |||
308 | db.resolve_extern_path(extern_source_id, &relative_file) | ||
289 | } | 309 | } |
290 | 310 | ||
291 | fn include_expand( | 311 | fn parse_string(tt: &tt::Subtree) -> Result<String, mbe::ExpandError> { |
292 | db: &dyn AstDatabase, | 312 | 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) | 313 | .get(0) |
299 | .and_then(|tt| match tt { | 314 | .and_then(|tt| match tt { |
300 | tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(&it), | 315 | tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(&it), |
301 | _ => None, | 316 | _ => None, |
302 | }) | 317 | }) |
303 | .ok_or_else(|| mbe::ExpandError::ConversionError)?; | 318 | .ok_or_else(|| mbe::ExpandError::ConversionError) |
319 | } | ||
304 | 320 | ||
321 | fn include_expand( | ||
322 | db: &dyn AstDatabase, | ||
323 | arg_id: EagerMacroId, | ||
324 | tt: &tt::Subtree, | ||
325 | ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> { | ||
326 | let path = parse_string(tt)?; | ||
305 | let file_id = | 327 | let file_id = |
306 | relative_file(db, arg_id.into(), &path).ok_or_else(|| mbe::ExpandError::ConversionError)?; | 328 | relative_file(db, arg_id.into(), &path).ok_or_else(|| mbe::ExpandError::ConversionError)?; |
307 | 329 | ||
@@ -314,12 +336,58 @@ fn include_expand( | |||
314 | Ok((res, FragmentKind::Items)) | 336 | Ok((res, FragmentKind::Items)) |
315 | } | 337 | } |
316 | 338 | ||
339 | fn get_env_inner(db: &dyn AstDatabase, arg_id: EagerMacroId, key: &str) -> Option<String> { | ||
340 | let call_id: MacroCallId = arg_id.into(); | ||
341 | let original_file = call_id.as_file().original_file(db); | ||
342 | |||
343 | let krate = db.relevant_crates(original_file).get(0)?.clone(); | ||
344 | db.crate_graph()[krate].env.get(key) | ||
345 | } | ||
346 | |||
347 | fn env_expand( | ||
348 | db: &dyn AstDatabase, | ||
349 | arg_id: EagerMacroId, | ||
350 | tt: &tt::Subtree, | ||
351 | ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> { | ||
352 | let key = parse_string(tt)?; | ||
353 | |||
354 | // FIXME: | ||
355 | // If the environment variable is not defined int rustc, then a compilation error will be emitted. | ||
356 | // We might do the same if we fully support all other stuffs. | ||
357 | // But for now on, we should return some dummy string for better type infer purpose. | ||
358 | // However, we cannot use an empty string here, because for | ||
359 | // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become | ||
360 | // `include!("foo.rs"), which might go to infinite loop | ||
361 | let s = get_env_inner(db, arg_id, &key).unwrap_or("__RA_UNIMPLEMENTATED__".to_string()); | ||
362 | let expanded = quote! { #s }; | ||
363 | |||
364 | Ok((expanded, FragmentKind::Expr)) | ||
365 | } | ||
366 | |||
367 | fn option_env_expand( | ||
368 | db: &dyn AstDatabase, | ||
369 | arg_id: EagerMacroId, | ||
370 | tt: &tt::Subtree, | ||
371 | ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> { | ||
372 | let key = parse_string(tt)?; | ||
373 | let expanded = match get_env_inner(db, arg_id, &key) { | ||
374 | None => quote! { std::option::Option::None::<&str> }, | ||
375 | Some(s) => quote! { std::option::Some(#s) }, | ||
376 | }; | ||
377 | |||
378 | Ok((expanded, FragmentKind::Expr)) | ||
379 | } | ||
380 | |||
317 | #[cfg(test)] | 381 | #[cfg(test)] |
318 | mod tests { | 382 | mod tests { |
319 | use super::*; | 383 | use super::*; |
320 | use crate::{name::AsName, test_db::TestDB, AstNode, MacroCallId, MacroCallKind, MacroCallLoc}; | 384 | use crate::{ |
385 | name::AsName, test_db::TestDB, AstNode, EagerCallLoc, MacroCallId, MacroCallKind, | ||
386 | MacroCallLoc, | ||
387 | }; | ||
321 | use ra_db::{fixture::WithFixture, SourceDatabase}; | 388 | use ra_db::{fixture::WithFixture, SourceDatabase}; |
322 | use ra_syntax::ast::NameOwner; | 389 | use ra_syntax::ast::NameOwner; |
390 | use std::sync::Arc; | ||
323 | 391 | ||
324 | fn expand_builtin_macro(ra_fixture: &str) -> String { | 392 | fn expand_builtin_macro(ra_fixture: &str) -> String { |
325 | let (db, file_id) = TestDB::with_single_file(&ra_fixture); | 393 | let (db, file_id) = TestDB::with_single_file(&ra_fixture); |
@@ -330,27 +398,61 @@ mod tests { | |||
330 | let ast_id_map = db.ast_id_map(file_id.into()); | 398 | let ast_id_map = db.ast_id_map(file_id.into()); |
331 | 399 | ||
332 | let expander = find_by_name(¯o_calls[0].name().unwrap().as_name()).unwrap(); | 400 | let expander = find_by_name(¯o_calls[0].name().unwrap().as_name()).unwrap(); |
333 | let expander = expander.left().unwrap(); | ||
334 | 401 | ||
335 | // the first one should be a macro_rules | 402 | let file_id = match expander { |
336 | let def = MacroDefId { | 403 | Either::Left(expander) => { |
337 | krate: Some(CrateId(0)), | 404 | // 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]))), | 405 | let def = MacroDefId { |
339 | kind: MacroDefKind::BuiltIn(expander), | 406 | krate: Some(CrateId(0)), |
340 | }; | 407 | ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), |
408 | kind: MacroDefKind::BuiltIn(expander), | ||
409 | }; | ||
341 | 410 | ||
342 | let loc = MacroCallLoc { | 411 | let loc = MacroCallLoc { |
343 | def, | 412 | def, |
344 | kind: MacroCallKind::FnLike(AstId::new( | 413 | kind: MacroCallKind::FnLike(AstId::new( |
345 | file_id.into(), | 414 | file_id.into(), |
346 | ast_id_map.ast_id(¯o_calls[1]), | 415 | ast_id_map.ast_id(¯o_calls[1]), |
347 | )), | 416 | )), |
348 | }; | 417 | }; |
418 | |||
419 | let id: MacroCallId = db.intern_macro(loc).into(); | ||
420 | id.as_file() | ||
421 | } | ||
422 | Either::Right(expander) => { | ||
423 | // the first one should be a macro_rules | ||
424 | let def = MacroDefId { | ||
425 | krate: Some(CrateId(0)), | ||
426 | ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), | ||
427 | kind: MacroDefKind::BuiltInEager(expander), | ||
428 | }; | ||
349 | 429 | ||
350 | let id: MacroCallId = db.intern_macro(loc).into(); | 430 | let args = macro_calls[1].token_tree().unwrap(); |
351 | let parsed = db.parse_or_expand(id.as_file()).unwrap(); | 431 | let parsed_args = mbe::ast_to_token_tree(&args).unwrap().0; |
432 | |||
433 | let arg_id = db.intern_eager_expansion({ | ||
434 | EagerCallLoc { | ||
435 | def, | ||
436 | fragment: FragmentKind::Expr, | ||
437 | subtree: Arc::new(parsed_args.clone()), | ||
438 | file_id: file_id.into(), | ||
439 | } | ||
440 | }); | ||
441 | |||
442 | let (subtree, fragment) = expander.expand(&db, arg_id, &parsed_args).unwrap(); | ||
443 | let eager = EagerCallLoc { | ||
444 | def, | ||
445 | fragment, | ||
446 | subtree: Arc::new(subtree), | ||
447 | file_id: file_id.into(), | ||
448 | }; | ||
352 | 449 | ||
353 | parsed.text().to_string() | 450 | let id: MacroCallId = db.intern_eager_expansion(eager.into()).into(); |
451 | id.as_file() | ||
452 | } | ||
453 | }; | ||
454 | |||
455 | db.parse_or_expand(file_id).unwrap().to_string() | ||
354 | } | 456 | } |
355 | 457 | ||
356 | #[test] | 458 | #[test] |
@@ -432,6 +534,22 @@ mod tests { | |||
432 | } | 534 | } |
433 | 535 | ||
434 | #[test] | 536 | #[test] |
537 | fn test_assert_expand() { | ||
538 | let expanded = expand_builtin_macro( | ||
539 | r#" | ||
540 | #[rustc_builtin_macro] | ||
541 | macro_rules! assert { | ||
542 | ($cond:expr) => ({ /* compiler built-in */ }); | ||
543 | ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ }) | ||
544 | } | ||
545 | assert!(true, "{} {:?}", arg1(a, b, c), arg2); | ||
546 | "#, | ||
547 | ); | ||
548 | |||
549 | assert_eq!(expanded, "{{(&(true), &(\"{} {:?}\"), &(arg1(a,b,c)), &(arg2),);}}"); | ||
550 | } | ||
551 | |||
552 | #[test] | ||
435 | fn test_compile_error_expand() { | 553 | fn test_compile_error_expand() { |
436 | let expanded = expand_builtin_macro( | 554 | let expanded = expand_builtin_macro( |
437 | r#" | 555 | r#" |
diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs index 6d201256f..25cc1e9fc 100644 --- a/crates/ra_hir_expand/src/name.rs +++ b/crates/ra_hir_expand/src/name.rs | |||
@@ -172,6 +172,7 @@ pub mod known { | |||
172 | column, | 172 | column, |
173 | compile_error, | 173 | compile_error, |
174 | line, | 174 | line, |
175 | assert, | ||
175 | stringify, | 176 | stringify, |
176 | concat, | 177 | concat, |
177 | include, | 178 | include, |
diff --git a/crates/ra_hir_expand/src/quote.rs b/crates/ra_hir_expand/src/quote.rs index 57e7eebf9..3fd4233da 100644 --- a/crates/ra_hir_expand/src/quote.rs +++ b/crates/ra_hir_expand/src/quote.rs | |||
@@ -99,6 +99,7 @@ macro_rules! __quote { | |||
99 | ( & ) => {$crate::__quote!(@PUNCT '&')}; | 99 | ( & ) => {$crate::__quote!(@PUNCT '&')}; |
100 | ( , ) => {$crate::__quote!(@PUNCT ',')}; | 100 | ( , ) => {$crate::__quote!(@PUNCT ',')}; |
101 | ( : ) => {$crate::__quote!(@PUNCT ':')}; | 101 | ( : ) => {$crate::__quote!(@PUNCT ':')}; |
102 | ( ; ) => {$crate::__quote!(@PUNCT ';')}; | ||
102 | ( :: ) => {$crate::__quote!(@PUNCT ':', ':')}; | 103 | ( :: ) => {$crate::__quote!(@PUNCT ':', ':')}; |
103 | ( . ) => {$crate::__quote!(@PUNCT '.')}; | 104 | ( . ) => {$crate::__quote!(@PUNCT '.')}; |
104 | ( < ) => {$crate::__quote!(@PUNCT '<')}; | 105 | ( < ) => {$crate::__quote!(@PUNCT '<')}; |
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 | } |