Skip to content

Commit 0bee7cb

Browse files
Merge #9453
9453: Add first-class limits. r=matklad,lnicola a=rbartlensky Partially fixes #9286. This introduces a new `Limits` structure which is passed as an input to `SourceDatabase`. This makes limits accessible almost everywhere in the code, since most places have a database in scope. One downside of this approach is that whenever you query limits, you essentially do an `Arc::clone` which is less than ideal. Let me know if I missed anything, or would like me to take a different approach! Co-authored-by: Robert Bartlensky <[email protected]>
2 parents 33748a6 + 0b3d0cd commit 0bee7cb

File tree

16 files changed

+84
-24
lines changed

16 files changed

+84
-24
lines changed

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/hir_def/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ hir_expand = { path = "../hir_expand", version = "0.0.0" }
3131
mbe = { path = "../mbe", version = "0.0.0" }
3232
cfg = { path = "../cfg", version = "0.0.0" }
3333
tt = { path = "../tt", version = "0.0.0" }
34+
limit = { path = "../limit", version = "0.0.0" }
3435

3536
[dev-dependencies]
3637
test_utils = { path = "../test_utils" }

crates/hir_def/src/body.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use hir_expand::{
1515
ast_id_map::AstIdMap, hygiene::Hygiene, AstId, ExpandResult, HirFileId, InFile, MacroDefId,
1616
};
1717
use la_arena::{Arena, ArenaMap};
18+
use limit::Limit;
1819
use profile::Count;
1920
use rustc_hash::FxHashMap;
2021
use syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
@@ -53,10 +54,10 @@ pub struct Expander {
5354
}
5455

5556
#[cfg(test)]
56-
const EXPANSION_RECURSION_LIMIT: usize = 32;
57+
const EXPANSION_RECURSION_LIMIT: Limit = Limit::new(32);
5758

5859
#[cfg(not(test))]
59-
const EXPANSION_RECURSION_LIMIT: usize = 128;
60+
const EXPANSION_RECURSION_LIMIT: Limit = Limit::new(128);
6061

6162
impl CfgExpander {
6263
pub(crate) fn new(
@@ -99,7 +100,7 @@ impl Expander {
99100
db: &dyn DefDatabase,
100101
macro_call: ast::MacroCall,
101102
) -> Result<ExpandResult<Option<(Mark, T)>>, UnresolvedMacro> {
102-
if self.recursion_limit + 1 > EXPANSION_RECURSION_LIMIT {
103+
if EXPANSION_RECURSION_LIMIT.check(self.recursion_limit + 1).is_err() {
103104
cov_mark::hit!(your_stack_belongs_to_me);
104105
return Ok(ExpandResult::str_err(
105106
"reached recursion limit during macro expansion".into(),

crates/hir_def/src/nameres/collector.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use hir_expand::{
1919
use hir_expand::{InFile, MacroCallLoc};
2020
use itertools::Itertools;
2121
use la_arena::Idx;
22+
use limit::Limit;
2223
use rustc_hash::{FxHashMap, FxHashSet};
2324
use syntax::ast;
2425

@@ -49,9 +50,9 @@ use crate::{
4950
UnresolvedMacro,
5051
};
5152

52-
const GLOB_RECURSION_LIMIT: usize = 100;
53-
const EXPANSION_DEPTH_LIMIT: usize = 128;
54-
const FIXED_POINT_LIMIT: usize = 8192;
53+
const GLOB_RECURSION_LIMIT: Limit = Limit::new(100);
54+
const EXPANSION_DEPTH_LIMIT: Limit = Limit::new(128);
55+
const FIXED_POINT_LIMIT: Limit = Limit::new(8192);
5556

5657
pub(super) fn collect_defs(
5758
db: &dyn DefDatabase,
@@ -356,7 +357,7 @@ impl DefCollector<'_> {
356357
}
357358

358359
i += 1;
359-
if i == FIXED_POINT_LIMIT {
360+
if FIXED_POINT_LIMIT.check(i).is_err() {
360361
log::error!("name resolution is stuck");
361362
break 'outer;
362363
}
@@ -925,7 +926,7 @@ impl DefCollector<'_> {
925926
import_type: ImportType,
926927
depth: usize,
927928
) {
928-
if depth > GLOB_RECURSION_LIMIT {
929+
if GLOB_RECURSION_LIMIT.check(depth).is_err() {
929930
// prevent stack overflows (but this shouldn't be possible)
930931
panic!("infinite recursion in glob imports!");
931932
}
@@ -1158,7 +1159,7 @@ impl DefCollector<'_> {
11581159
macro_call_id: MacroCallId,
11591160
depth: usize,
11601161
) {
1161-
if depth > EXPANSION_DEPTH_LIMIT {
1162+
if EXPANSION_DEPTH_LIMIT.check(depth).is_err() {
11621163
cov_mark::hit!(macro_expansion_overflow);
11631164
log::warn!("macro expansion is too deep");
11641165
return;

crates/hir_def/src/nameres/mod_resolution.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
//! This module resolves `mod foo;` declaration to file.
22
use base_db::{AnchoredPath, FileId};
33
use hir_expand::name::Name;
4+
use limit::Limit;
45
use syntax::SmolStr;
56

67
use crate::{db::DefDatabase, HirFileId};
78

8-
const MOD_DEPTH_LIMIT: u32 = 32;
9+
const MOD_DEPTH_LIMIT: Limit = Limit::new(32);
910

1011
#[derive(Clone, Debug)]
1112
pub(super) struct ModDir {
@@ -25,7 +26,7 @@ impl ModDir {
2526
}
2627
fn child(&self, dir_path: DirPath, root_non_dir_owner: bool) -> Option<ModDir> {
2728
let depth = self.depth + 1;
28-
if depth > MOD_DEPTH_LIMIT {
29+
if MOD_DEPTH_LIMIT.check(depth as usize).is_err() {
2930
log::error!("MOD_DEPTH_LIMIT exceeded");
3031
cov_mark::hit!(circular_mods);
3132
return None;

crates/hir_expand/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ parser = { path = "../parser", version = "0.0.0" }
2121
profile = { path = "../profile", version = "0.0.0" }
2222
tt = { path = "../tt", version = "0.0.0" }
2323
mbe = { path = "../mbe", version = "0.0.0" }
24+
limit = { path = "../limit", version = "0.0.0" }
2425

2526
[dev-dependencies]
2627
test_utils = { path = "../test_utils" }

crates/hir_expand/src/db.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::sync::Arc;
44

55
use base_db::{salsa, SourceDatabase};
6+
use limit::Limit;
67
use mbe::{ExpandError, ExpandResult};
78
use parser::FragmentKind;
89
use syntax::{
@@ -21,7 +22,7 @@ use crate::{
2122
///
2223
/// If an invocation produces more tokens than this limit, it will not be stored in the database and
2324
/// an error will be emitted.
24-
const TOKEN_LIMIT: usize = 524288;
25+
const TOKEN_LIMIT: Limit = Limit::new(524288);
2526

2627
#[derive(Debug, Clone, Eq, PartialEq)]
2728
pub enum TokenExpander {
@@ -356,10 +357,12 @@ fn macro_expand_with_arg(
356357
let ExpandResult { value: tt, err } = macro_rules.expand(db, id, &macro_arg.0);
357358
// Set a hard limit for the expanded tt
358359
let count = tt.count();
359-
if count > TOKEN_LIMIT {
360+
// XXX: Make ExpandResult a real error and use .map_err instead?
361+
if TOKEN_LIMIT.check(count).is_err() {
360362
return ExpandResult::str_err(format!(
361363
"macro invocation exceeds token limit: produced {} tokens, limit is {}",
362-
count, TOKEN_LIMIT,
364+
count,
365+
TOKEN_LIMIT.inner(),
363366
));
364367
}
365368

crates/hir_ty/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ hir_expand = { path = "../hir_expand", version = "0.0.0" }
2929
base_db = { path = "../base_db", version = "0.0.0" }
3030
profile = { path = "../profile", version = "0.0.0" }
3131
syntax = { path = "../syntax", version = "0.0.0" }
32+
limit = { path = "../limit", version = "0.0.0" }
3233

3334
[dev-dependencies]
3435
test_utils = { path = "../test_utils" }

crates/hir_ty/src/autoderef.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use base_db::CrateId;
99
use chalk_ir::{cast::Cast, fold::Fold, interner::HasInterner, VariableKind};
1010
use hir_def::lang_item::LangItemTarget;
1111
use hir_expand::name::name;
12+
use limit::Limit;
1213
use log::{info, warn};
1314

1415
use crate::{
@@ -17,6 +18,8 @@ use crate::{
1718
Ty, TyBuilder, TyKind,
1819
};
1920

21+
const AUTODEREF_RECURSION_LIMIT: Limit = Limit::new(10);
22+
2023
pub(crate) enum AutoderefKind {
2124
Builtin,
2225
Overloaded,
@@ -63,7 +66,7 @@ impl Iterator for Autoderef<'_> {
6366
return Some((self.ty.clone(), 0));
6467
}
6568

66-
if self.steps.len() >= AUTODEREF_RECURSION_LIMIT {
69+
if AUTODEREF_RECURSION_LIMIT.check(self.steps.len() + 1).is_err() {
6770
return None;
6871
}
6972

@@ -87,8 +90,6 @@ impl Iterator for Autoderef<'_> {
8790
}
8891
}
8992

90-
const AUTODEREF_RECURSION_LIMIT: usize = 10;
91-
9293
// FIXME: replace uses of this with Autoderef above
9394
pub fn autoderef<'a>(
9495
db: &'a dyn HirDatabase,
@@ -99,7 +100,7 @@ pub fn autoderef<'a>(
99100
successors(Some(ty), move |ty| {
100101
deref(db, krate?, InEnvironment { goal: ty, environment: environment.clone() })
101102
})
102-
.take(AUTODEREF_RECURSION_LIMIT)
103+
.take(AUTODEREF_RECURSION_LIMIT.inner())
103104
}
104105

105106
pub(crate) fn deref(

crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ pub(crate) fn replace_derive_with_manual_impl(
6565
current_crate,
6666
NameToImport::Exact(trait_name.to_string()),
6767
items_locator::AssocItemSearch::Exclude,
68-
Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT),
68+
Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()),
6969
)
7070
.filter_map(|item| match ModuleDef::from(item.as_module_def_id()?) {
7171
ModuleDef::Trait(trait_) => Some(trait_),

crates/ide_completion/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ pub fn resolve_completion_edits(
187187
current_crate,
188188
NameToImport::Exact(imported_name),
189189
items_locator::AssocItemSearch::Include,
190-
Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT),
190+
Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()),
191191
)
192192
.filter_map(|candidate| {
193193
current_module

crates/ide_db/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ profile = { path = "../profile", version = "0.0.0" }
2626
# ide should depend only on the top-level `hir` package. if you need
2727
# something from some `hir_xxx` subpackage, reexport the API via `hir`.
2828
hir = { path = "../hir", version = "0.0.0" }
29+
limit = { path = "../limit", version = "0.0.0" }
2930

3031
[dev-dependencies]
3132
test_utils = { path = "../test_utils" }

crates/ide_db/src/helpers/import_assets.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ fn path_applicable_imports(
282282
//
283283
// see also an ignored test under FIXME comment in the qualify_path.rs module
284284
AssocItemSearch::Exclude,
285-
Some(DEFAULT_QUERY_SEARCH_LIMIT),
285+
Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
286286
)
287287
.filter_map(|item| {
288288
let mod_path = mod_path(item)?;
@@ -299,7 +299,7 @@ fn path_applicable_imports(
299299
current_crate,
300300
path_candidate.name.clone(),
301301
AssocItemSearch::Include,
302-
Some(DEFAULT_QUERY_SEARCH_LIMIT),
302+
Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
303303
)
304304
.filter_map(|item| {
305305
import_for_item(
@@ -445,7 +445,7 @@ fn trait_applicable_items(
445445
current_crate,
446446
trait_candidate.assoc_item_name.clone(),
447447
AssocItemSearch::AssocItemsOnly,
448-
Some(DEFAULT_QUERY_SEARCH_LIMIT),
448+
Some(DEFAULT_QUERY_SEARCH_LIMIT.inner()),
449449
)
450450
.filter_map(|input| item_as_assoc(db, input))
451451
.filter_map(|assoc| {

crates/ide_db/src/items_locator.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use hir::{
77
import_map::{self, ImportKind},
88
AsAssocItem, Crate, ItemInNs, ModuleDef, Semantics,
99
};
10+
use limit::Limit;
1011
use syntax::{ast, AstNode, SyntaxKind::NAME};
1112

1213
use crate::{
@@ -17,7 +18,7 @@ use crate::{
1718
};
1819

1920
/// A value to use, when uncertain which limit to pick.
20-
pub const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40;
21+
pub const DEFAULT_QUERY_SEARCH_LIMIT: Limit = Limit::new(40);
2122

2223
/// Three possible ways to search for the name in associated and/or other items.
2324
#[derive(Debug, Clone, Copy)]

crates/limit/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "limit"
3+
version = "0.0.0"
4+
description = "TBD"
5+
license = "MIT OR Apache-2.0"
6+
authors = ["rust-analyzer developers"]
7+
edition = "2018"
8+
9+
[dependencies]

crates/limit/src/lib.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//! limit defines a struct to enforce limits.
2+
3+
/// Represents a struct used to enforce a numerical limit.
4+
pub struct Limit {
5+
upper_bound: usize,
6+
}
7+
8+
impl Limit {
9+
/// Creates a new limit.
10+
#[inline]
11+
pub const fn new(upper_bound: usize) -> Self {
12+
Self { upper_bound }
13+
}
14+
15+
/// Gets the underlying numeric limit.
16+
#[inline]
17+
pub const fn inner(&self) -> usize {
18+
self.upper_bound
19+
}
20+
21+
/// Checks whether the given value is below the limit.
22+
/// Returns `Ok` when `other` is below `self`, and `Err` otherwise.
23+
#[inline]
24+
pub const fn check(&self, other: usize) -> Result<(), ()> {
25+
if other > self.upper_bound {
26+
Err(())
27+
} else {
28+
Ok(())
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)