Repository: rust-analyzer/rust-analyzer
Pull request #2360 (2019-11-22 17:48:41)

Refactor builtin macro
Refactor builtin macro and add some local tests.


Patch:
diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs
index 4da56529de..c0e0436c0a 100644
--- a/crates/ra_hir_expand/src/builtin_macro.rs
+++ b/crates/ra_hir_expand/src/builtin_macro.rs
@@ -8,47 +8,47 @@ use crate::{
 
 use crate::quote;
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum BuiltinExpander {
-    Column,
-    File,
-    Line,
-    Stringify,
-}
+macro_rules! register_builtin {
+    ( $(($name:ident, $kind: ident) => $expand:ident),* ) => {
+        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+        pub enum BuiltinFnLikeExpander {
+            $($kind),*
+        }
 
-impl BuiltinExpander {
-    pub fn expand(
-        &self,
-        db: &dyn AstDatabase,
-        id: MacroCallId,
-        tt: &tt::Subtree,
-    ) -> Result<tt::Subtree, mbe::ExpandError> {
-        match self {
-            BuiltinExpander::Column => column_expand(db, id, tt),
-            BuiltinExpander::File => file_expand(db, id, tt),
-            BuiltinExpander::Line => line_expand(db, id, tt),
-            BuiltinExpander::Stringify => stringify_expand(db, id, tt),
+        impl BuiltinFnLikeExpander {
+            pub fn expand(
+                &self,
+                db: &dyn AstDatabase,
+                id: MacroCallId,
+                tt: &tt::Subtree,
+            ) -> Result<tt::Subtree, mbe::ExpandError> {
+                let expander = match *self {
+                    $( BuiltinFnLikeExpander::$kind => $expand, )*
+                };
+                expander(db, id, tt)
+            }
         }
-    }
+
+        pub fn find_builtin_macro(
+            ident: &name::Name,
+            krate: CrateId,
+            ast_id: AstId<ast::MacroCall>,
+        ) -> Option<MacroDefId> {
+            let kind = match ident {
+                 $( id if id == &name::$name => BuiltinFnLikeExpander::$kind, )*
+                 _ => return None,
+            };
+
+            Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(kind) })
+        }
+    };
 }
 
-pub fn find_builtin_macro(
-    ident: &name::Name,
-    krate: CrateId,
-    ast_id: AstId<ast::MacroCall>,
-) -> Option<MacroDefId> {
-    // FIXME: Better registering method
-    if ident == &name::COLUMN_MACRO {
-        Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(BuiltinExpander::Column) })
-    } else if ident == &name::FILE_MACRO {
-        Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(BuiltinExpander::File) })
-    } else if ident == &name::LINE_MACRO {
-        Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(BuiltinExpander::Line) })
-    } else if ident == &name::STRINGIFY_MACRO {
-        Some(MacroDefId { krate, ast_id, kind: MacroDefKind::BuiltIn(BuiltinExpander::Stringify) })
-    } else {
-        None
-    }
+register_builtin! {
+    (COLUMN_MACRO, Column) => column_expand,
+    (FILE_MACRO, File) => file_expand,
+    (LINE_MACRO, Line) => line_expand,
+    (STRINGIFY_MACRO, Stringify) => stringify_expand
 }
 
 fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize {
@@ -171,3 +171,92 @@ fn file_expand(
 
     Ok(expanded)
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{test_db::TestDB, MacroCallLoc};
+    use ra_db::{fixture::WithFixture, SourceDatabase};
+
+    fn expand_builtin_macro(s: &str, expander: BuiltinFnLikeExpander) -> String {
+        let (db, file_id) = TestDB::with_single_file(&s);
+        let parsed = db.parse(file_id);
+        let macro_calls: Vec<_> =
+            parsed.syntax_node().descendants().filter_map(|it| ast::MacroCall::cast(it)).collect();
+
+        let ast_id_map = db.ast_id_map(file_id.into());
+
+        // the first one should be a macro_rules
+        let def = MacroDefId {
+            krate: CrateId(0),
+            ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(&macro_calls[0])),
+            kind: MacroDefKind::BuiltIn(expander),
+        };
+
+        let loc = MacroCallLoc {
+            def,
+            ast_id: AstId::new(file_id.into(), ast_id_map.ast_id(&macro_calls[1])),
+        };
+
+        let id = db.intern_macro(loc);
+        let parsed = db.parse_or_expand(id.as_file(MacroFileKind::Expr)).unwrap();
+
+        parsed.text().to_string()
+    }
+
+    #[test]
+    fn test_column_expand() {
+        let expanded = expand_builtin_macro(
+            r#"
+        #[rustc_builtin_macro]
+        macro_rules! column {() => {}}
+        column!()
+"#,
+            BuiltinFnLikeExpander::Column,
+        );
+
+        assert_eq!(expanded, "9");
+    }
+
+    #[test]
+    fn test_line_expand() {
+        let expanded = expand_builtin_macro(
+            r#"
+        #[rustc_builtin_macro]
+        macro_rules! line {() => {}}
+        line!()
+"#,
+            BuiltinFnLikeExpander::Line,
+        );
+
+        assert_eq!(expanded, "4");
+    }
+
+    #[test]
+    fn test_stringify_expand() {
+        let expanded = expand_builtin_macro(
+            r#"
+        #[rustc_builtin_macro]
+        macro_rules! stringify {() => {}}
+        stringify!(a b c)
+"#,
+            BuiltinFnLikeExpander::Stringify,
+        );
+
+        assert_eq!(expanded, "\"a b c\"");
+    }
+
+    #[test]
+    fn test_file_expand() {
+        let expanded = expand_builtin_macro(
+            r#"
+        #[rustc_builtin_macro]
+        macro_rules! file {() => {}}
+        file!()
+"#,
+            BuiltinFnLikeExpander::File,
+        );
+
+        assert_eq!(expanded, "\"\"");
+    }
+}
diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs
index e1d93a8ef5..8e46fa177d 100644
--- a/crates/ra_hir_expand/src/db.rs
+++ b/crates/ra_hir_expand/src/db.rs
@@ -9,14 +9,14 @@ use ra_prof::profile;
 use ra_syntax::{AstNode, Parse, SyntaxNode};
 
 use crate::{
-    ast_id_map::AstIdMap, BuiltinExpander, HirFileId, HirFileIdRepr, MacroCallId, MacroCallLoc,
-    MacroDefId, MacroDefKind, MacroFile, MacroFileKind,
+    ast_id_map::AstIdMap, BuiltinFnLikeExpander, HirFileId, HirFileIdRepr, MacroCallId,
+    MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, MacroFileKind,
 };
 
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub enum TokenExpander {
     MacroRules(mbe::MacroRules),
-    Builtin(BuiltinExpander),
+    Builtin(BuiltinFnLikeExpander),
 }
 
 impl TokenExpander {
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs
index 126d12fbbe..4f3ccf1d0d 100644
--- a/crates/ra_hir_expand/src/lib.rs
+++ b/crates/ra_hir_expand/src/lib.rs
@@ -24,7 +24,10 @@ use ra_syntax::{
 };
 
 use crate::ast_id_map::FileAstId;
-use crate::builtin_macro::BuiltinExpander;
+use crate::builtin_macro::BuiltinFnLikeExpander;
+
+#[cfg(test)]
+mod test_db;
 
 /// Input to the analyzer is a set of files, where each file is identified by
 /// `FileId` and contains source code. However, another source of source code in
@@ -135,7 +138,7 @@ pub struct MacroDefId {
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub enum MacroDefKind {
     Declarative,
-    BuiltIn(BuiltinExpander),
+    BuiltIn(BuiltinFnLikeExpander),
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]


Test patch:
diff --git a/crates/ra_hir_expand/src/test_db.rs b/crates/ra_hir_expand/src/test_db.rs
new file mode 100644
index 0000000000..d23e75d9ee
--- /dev/null
+++ b/crates/ra_hir_expand/src/test_db.rs
@@ -0,0 +1,50 @@
+//! Database used for testing `hir_expand`.
+
+use std::{
+    panic,
+    sync::{Arc, Mutex},
+};
+
+use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath};
+
+#[salsa::database(
+    ra_db::SourceDatabaseExtStorage,
+    ra_db::SourceDatabaseStorage,
+    crate::db::AstDatabaseStorage
+)]
+#[derive(Debug, Default)]
+pub struct TestDB {
+    runtime: salsa::Runtime<TestDB>,
+    events: Mutex<Option<Vec<salsa::Event<TestDB>>>>,
+}
+
+impl salsa::Database for TestDB {
+    fn salsa_runtime(&self) -> &salsa::Runtime<Self> {
+        &self.runtime
+    }
+
+    fn salsa_event(&self, event: impl Fn() -> salsa::Event<TestDB>) {
+        let mut events = self.events.lock().unwrap();
+        if let Some(events) = &mut *events {
+            events.push(event());
+        }
+    }
+}
+
+impl panic::RefUnwindSafe for TestDB {}
+
+impl FileLoader for TestDB {
+    fn file_text(&self, file_id: FileId) -> Arc<String> {
+        FileLoaderDelegate(self).file_text(file_id)
+    }
+    fn resolve_relative_path(
+        &self,
+        anchor: FileId,
+        relative_path: &RelativePath,
+    ) -> Option<FileId> {
+        FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path)
+    }
+    fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> {
+        FileLoaderDelegate(self).relevant_crates(file_id)
+    }
+}