From c932ca5f1cf62d041c943e5cfdcc1767c0664692 Mon Sep 17 00:00:00 2001
From: Maybe Waffle <waffle.lapkin@gmail.com>
Date: Thu, 27 Jan 2022 23:34:33 +0300
Subject: [PATCH 1/3] Add `crate_limits` query to `SourceDatabase`

This allows fetching crate limits like `recursion_limit`. The
implementation is currently dummy and just returns the defaults.

Future work: Use this query instead of the hardcoded constant.

Future work: Actually implement this query by parsing
`#![recursion_limit = N]` attribute.
---
 crates/base_db/src/lib.rs | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/crates/base_db/src/lib.rs b/crates/base_db/src/lib.rs
index d80660f7c9a..a4c590cf6d9 100644
--- a/crates/base_db/src/lib.rs
+++ b/crates/base_db/src/lib.rs
@@ -69,6 +69,21 @@ pub trait SourceDatabase: FileLoader + std::fmt::Debug {
     /// The crate graph.
     #[salsa::input]
     fn crate_graph(&self) -> Arc<CrateGraph>;
+
+    #[salsa::transparent]
+    fn crate_limits(&self, crate_id: CrateId) -> CrateLimits;
+}
+
+pub struct CrateLimits {
+    /// The maximum depth for potentially infinitely-recursive compile-time operations like macro expansion or auto-dereference.
+    pub recursion_limit: u32,
+}
+
+fn crate_limits(_db: &dyn SourceDatabase, _crate_id: CrateId) -> CrateLimits {
+    CrateLimits {
+        // 128 is the default in rustc.
+        recursion_limit: 128,
+    }
 }
 
 fn parse_query(db: &dyn SourceDatabase, file_id: FileId) -> Parse<ast::SourceFile> {

From 81211f538ccfbcd1cfb83dc0ad29c7577ad74fa1 Mon Sep 17 00:00:00 2001
From: Maybe Waffle <waffle.lapkin@gmail.com>
Date: Thu, 27 Jan 2022 23:53:49 +0300
Subject: [PATCH 2/3] Use `crate_limits` query in macro expansion

---
 crates/hir_def/src/body.rs | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs
index 505b07cc8ac..8488b4f0d03 100644
--- a/crates/hir_def/src/body.rs
+++ b/crates/hir_def/src/body.rs
@@ -54,12 +54,6 @@ pub struct Expander {
     recursion_limit: usize,
 }
 
-#[cfg(test)]
-static EXPANSION_RECURSION_LIMIT: Limit = Limit::new(32);
-
-#[cfg(not(test))]
-static EXPANSION_RECURSION_LIMIT: Limit = Limit::new(128);
-
 impl CfgExpander {
     pub(crate) fn new(
         db: &dyn DefDatabase,
@@ -101,7 +95,7 @@ impl Expander {
         db: &dyn DefDatabase,
         macro_call: ast::MacroCall,
     ) -> Result<ExpandResult<Option<(Mark, T)>>, UnresolvedMacro> {
-        if EXPANSION_RECURSION_LIMIT.check(self.recursion_limit + 1).is_err() {
+        if self.recursion_limit(db).check(self.recursion_limit + 1).is_err() {
             cov_mark::hit!(your_stack_belongs_to_me);
             return Ok(ExpandResult::str_err(
                 "reached recursion limit during macro expansion".into(),
@@ -222,6 +216,17 @@ impl Expander {
         let file_local_id = self.ast_id_map.ast_id(item);
         AstId::new(self.current_file_id, file_local_id)
     }
+
+    fn recursion_limit(&self, db: &dyn DefDatabase) -> Limit {
+        let limit = db.crate_limits(self.cfg_expander.krate).recursion_limit as _;
+
+        #[cfg(not(test))]
+        return Limit::new(limit);
+
+        // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug
+        #[cfg(test)]
+        return Limit::new(std::cmp::min(32, limit));
+    }
 }
 
 #[derive(Debug)]

From 6d18c5b69d9751bdee8feaa90b095247be350d61 Mon Sep 17 00:00:00 2001
From: Maybe Waffle <waffle.lapkin@gmail.com>
Date: Fri, 28 Jan 2022 01:23:09 +0300
Subject: [PATCH 3/3] feat: Honor recursion limit configuration

This patch makes RA understand `#![recursion_limit = "N"]` annotations.

- `crate_limits` query is moved to `DefDatabase`
- `DefMap` now has `recursion_limit: Option<u32>` field
---
 crates/base_db/src/lib.rs               | 15 ---------------
 crates/hir_def/src/body/tests.rs        | 18 ++++++++++++++++++
 crates/hir_def/src/db.rs                | 17 +++++++++++++++++
 crates/hir_def/src/nameres.rs           |  7 +++++++
 crates/hir_def/src/nameres/collector.rs | 11 +++++++++++
 crates/hir_expand/src/name.rs           |  1 +
 6 files changed, 54 insertions(+), 15 deletions(-)

diff --git a/crates/base_db/src/lib.rs b/crates/base_db/src/lib.rs
index a4c590cf6d9..d80660f7c9a 100644
--- a/crates/base_db/src/lib.rs
+++ b/crates/base_db/src/lib.rs
@@ -69,21 +69,6 @@ pub trait SourceDatabase: FileLoader + std::fmt::Debug {
     /// The crate graph.
     #[salsa::input]
     fn crate_graph(&self) -> Arc<CrateGraph>;
-
-    #[salsa::transparent]
-    fn crate_limits(&self, crate_id: CrateId) -> CrateLimits;
-}
-
-pub struct CrateLimits {
-    /// The maximum depth for potentially infinitely-recursive compile-time operations like macro expansion or auto-dereference.
-    pub recursion_limit: u32,
-}
-
-fn crate_limits(_db: &dyn SourceDatabase, _crate_id: CrateId) -> CrateLimits {
-    CrateLimits {
-        // 128 is the default in rustc.
-        recursion_limit: 128,
-    }
 }
 
 fn parse_query(db: &dyn SourceDatabase, file_id: FileId) -> Parse<ast::SourceFile> {
diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs
index 673a7538616..1d84da48fb9 100644
--- a/crates/hir_def/src/body/tests.rs
+++ b/crates/hir_def/src/body/tests.rs
@@ -61,6 +61,24 @@ fn main() { n_nuple!(1,2,3); }
     );
 }
 
+#[test]
+fn recursion_limit() {
+    cov_mark::check!(your_stack_belongs_to_me);
+
+    lower(
+        r#"
+#![recursion_limit = "2"]
+macro_rules! n_nuple {
+    ($e:tt) => ();
+    ($first:tt $($rest:tt)*) => {{
+        n_nuple!($($rest)*)
+    }};
+}
+fn main() { n_nuple!(1,2,3); }
+"#,
+    );
+}
+
 #[test]
 fn macro_resolve() {
     // Regression test for a path resolution bug introduced with inner item handling.
diff --git a/crates/hir_def/src/db.rs b/crates/hir_def/src/db.rs
index 7946558311a..f9dd935c4b0 100644
--- a/crates/hir_def/src/db.rs
+++ b/crates/hir_def/src/db.rs
@@ -157,9 +157,26 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
 
     #[salsa::invoke(visibility::function_visibility_query)]
     fn function_visibility(&self, def: FunctionId) -> Visibility;
+
+    #[salsa::transparent]
+    fn crate_limits(&self, crate_id: CrateId) -> CrateLimits;
 }
 
 fn crate_def_map_wait(db: &dyn DefDatabase, krate: CrateId) -> Arc<DefMap> {
     let _p = profile::span("crate_def_map:wait");
     db.crate_def_map_query(krate)
 }
+
+pub struct CrateLimits {
+    /// The maximum depth for potentially infinitely-recursive compile-time operations like macro expansion or auto-dereference.
+    pub recursion_limit: u32,
+}
+
+fn crate_limits(db: &dyn DefDatabase, crate_id: CrateId) -> CrateLimits {
+    let def_map = db.crate_def_map(crate_id);
+
+    CrateLimits {
+        // 128 is the default in rustc.
+        recursion_limit: def_map.recursion_limit().unwrap_or(128),
+    }
+}
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index 64929c02b40..ca4255c5819 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -114,6 +114,7 @@ pub struct DefMap {
     registered_tools: Vec<SmolStr>,
 
     edition: Edition,
+    recursion_limit: Option<u32>,
     diagnostics: Vec<DefDiagnostic>,
 }
 
@@ -272,6 +273,7 @@ impl DefMap {
             block: None,
             krate,
             edition,
+            recursion_limit: None,
             extern_prelude: FxHashMap::default(),
             exported_proc_macros: FxHashMap::default(),
             prelude: None,
@@ -461,6 +463,7 @@ impl DefMap {
             registered_tools,
             block: _,
             edition: _,
+            recursion_limit: _,
             krate: _,
             prelude: _,
             root: _,
@@ -482,6 +485,10 @@ impl DefMap {
     pub fn diagnostics(&self) -> &[DefDiagnostic] {
         self.diagnostics.as_slice()
     }
+
+    pub fn recursion_limit(&self) -> Option<u32> {
+        self.recursion_limit
+    }
 }
 
 impl ModuleData {
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index dc75dc4d91a..7f7213f4c3e 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -271,6 +271,17 @@ impl DefCollector<'_> {
                     None => continue,
                 };
 
+                if *attr_name == hir_expand::name![recursion_limit] {
+                    if let Some(input) = &attr.input {
+                        if let AttrInput::Literal(limit) = &**input {
+                            if let Ok(limit) = limit.parse() {
+                                self.def_map.recursion_limit = Some(limit);
+                            }
+                        }
+                    }
+                    continue;
+                }
+
                 let attr_is_register_like = *attr_name == hir_expand::name![register_attr]
                     || *attr_name == hir_expand::name![register_tool];
                 if !attr_is_register_like {
diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs
index 4dcda0fcdc8..c36bd09e2b3 100644
--- a/crates/hir_expand/src/name.rs
+++ b/crates/hir_expand/src/name.rs
@@ -270,6 +270,7 @@ pub mod known {
         global_allocator,
         test,
         test_case,
+        recursion_limit,
         // Safe intrinsics
         abort,
         add_with_overflow,