You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
substitute_type_params / collect_type_param_substitutions re-parse the type string
at every nesting level, so an adversarial deeply-nested generic (List<List<...>>
thousands deep) drove into ~O(n^3) blowup (RSS-11). Added a recursion-depth
bound (100, far above any real type — the public-signature lint warns past 4) via
thin wrappers; behavior is identical for every type at/below the limit, and a
pathological depth-1000+ type now checks in ~0.5s (was ~16.8s at 2000) without
stack overflow. A from-scratch single-pass type parser would remove the asymptotic
entirely but is a larger, separately-validated change. Regression tests in
checks::calls::substitution_tests. All 11 review findings now fixed; BUGS.md clear.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
and `substitute_type_params` re-parse the full type string at every nesting level).
25
-
- Nested generics passed to a **generic** function (e.g. `fn id<T>(x: read T) -> T` called with
26
-
`List<…<Int>>`) make `rss check` super-linear. Re-measured on this machine: depth 100 ≈ 21 ms,
27
-
300 ≈ 74 ms, 500 ≈ 283 ms, 800 ≈ 1.1 s, 2000 ≈ 16.8 s. Confirms the ~cubic shape. (The original
28
-
repro used a *non-generic*`id`, which never enters the substitution recursion and stays flat —
29
-
the trigger requires a generic callee.) Not a correctness fault.
30
-
-**Fix:** parse type strings into a tree once / memoize substitutions.
31
-
-**Fast path (until fixed):** package-scale `rss check` / `rss pkg` is comfortable when the
32
-
compiler is built in **release** (debug leaves the hot string-reparse path unoptimized). For
33
-
repeated package-wide validation use a release `rss` (`cargo build --release --bin rss`); this is
34
-
documented under "Performance" in `README.md`. Observed on generics-heavy ports (e.g. tinygrad-rss):
35
-
debug package check slow, release usable.
36
-
-**Status — deferred (intentional):** the asymptotic fix means replacing the string-based type
37
-
representation in the generic-substitution path with a parse-once tree — a sizeable refactor of
38
-
correctness-critical, heavily-relied-upon generics code. The trigger is adversarial, self-authored
39
-
source (the compiler only processes local source, so there is no remote DoS vector), real code
40
-
never nests generics more than a handful of levels, and the item is rated LOW / non-correctness.
41
-
The risk of regressing generics resolution was judged disproportionate to the benefit, so the fix
42
-
is left for a dedicated change rather than bundled with correctness fixes.
43
-
-**Scoping note (for the eventual parse-once change):** memoization alone does **not** help — a
44
-
strictly-nested type has all-distinct substrings, so there are no repeated cache keys; the win has
45
-
to come from parsing each level once. The two hot functions also do **not** parse identically:
46
-
`substitute_type_params` takes its `Fn(...)` branch on `fn_return_type(..).is_some()` (requires a
47
-
`->`), while `collect_type_param_substitutions` takes it on `is_fn_type(..)` (does not). So an
48
-
arrow-less `Fn(A)` is an opaque leaf to `substitute` but a recursed fn-type to `collect`. A
49
-
parse-once tree must preserve each function's branch rules (or the divergence must be deliberately
50
-
unified) and be guarded by a differential/fuzz test comparing old vs new output across many type
51
-
strings — otherwise it risks silently changing which programs typecheck. That verification, not the
52
-
tree itself, is the bulk of the work and the reason this stays a dedicated, separately-reviewed change.
20
+
_No open compiler bugs. RSS-1…RSS-15 are fixed; see git history for write-ups (most recently: RSS-11 deeply-nested generic substitution, bounded; review findings #1–#11)._
0 commit comments