From: Aleksey Kladov Date: Sun, 10 Oct 2021 12:44:03 +0000 (+0300) Subject: internal: consistent module naming X-Git-Url: https://git.lizzy.rs/?a=commitdiff_plain;h=55d8be5a241518e9d58ee62e2947781d7bbd763c;p=rust.git internal: consistent module naming --- diff --git a/crates/hir_def/src/macro_expansion_tests.rs b/crates/hir_def/src/macro_expansion_tests.rs index c317dc27a58..f29d1443b92 100644 --- a/crates/hir_def/src/macro_expansion_tests.rs +++ b/crates/hir_def/src/macro_expansion_tests.rs @@ -10,7 +10,7 @@ //! and harder to understand. mod mbe; -mod builtin; +mod builtin_fn_macro; use std::{iter, ops::Range}; diff --git a/crates/hir_def/src/macro_expansion_tests/builtin.rs b/crates/hir_def/src/macro_expansion_tests/builtin.rs deleted file mode 100644 index 6982116522c..00000000000 --- a/crates/hir_def/src/macro_expansion_tests/builtin.rs +++ /dev/null @@ -1,332 +0,0 @@ -//! Tests for builtin macros (see `builtin_macro.rs` in `hir_expand`). - -use expect_test::expect; - -use crate::macro_expansion_tests::check; - -#[test] -fn test_column_expand() { - check( - r#" -#[rustc_builtin_macro] -macro_rules! column {() => {}} - -fn main() { column!(); } -"#, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! column {() => {}} - -fn main() { 0; } -"##]], - ); -} - -#[test] -fn test_line_expand() { - check( - r#" -#[rustc_builtin_macro] -macro_rules! line {() => {}} - -fn main() { line!() } -"#, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! line {() => {}} - -fn main() { 0 } -"##]], - ); -} - -#[test] -fn test_stringify_expand() { - check( - r#" -#[rustc_builtin_macro] -macro_rules! stringify {() => {}} - -fn main() { - stringify!( - a - b - c - ); -} -"#, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! stringify {() => {}} - -fn main() { - "a b c"; -} -"##]], - ); -} - -#[test] -fn test_env_expand() { - check( - r#" -#[rustc_builtin_macro] -macro_rules! env {() => {}} - -fn main() { env!("TEST_ENV_VAR"); } -"#, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! env {() => {}} - -fn main() { "__RA_UNIMPLEMENTED__"; } -"##]], - ); -} - -#[test] -fn test_option_env_expand() { - check( - r#" -#[rustc_builtin_macro] -macro_rules! option_env {() => {}} - -fn main() { option_env!("TEST_ENV_VAR"); } -"#, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! option_env {() => {}} - -fn main() { std::option::Option::None:: < &str>; } -"##]], - ); -} - -#[test] -fn test_file_expand() { - check( - r#" -#[rustc_builtin_macro] -macro_rules! file {() => {}} - -fn main() { file!(); } -"#, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! file {() => {}} - -fn main() { ""; } -"##]], - ); -} - -#[test] -fn test_assert_expand() { - check( - r#" -#[rustc_builtin_macro] -macro_rules! assert { - ($cond:expr) => ({ /* compiler built-in */ }); - ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ }) -} - -fn main() { - assert!(true, "{} {:?}", arg1(a, b, c), arg2); -} -"#, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! assert { - ($cond:expr) => ({ /* compiler built-in */ }); - ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ }) -} - -fn main() { - { - if !true { - $crate::panic!("{} {:?}", arg1(a, b, c), arg2); - } - }; -} -"##]], - ); -} - -#[test] -fn test_compile_error_expand() { - check( - r#" -#[rustc_builtin_macro] -macro_rules! compile_error { - ($msg:expr) => ({ /* compiler built-in */ }); - ($msg:expr,) => ({ /* compiler built-in */ }) -} - -// This expands to nothing (since it's in item position), but emits an error. -compile_error!("error!"); -"#, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! compile_error { - ($msg:expr) => ({ /* compiler built-in */ }); - ($msg:expr,) => ({ /* compiler built-in */ }) -} - -/* error: error! */ -"##]], - ); -} - -#[test] -fn test_format_args_expand() { - check( - r#" -#[rustc_builtin_macro] -macro_rules! format_args { - ($fmt:expr) => ({ /* compiler built-in */ }); - ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) -} - -fn main() { - format_args!("{} {:?}", arg1(a, b, c), arg2); -} -"#, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! format_args { - ($fmt:expr) => ({ /* compiler built-in */ }); - ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) -} - -fn main() { - unsafe { - 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), ]) - }; -} -"##]], - ); -} - -#[test] -fn test_format_args_expand_with_comma_exprs() { - check( - r#" -#[rustc_builtin_macro] -macro_rules! format_args { - ($fmt:expr) => ({ /* compiler built-in */ }); - ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) -} - -fn main() { - format_args!("{} {:?}", a::(), b); -} -"#, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! format_args { - ($fmt:expr) => ({ /* compiler built-in */ }); - ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) -} - -fn main() { - unsafe { - std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(a::()), std::fmt::Display::fmt), std::fmt::ArgumentV1::new(&(b), std::fmt::Display::fmt), ]) - }; -} -"##]], - ); -} - -#[test] -fn test_format_args_expand_with_broken_member_access() { - check( - r#" -#[rustc_builtin_macro] -macro_rules! format_args { - ($fmt:expr) => ({ /* compiler built-in */ }); - ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) -} - -fn main() { - let _ = - // +errors - format_args!("{} {:?}", a.); -} -"#, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! format_args { - ($fmt:expr) => ({ /* compiler built-in */ }); - ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) -} - -fn main() { - let _ = - /* parse error: expected field name or number */ -unsafe { - std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(a.), std::fmt::Display::fmt), ]) - }; -} -"##]], - ); -} - -#[test] -fn test_include_bytes_expand() { - check( - r#" -#[rustc_builtin_macro] -macro_rules! include_bytes { - ($file:expr) => {{ /* compiler built-in */ }}; - ($file:expr,) => {{ /* compiler built-in */ }}; -} - -fn main() { include_bytes("foo"); } -"#, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! include_bytes { - ($file:expr) => {{ /* compiler built-in */ }}; - ($file:expr,) => {{ /* compiler built-in */ }}; -} - -fn main() { include_bytes("foo"); } -"##]], - ); -} - -#[test] -fn test_concat_expand() { - check( - r##" -#[rustc_builtin_macro] -macro_rules! concat {} - -fn main() { concat!("foo", "r", 0, r#"bar"#, "\n", false); } -"##, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! concat {} - -fn main() { "foor0bar\nfalse"; } -"##]], - ); -} - -#[test] -fn test_concat_idents_expand() { - check( - r##" -#[rustc_builtin_macro] -macro_rules! concat_idents {} - -fn main() { concat_idents!(foo, bar); } -"##, - expect![[r##" -#[rustc_builtin_macro] -macro_rules! concat_idents {} - -fn main() { foobar; } -"##]], - ); -} diff --git a/crates/hir_def/src/macro_expansion_tests/builtin_fn_macro.rs b/crates/hir_def/src/macro_expansion_tests/builtin_fn_macro.rs new file mode 100644 index 00000000000..6982116522c --- /dev/null +++ b/crates/hir_def/src/macro_expansion_tests/builtin_fn_macro.rs @@ -0,0 +1,332 @@ +//! Tests for builtin macros (see `builtin_macro.rs` in `hir_expand`). + +use expect_test::expect; + +use crate::macro_expansion_tests::check; + +#[test] +fn test_column_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! column {() => {}} + +fn main() { column!(); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! column {() => {}} + +fn main() { 0; } +"##]], + ); +} + +#[test] +fn test_line_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! line {() => {}} + +fn main() { line!() } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! line {() => {}} + +fn main() { 0 } +"##]], + ); +} + +#[test] +fn test_stringify_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! stringify {() => {}} + +fn main() { + stringify!( + a + b + c + ); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! stringify {() => {}} + +fn main() { + "a b c"; +} +"##]], + ); +} + +#[test] +fn test_env_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! env {() => {}} + +fn main() { env!("TEST_ENV_VAR"); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! env {() => {}} + +fn main() { "__RA_UNIMPLEMENTED__"; } +"##]], + ); +} + +#[test] +fn test_option_env_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! option_env {() => {}} + +fn main() { option_env!("TEST_ENV_VAR"); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! option_env {() => {}} + +fn main() { std::option::Option::None:: < &str>; } +"##]], + ); +} + +#[test] +fn test_file_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! file {() => {}} + +fn main() { file!(); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! file {() => {}} + +fn main() { ""; } +"##]], + ); +} + +#[test] +fn test_assert_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! assert { + ($cond:expr) => ({ /* compiler built-in */ }); + ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + assert!(true, "{} {:?}", arg1(a, b, c), arg2); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! assert { + ($cond:expr) => ({ /* compiler built-in */ }); + ($cond:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + { + if !true { + $crate::panic!("{} {:?}", arg1(a, b, c), arg2); + } + }; +} +"##]], + ); +} + +#[test] +fn test_compile_error_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! compile_error { + ($msg:expr) => ({ /* compiler built-in */ }); + ($msg:expr,) => ({ /* compiler built-in */ }) +} + +// This expands to nothing (since it's in item position), but emits an error. +compile_error!("error!"); +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! compile_error { + ($msg:expr) => ({ /* compiler built-in */ }); + ($msg:expr,) => ({ /* compiler built-in */ }) +} + +/* error: error! */ +"##]], + ); +} + +#[test] +fn test_format_args_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + format_args!("{} {:?}", arg1(a, b, c), arg2); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + unsafe { + 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), ]) + }; +} +"##]], + ); +} + +#[test] +fn test_format_args_expand_with_comma_exprs() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + format_args!("{} {:?}", a::(), b); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + unsafe { + std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(a::()), std::fmt::Display::fmt), std::fmt::ArgumentV1::new(&(b), std::fmt::Display::fmt), ]) + }; +} +"##]], + ); +} + +#[test] +fn test_format_args_expand_with_broken_member_access() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + let _ = + // +errors + format_args!("{} {:?}", a.); +} +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! format_args { + ($fmt:expr) => ({ /* compiler built-in */ }); + ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ }) +} + +fn main() { + let _ = + /* parse error: expected field name or number */ +unsafe { + std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(a.), std::fmt::Display::fmt), ]) + }; +} +"##]], + ); +} + +#[test] +fn test_include_bytes_expand() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! include_bytes { + ($file:expr) => {{ /* compiler built-in */ }}; + ($file:expr,) => {{ /* compiler built-in */ }}; +} + +fn main() { include_bytes("foo"); } +"#, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! include_bytes { + ($file:expr) => {{ /* compiler built-in */ }}; + ($file:expr,) => {{ /* compiler built-in */ }}; +} + +fn main() { include_bytes("foo"); } +"##]], + ); +} + +#[test] +fn test_concat_expand() { + check( + r##" +#[rustc_builtin_macro] +macro_rules! concat {} + +fn main() { concat!("foo", "r", 0, r#"bar"#, "\n", false); } +"##, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! concat {} + +fn main() { "foor0bar\nfalse"; } +"##]], + ); +} + +#[test] +fn test_concat_idents_expand() { + check( + r##" +#[rustc_builtin_macro] +macro_rules! concat_idents {} + +fn main() { concat_idents!(foo, bar); } +"##, + expect![[r##" +#[rustc_builtin_macro] +macro_rules! concat_idents {} + +fn main() { foobar; } +"##]], + ); +} diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index dbd4378968c..d1cc61e235f 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs @@ -9,9 +9,9 @@ use cfg::{CfgExpr, CfgOptions}; use hir_expand::{ ast_id_map::FileAstId, - builtin_attr::find_builtin_attr, - builtin_derive::find_builtin_derive, - builtin_macro::find_builtin_macro, + builtin_attr_macro::find_builtin_attr, + builtin_derive_macro::find_builtin_derive, + builtin_fn_macro::find_builtin_macro, name::{name, AsName, Name}, proc_macro::ProcMacroExpander, ExpandTo, HirFileId, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, diff --git a/crates/hir_expand/src/builtin_attr.rs b/crates/hir_expand/src/builtin_attr.rs deleted file mode 100644 index 2e461864a0b..00000000000 --- a/crates/hir_expand/src/builtin_attr.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Builtin attributes. - -use mbe::ExpandResult; -use syntax::ast; - -use crate::{db::AstDatabase, name, AstId, CrateId, MacroCallId, MacroDefId, MacroDefKind}; - -macro_rules! register_builtin { - ( $(($name:ident, $variant:ident) => $expand:ident),* ) => { - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum BuiltinAttrExpander { - $($variant),* - } - - impl BuiltinAttrExpander { - pub fn expand( - &self, - db: &dyn AstDatabase, - id: MacroCallId, - tt: &tt::Subtree, - ) -> ExpandResult { - let expander = match *self { - $( BuiltinAttrExpander::$variant => $expand, )* - }; - expander(db, id, tt) - } - - fn find_by_name(name: &name::Name) -> Option { - match name { - $( id if id == &name::name![$name] => Some(BuiltinAttrExpander::$variant), )* - _ => None, - } - } - } - - }; -} - -register_builtin! { - (bench, Bench) => dummy_attr_expand, - (cfg_accessible, CfgAccessible) => dummy_attr_expand, - (cfg_eval, CfgEval) => dummy_attr_expand, - (derive, Derive) => dummy_attr_expand, - (global_allocator, GlobalAllocator) => dummy_attr_expand, - (test, Test) => dummy_attr_expand, - (test_case, TestCase) => dummy_attr_expand -} - -pub fn find_builtin_attr( - ident: &name::Name, - krate: CrateId, - ast_id: AstId, -) -> Option { - let expander = BuiltinAttrExpander::find_by_name(ident)?; - Some(MacroDefId { - krate, - kind: MacroDefKind::BuiltInAttr(expander, ast_id), - local_inner: false, - }) -} - -fn dummy_attr_expand( - _db: &dyn AstDatabase, - _id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - ExpandResult::ok(tt.clone()) -} diff --git a/crates/hir_expand/src/builtin_attr_macro.rs b/crates/hir_expand/src/builtin_attr_macro.rs new file mode 100644 index 00000000000..2e461864a0b --- /dev/null +++ b/crates/hir_expand/src/builtin_attr_macro.rs @@ -0,0 +1,68 @@ +//! Builtin attributes. + +use mbe::ExpandResult; +use syntax::ast; + +use crate::{db::AstDatabase, name, AstId, CrateId, MacroCallId, MacroDefId, MacroDefKind}; + +macro_rules! register_builtin { + ( $(($name:ident, $variant:ident) => $expand:ident),* ) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum BuiltinAttrExpander { + $($variant),* + } + + impl BuiltinAttrExpander { + pub fn expand( + &self, + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, + ) -> ExpandResult { + let expander = match *self { + $( BuiltinAttrExpander::$variant => $expand, )* + }; + expander(db, id, tt) + } + + fn find_by_name(name: &name::Name) -> Option { + match name { + $( id if id == &name::name![$name] => Some(BuiltinAttrExpander::$variant), )* + _ => None, + } + } + } + + }; +} + +register_builtin! { + (bench, Bench) => dummy_attr_expand, + (cfg_accessible, CfgAccessible) => dummy_attr_expand, + (cfg_eval, CfgEval) => dummy_attr_expand, + (derive, Derive) => dummy_attr_expand, + (global_allocator, GlobalAllocator) => dummy_attr_expand, + (test, Test) => dummy_attr_expand, + (test_case, TestCase) => dummy_attr_expand +} + +pub fn find_builtin_attr( + ident: &name::Name, + krate: CrateId, + ast_id: AstId, +) -> Option { + let expander = BuiltinAttrExpander::find_by_name(ident)?; + Some(MacroDefId { + krate, + kind: MacroDefKind::BuiltInAttr(expander, ast_id), + local_inner: false, + }) +} + +fn dummy_attr_expand( + _db: &dyn AstDatabase, + _id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + ExpandResult::ok(tt.clone()) +} diff --git a/crates/hir_expand/src/builtin_derive.rs b/crates/hir_expand/src/builtin_derive.rs deleted file mode 100644 index eeebe87acd6..00000000000 --- a/crates/hir_expand/src/builtin_derive.rs +++ /dev/null @@ -1,385 +0,0 @@ -//! Builtin derives. - -use tracing::debug; - -use mbe::ExpandResult; -use syntax::{ - ast::{self, AstNode, HasGenericParams, HasModuleItem, HasName}, - match_ast, -}; - -use crate::{db::AstDatabase, name, quote, AstId, CrateId, MacroCallId, MacroDefId, MacroDefKind}; - -macro_rules! register_builtin { - ( $($trait:ident => $expand:ident),* ) => { - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum BuiltinDeriveExpander { - $($trait),* - } - - impl BuiltinDeriveExpander { - pub fn expand( - &self, - db: &dyn AstDatabase, - id: MacroCallId, - tt: &tt::Subtree, - ) -> ExpandResult { - let expander = match *self { - $( BuiltinDeriveExpander::$trait => $expand, )* - }; - expander(db, id, tt) - } - - fn find_by_name(name: &name::Name) -> Option { - match name { - $( id if id == &name::name![$trait] => Some(BuiltinDeriveExpander::$trait), )* - _ => None, - } - } - } - - }; -} - -register_builtin! { - Copy => copy_expand, - Clone => clone_expand, - Default => default_expand, - Debug => debug_expand, - Hash => hash_expand, - Ord => ord_expand, - PartialOrd => partial_ord_expand, - Eq => eq_expand, - PartialEq => partial_eq_expand -} - -pub fn find_builtin_derive( - ident: &name::Name, - krate: CrateId, - ast_id: AstId, -) -> Option { - let expander = BuiltinDeriveExpander::find_by_name(ident)?; - Some(MacroDefId { - krate, - kind: MacroDefKind::BuiltInDerive(expander, ast_id), - local_inner: false, - }) -} - -struct BasicAdtInfo { - name: tt::Ident, - type_params: usize, -} - -fn parse_adt(tt: &tt::Subtree) -> Result { - let (parsed, token_map) = mbe::token_tree_to_syntax_node(tt, mbe::ParserEntryPoint::Items)?; // FragmentKind::Items doesn't parse attrs? - let macro_items = ast::MacroItems::cast(parsed.syntax_node()).ok_or_else(|| { - debug!("derive node didn't parse"); - mbe::ExpandError::UnexpectedToken - })?; - let item = macro_items.items().next().ok_or_else(|| { - debug!("no module item parsed"); - mbe::ExpandError::NoMatchingRule - })?; - let node = item.syntax(); - let (name, params) = match_ast! { - match node { - ast::Struct(it) => (it.name(), it.generic_param_list()), - ast::Enum(it) => (it.name(), it.generic_param_list()), - ast::Union(it) => (it.name(), it.generic_param_list()), - _ => { - debug!("unexpected node is {:?}", node); - return Err(mbe::ExpandError::ConversionError) - }, - } - }; - let name = name.ok_or_else(|| { - debug!("parsed item has no name"); - mbe::ExpandError::NoMatchingRule - })?; - let name_token_id = token_map.token_by_range(name.syntax().text_range()).ok_or_else(|| { - debug!("name token not found"); - mbe::ExpandError::ConversionError - })?; - let name_token = tt::Ident { id: name_token_id, text: name.text().into() }; - let type_params = params.map_or(0, |type_param_list| type_param_list.type_params().count()); - Ok(BasicAdtInfo { name: name_token, type_params }) -} - -fn make_type_args(n: usize, bound: Vec) -> Vec { - let mut result = Vec::::with_capacity(n * 2); - result.push( - tt::Leaf::Punct(tt::Punct { - char: '<', - spacing: tt::Spacing::Alone, - id: tt::TokenId::unspecified(), - }) - .into(), - ); - for i in 0..n { - if i > 0 { - result.push( - tt::Leaf::Punct(tt::Punct { - char: ',', - spacing: tt::Spacing::Alone, - id: tt::TokenId::unspecified(), - }) - .into(), - ); - } - result.push( - tt::Leaf::Ident(tt::Ident { - id: tt::TokenId::unspecified(), - text: format!("T{}", i).into(), - }) - .into(), - ); - result.extend(bound.iter().cloned()); - } - result.push( - tt::Leaf::Punct(tt::Punct { - char: '>', - spacing: tt::Spacing::Alone, - id: tt::TokenId::unspecified(), - }) - .into(), - ); - result -} - -fn expand_simple_derive(tt: &tt::Subtree, trait_path: tt::Subtree) -> ExpandResult { - let info = match parse_adt(tt) { - Ok(info) => info, - Err(e) => return ExpandResult::only_err(e), - }; - let name = info.name; - let trait_path_clone = trait_path.token_trees.clone(); - let bound = (quote! { : ##trait_path_clone }).token_trees; - let type_params = make_type_args(info.type_params, bound); - let type_args = make_type_args(info.type_params, Vec::new()); - let trait_path = trait_path.token_trees; - let expanded = quote! { - impl ##type_params ##trait_path for #name ##type_args {} - }; - ExpandResult::ok(expanded) -} - -fn find_builtin_crate(db: &dyn AstDatabase, id: MacroCallId) -> tt::TokenTree { - // FIXME: make hygiene works for builtin derive macro - // such that $crate can be used here. - let cg = db.crate_graph(); - let krate = db.lookup_intern_macro(id).krate; - - // XXX - // All crates except core itself should have a dependency on core, - // We detect `core` by seeing whether it doesn't have such a dependency. - let tt = if cg[krate].dependencies.iter().any(|dep| &*dep.name == "core") { - quote! { core } - } else { - quote! { crate } - }; - - tt.token_trees[0].clone() -} - -fn copy_expand( - db: &dyn AstDatabase, - id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::marker::Copy }) -} - -fn clone_expand( - db: &dyn AstDatabase, - id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::clone::Clone }) -} - -fn default_expand( - db: &dyn AstDatabase, - id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::default::Default }) -} - -fn debug_expand( - db: &dyn AstDatabase, - id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::fmt::Debug }) -} - -fn hash_expand( - db: &dyn AstDatabase, - id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::hash::Hash }) -} - -fn eq_expand(db: &dyn AstDatabase, id: MacroCallId, tt: &tt::Subtree) -> ExpandResult { - let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::cmp::Eq }) -} - -fn partial_eq_expand( - db: &dyn AstDatabase, - id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::cmp::PartialEq }) -} - -fn ord_expand( - db: &dyn AstDatabase, - id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::cmp::Ord }) -} - -fn partial_ord_expand( - db: &dyn AstDatabase, - id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - let krate = find_builtin_crate(db, id); - expand_simple_derive(tt, quote! { #krate::cmp::PartialOrd }) -} - -#[cfg(test)] -mod tests { - use base_db::{fixture::WithFixture, CrateId, SourceDatabase}; - use expect_test::{expect, Expect}; - use name::AsName; - - use crate::{test_db::TestDB, AstId, MacroCallId, MacroCallKind, MacroCallLoc}; - - use super::*; - - fn expand_builtin_derive(ra_fixture: &str) -> String { - let fixture = format!( - r#"//- /main.rs crate:main deps:core -$0 -{} -//- /lib.rs crate:core -// empty -"#, - ra_fixture - ); - - let (db, file_pos) = TestDB::with_position(&fixture); - let file_id = file_pos.file_id; - let ast_id_map = db.ast_id_map(file_id.into()); - let parsed = db.parse(file_id); - let macros: Vec<_> = - parsed.syntax_node().descendants().filter_map(ast::Macro::cast).collect(); - let items: Vec<_> = parsed - .syntax_node() - .descendants() - .filter(|node| !ast::Macro::can_cast(node.kind())) - .filter_map(ast::Item::cast) - .collect(); - - assert_eq!(macros.len(), 1, "test must contain exactly 1 macro definition"); - assert_eq!(items.len(), 1, "test must contain exactly 1 item"); - - let macro_ast_id = AstId::new(file_id.into(), ast_id_map.ast_id(¯os[0])); - let name = match ¯os[0] { - ast::Macro::MacroRules(rules) => rules.name().unwrap().as_name(), - ast::Macro::MacroDef(def) => def.name().unwrap().as_name(), - }; - - let expander = BuiltinDeriveExpander::find_by_name(&name).unwrap(); - - let ast_id = AstId::new(file_id.into(), ast_id_map.ast_id(&items[0])); - - let loc = MacroCallLoc { - def: MacroDefId { - krate: CrateId(0), - kind: MacroDefKind::BuiltInDerive(expander, macro_ast_id), - local_inner: false, - }, - krate: CrateId(0), - eager: None, - kind: MacroCallKind::Derive { - ast_id, - derive_name: name.to_string(), - derive_attr_index: 0, - }, - }; - - let id: MacroCallId = db.intern_macro(loc); - let parsed = db.parse_or_expand(id.as_file()).unwrap(); - - // FIXME text() for syntax nodes parsed from token tree looks weird - // because there's no whitespace, see below - parsed.text().to_string() - } - - fn check_derive(ra_fixture: &str, expected: Expect) { - let expanded = expand_builtin_derive(ra_fixture); - expected.assert_eq(&expanded); - } - - #[test] - fn test_copy_expand_simple() { - check_derive( - r#" - macro Copy {} - #[derive(Copy)] - struct Foo; - "#, - expect![["impl< >core::marker::CopyforFoo< >{}"]], - ); - } - - #[test] - fn test_copy_expand_with_type_params() { - check_derive( - r#" - macro Copy {} - #[derive(Copy)] - struct Foo; - "#, - expect![["implcore::marker::CopyforFoo{}"]], - ); - } - - #[test] - fn test_copy_expand_with_lifetimes() { - check_derive( - r#" - macro Copy {} - #[derive(Copy)] - struct Foo; - "#, - // We currently just ignore lifetimes - expect![["implcore::marker::CopyforFoo{}"]], - ); - } - - #[test] - fn test_clone_expand() { - check_derive( - r#" - macro Clone {} - #[derive(Clone)] - struct Foo; - "#, - expect![["implcore::clone::CloneforFoo{}"]], - ); - } -} diff --git a/crates/hir_expand/src/builtin_derive_macro.rs b/crates/hir_expand/src/builtin_derive_macro.rs new file mode 100644 index 00000000000..eeebe87acd6 --- /dev/null +++ b/crates/hir_expand/src/builtin_derive_macro.rs @@ -0,0 +1,385 @@ +//! Builtin derives. + +use tracing::debug; + +use mbe::ExpandResult; +use syntax::{ + ast::{self, AstNode, HasGenericParams, HasModuleItem, HasName}, + match_ast, +}; + +use crate::{db::AstDatabase, name, quote, AstId, CrateId, MacroCallId, MacroDefId, MacroDefKind}; + +macro_rules! register_builtin { + ( $($trait:ident => $expand:ident),* ) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum BuiltinDeriveExpander { + $($trait),* + } + + impl BuiltinDeriveExpander { + pub fn expand( + &self, + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, + ) -> ExpandResult { + let expander = match *self { + $( BuiltinDeriveExpander::$trait => $expand, )* + }; + expander(db, id, tt) + } + + fn find_by_name(name: &name::Name) -> Option { + match name { + $( id if id == &name::name![$trait] => Some(BuiltinDeriveExpander::$trait), )* + _ => None, + } + } + } + + }; +} + +register_builtin! { + Copy => copy_expand, + Clone => clone_expand, + Default => default_expand, + Debug => debug_expand, + Hash => hash_expand, + Ord => ord_expand, + PartialOrd => partial_ord_expand, + Eq => eq_expand, + PartialEq => partial_eq_expand +} + +pub fn find_builtin_derive( + ident: &name::Name, + krate: CrateId, + ast_id: AstId, +) -> Option { + let expander = BuiltinDeriveExpander::find_by_name(ident)?; + Some(MacroDefId { + krate, + kind: MacroDefKind::BuiltInDerive(expander, ast_id), + local_inner: false, + }) +} + +struct BasicAdtInfo { + name: tt::Ident, + type_params: usize, +} + +fn parse_adt(tt: &tt::Subtree) -> Result { + let (parsed, token_map) = mbe::token_tree_to_syntax_node(tt, mbe::ParserEntryPoint::Items)?; // FragmentKind::Items doesn't parse attrs? + let macro_items = ast::MacroItems::cast(parsed.syntax_node()).ok_or_else(|| { + debug!("derive node didn't parse"); + mbe::ExpandError::UnexpectedToken + })?; + let item = macro_items.items().next().ok_or_else(|| { + debug!("no module item parsed"); + mbe::ExpandError::NoMatchingRule + })?; + let node = item.syntax(); + let (name, params) = match_ast! { + match node { + ast::Struct(it) => (it.name(), it.generic_param_list()), + ast::Enum(it) => (it.name(), it.generic_param_list()), + ast::Union(it) => (it.name(), it.generic_param_list()), + _ => { + debug!("unexpected node is {:?}", node); + return Err(mbe::ExpandError::ConversionError) + }, + } + }; + let name = name.ok_or_else(|| { + debug!("parsed item has no name"); + mbe::ExpandError::NoMatchingRule + })?; + let name_token_id = token_map.token_by_range(name.syntax().text_range()).ok_or_else(|| { + debug!("name token not found"); + mbe::ExpandError::ConversionError + })?; + let name_token = tt::Ident { id: name_token_id, text: name.text().into() }; + let type_params = params.map_or(0, |type_param_list| type_param_list.type_params().count()); + Ok(BasicAdtInfo { name: name_token, type_params }) +} + +fn make_type_args(n: usize, bound: Vec) -> Vec { + let mut result = Vec::::with_capacity(n * 2); + result.push( + tt::Leaf::Punct(tt::Punct { + char: '<', + spacing: tt::Spacing::Alone, + id: tt::TokenId::unspecified(), + }) + .into(), + ); + for i in 0..n { + if i > 0 { + result.push( + tt::Leaf::Punct(tt::Punct { + char: ',', + spacing: tt::Spacing::Alone, + id: tt::TokenId::unspecified(), + }) + .into(), + ); + } + result.push( + tt::Leaf::Ident(tt::Ident { + id: tt::TokenId::unspecified(), + text: format!("T{}", i).into(), + }) + .into(), + ); + result.extend(bound.iter().cloned()); + } + result.push( + tt::Leaf::Punct(tt::Punct { + char: '>', + spacing: tt::Spacing::Alone, + id: tt::TokenId::unspecified(), + }) + .into(), + ); + result +} + +fn expand_simple_derive(tt: &tt::Subtree, trait_path: tt::Subtree) -> ExpandResult { + let info = match parse_adt(tt) { + Ok(info) => info, + Err(e) => return ExpandResult::only_err(e), + }; + let name = info.name; + let trait_path_clone = trait_path.token_trees.clone(); + let bound = (quote! { : ##trait_path_clone }).token_trees; + let type_params = make_type_args(info.type_params, bound); + let type_args = make_type_args(info.type_params, Vec::new()); + let trait_path = trait_path.token_trees; + let expanded = quote! { + impl ##type_params ##trait_path for #name ##type_args {} + }; + ExpandResult::ok(expanded) +} + +fn find_builtin_crate(db: &dyn AstDatabase, id: MacroCallId) -> tt::TokenTree { + // FIXME: make hygiene works for builtin derive macro + // such that $crate can be used here. + let cg = db.crate_graph(); + let krate = db.lookup_intern_macro(id).krate; + + // XXX + // All crates except core itself should have a dependency on core, + // We detect `core` by seeing whether it doesn't have such a dependency. + let tt = if cg[krate].dependencies.iter().any(|dep| &*dep.name == "core") { + quote! { core } + } else { + quote! { crate } + }; + + tt.token_trees[0].clone() +} + +fn copy_expand( + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + let krate = find_builtin_crate(db, id); + expand_simple_derive(tt, quote! { #krate::marker::Copy }) +} + +fn clone_expand( + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + let krate = find_builtin_crate(db, id); + expand_simple_derive(tt, quote! { #krate::clone::Clone }) +} + +fn default_expand( + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + let krate = find_builtin_crate(db, id); + expand_simple_derive(tt, quote! { #krate::default::Default }) +} + +fn debug_expand( + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + let krate = find_builtin_crate(db, id); + expand_simple_derive(tt, quote! { #krate::fmt::Debug }) +} + +fn hash_expand( + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + let krate = find_builtin_crate(db, id); + expand_simple_derive(tt, quote! { #krate::hash::Hash }) +} + +fn eq_expand(db: &dyn AstDatabase, id: MacroCallId, tt: &tt::Subtree) -> ExpandResult { + let krate = find_builtin_crate(db, id); + expand_simple_derive(tt, quote! { #krate::cmp::Eq }) +} + +fn partial_eq_expand( + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + let krate = find_builtin_crate(db, id); + expand_simple_derive(tt, quote! { #krate::cmp::PartialEq }) +} + +fn ord_expand( + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + let krate = find_builtin_crate(db, id); + expand_simple_derive(tt, quote! { #krate::cmp::Ord }) +} + +fn partial_ord_expand( + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + let krate = find_builtin_crate(db, id); + expand_simple_derive(tt, quote! { #krate::cmp::PartialOrd }) +} + +#[cfg(test)] +mod tests { + use base_db::{fixture::WithFixture, CrateId, SourceDatabase}; + use expect_test::{expect, Expect}; + use name::AsName; + + use crate::{test_db::TestDB, AstId, MacroCallId, MacroCallKind, MacroCallLoc}; + + use super::*; + + fn expand_builtin_derive(ra_fixture: &str) -> String { + let fixture = format!( + r#"//- /main.rs crate:main deps:core +$0 +{} +//- /lib.rs crate:core +// empty +"#, + ra_fixture + ); + + let (db, file_pos) = TestDB::with_position(&fixture); + let file_id = file_pos.file_id; + let ast_id_map = db.ast_id_map(file_id.into()); + let parsed = db.parse(file_id); + let macros: Vec<_> = + parsed.syntax_node().descendants().filter_map(ast::Macro::cast).collect(); + let items: Vec<_> = parsed + .syntax_node() + .descendants() + .filter(|node| !ast::Macro::can_cast(node.kind())) + .filter_map(ast::Item::cast) + .collect(); + + assert_eq!(macros.len(), 1, "test must contain exactly 1 macro definition"); + assert_eq!(items.len(), 1, "test must contain exactly 1 item"); + + let macro_ast_id = AstId::new(file_id.into(), ast_id_map.ast_id(¯os[0])); + let name = match ¯os[0] { + ast::Macro::MacroRules(rules) => rules.name().unwrap().as_name(), + ast::Macro::MacroDef(def) => def.name().unwrap().as_name(), + }; + + let expander = BuiltinDeriveExpander::find_by_name(&name).unwrap(); + + let ast_id = AstId::new(file_id.into(), ast_id_map.ast_id(&items[0])); + + let loc = MacroCallLoc { + def: MacroDefId { + krate: CrateId(0), + kind: MacroDefKind::BuiltInDerive(expander, macro_ast_id), + local_inner: false, + }, + krate: CrateId(0), + eager: None, + kind: MacroCallKind::Derive { + ast_id, + derive_name: name.to_string(), + derive_attr_index: 0, + }, + }; + + let id: MacroCallId = db.intern_macro(loc); + let parsed = db.parse_or_expand(id.as_file()).unwrap(); + + // FIXME text() for syntax nodes parsed from token tree looks weird + // because there's no whitespace, see below + parsed.text().to_string() + } + + fn check_derive(ra_fixture: &str, expected: Expect) { + let expanded = expand_builtin_derive(ra_fixture); + expected.assert_eq(&expanded); + } + + #[test] + fn test_copy_expand_simple() { + check_derive( + r#" + macro Copy {} + #[derive(Copy)] + struct Foo; + "#, + expect![["impl< >core::marker::CopyforFoo< >{}"]], + ); + } + + #[test] + fn test_copy_expand_with_type_params() { + check_derive( + r#" + macro Copy {} + #[derive(Copy)] + struct Foo; + "#, + expect![["implcore::marker::CopyforFoo{}"]], + ); + } + + #[test] + fn test_copy_expand_with_lifetimes() { + check_derive( + r#" + macro Copy {} + #[derive(Copy)] + struct Foo; + "#, + // We currently just ignore lifetimes + expect![["implcore::marker::CopyforFoo{}"]], + ); + } + + #[test] + fn test_clone_expand() { + check_derive( + r#" + macro Clone {} + #[derive(Clone)] + struct Foo; + "#, + expect![["implcore::clone::CloneforFoo{}"]], + ); + } +} diff --git a/crates/hir_expand/src/builtin_fn_macro.rs b/crates/hir_expand/src/builtin_fn_macro.rs new file mode 100644 index 00000000000..4b801eb2a15 --- /dev/null +++ b/crates/hir_expand/src/builtin_fn_macro.rs @@ -0,0 +1,567 @@ +//! Builtin macro +use crate::{ + db::AstDatabase, name, quote, AstId, CrateId, MacroCallId, MacroCallLoc, MacroDefId, + MacroDefKind, +}; + +use base_db::{AnchoredPath, Edition, FileId}; +use cfg::CfgExpr; +use either::Either; +use mbe::{parse_exprs_with_sep, parse_to_token_tree, ExpandResult}; +use syntax::ast::{self, AstToken}; + +macro_rules! register_builtin { + ( LAZY: $(($name:ident, $kind: ident) => $expand:ident),* , EAGER: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum BuiltinFnLikeExpander { + $($kind),* + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum EagerExpander { + $($e_kind),* + } + + impl BuiltinFnLikeExpander { + pub fn expand( + &self, + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, + ) -> ExpandResult { + let expander = match *self { + $( BuiltinFnLikeExpander::$kind => $expand, )* + }; + expander(db, id, tt) + } + } + + impl EagerExpander { + pub fn expand( + &self, + db: &dyn AstDatabase, + arg_id: MacroCallId, + tt: &tt::Subtree, + ) -> ExpandResult> { + let expander = match *self { + $( EagerExpander::$e_kind => $e_expand, )* + }; + expander(db, arg_id, tt) + } + } + + fn find_by_name(ident: &name::Name) -> Option> { + match ident { + $( id if id == &name::name![$name] => Some(Either::Left(BuiltinFnLikeExpander::$kind)), )* + $( id if id == &name::name![$e_name] => Some(Either::Right(EagerExpander::$e_kind)), )* + _ => return None, + } + } + }; +} + +#[derive(Debug)] +pub struct ExpandedEager { + pub(crate) subtree: tt::Subtree, + /// The included file ID of the include macro. + pub(crate) included_file: Option, +} + +impl ExpandedEager { + fn new(subtree: tt::Subtree) -> Self { + ExpandedEager { subtree, included_file: None } + } +} + +pub fn find_builtin_macro( + ident: &name::Name, + krate: CrateId, + ast_id: AstId, +) -> Option { + let kind = find_by_name(ident)?; + + match kind { + Either::Left(kind) => Some(MacroDefId { + krate, + kind: MacroDefKind::BuiltIn(kind, ast_id), + local_inner: false, + }), + Either::Right(kind) => Some(MacroDefId { + krate, + kind: MacroDefKind::BuiltInEager(kind, ast_id), + local_inner: false, + }), + } +} + +register_builtin! { + LAZY: + (column, Column) => column_expand, + (file, File) => file_expand, + (line, Line) => line_expand, + (module_path, ModulePath) => module_path_expand, + (assert, Assert) => assert_expand, + (stringify, Stringify) => stringify_expand, + (format_args, FormatArgs) => format_args_expand, + (const_format_args, ConstFormatArgs) => format_args_expand, + // format_args_nl only differs in that it adds a newline in the end, + // so we use the same stub expansion for now + (format_args_nl, FormatArgsNl) => format_args_expand, + (llvm_asm, LlvmAsm) => asm_expand, + (asm, Asm) => asm_expand, + (global_asm, GlobalAsm) => global_asm_expand, + (cfg, Cfg) => cfg_expand, + (core_panic, CorePanic) => panic_expand, + (std_panic, StdPanic) => panic_expand, + + EAGER: + (compile_error, CompileError) => compile_error_expand, + (concat, Concat) => concat_expand, + (concat_idents, ConcatIdents) => concat_idents_expand, + (include, Include) => include_expand, + (include_bytes, IncludeBytes) => include_bytes_expand, + (include_str, IncludeStr) => include_str_expand, + (env, Env) => env_expand, + (option_env, OptionEnv) => option_env_expand +} + +fn module_path_expand( + _db: &dyn AstDatabase, + _id: MacroCallId, + _tt: &tt::Subtree, +) -> ExpandResult { + // Just return a dummy result. + ExpandResult::ok(quote! { "module::path" }) +} + +fn line_expand( + _db: &dyn AstDatabase, + _id: MacroCallId, + _tt: &tt::Subtree, +) -> ExpandResult { + // dummy implementation for type-checking purposes + let line_num = 0; + let expanded = quote! { + #line_num + }; + + ExpandResult::ok(expanded) +} + +fn stringify_expand( + _db: &dyn AstDatabase, + _id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + let pretty = tt::pretty(&tt.token_trees); + + let expanded = quote! { + #pretty + }; + + ExpandResult::ok(expanded) +} + +fn column_expand( + _db: &dyn AstDatabase, + _id: MacroCallId, + _tt: &tt::Subtree, +) -> ExpandResult { + // dummy implementation for type-checking purposes + let col_num = 0; + let expanded = quote! { + #col_num + }; + + ExpandResult::ok(expanded) +} + +fn assert_expand( + _db: &dyn AstDatabase, + _id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + let krate = tt::Ident { text: "$crate".into(), id: tt::TokenId::unspecified() }; + let args = parse_exprs_with_sep(tt, ','); + let expanded = match &*args { + [cond, panic_args @ ..] => { + let comma = tt::Subtree { + delimiter: None, + token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { + char: ',', + spacing: tt::Spacing::Alone, + id: tt::TokenId::unspecified(), + }))], + }; + let cond = cond.clone(); + let panic_args = itertools::Itertools::intersperse(panic_args.iter().cloned(), comma); + quote! {{ + if !#cond { + #krate::panic!(##panic_args); + } + }} + } + [] => quote! {{}}, + }; + + ExpandResult::ok(expanded) +} + +fn file_expand( + _db: &dyn AstDatabase, + _id: MacroCallId, + _tt: &tt::Subtree, +) -> ExpandResult { + // FIXME: RA purposefully lacks knowledge of absolute file names + // so just return "". + let file_name = ""; + + let expanded = quote! { + #file_name + }; + + ExpandResult::ok(expanded) +} + +fn format_args_expand( + _db: &dyn AstDatabase, + _id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + // We expand `format_args!("", a1, a2)` to + // ``` + // std::fmt::Arguments::new_v1(&[], &[ + // std::fmt::ArgumentV1::new(&arg1,std::fmt::Display::fmt), + // std::fmt::ArgumentV1::new(&arg2,std::fmt::Display::fmt), + // ]) + // ```, + // which is still not really correct, but close enough for now + let mut args = parse_exprs_with_sep(tt, ','); + + if args.is_empty() { + return ExpandResult::only_err(mbe::ExpandError::NoMatchingRule); + } + for arg in &mut args { + // Remove `key =`. + if matches!(arg.token_trees.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=' && p.spacing != tt::Spacing::Joint) + { + arg.token_trees.drain(..2); + } + } + let _format_string = args.remove(0); + let arg_tts = args.into_iter().flat_map(|arg| { + quote! { std::fmt::ArgumentV1::new(&(#arg), std::fmt::Display::fmt), } + }.token_trees); + let expanded = quote! { + // It's unsafe since https://github.com/rust-lang/rust/pull/83302 + // Wrap an unsafe block to avoid false-positive `missing-unsafe` lint. + // FIXME: Currently we don't have `unused_unsafe` lint so an extra unsafe block won't cause issues on early + // stable rust-src. + unsafe { + std::fmt::Arguments::new_v1(&[], &[##arg_tts]) + } + }; + ExpandResult::ok(expanded) +} + +fn asm_expand( + _db: &dyn AstDatabase, + _id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + // We expand all assembly snippets to `format_args!` invocations to get format syntax + // highlighting for them. + + let krate = tt::Ident { text: "$crate".into(), id: tt::TokenId::unspecified() }; + + let mut literals = Vec::new(); + for tt in tt.token_trees.chunks(2) { + match tt { + [tt::TokenTree::Leaf(tt::Leaf::Literal(lit))] + | [tt::TokenTree::Leaf(tt::Leaf::Literal(lit)), tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', id: _, spacing: _ }))] => + { + let krate = krate.clone(); + literals.push(quote!(#krate::format_args!(#lit);)); + } + _ => break, + } + } + + let expanded = quote! {{ + ##literals + () + }}; + ExpandResult::ok(expanded) +} + +fn global_asm_expand( + _db: &dyn AstDatabase, + _id: MacroCallId, + _tt: &tt::Subtree, +) -> ExpandResult { + // Expand to nothing (at item-level) + ExpandResult::ok(quote! {}) +} + +fn cfg_expand( + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + let loc = db.lookup_intern_macro(id); + let expr = CfgExpr::parse(tt); + let enabled = db.crate_graph()[loc.krate].cfg_options.check(&expr) != Some(false); + let expanded = if enabled { quote!(true) } else { quote!(false) }; + ExpandResult::ok(expanded) +} + +fn panic_expand( + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult { + let loc: MacroCallLoc = db.lookup_intern_macro(id); + // Expand to a macro call `$crate::panic::panic_{edition}` + let krate = tt::Ident { text: "$crate".into(), id: tt::TokenId::unspecified() }; + let mut call = if db.crate_graph()[loc.krate].edition == Edition::Edition2021 { + quote!(#krate::panic::panic_2021!) + } else { + quote!(#krate::panic::panic_2015!) + }; + + // Pass the original arguments + call.token_trees.push(tt::TokenTree::Subtree(tt.clone())); + ExpandResult::ok(call) +} + +fn unquote_str(lit: &tt::Literal) -> Option { + let lit = ast::make::tokens::literal(&lit.to_string()); + let token = ast::String::cast(lit)?; + token.value().map(|it| it.into_owned()) +} + +fn compile_error_expand( + _db: &dyn AstDatabase, + _id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult> { + let err = match &*tt.token_trees { + [tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => { + let text = it.text.as_str(); + if text.starts_with('"') && text.ends_with('"') { + // FIXME: does not handle raw strings + mbe::ExpandError::Other(text[1..text.len() - 1].to_string()) + } else { + mbe::ExpandError::BindingError("`compile_error!` argument must be a string".into()) + } + } + _ => mbe::ExpandError::BindingError("`compile_error!` argument must be a string".into()), + }; + + ExpandResult { value: Some(ExpandedEager::new(quote! {})), err: Some(err) } +} + +fn concat_expand( + _db: &dyn AstDatabase, + _arg_id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult> { + let mut err = None; + let mut text = String::new(); + for (i, t) in tt.token_trees.iter().enumerate() { + match t { + tt::TokenTree::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => { + // concat works with string and char literals, so remove any quotes. + // It also works with integer, float and boolean literals, so just use the rest + // as-is. + let component = unquote_str(it).unwrap_or_else(|| it.text.to_string()); + text.push_str(&component); + } + // handle boolean literals + tt::TokenTree::Leaf(tt::Leaf::Ident(id)) + if i % 2 == 0 && (id.text == "true" || id.text == "false") => + { + text.push_str(id.text.as_str()); + } + tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), + _ => { + err.get_or_insert(mbe::ExpandError::UnexpectedToken); + } + } + } + ExpandResult { value: Some(ExpandedEager::new(quote!(#text))), err } +} + +fn concat_idents_expand( + _db: &dyn AstDatabase, + _arg_id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult> { + let mut err = None; + let mut ident = String::new(); + for (i, t) in tt.token_trees.iter().enumerate() { + match t { + tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => { + ident.push_str(id.text.as_str()); + } + tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), + _ => { + err.get_or_insert(mbe::ExpandError::UnexpectedToken); + } + } + } + let ident = tt::Ident { text: ident.into(), id: tt::TokenId::unspecified() }; + ExpandResult { value: Some(ExpandedEager::new(quote!(#ident))), err } +} + +fn relative_file( + db: &dyn AstDatabase, + call_id: MacroCallId, + path_str: &str, + allow_recursion: bool, +) -> Result { + let call_site = call_id.as_file().original_file(db); + let path = AnchoredPath { anchor: call_site, path: path_str }; + let res = db + .resolve_path(path) + .ok_or_else(|| mbe::ExpandError::Other(format!("failed to load file `{}`", path_str)))?; + // Prevent include itself + if res == call_site && !allow_recursion { + Err(mbe::ExpandError::Other(format!("recursive inclusion of `{}`", path_str))) + } else { + Ok(res) + } +} + +fn parse_string(tt: &tt::Subtree) -> Result { + tt.token_trees + .get(0) + .and_then(|tt| match tt { + tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(it), + _ => None, + }) + .ok_or(mbe::ExpandError::ConversionError) +} + +fn include_expand( + db: &dyn AstDatabase, + arg_id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult> { + let res = (|| { + let path = parse_string(tt)?; + let file_id = relative_file(db, arg_id, &path, false)?; + + let subtree = + parse_to_token_tree(&db.file_text(file_id)).ok_or(mbe::ExpandError::ConversionError)?.0; + Ok((subtree, file_id)) + })(); + + match res { + Ok((subtree, file_id)) => { + ExpandResult::ok(Some(ExpandedEager { subtree, included_file: Some(file_id) })) + } + Err(e) => ExpandResult::only_err(e), + } +} + +fn include_bytes_expand( + _db: &dyn AstDatabase, + _arg_id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult> { + if let Err(e) = parse_string(tt) { + return ExpandResult::only_err(e); + } + + // FIXME: actually read the file here if the user asked for macro expansion + let res = tt::Subtree { + delimiter: None, + token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { + text: r#"b"""#.into(), + id: tt::TokenId::unspecified(), + }))], + }; + ExpandResult::ok(Some(ExpandedEager::new(res))) +} + +fn include_str_expand( + db: &dyn AstDatabase, + arg_id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult> { + let path = match parse_string(tt) { + Ok(it) => it, + Err(e) => return ExpandResult::only_err(e), + }; + + // FIXME: we're not able to read excluded files (which is most of them because + // it's unusual to `include_str!` a Rust file), but we can return an empty string. + // Ideally, we'd be able to offer a precise expansion if the user asks for macro + // expansion. + let file_id = match relative_file(db, arg_id, &path, true) { + Ok(file_id) => file_id, + Err(_) => { + return ExpandResult::ok(Some(ExpandedEager::new(quote!("")))); + } + }; + + let text = db.file_text(file_id); + let text = &*text; + + ExpandResult::ok(Some(ExpandedEager::new(quote!(#text)))) +} + +fn get_env_inner(db: &dyn AstDatabase, arg_id: MacroCallId, key: &str) -> Option { + let krate = db.lookup_intern_macro(arg_id).krate; + db.crate_graph()[krate].env.get(key) +} + +fn env_expand( + db: &dyn AstDatabase, + arg_id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult> { + let key = match parse_string(tt) { + Ok(it) => it, + Err(e) => return ExpandResult::only_err(e), + }; + + let mut err = None; + let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| { + // The only variable rust-analyzer ever sets is `OUT_DIR`, so only diagnose that to avoid + // unnecessary diagnostics for eg. `CARGO_PKG_NAME`. + if key == "OUT_DIR" { + err = Some(mbe::ExpandError::Other( + r#"`OUT_DIR` not set, enable "run build scripts" to fix"#.into(), + )); + } + + // If the variable is unset, still return a dummy string to help type inference along. + // We cannot use an empty string here, because for + // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become + // `include!("foo.rs"), which might go to infinite loop + "__RA_UNIMPLEMENTED__".to_string() + }); + let expanded = quote! { #s }; + + ExpandResult { value: Some(ExpandedEager::new(expanded)), err } +} + +fn option_env_expand( + db: &dyn AstDatabase, + arg_id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult> { + let key = match parse_string(tt) { + Ok(it) => it, + Err(e) => return ExpandResult::only_err(e), + }; + + let expanded = match get_env_inner(db, arg_id, &key) { + None => quote! { std::option::Option::None::<&str> }, + Some(s) => quote! { std::option::Some(#s) }, + }; + + ExpandResult::ok(Some(ExpandedEager::new(expanded))) +} diff --git a/crates/hir_expand/src/builtin_macro.rs b/crates/hir_expand/src/builtin_macro.rs deleted file mode 100644 index 4b801eb2a15..00000000000 --- a/crates/hir_expand/src/builtin_macro.rs +++ /dev/null @@ -1,567 +0,0 @@ -//! Builtin macro -use crate::{ - db::AstDatabase, name, quote, AstId, CrateId, MacroCallId, MacroCallLoc, MacroDefId, - MacroDefKind, -}; - -use base_db::{AnchoredPath, Edition, FileId}; -use cfg::CfgExpr; -use either::Either; -use mbe::{parse_exprs_with_sep, parse_to_token_tree, ExpandResult}; -use syntax::ast::{self, AstToken}; - -macro_rules! register_builtin { - ( LAZY: $(($name:ident, $kind: ident) => $expand:ident),* , EAGER: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => { - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum BuiltinFnLikeExpander { - $($kind),* - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum EagerExpander { - $($e_kind),* - } - - impl BuiltinFnLikeExpander { - pub fn expand( - &self, - db: &dyn AstDatabase, - id: MacroCallId, - tt: &tt::Subtree, - ) -> ExpandResult { - let expander = match *self { - $( BuiltinFnLikeExpander::$kind => $expand, )* - }; - expander(db, id, tt) - } - } - - impl EagerExpander { - pub fn expand( - &self, - db: &dyn AstDatabase, - arg_id: MacroCallId, - tt: &tt::Subtree, - ) -> ExpandResult> { - let expander = match *self { - $( EagerExpander::$e_kind => $e_expand, )* - }; - expander(db, arg_id, tt) - } - } - - fn find_by_name(ident: &name::Name) -> Option> { - match ident { - $( id if id == &name::name![$name] => Some(Either::Left(BuiltinFnLikeExpander::$kind)), )* - $( id if id == &name::name![$e_name] => Some(Either::Right(EagerExpander::$e_kind)), )* - _ => return None, - } - } - }; -} - -#[derive(Debug)] -pub struct ExpandedEager { - pub(crate) subtree: tt::Subtree, - /// The included file ID of the include macro. - pub(crate) included_file: Option, -} - -impl ExpandedEager { - fn new(subtree: tt::Subtree) -> Self { - ExpandedEager { subtree, included_file: None } - } -} - -pub fn find_builtin_macro( - ident: &name::Name, - krate: CrateId, - ast_id: AstId, -) -> Option { - let kind = find_by_name(ident)?; - - match kind { - Either::Left(kind) => Some(MacroDefId { - krate, - kind: MacroDefKind::BuiltIn(kind, ast_id), - local_inner: false, - }), - Either::Right(kind) => Some(MacroDefId { - krate, - kind: MacroDefKind::BuiltInEager(kind, ast_id), - local_inner: false, - }), - } -} - -register_builtin! { - LAZY: - (column, Column) => column_expand, - (file, File) => file_expand, - (line, Line) => line_expand, - (module_path, ModulePath) => module_path_expand, - (assert, Assert) => assert_expand, - (stringify, Stringify) => stringify_expand, - (format_args, FormatArgs) => format_args_expand, - (const_format_args, ConstFormatArgs) => format_args_expand, - // format_args_nl only differs in that it adds a newline in the end, - // so we use the same stub expansion for now - (format_args_nl, FormatArgsNl) => format_args_expand, - (llvm_asm, LlvmAsm) => asm_expand, - (asm, Asm) => asm_expand, - (global_asm, GlobalAsm) => global_asm_expand, - (cfg, Cfg) => cfg_expand, - (core_panic, CorePanic) => panic_expand, - (std_panic, StdPanic) => panic_expand, - - EAGER: - (compile_error, CompileError) => compile_error_expand, - (concat, Concat) => concat_expand, - (concat_idents, ConcatIdents) => concat_idents_expand, - (include, Include) => include_expand, - (include_bytes, IncludeBytes) => include_bytes_expand, - (include_str, IncludeStr) => include_str_expand, - (env, Env) => env_expand, - (option_env, OptionEnv) => option_env_expand -} - -fn module_path_expand( - _db: &dyn AstDatabase, - _id: MacroCallId, - _tt: &tt::Subtree, -) -> ExpandResult { - // Just return a dummy result. - ExpandResult::ok(quote! { "module::path" }) -} - -fn line_expand( - _db: &dyn AstDatabase, - _id: MacroCallId, - _tt: &tt::Subtree, -) -> ExpandResult { - // dummy implementation for type-checking purposes - let line_num = 0; - let expanded = quote! { - #line_num - }; - - ExpandResult::ok(expanded) -} - -fn stringify_expand( - _db: &dyn AstDatabase, - _id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - let pretty = tt::pretty(&tt.token_trees); - - let expanded = quote! { - #pretty - }; - - ExpandResult::ok(expanded) -} - -fn column_expand( - _db: &dyn AstDatabase, - _id: MacroCallId, - _tt: &tt::Subtree, -) -> ExpandResult { - // dummy implementation for type-checking purposes - let col_num = 0; - let expanded = quote! { - #col_num - }; - - ExpandResult::ok(expanded) -} - -fn assert_expand( - _db: &dyn AstDatabase, - _id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - let krate = tt::Ident { text: "$crate".into(), id: tt::TokenId::unspecified() }; - let args = parse_exprs_with_sep(tt, ','); - let expanded = match &*args { - [cond, panic_args @ ..] => { - let comma = tt::Subtree { - delimiter: None, - token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { - char: ',', - spacing: tt::Spacing::Alone, - id: tt::TokenId::unspecified(), - }))], - }; - let cond = cond.clone(); - let panic_args = itertools::Itertools::intersperse(panic_args.iter().cloned(), comma); - quote! {{ - if !#cond { - #krate::panic!(##panic_args); - } - }} - } - [] => quote! {{}}, - }; - - ExpandResult::ok(expanded) -} - -fn file_expand( - _db: &dyn AstDatabase, - _id: MacroCallId, - _tt: &tt::Subtree, -) -> ExpandResult { - // FIXME: RA purposefully lacks knowledge of absolute file names - // so just return "". - let file_name = ""; - - let expanded = quote! { - #file_name - }; - - ExpandResult::ok(expanded) -} - -fn format_args_expand( - _db: &dyn AstDatabase, - _id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - // We expand `format_args!("", a1, a2)` to - // ``` - // std::fmt::Arguments::new_v1(&[], &[ - // std::fmt::ArgumentV1::new(&arg1,std::fmt::Display::fmt), - // std::fmt::ArgumentV1::new(&arg2,std::fmt::Display::fmt), - // ]) - // ```, - // which is still not really correct, but close enough for now - let mut args = parse_exprs_with_sep(tt, ','); - - if args.is_empty() { - return ExpandResult::only_err(mbe::ExpandError::NoMatchingRule); - } - for arg in &mut args { - // Remove `key =`. - if matches!(arg.token_trees.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=' && p.spacing != tt::Spacing::Joint) - { - arg.token_trees.drain(..2); - } - } - let _format_string = args.remove(0); - let arg_tts = args.into_iter().flat_map(|arg| { - quote! { std::fmt::ArgumentV1::new(&(#arg), std::fmt::Display::fmt), } - }.token_trees); - let expanded = quote! { - // It's unsafe since https://github.com/rust-lang/rust/pull/83302 - // Wrap an unsafe block to avoid false-positive `missing-unsafe` lint. - // FIXME: Currently we don't have `unused_unsafe` lint so an extra unsafe block won't cause issues on early - // stable rust-src. - unsafe { - std::fmt::Arguments::new_v1(&[], &[##arg_tts]) - } - }; - ExpandResult::ok(expanded) -} - -fn asm_expand( - _db: &dyn AstDatabase, - _id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - // We expand all assembly snippets to `format_args!` invocations to get format syntax - // highlighting for them. - - let krate = tt::Ident { text: "$crate".into(), id: tt::TokenId::unspecified() }; - - let mut literals = Vec::new(); - for tt in tt.token_trees.chunks(2) { - match tt { - [tt::TokenTree::Leaf(tt::Leaf::Literal(lit))] - | [tt::TokenTree::Leaf(tt::Leaf::Literal(lit)), tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', id: _, spacing: _ }))] => - { - let krate = krate.clone(); - literals.push(quote!(#krate::format_args!(#lit);)); - } - _ => break, - } - } - - let expanded = quote! {{ - ##literals - () - }}; - ExpandResult::ok(expanded) -} - -fn global_asm_expand( - _db: &dyn AstDatabase, - _id: MacroCallId, - _tt: &tt::Subtree, -) -> ExpandResult { - // Expand to nothing (at item-level) - ExpandResult::ok(quote! {}) -} - -fn cfg_expand( - db: &dyn AstDatabase, - id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - let loc = db.lookup_intern_macro(id); - let expr = CfgExpr::parse(tt); - let enabled = db.crate_graph()[loc.krate].cfg_options.check(&expr) != Some(false); - let expanded = if enabled { quote!(true) } else { quote!(false) }; - ExpandResult::ok(expanded) -} - -fn panic_expand( - db: &dyn AstDatabase, - id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult { - let loc: MacroCallLoc = db.lookup_intern_macro(id); - // Expand to a macro call `$crate::panic::panic_{edition}` - let krate = tt::Ident { text: "$crate".into(), id: tt::TokenId::unspecified() }; - let mut call = if db.crate_graph()[loc.krate].edition == Edition::Edition2021 { - quote!(#krate::panic::panic_2021!) - } else { - quote!(#krate::panic::panic_2015!) - }; - - // Pass the original arguments - call.token_trees.push(tt::TokenTree::Subtree(tt.clone())); - ExpandResult::ok(call) -} - -fn unquote_str(lit: &tt::Literal) -> Option { - let lit = ast::make::tokens::literal(&lit.to_string()); - let token = ast::String::cast(lit)?; - token.value().map(|it| it.into_owned()) -} - -fn compile_error_expand( - _db: &dyn AstDatabase, - _id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult> { - let err = match &*tt.token_trees { - [tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => { - let text = it.text.as_str(); - if text.starts_with('"') && text.ends_with('"') { - // FIXME: does not handle raw strings - mbe::ExpandError::Other(text[1..text.len() - 1].to_string()) - } else { - mbe::ExpandError::BindingError("`compile_error!` argument must be a string".into()) - } - } - _ => mbe::ExpandError::BindingError("`compile_error!` argument must be a string".into()), - }; - - ExpandResult { value: Some(ExpandedEager::new(quote! {})), err: Some(err) } -} - -fn concat_expand( - _db: &dyn AstDatabase, - _arg_id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult> { - let mut err = None; - let mut text = String::new(); - for (i, t) in tt.token_trees.iter().enumerate() { - match t { - tt::TokenTree::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => { - // concat works with string and char literals, so remove any quotes. - // It also works with integer, float and boolean literals, so just use the rest - // as-is. - let component = unquote_str(it).unwrap_or_else(|| it.text.to_string()); - text.push_str(&component); - } - // handle boolean literals - tt::TokenTree::Leaf(tt::Leaf::Ident(id)) - if i % 2 == 0 && (id.text == "true" || id.text == "false") => - { - text.push_str(id.text.as_str()); - } - tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), - _ => { - err.get_or_insert(mbe::ExpandError::UnexpectedToken); - } - } - } - ExpandResult { value: Some(ExpandedEager::new(quote!(#text))), err } -} - -fn concat_idents_expand( - _db: &dyn AstDatabase, - _arg_id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult> { - let mut err = None; - let mut ident = String::new(); - for (i, t) in tt.token_trees.iter().enumerate() { - match t { - tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => { - ident.push_str(id.text.as_str()); - } - tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), - _ => { - err.get_or_insert(mbe::ExpandError::UnexpectedToken); - } - } - } - let ident = tt::Ident { text: ident.into(), id: tt::TokenId::unspecified() }; - ExpandResult { value: Some(ExpandedEager::new(quote!(#ident))), err } -} - -fn relative_file( - db: &dyn AstDatabase, - call_id: MacroCallId, - path_str: &str, - allow_recursion: bool, -) -> Result { - let call_site = call_id.as_file().original_file(db); - let path = AnchoredPath { anchor: call_site, path: path_str }; - let res = db - .resolve_path(path) - .ok_or_else(|| mbe::ExpandError::Other(format!("failed to load file `{}`", path_str)))?; - // Prevent include itself - if res == call_site && !allow_recursion { - Err(mbe::ExpandError::Other(format!("recursive inclusion of `{}`", path_str))) - } else { - Ok(res) - } -} - -fn parse_string(tt: &tt::Subtree) -> Result { - tt.token_trees - .get(0) - .and_then(|tt| match tt { - tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => unquote_str(it), - _ => None, - }) - .ok_or(mbe::ExpandError::ConversionError) -} - -fn include_expand( - db: &dyn AstDatabase, - arg_id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult> { - let res = (|| { - let path = parse_string(tt)?; - let file_id = relative_file(db, arg_id, &path, false)?; - - let subtree = - parse_to_token_tree(&db.file_text(file_id)).ok_or(mbe::ExpandError::ConversionError)?.0; - Ok((subtree, file_id)) - })(); - - match res { - Ok((subtree, file_id)) => { - ExpandResult::ok(Some(ExpandedEager { subtree, included_file: Some(file_id) })) - } - Err(e) => ExpandResult::only_err(e), - } -} - -fn include_bytes_expand( - _db: &dyn AstDatabase, - _arg_id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult> { - if let Err(e) = parse_string(tt) { - return ExpandResult::only_err(e); - } - - // FIXME: actually read the file here if the user asked for macro expansion - let res = tt::Subtree { - delimiter: None, - token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - text: r#"b"""#.into(), - id: tt::TokenId::unspecified(), - }))], - }; - ExpandResult::ok(Some(ExpandedEager::new(res))) -} - -fn include_str_expand( - db: &dyn AstDatabase, - arg_id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult> { - let path = match parse_string(tt) { - Ok(it) => it, - Err(e) => return ExpandResult::only_err(e), - }; - - // FIXME: we're not able to read excluded files (which is most of them because - // it's unusual to `include_str!` a Rust file), but we can return an empty string. - // Ideally, we'd be able to offer a precise expansion if the user asks for macro - // expansion. - let file_id = match relative_file(db, arg_id, &path, true) { - Ok(file_id) => file_id, - Err(_) => { - return ExpandResult::ok(Some(ExpandedEager::new(quote!("")))); - } - }; - - let text = db.file_text(file_id); - let text = &*text; - - ExpandResult::ok(Some(ExpandedEager::new(quote!(#text)))) -} - -fn get_env_inner(db: &dyn AstDatabase, arg_id: MacroCallId, key: &str) -> Option { - let krate = db.lookup_intern_macro(arg_id).krate; - db.crate_graph()[krate].env.get(key) -} - -fn env_expand( - db: &dyn AstDatabase, - arg_id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult> { - let key = match parse_string(tt) { - Ok(it) => it, - Err(e) => return ExpandResult::only_err(e), - }; - - let mut err = None; - let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| { - // The only variable rust-analyzer ever sets is `OUT_DIR`, so only diagnose that to avoid - // unnecessary diagnostics for eg. `CARGO_PKG_NAME`. - if key == "OUT_DIR" { - err = Some(mbe::ExpandError::Other( - r#"`OUT_DIR` not set, enable "run build scripts" to fix"#.into(), - )); - } - - // If the variable is unset, still return a dummy string to help type inference along. - // We cannot use an empty string here, because for - // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become - // `include!("foo.rs"), which might go to infinite loop - "__RA_UNIMPLEMENTED__".to_string() - }); - let expanded = quote! { #s }; - - ExpandResult { value: Some(ExpandedEager::new(expanded)), err } -} - -fn option_env_expand( - db: &dyn AstDatabase, - arg_id: MacroCallId, - tt: &tt::Subtree, -) -> ExpandResult> { - let key = match parse_string(tt) { - Ok(it) => it, - Err(e) => return ExpandResult::only_err(e), - }; - - let expanded = match get_env_inner(db, arg_id, &key) { - None => quote! { std::option::Option::None::<&str> }, - Some(s) => quote! { std::option::Some(#s) }, - }; - - ExpandResult::ok(Some(ExpandedEager::new(expanded))) -} diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs index ad6b84dd17e..de32d02415d 100644 --- a/crates/hir_expand/src/lib.rs +++ b/crates/hir_expand/src/lib.rs @@ -8,9 +8,9 @@ pub mod ast_id_map; pub mod name; pub mod hygiene; -pub mod builtin_attr; -pub mod builtin_derive; -pub mod builtin_macro; +pub mod builtin_attr_macro; +pub mod builtin_derive_macro; +pub mod builtin_fn_macro; pub mod proc_macro; pub mod quote; pub mod eager; @@ -31,9 +31,9 @@ use crate::{ ast_id_map::FileAstId, - builtin_attr::BuiltinAttrExpander, - builtin_derive::BuiltinDeriveExpander, - builtin_macro::{BuiltinFnLikeExpander, EagerExpander}, + builtin_attr_macro::BuiltinAttrExpander, + builtin_derive_macro::BuiltinDeriveExpander, + builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander}, db::TokenExpander, proc_macro::ProcMacroExpander, };