//! and harder to understand.
mod mbe;
-mod builtin;
+mod builtin_fn_macro;
use std::{iter, ops::Range};
+++ /dev/null
-//! 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::<A,B>(), 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::<A, B>()), 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; }
-"##]],
- );
-}
--- /dev/null
+//! 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::<A,B>(), 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::<A, B>()), 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; }
+"##]],
+ );
+}
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,
+++ /dev/null
-//! 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<tt::Subtree> {
- let expander = match *self {
- $( BuiltinAttrExpander::$variant => $expand, )*
- };
- expander(db, id, tt)
- }
-
- fn find_by_name(name: &name::Name) -> Option<Self> {
- 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<ast::Macro>,
-) -> Option<MacroDefId> {
- 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<tt::Subtree> {
- ExpandResult::ok(tt.clone())
-}
--- /dev/null
+//! 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<tt::Subtree> {
+ let expander = match *self {
+ $( BuiltinAttrExpander::$variant => $expand, )*
+ };
+ expander(db, id, tt)
+ }
+
+ fn find_by_name(name: &name::Name) -> Option<Self> {
+ 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<ast::Macro>,
+) -> Option<MacroDefId> {
+ 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<tt::Subtree> {
+ ExpandResult::ok(tt.clone())
+}
+++ /dev/null
-//! 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<tt::Subtree> {
- let expander = match *self {
- $( BuiltinDeriveExpander::$trait => $expand, )*
- };
- expander(db, id, tt)
- }
-
- fn find_by_name(name: &name::Name) -> Option<Self> {
- 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<ast::Macro>,
-) -> Option<MacroDefId> {
- 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<BasicAdtInfo, mbe::ExpandError> {
- 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<tt::TokenTree>) -> Vec<tt::TokenTree> {
- let mut result = Vec::<tt::TokenTree>::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<tt::Subtree> {
- 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<tt::Subtree> {
- 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<tt::Subtree> {
- 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<tt::Subtree> {
- 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<tt::Subtree> {
- 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<tt::Subtree> {
- 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<tt::Subtree> {
- 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<tt::Subtree> {
- 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<tt::Subtree> {
- 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<tt::Subtree> {
- 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<A, B>;
- "#,
- expect![["impl<T0:core::marker::Copy,T1:core::marker::Copy>core::marker::CopyforFoo<T0,T1>{}"]],
- );
- }
-
- #[test]
- fn test_copy_expand_with_lifetimes() {
- check_derive(
- r#"
- macro Copy {}
- #[derive(Copy)]
- struct Foo<A, B, 'a, 'b>;
- "#,
- // We currently just ignore lifetimes
- expect![["impl<T0:core::marker::Copy,T1:core::marker::Copy>core::marker::CopyforFoo<T0,T1>{}"]],
- );
- }
-
- #[test]
- fn test_clone_expand() {
- check_derive(
- r#"
- macro Clone {}
- #[derive(Clone)]
- struct Foo<A, B>;
- "#,
- expect![["impl<T0:core::clone::Clone,T1:core::clone::Clone>core::clone::CloneforFoo<T0,T1>{}"]],
- );
- }
-}
--- /dev/null
+//! 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<tt::Subtree> {
+ let expander = match *self {
+ $( BuiltinDeriveExpander::$trait => $expand, )*
+ };
+ expander(db, id, tt)
+ }
+
+ fn find_by_name(name: &name::Name) -> Option<Self> {
+ 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<ast::Macro>,
+) -> Option<MacroDefId> {
+ 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<BasicAdtInfo, mbe::ExpandError> {
+ 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<tt::TokenTree>) -> Vec<tt::TokenTree> {
+ let mut result = Vec::<tt::TokenTree>::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<tt::Subtree> {
+ 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<tt::Subtree> {
+ 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<tt::Subtree> {
+ 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<tt::Subtree> {
+ 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<tt::Subtree> {
+ 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<tt::Subtree> {
+ 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<tt::Subtree> {
+ 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<tt::Subtree> {
+ 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<tt::Subtree> {
+ 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<tt::Subtree> {
+ 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<A, B>;
+ "#,
+ expect![["impl<T0:core::marker::Copy,T1:core::marker::Copy>core::marker::CopyforFoo<T0,T1>{}"]],
+ );
+ }
+
+ #[test]
+ fn test_copy_expand_with_lifetimes() {
+ check_derive(
+ r#"
+ macro Copy {}
+ #[derive(Copy)]
+ struct Foo<A, B, 'a, 'b>;
+ "#,
+ // We currently just ignore lifetimes
+ expect![["impl<T0:core::marker::Copy,T1:core::marker::Copy>core::marker::CopyforFoo<T0,T1>{}"]],
+ );
+ }
+
+ #[test]
+ fn test_clone_expand() {
+ check_derive(
+ r#"
+ macro Clone {}
+ #[derive(Clone)]
+ struct Foo<A, B>;
+ "#,
+ expect![["impl<T0:core::clone::Clone,T1:core::clone::Clone>core::clone::CloneforFoo<T0,T1>{}"]],
+ );
+ }
+}
--- /dev/null
+//! 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<tt::Subtree> {
+ 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<Option<ExpandedEager>> {
+ let expander = match *self {
+ $( EagerExpander::$e_kind => $e_expand, )*
+ };
+ expander(db, arg_id, tt)
+ }
+ }
+
+ fn find_by_name(ident: &name::Name) -> Option<Either<BuiltinFnLikeExpander, EagerExpander>> {
+ 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<FileId>,
+}
+
+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<ast::Macro>,
+) -> Option<MacroDefId> {
+ 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<tt::Subtree> {
+ // Just return a dummy result.
+ ExpandResult::ok(quote! { "module::path" })
+}
+
+fn line_expand(
+ _db: &dyn AstDatabase,
+ _id: MacroCallId,
+ _tt: &tt::Subtree,
+) -> ExpandResult<tt::Subtree> {
+ // 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<tt::Subtree> {
+ 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<tt::Subtree> {
+ // 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<tt::Subtree> {
+ 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<tt::Subtree> {
+ // 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<tt::Subtree> {
+ // 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<tt::Subtree> {
+ // 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<tt::Subtree> {
+ // Expand to nothing (at item-level)
+ ExpandResult::ok(quote! {})
+}
+
+fn cfg_expand(
+ db: &dyn AstDatabase,
+ id: MacroCallId,
+ tt: &tt::Subtree,
+) -> ExpandResult<tt::Subtree> {
+ 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<tt::Subtree> {
+ 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<String> {
+ 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<Option<ExpandedEager>> {
+ 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<Option<ExpandedEager>> {
+ 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<Option<ExpandedEager>> {
+ 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<FileId, mbe::ExpandError> {
+ 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<String, mbe::ExpandError> {
+ 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<Option<ExpandedEager>> {
+ 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<Option<ExpandedEager>> {
+ 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<Option<ExpandedEager>> {
+ 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<String> {
+ 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<Option<ExpandedEager>> {
+ 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<Option<ExpandedEager>> {
+ 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)))
+}
+++ /dev/null
-//! 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<tt::Subtree> {
- 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<Option<ExpandedEager>> {
- let expander = match *self {
- $( EagerExpander::$e_kind => $e_expand, )*
- };
- expander(db, arg_id, tt)
- }
- }
-
- fn find_by_name(ident: &name::Name) -> Option<Either<BuiltinFnLikeExpander, EagerExpander>> {
- 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<FileId>,
-}
-
-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<ast::Macro>,
-) -> Option<MacroDefId> {
- 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<tt::Subtree> {
- // Just return a dummy result.
- ExpandResult::ok(quote! { "module::path" })
-}
-
-fn line_expand(
- _db: &dyn AstDatabase,
- _id: MacroCallId,
- _tt: &tt::Subtree,
-) -> ExpandResult<tt::Subtree> {
- // 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<tt::Subtree> {
- 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<tt::Subtree> {
- // 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<tt::Subtree> {
- 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<tt::Subtree> {
- // 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<tt::Subtree> {
- // 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<tt::Subtree> {
- // 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<tt::Subtree> {
- // Expand to nothing (at item-level)
- ExpandResult::ok(quote! {})
-}
-
-fn cfg_expand(
- db: &dyn AstDatabase,
- id: MacroCallId,
- tt: &tt::Subtree,
-) -> ExpandResult<tt::Subtree> {
- 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<tt::Subtree> {
- 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<String> {
- 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<Option<ExpandedEager>> {
- 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<Option<ExpandedEager>> {
- 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<Option<ExpandedEager>> {
- 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<FileId, mbe::ExpandError> {
- 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<String, mbe::ExpandError> {
- 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<Option<ExpandedEager>> {
- 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<Option<ExpandedEager>> {
- 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<Option<ExpandedEager>> {
- 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<String> {
- 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<Option<ExpandedEager>> {
- 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<Option<ExpandedEager>> {
- 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)))
-}
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;
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,
};