@@ -1328,6 +1328,13 @@ export class Compiler {
13281328 const patternsByRule = new Map < string , Map < string , Expr [ ] > > ( ) ;
13291329 const refCounts = new Map ( ) ;
13301330
1331+ // Track the recursive specialization depth per base rule name.
1332+ // If specializing a rule leads to specializing the *same* rule again
1333+ // beyond this depth, the parameters are expanding without bound
1334+ // (e.g. `grow<e> = e | grow<(e | "x")>`).
1335+ const MAX_SPECIALIZATION_DEPTH = 32 ;
1336+ const specializationDepth = new Map < string , number > ( ) ;
1337+
13311338 const specialize = ( exp : Expr ) : Expr =>
13321339 ir . rewrite ( exp , {
13331340 Apply : app => {
@@ -1339,6 +1346,17 @@ export class Compiler {
13391346 // If not yet seen, recursively visit the body of the specialized
13401347 // rule. Note that this also applies to non-parameterized rules!
13411348 if ( ! newRules . has ( specializedName ) ) {
1349+ const prevDepth = specializationDepth . get ( ruleName ) ?? 0 ;
1350+ if ( children . length > 0 ) {
1351+ if ( prevDepth >= MAX_SPECIALIZATION_DEPTH ) {
1352+ throw new Error (
1353+ `Excessively deep specialization of rule '${ ruleName } ' (>${ MAX_SPECIALIZATION_DEPTH } levels). ` +
1354+ 'This usually means its parameters grow on each recursive call, ' +
1355+ 'producing an infinite number of specialized rules.'
1356+ ) ;
1357+ }
1358+ specializationDepth . set ( ruleName , prevDepth + 1 ) ;
1359+ }
13421360 newRules . set ( specializedName , { } as RuleInfo ) ; // Prevent infinite recursion.
13431361
13441362 // Visit the body with the parameter substituted, to ensure we
@@ -1347,6 +1365,9 @@ export class Compiler {
13471365 ir . substituteParams ( ruleInfo . body , children as Exclude < Expr , ir . Param > [ ] )
13481366 ) ;
13491367
1368+ // Restore the depth after the recursive visit.
1369+ specializationDepth . set ( ruleName , prevDepth ) ;
1370+
13501371 // If there are any args, replace the body with an application of
13511372 // the generalized rule.
13521373 if ( children . length > 0 ) {
0 commit comments