2 use crate::db::AstDatabase;
4 ast::{self, AstToken, HasStringValue},
5 name, AstId, CrateId, MacroDefId, MacroDefKind, TextSize,
8 use crate::{quote, EagerMacroId, LazyMacroId, MacroCallId};
10 use mbe::parse_to_token_tree;
12 use ra_parser::FragmentKind;
14 macro_rules! register_builtin {
15 ( LAZY: $(($name:ident, $kind: ident) => $expand:ident),* , EAGER: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => {
16 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17 pub enum BuiltinFnLikeExpander {
21 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22 pub enum EagerExpander {
26 impl BuiltinFnLikeExpander {
32 ) -> Result<tt::Subtree, mbe::ExpandError> {
33 let expander = match *self {
34 $( BuiltinFnLikeExpander::$kind => $expand, )*
46 ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
47 let expander = match *self {
48 $( EagerExpander::$e_kind => $e_expand, )*
50 expander(db,arg_id,tt)
54 fn find_by_name(ident: &name::Name) -> Option<Either<BuiltinFnLikeExpander, EagerExpander>> {
56 $( id if id == &name::name![$name] => Some(Either::Left(BuiltinFnLikeExpander::$kind)), )*
57 $( id if id == &name::name![$e_name] => Some(Either::Right(EagerExpander::$e_kind)), )*
64 pub fn find_builtin_macro(
67 ast_id: AstId<ast::MacroCall>,
68 ) -> Option<MacroDefId> {
69 let kind = find_by_name(ident)?;
72 Either::Left(kind) => Some(MacroDefId {
75 kind: MacroDefKind::BuiltIn(kind),
78 Either::Right(kind) => Some(MacroDefId {
81 kind: MacroDefKind::BuiltInEager(kind),
89 (column, Column) => column_expand,
90 (compile_error, CompileError) => compile_error_expand,
91 (file, File) => file_expand,
92 (line, Line) => line_expand,
93 (assert, Assert) => assert_expand,
94 (stringify, Stringify) => stringify_expand,
95 (format_args, FormatArgs) => format_args_expand,
96 // format_args_nl only differs in that it adds a newline in the end,
97 // so we use the same stub expansion for now
98 (format_args_nl, FormatArgsNl) => format_args_expand,
101 (concat, Concat) => concat_expand,
102 (include, Include) => include_expand,
103 (env, Env) => env_expand,
104 (option_env, OptionEnv) => option_env_expand
108 _db: &dyn AstDatabase,
111 ) -> Result<tt::Subtree, mbe::ExpandError> {
112 // dummy implementation for type-checking purposes
114 let expanded = quote! {
122 db: &dyn AstDatabase,
125 ) -> Result<tt::Subtree, mbe::ExpandError> {
126 let loc = db.lookup_intern_macro(id);
128 let macro_content = {
129 let arg = loc.kind.arg(db).ok_or_else(|| mbe::ExpandError::UnexpectedToken)?;
130 let macro_args = arg;
131 let text = macro_args.text();
132 let without_parens = TextSize::of('(')..text.len() - TextSize::of(')');
133 text.slice(without_parens).to_string()
136 let expanded = quote! {
144 _db: &dyn AstDatabase,
147 ) -> Result<tt::Subtree, mbe::ExpandError> {
148 // dummy implementation for type-checking purposes
150 let expanded = quote! {
158 _db: &dyn AstDatabase,
161 ) -> Result<tt::Subtree, mbe::ExpandError> {
162 // A hacky implementation for goto def and hover
163 // We expand `assert!(cond, arg1, arg2)` to
165 // {(cond, &(arg1), &(arg2));}
167 // which is wrong but useful.
169 let mut args = Vec::new();
170 let mut current = Vec::new();
171 for tt in tt.token_trees.iter().cloned() {
173 tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => {
175 current = Vec::new();
182 if !current.is_empty() {
186 let arg_tts = args.into_iter().flat_map(|arg| {
188 }.token_trees).collect::<Vec<_>>();
190 let expanded = quote! {
197 _db: &dyn AstDatabase,
200 ) -> Result<tt::Subtree, mbe::ExpandError> {
201 // FIXME: RA purposefully lacks knowledge of absolute file names
202 // so just return "".
205 let expanded = quote! {
212 fn compile_error_expand(
213 _db: &dyn AstDatabase,
216 ) -> Result<tt::Subtree, mbe::ExpandError> {
218 if let tt::TokenTree::Leaf(tt::Leaf::Literal(it)) = &tt.token_trees[0] {
219 let s = it.text.as_str();
221 return Ok(quote! { loop { #it }});
226 Err(mbe::ExpandError::BindingError("Must be a string".into()))
229 fn format_args_expand(
230 _db: &dyn AstDatabase,
233 ) -> Result<tt::Subtree, mbe::ExpandError> {
234 // We expand `format_args!("", a1, a2)` to
236 // std::fmt::Arguments::new_v1(&[], &[
237 // std::fmt::ArgumentV1::new(&arg1,std::fmt::Display::fmt),
238 // std::fmt::ArgumentV1::new(&arg2,std::fmt::Display::fmt),
241 // which is still not really correct, but close enough for now
242 let mut args = Vec::new();
243 let mut current = Vec::new();
244 for tt in tt.token_trees.iter().cloned() {
246 tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => {
248 current = Vec::new();
255 if !current.is_empty() {
259 return Err(mbe::ExpandError::NoMatchingRule);
261 let _format_string = args.remove(0);
262 let arg_tts = args.into_iter().flat_map(|arg| {
263 quote! { std::fmt::ArgumentV1::new(&(##arg), std::fmt::Display::fmt), }
264 }.token_trees).collect::<Vec<_>>();
265 let expanded = quote! {
266 std::fmt::Arguments::new_v1(&[], &[##arg_tts])
271 fn unquote_str(lit: &tt::Literal) -> Option<String> {
272 let lit = ast::make::tokens::literal(&lit.to_string());
273 let token = ast::String::cast(lit)?;
278 _db: &dyn AstDatabase,
279 _arg_id: EagerMacroId,
281 ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
282 let mut text = String::new();
283 for (i, t) in tt.token_trees.iter().enumerate() {
285 tt::TokenTree::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => {
286 text += &unquote_str(&it).ok_or_else(|| mbe::ExpandError::ConversionError)?;
288 tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
289 _ => return Err(mbe::ExpandError::UnexpectedToken),
293 Ok((quote!(#text), FragmentKind::Expr))
296 fn relative_file(db: &dyn AstDatabase, call_id: MacroCallId, path: &str) -> Option<FileId> {
297 let call_site = call_id.as_file().original_file(db);
298 let res = db.resolve_path(call_site, path)?;
299 // Prevent include itself
300 if res == call_site {
307 fn parse_string(tt: &tt::Subtree) -> Result<String, mbe::ExpandError> {
310 .and_then(|tt| match tt {
311 tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(&it),
314 .ok_or_else(|| mbe::ExpandError::ConversionError)
318 db: &dyn AstDatabase,
319 arg_id: EagerMacroId,
321 ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
322 let path = parse_string(tt)?;
324 relative_file(db, arg_id.into(), &path).ok_or_else(|| mbe::ExpandError::ConversionError)?;
327 // Handle include as expression
328 let res = parse_to_token_tree(&db.file_text(file_id))
329 .ok_or_else(|| mbe::ExpandError::ConversionError)?
332 Ok((res, FragmentKind::Items))
335 fn get_env_inner(db: &dyn AstDatabase, arg_id: EagerMacroId, key: &str) -> Option<String> {
336 let call_id: MacroCallId = arg_id.into();
337 let original_file = call_id.as_file().original_file(db);
339 let krate = *db.relevant_crates(original_file).get(0)?;
340 db.crate_graph()[krate].env.get(key)
344 db: &dyn AstDatabase,
345 arg_id: EagerMacroId,
347 ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
348 let key = parse_string(tt)?;
351 // If the environment variable is not defined int rustc, then a compilation error will be emitted.
352 // We might do the same if we fully support all other stuffs.
353 // But for now on, we should return some dummy string for better type infer purpose.
354 // However, we cannot use an empty string here, because for
355 // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become
356 // `include!("foo.rs"), which might go to infinite loop
357 let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| "__RA_UNIMPLEMENTED__".to_string());
358 let expanded = quote! { #s };
360 Ok((expanded, FragmentKind::Expr))
363 fn option_env_expand(
364 db: &dyn AstDatabase,
365 arg_id: EagerMacroId,
367 ) -> Result<(tt::Subtree, FragmentKind), mbe::ExpandError> {
368 let key = parse_string(tt)?;
369 let expanded = match get_env_inner(db, arg_id, &key) {
370 None => quote! { std::option::Option::None::<&str> },
371 Some(s) => quote! { std::option::Some(#s) },
374 Ok((expanded, FragmentKind::Expr))
381 name::AsName, test_db::TestDB, AstNode, EagerCallLoc, MacroCallId, MacroCallKind,
384 use ra_db::{fixture::WithFixture, SourceDatabase};
385 use ra_syntax::ast::NameOwner;
388 fn expand_builtin_macro(ra_fixture: &str) -> String {
389 let (db, file_id) = TestDB::with_single_file(&ra_fixture);
390 let parsed = db.parse(file_id);
391 let macro_calls: Vec<_> =
392 parsed.syntax_node().descendants().filter_map(ast::MacroCall::cast).collect();
394 let ast_id_map = db.ast_id_map(file_id.into());
396 let expander = find_by_name(¯o_calls[0].name().unwrap().as_name()).unwrap();
398 let file_id = match expander {
399 Either::Left(expander) => {
400 // the first one should be a macro_rules
401 let def = MacroDefId {
402 krate: Some(CrateId(0)),
403 ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))),
404 kind: MacroDefKind::BuiltIn(expander),
408 let loc = MacroCallLoc {
410 kind: MacroCallKind::FnLike(AstId::new(
412 ast_id_map.ast_id(¯o_calls[1]),
416 let id: MacroCallId = db.intern_macro(loc).into();
419 Either::Right(expander) => {
420 // the first one should be a macro_rules
421 let def = MacroDefId {
422 krate: Some(CrateId(0)),
423 ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))),
424 kind: MacroDefKind::BuiltInEager(expander),
428 let args = macro_calls[1].token_tree().unwrap();
429 let parsed_args = mbe::ast_to_token_tree(&args).unwrap().0;
431 let arg_id = db.intern_eager_expansion({
434 fragment: FragmentKind::Expr,
435 subtree: Arc::new(parsed_args.clone()),
436 file_id: file_id.into(),
440 let (subtree, fragment) = expander.expand(&db, arg_id, &parsed_args).unwrap();
441 let eager = EagerCallLoc {
444 subtree: Arc::new(subtree),
445 file_id: file_id.into(),
448 let id: MacroCallId = db.intern_eager_expansion(eager).into();
453 db.parse_or_expand(file_id).unwrap().to_string()
457 fn test_column_expand() {
458 let expanded = expand_builtin_macro(
460 #[rustc_builtin_macro]
461 macro_rules! column {() => {}}
466 assert_eq!(expanded, "0");
470 fn test_line_expand() {
471 let expanded = expand_builtin_macro(
473 #[rustc_builtin_macro]
474 macro_rules! line {() => {}}
479 assert_eq!(expanded, "0");
483 fn test_stringify_expand() {
484 let expanded = expand_builtin_macro(
486 #[rustc_builtin_macro]
487 macro_rules! stringify {() => {}}
492 assert_eq!(expanded, "\"a b c\"");
496 fn test_env_expand() {
497 let expanded = expand_builtin_macro(
499 #[rustc_builtin_macro]
500 macro_rules! env {() => {}}
505 assert_eq!(expanded, "\"__RA_UNIMPLEMENTED__\"");
509 fn test_option_env_expand() {
510 let expanded = expand_builtin_macro(
512 #[rustc_builtin_macro]
513 macro_rules! option_env {() => {}}
514 option_env!("TEST_ENV_VAR")
518 assert_eq!(expanded, "std::option::Option::None:: < &str>");
522 fn test_file_expand() {
523 let expanded = expand_builtin_macro(
525 #[rustc_builtin_macro]
526 macro_rules! file {() => {}}
531 assert_eq!(expanded, "\"\"");
535 fn test_assert_expand() {
536 let expanded = expand_builtin_macro(
538 #[rustc_builtin_macro]
539 macro_rules! assert {
540 ($cond:expr) => ({ /* compiler built-in */ });
541 ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ })
543 assert!(true, "{} {:?}", arg1(a, b, c), arg2);
547 assert_eq!(expanded, "{{(&(true), &(\"{} {:?}\"), &(arg1(a,b,c)), &(arg2),);}}");
551 fn test_compile_error_expand() {
552 let expanded = expand_builtin_macro(
554 #[rustc_builtin_macro]
555 macro_rules! compile_error {
556 ($msg:expr) => ({ /* compiler built-in */ });
557 ($msg:expr,) => ({ /* compiler built-in */ })
559 compile_error!("error!");
563 assert_eq!(expanded, r#"loop{"error!"}"#);
567 fn test_format_args_expand() {
568 let expanded = expand_builtin_macro(
570 #[rustc_builtin_macro]
571 macro_rules! format_args {
572 ($fmt:expr) => ({ /* compiler built-in */ });
573 ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
575 format_args!("{} {:?}", arg1(a, b, c), arg2);
581 r#"std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(arg1(a,b,c)),std::fmt::Display::fmt),std::fmt::ArgumentV1::new(&(arg2),std::fmt::Display::fmt),])"#