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,