Skip to content

Commit f580fb8

Browse files
docs(checker): reframe MAX_EXPR_DEPTH as a stack-recursion bound (closes #14) (#36)
The #14 investigation conclusively showed check_expr / is_assignable_from are linear in AST size on Linux and on the Windows CI leg, so the depth guard is NOT a defence against runaway allocation. Reword its doc-comment and the related inline comments to state what it actually does -- bound stack recursion -- and point at the investigation record. No behaviour change. Residual subtleties (recursive AST Drop; owner-dependent exact repro + Windows ETW deep-dive; re-tuning the 256 value) are carved into separate tracking issues so #14 can close on its answered question. Closes #14 https://claude.ai/code/session_016X6sPXqrpMxHRMuy46BjeS Co-authored-by: Claude <noreply@anthropic.com>
1 parent aff9d32 commit f580fb8

1 file changed

Lines changed: 15 additions & 8 deletions

File tree

crates/my-lang/src/checker.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,16 @@ pub enum CheckError {
113113
/// emitting [`CheckError::ExpressionTooDeep`] and bailing out of the
114114
/// subexpression.
115115
///
116-
/// This is a defence against pathological inputs (deeply chained
117-
/// `str_concat`/`format`, parser-generated unbalanced trees) that could
118-
/// otherwise drive the checker into runaway memory allocation. See
119-
/// hyperpolymath/my-lang#1.
116+
/// This bounds **stack recursion**, not heap growth. The #14 investigation
117+
/// (see `docs/wiki/internals/checker-allocation-investigation.md`) measured
118+
/// `check_expr` / `is_assignable_from` to be *linear* in AST size on Linux
119+
/// and on a Windows CI leg — there is no super-linear allocation for the
120+
/// guard to defend against. What deep nesting *does* threaten is the
121+
/// recursive descent through `check_expr` (and the recursive `Drop` of the
122+
/// resulting AST). The original #1 OOM was shown not to be checker
123+
/// algorithmic complexity; this limit therefore exists to keep recursion
124+
/// depth finite, and rejecting deep-but-legal programs at 256 is its cost,
125+
/// not a memory safeguard. Re-tuning the value is tracked separately.
120126
pub const MAX_EXPR_DEPTH: usize = 256;
121127

122128
pub type CheckResult<T> = Result<T, CheckError>;
@@ -128,8 +134,8 @@ pub struct Checker {
128134
errors: Vec<CheckError>,
129135
/// Current function's return type (for checking return statements)
130136
current_return_type: Option<Ty>,
131-
/// Current expression-recursion depth (guards against OOM on pathological
132-
/// nesting, see [`MAX_EXPR_DEPTH`]).
137+
/// Current expression-recursion depth (bounds stack recursion on
138+
/// pathological nesting, see [`MAX_EXPR_DEPTH`]).
133139
expr_depth: usize,
134140
/// Set once an [`CheckError::ExpressionTooDeep`] has been reported, to
135141
/// avoid spamming a duplicate error for every parent expression on the
@@ -841,8 +847,9 @@ impl Checker {
841847

842848
fn check_expr_guarded(&mut self, expr: &Expr) -> Ty {
843849
// Depth guard: stops pathological inputs (e.g. deeply chained
844-
// str_concat/format) from driving the checker into runaway memory
845-
// allocation or stack overflow. Reports a single ExpressionTooDeep
850+
// str_concat/format) from driving the checker into a stack overflow
851+
// via unbounded recursion (allocation is linear — see #14).
852+
// Reports a single ExpressionTooDeep
846853
// error for the whole offending subtree and returns Ty::Error so the
847854
// surrounding code keeps type-checking in error-recovery mode.
848855
if self.expr_depth >= MAX_EXPR_DEPTH {

0 commit comments

Comments
 (0)