Skip to main content

submilli_engine/
tree_height.rs

1//! Structural height limits for syntax and typed trees.
2//!
3//! The parser's recursion budget does not bound tree height: operator, postfix
4//! and statement-level chains are parsed iteratively, and inference lowers flat
5//! syntax such as template literals into nested operations. Every later compiler
6//! walk recurses over these trees, so their height is measured iteratively and
7//! rejected before a recursive phase can consume them.
8
9use crate::ast::{
10    ArrowBody, ChainPart, ClassMember, ExprKind, InterfaceMember, ParamDecl, StmtKind,
11    TypeAnnotation, TypeAnnotationKind,
12};
13use crate::compiler_error::{CompilerFailure, CompilerStage};
14use crate::typed_ast::{
15    ClosureBody, PostfixTarget, TypedChainPart, TypedExprKind, TypedObjectFieldSource,
16    TypedStmtKind,
17};
18use std::ops::Range;
19
20use crate::{Ast, ExprId, Span, StmtId, TypedAst};
21
22/// Height of parsed syntax, counting expressions, statements and type annotations.
23pub const MAX_SYNTAX_HEIGHT: u32 = 256;
24
25/// Height of the typed tree after inference and lowering, which may add wrapper
26/// nodes and expand flat syntax such as template literal substitutions.
27pub const MAX_TYPED_HEIGHT: u32 = 1024;
28
29/// Rejects parsed syntax whose recursive walks could exhaust the compiler stack.
30pub fn check_syntax(ast: &Ast) -> Result<(), CompilerFailure> {
31    measure(
32        &SyntaxTree(ast),
33        MAX_SYNTAX_HEIGHT,
34        CompilerStage::Parse,
35        |span| CompilerFailure::Limit {
36            stage: CompilerStage::Parse,
37            span: Some(span),
38            message: format!(
39                "syntax nesting exceeds the compiler limit of {MAX_SYNTAX_HEIGHT} levels"
40            ),
41            help: vec![
42                "split long operator chains or deeply nested code into intermediate declarations"
43                    .into(),
44            ],
45        },
46    )
47}
48
49/// Rejects a typed tree whose recursive walks could exhaust the compiler stack.
50/// This checks arena edges and owned expression/statement nesting. Embedded
51/// type payloads still require [`TypedAst`]'s phase input contract; success here
52/// does not certify an arbitrary hand-built tree for later compiler phases.
53pub fn check_typed(ta: &TypedAst, stage: CompilerStage) -> Result<(), CompilerFailure> {
54    measure(&TypedTree(ta), MAX_TYPED_HEIGHT, stage, |span| {
55        CompilerFailure::Limit {
56            stage,
57            span: Some(span),
58            message: format!(
59                "lowered expression nesting exceeds the compiler limit of {MAX_TYPED_HEIGHT} levels"
60            ),
61            help: vec![
62                "split long operator chains, template literals or spreads into intermediate declarations"
63                    .into(),
64            ],
65        }
66    })
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70enum Node {
71    Expr(ExprId),
72    Stmt(StmtId),
73}
74
75/// A node's children in the arena plus its owned height: levels the compiler
76/// recurses through that have no arena node of their own. These are type
77/// annotations, spread fallback links, and optional-chain links, which are
78/// flat in the tree but nested by code generation.
79struct NodeInfo {
80    span: Span,
81    children: Vec<Child>,
82    owned_height: u32,
83}
84
85/// `offset` counts the owned levels, such as spread fallback or optional-chain
86/// links, that the compiler recurses through before reaching the child.
87#[derive(Clone, Copy)]
88struct Child {
89    node: Node,
90    offset: u32,
91}
92
93impl Child {
94    fn direct(node: Node) -> Self {
95        Self { node, offset: 0 }
96    }
97}
98
99trait Tree {
100    fn expr_ids(&self) -> Result<Range<u32>, CompilerFailure>;
101    fn stmt_ids(&self) -> Result<Range<u32>, CompilerFailure>;
102    fn node(&self, node: Node) -> Result<NodeInfo, CompilerFailure>;
103}
104
105#[derive(Clone, Copy, PartialEq, Eq)]
106enum Measured {
107    Unvisited,
108    InProgress,
109    Done {
110        height: u32,
111        /// The tallest child counting its offset. The node's owned levels
112        /// may still be taller.
113        deepest: Option<Node>,
114        owned_height: u32,
115    },
116}
117
118struct Frame {
119    node: Node,
120    info: NodeInfo,
121    next_child: usize,
122    /// The child being measured on the frame above this one.
123    pending: Option<Child>,
124    child_height: u32,
125    /// The tallest child so far, counting its offset. The first of equally
126    /// tall children is kept, so a report starts at the earlier one.
127    deepest: Option<Node>,
128}
129
130impl Frame {
131    fn include(&mut self, child: Child, height: u32) {
132        let height = height.saturating_add(child.offset);
133        if self.deepest.is_none() || height > self.child_height {
134            self.child_height = height;
135            self.deepest = Some(child.node);
136        }
137    }
138}
139
140struct Heights {
141    exprs: Vec<Measured>,
142    stmts: Vec<Measured>,
143}
144
145impl Heights {
146    fn get(&self, node: Node) -> Option<Measured> {
147        match node {
148            Node::Expr(id) => self.exprs.get(id.0 as usize).copied(),
149            Node::Stmt(id) => self.stmts.get(id.0 as usize).copied(),
150        }
151    }
152
153    fn slot(&mut self, node: Node) -> Option<&mut Measured> {
154        match node {
155            Node::Expr(id) => self.exprs.get_mut(id.0 as usize),
156            Node::Stmt(id) => self.stmts.get_mut(id.0 as usize),
157        }
158    }
159}
160
161/// Computes each node's height once with an explicit stack, so measuring a
162/// too-deep tree cannot itself recurse. Shared children are visited once.
163fn measure(
164    tree: &impl Tree,
165    limit: u32,
166    stage: CompilerStage,
167    exceeded: impl Fn(Span) -> CompilerFailure,
168) -> Result<(), CompilerFailure> {
169    let expr_ids = tree.expr_ids().map_err(|error| error.with_stage(stage))?;
170    let stmt_ids = tree.stmt_ids().map_err(|error| error.with_stage(stage))?;
171    let mut heights = Heights {
172        exprs: vec![Measured::Unvisited; expr_ids.len()],
173        stmts: vec![Measured::Unvisited; stmt_ids.len()],
174    };
175    let roots = expr_ids
176        .map(|id| Node::Expr(ExprId(id)))
177        .chain(stmt_ids.map(|id| Node::Stmt(StmtId(id))));
178    // Measuring continues past the first node over the limit, which is often
179    // deep inside the expression a user has to split, so the report can start
180    // from the tallest node with its whole path measured. A parent is taller
181    // than its children, so the tallest node is a root; the first of equally
182    // tall roots is reported.
183    let mut tallest: Option<(Node, u32)> = None;
184    for root in roots {
185        if heights.get(root) != Some(Measured::Unvisited) {
186            continue;
187        }
188        measure_from(tree, root, &mut heights, stage)?;
189        if let Some(Measured::Done { height, .. }) = heights.get(root)
190            && height > limit
191            && tallest.is_none_or(|(_, tallest_height)| height > tallest_height)
192        {
193            tallest = Some((root, height));
194        }
195    }
196    match tallest {
197        Some((node, _)) => Err(exceeded(reported_span(tree, &heights, node, stage)?)),
198        None => Ok(()),
199    }
200}
201
202fn measure_from(
203    tree: &impl Tree,
204    root: Node,
205    heights: &mut Heights,
206    stage: CompilerStage,
207) -> Result<(), CompilerFailure> {
208    let mut stack = vec![enter(tree, root, heights, stage)?];
209    while let Some(frame) = stack.last_mut() {
210        if let Some(&child) = frame.info.children.get(frame.next_child) {
211            frame.next_child += 1;
212            match heights.get(child.node) {
213                // An out-of-range child fails in the arena lookup with its ID.
214                Some(Measured::Unvisited) | None => {
215                    let child_frame = enter(tree, child.node, heights, stage)?;
216                    frame.pending = Some(child);
217                    stack.push(child_frame);
218                }
219                Some(Measured::InProgress) => {
220                    return Err(internal(stage, "tree node is its own descendant"));
221                }
222                Some(Measured::Done { height, .. }) => frame.include(child, height),
223            }
224            continue;
225        }
226        let Some(frame) = stack.pop() else {
227            break;
228        };
229        let height = frame
230            .child_height
231            .max(frame.info.owned_height)
232            .saturating_add(1);
233        let Some(stored) = heights.slot(frame.node) else {
234            return Err(internal(stage, "tree node is outside its arena"));
235        };
236        *stored = Measured::Done {
237            height,
238            deepest: frame.deepest,
239            owned_height: frame.info.owned_height,
240        };
241        if let Some(parent) = stack.last_mut() {
242            let child = parent
243                .pending
244                .take()
245                .ok_or_else(|| internal(stage, "tree walk lost its parent's child"))?;
246            parent.include(child, height);
247        }
248    }
249    Ok(())
250}
251
252/// The tallest node over the limit is often a statement or closure that only
253/// wraps the construct that made the tree tall. Along its deepest path, an
254/// expression with no segment open (the start of the path, or the first
255/// expression below a statement) opens a segment of levels that ends at the
256/// next statement. An expression whose owned levels, such as optional-chain
257/// links, decide its height ends the segment there with those levels counted,
258/// and the expression below it opens the next. The expression with the longest
259/// segment is what a user has to split; ties go to the outermost, and with no
260/// expression on the path the node over the limit is reported. The span marks
261/// where that expression starts, since a tall one can span many lines.
262fn reported_span(
263    tree: &impl Tree,
264    heights: &Heights,
265    over_limit: Node,
266    stage: CompilerStage,
267) -> Result<Span, CompilerFailure> {
268    let span_of = |node| {
269        tree.node(node)
270            .map(|info| info.span)
271            .map_err(|error| error.with_stage(stage))
272    };
273    let mut longest = Longest {
274        span: span_of(over_limit)?,
275        levels: 0,
276    };
277    let mut segment = None;
278    let mut next = Some(over_limit);
279    // The path ends at a node without children: a statement, which closes the
280    // open segment, or an expression, whose owned levels decide its height.
281    while let Some(node) = next {
282        let Some(Measured::Done {
283            height,
284            deepest,
285            owned_height,
286        }) = heights.get(node)
287        else {
288            return Err(internal(
289                stage,
290                "a node on the deepest path was not measured",
291            ));
292        };
293        segment = match (node, segment) {
294            (Node::Stmt(_), Some(open)) => {
295                longest.offer(open, height);
296                None
297            }
298            (Node::Stmt(_), None) => None,
299            (Node::Expr(_), open) => {
300                let open = match open {
301                    Some(open) => open,
302                    None => Segment {
303                        expression: span_of(node)?,
304                        height,
305                    },
306                };
307                if owns_its_height(height, owned_height) {
308                    longest.offer(open, 0);
309                    None
310                } else {
311                    Some(open)
312                }
313            }
314        };
315        next = deepest;
316    }
317    Ok(Span {
318        end: longest.span.start,
319        ..longest.span
320    })
321}
322
323/// Whether a node's owned levels are at least as tall as each of its children
324/// (counting offsets), so they alone account for its height.
325fn owns_its_height(height: u32, owned_height: u32) -> bool {
326    height == owned_height.saturating_add(1)
327}
328
329#[derive(Clone, Copy)]
330struct Segment {
331    expression: Span,
332    height: u32,
333}
334
335struct Longest {
336    span: Span,
337    levels: u32,
338}
339
340impl Longest {
341    /// Offers a segment ending at `end_height`. Outer segments are offered
342    /// first, so a tie keeps the outer one.
343    fn offer(&mut self, segment: Segment, end_height: u32) {
344        let levels = segment.height.saturating_sub(end_height);
345        if levels > self.levels {
346            self.span = segment.expression;
347            self.levels = levels;
348        }
349    }
350}
351
352fn enter(
353    tree: &impl Tree,
354    node: Node,
355    heights: &mut Heights,
356    stage: CompilerStage,
357) -> Result<Frame, CompilerFailure> {
358    let info = tree.node(node).map_err(|error| error.with_stage(stage))?;
359    let Some(stored) = heights.slot(node) else {
360        return Err(internal(stage, "tree node is outside its arena"));
361    };
362    *stored = Measured::InProgress;
363    Ok(Frame {
364        node,
365        info,
366        next_child: 0,
367        pending: None,
368        child_height: 0,
369        deepest: None,
370    })
371}
372
373/// Arena ID iterators are dense ranges starting at zero.
374fn id_range(mut ids: impl DoubleEndedIterator<Item = u32>) -> Range<u32> {
375    let end = ids.next_back().map_or(0, |last| last.saturating_add(1));
376    0..end
377}
378
379fn syntax_arena_error(error: crate::arena::ArenaError) -> CompilerFailure {
380    error.into_compiler_failure(CompilerStage::Parse)
381}
382
383fn typed_arena_error(error: crate::arena::ArenaError) -> CompilerFailure {
384    error.into_compiler_failure(CompilerStage::Infer)
385}
386
387fn internal(stage: CompilerStage, message: &str) -> CompilerFailure {
388    CompilerFailure::Internal {
389        stage,
390        span: None,
391        message: message.into(),
392    }
393}
394
395struct SyntaxTree<'a>(&'a Ast);
396
397impl Tree for SyntaxTree<'_> {
398    fn expr_ids(&self) -> Result<Range<u32>, CompilerFailure> {
399        let ids = self.0.expr_ids().map_err(syntax_arena_error)?;
400        Ok(id_range(ids.map(|id| id.0)))
401    }
402
403    fn stmt_ids(&self) -> Result<Range<u32>, CompilerFailure> {
404        let ids = self.0.stmt_ids().map_err(syntax_arena_error)?;
405        Ok(id_range(ids.map(|id| id.0)))
406    }
407
408    fn node(&self, node: Node) -> Result<NodeInfo, CompilerFailure> {
409        let mut children = SyntaxChildren::default();
410        let span = match node {
411            Node::Expr(id) => {
412                let expr = self.0.try_expr(id).map_err(syntax_arena_error)?;
413                children.expr_kind(&expr.kind);
414                expr.span
415            }
416            Node::Stmt(id) => {
417                let stmt = self.0.try_stmt(id).map_err(syntax_arena_error)?;
418                children.stmt_kind(&stmt.kind);
419                stmt.span
420            }
421        };
422        if let Some(failure) = children.failure {
423            return Err(failure.with_span(span));
424        }
425        Ok(NodeInfo {
426            span,
427            children: children
428                .nodes
429                .into_iter()
430                .map(Child::direct)
431                .chain(children.behind_owned)
432                .collect(),
433            owned_height: children.owned_height,
434        })
435    }
436}
437
438#[derive(Default)]
439struct SyntaxChildren {
440    failure: Option<CompilerFailure>,
441    nodes: Vec<Node>,
442    /// Children reached through owned levels, measured with that offset.
443    behind_owned: Vec<Child>,
444    owned_height: u32,
445}
446
447impl SyntaxChildren {
448    fn expr(&mut self, id: ExprId) {
449        self.nodes.push(Node::Expr(id));
450    }
451
452    fn exprs(&mut self, ids: &[ExprId]) {
453        self.nodes.extend(ids.iter().copied().map(Node::Expr));
454    }
455
456    fn stmt(&mut self, id: StmtId) {
457        self.nodes.push(Node::Stmt(id));
458    }
459
460    fn annotation(&mut self, ty: &TypeAnnotation) {
461        self.annotation_at(ty, 0);
462    }
463
464    fn annotation_at(&mut self, ty: &TypeAnnotation, offset: u32) {
465        if self.failure.is_some() {
466            return;
467        }
468        match annotation_height(ty) {
469            Ok(height) => self.owned_height = self.owned_height.max(height.saturating_add(offset)),
470            Err(failure) => self.failure = Some(failure),
471        }
472    }
473
474    fn annotations<'a>(&mut self, types: impl IntoIterator<Item = &'a TypeAnnotation>) {
475        for ty in types {
476            self.annotation(ty);
477        }
478    }
479
480    fn params(&mut self, params: &[ParamDecl]) {
481        for param in params {
482            self.annotations(&param.ty);
483            self.nodes.extend(param.default.map(Node::Expr));
484            if let Some(pattern) = &param.pattern {
485                self.binding(pattern);
486            }
487        }
488    }
489
490    fn binding(&mut self, binding: &crate::Binding) {
491        match binding {
492            crate::Binding::Object { fields, .. } => {
493                self.nodes.extend(
494                    fields
495                        .iter()
496                        .filter_map(|field| field.default)
497                        .map(Node::Expr),
498                );
499            }
500            crate::Binding::Array { defaults, .. } => {
501                self.nodes
502                    .extend(defaults.iter().flatten().copied().map(Node::Expr));
503            }
504        }
505    }
506
507    /// Code generation nests each chain link inside the previous one, so the
508    /// link at depth `n` and the operands it owns sit `n` levels down.
509    fn chain_parts(&mut self, parts: &[ChainPart]) {
510        let mut depth = 0u32;
511        for part in parts {
512            depth = depth.saturating_add(1);
513            self.owned_height = self.owned_height.max(depth);
514            match part {
515                ChainPart::Field { .. } | ChainPart::NonNull { .. } => {}
516                ChainPart::Index { idx, .. } => self.expr_at(*idx, depth),
517                ChainPart::Call {
518                    args, type_args, ..
519                } => {
520                    for arg in args {
521                        self.expr_at(*arg, depth);
522                    }
523                    for ty in type_args.iter().flatten() {
524                        self.annotation_at(ty, depth);
525                    }
526                }
527            }
528        }
529    }
530
531    fn expr_at(&mut self, id: ExprId, offset: u32) {
532        self.behind_owned.push(Child {
533            node: Node::Expr(id),
534            offset,
535        });
536    }
537
538    fn expr_kind(&mut self, kind: &ExprKind) {
539        match kind {
540            ExprKind::Number(_)
541            | ExprKind::BigInt(_)
542            | ExprKind::String(_)
543            | ExprKind::Boolean(_)
544            | ExprKind::Null
545            | ExprKind::Identifier(_)
546            | ExprKind::This
547            | ExprKind::ThisOutsideReceiver
548            | ExprKind::Super
549            | ExprKind::Regex { .. } => {}
550            ExprKind::Binary { lhs, rhs, .. } => self.exprs(&[*lhs, *rhs]),
551            ExprKind::Unary { operand, .. }
552            | ExprKind::Paren(operand)
553            | ExprKind::Typeof { operand }
554            | ExprKind::Void { operand }
555            | ExprKind::Delete { operand }
556            | ExprKind::PostfixUnary { operand, .. } => self.expr(*operand),
557            ExprKind::Call {
558                callee,
559                type_args,
560                args,
561            }
562            | ExprKind::New {
563                callee,
564                type_args,
565                args,
566            } => {
567                self.expr(*callee);
568                self.annotations(type_args.iter().flatten());
569                self.exprs(args);
570            }
571            ExprKind::ObjectLiteral { members } => {
572                for member in members {
573                    self.nodes.extend(member.expressions().map(Node::Expr));
574                }
575            }
576            ExprKind::ArrayLiteral { elements } => {
577                self.nodes
578                    .extend(elements.iter().map(|element| Node::Expr(element.value())));
579            }
580            ExprKind::FieldAccess { receiver, .. } => self.expr(*receiver),
581            ExprKind::IndexAccess { receiver, index } => self.exprs(&[*receiver, *index]),
582            ExprKind::FunctionExpression {
583                this_type,
584                function,
585                ..
586            } => {
587                self.annotations(this_type);
588                self.expr(*function);
589            }
590            ExprKind::Arrow {
591                params,
592                return_type,
593                type_predicate,
594                body,
595            } => {
596                self.params(params);
597                self.annotations(return_type);
598                self.annotations(type_predicate.iter().map(|predicate| &predicate.asserted));
599                match body {
600                    ArrowBody::Expr(id) => self.expr(*id),
601                    ArrowBody::Block(id) => self.stmt(*id),
602                }
603            }
604            ExprKind::TemplateLiteral { exprs, .. } => self.exprs(exprs),
605            ExprKind::Ternary { cond, then_, else_ } => self.exprs(&[*cond, *then_, *else_]),
606            ExprKind::OptionalChain { base, parts } => {
607                self.expr(*base);
608                self.chain_parts(parts);
609            }
610            ExprKind::Assign { target, value, .. } => self.exprs(&[*target, *value]),
611            ExprKind::As { expr, ty } => {
612                self.expr(*expr);
613                self.annotation(ty);
614            }
615            ExprKind::InstanceOf { value, ty } => {
616                self.expr(*value);
617                self.annotation(ty);
618            }
619        }
620    }
621
622    fn stmt_kind(&mut self, kind: &StmtKind) {
623        match kind {
624            StmtKind::Let { ty, value, .. }
625            | StmtKind::Const { ty, value, .. }
626            | StmtKind::ObjectRest {
627                ty, source: value, ..
628            } => {
629                self.annotations(ty);
630                self.expr(*value);
631            }
632            StmtKind::LetPattern {
633                ty, value, binding, ..
634            }
635            | StmtKind::ConstPattern {
636                ty, value, binding, ..
637            } => {
638                self.annotations(ty);
639                self.expr(*value);
640                self.binding(binding);
641            }
642            StmtKind::Function {
643                params,
644                return_type,
645                type_predicate,
646                body,
647                ..
648            } => {
649                self.params(params);
650                self.annotations(return_type);
651                self.annotations(type_predicate.iter().map(|predicate| &predicate.asserted));
652                self.stmt(*body);
653            }
654            StmtKind::If {
655                condition,
656                then_block,
657                else_block,
658            } => {
659                self.expr(*condition);
660                self.stmt(*then_block);
661                self.nodes.extend(else_block.map(Node::Stmt));
662            }
663            StmtKind::While { condition, body } | StmtKind::DoWhile { body, condition } => {
664                self.expr(*condition);
665                self.stmt(*body);
666            }
667            StmtKind::For {
668                init,
669                condition,
670                update,
671                body,
672            } => {
673                self.nodes.extend(init.map(Node::Stmt));
674                self.nodes.extend(condition.map(Node::Expr));
675                self.nodes.extend(update.map(Node::Stmt));
676                self.stmt(*body);
677            }
678            StmtKind::ForOf { ty, iter, body, .. } => {
679                self.annotations(ty);
680                self.expr(*iter);
681                self.stmt(*body);
682            }
683            StmtKind::ForOfPattern {
684                ty,
685                iter,
686                body,
687                binding,
688                ..
689            } => {
690                self.annotations(ty);
691                self.expr(*iter);
692                self.stmt(*body);
693                self.binding(binding);
694            }
695            StmtKind::Switch {
696                discriminant,
697                cases,
698                default,
699            } => {
700                self.expr(*discriminant);
701                for case in cases {
702                    self.exprs(&case.values);
703                    self.stmt(case.body);
704                }
705                self.nodes
706                    .extend(default.iter().map(|default| Node::Stmt(default.body)));
707            }
708            StmtKind::Break
709            | StmtKind::Continue
710            | StmtKind::EnumDecl { .. }
711            | StmtKind::Import { .. }
712            | StmtKind::ExportFrom { .. } => {}
713            StmtKind::Return(value) => self.nodes.extend(value.map(Node::Expr)),
714            StmtKind::Throw { value } | StmtKind::Expr(value) => self.expr(*value),
715            StmtKind::Try {
716                body,
717                catches,
718                finally,
719            } => {
720                self.stmt(*body);
721                for catch in catches {
722                    self.annotations(&catch.ty);
723                    self.stmt(catch.body);
724                }
725                self.nodes.extend(finally.map(Node::Stmt));
726            }
727            StmtKind::Block(stmts) => self.nodes.extend(stmts.iter().copied().map(Node::Stmt)),
728            StmtKind::Assign { value, .. } | StmtKind::CompoundAssign { value, .. } => {
729                self.expr(*value);
730            }
731            StmtKind::AssignField {
732                receiver, value, ..
733            }
734            | StmtKind::CompoundAssignField {
735                receiver, value, ..
736            } => self.exprs(&[*receiver, *value]),
737            StmtKind::AssignIndex {
738                receiver,
739                index,
740                value,
741            }
742            | StmtKind::CompoundAssignIndex {
743                receiver,
744                index,
745                value,
746                ..
747            } => self.exprs(&[*receiver, *index, *value]),
748            StmtKind::InterfaceDecl {
749                extends, members, ..
750            } => {
751                self.annotations(extends);
752                for member in members {
753                    match member {
754                        InterfaceMember::IndexSignature(signature) => {
755                            self.annotation(&signature.value);
756                        }
757                        InterfaceMember::Method {
758                            params,
759                            return_type,
760                            ..
761                        } => {
762                            self.params(params);
763                            self.annotation(return_type);
764                        }
765                        InterfaceMember::Property { ty, .. } => self.annotation(ty),
766                    }
767                }
768            }
769            StmtKind::ClassDecl {
770                extends,
771                implements,
772                members,
773                ..
774            } => {
775                self.annotations(extends);
776                self.annotations(implements);
777                for member in members {
778                    self.class_member(member);
779                }
780            }
781            StmtKind::TypeAliasDecl { ty, .. } => self.annotation(ty),
782        }
783    }
784
785    fn class_member(&mut self, member: &ClassMember) {
786        match member {
787            ClassMember::Field {
788                ty, initializer, ..
789            } => {
790                self.annotation(ty);
791                self.nodes.extend(initializer.map(Node::Expr));
792            }
793            ClassMember::Method {
794                params,
795                return_type,
796                body,
797                ..
798            } => {
799                self.params(params);
800                self.annotation(return_type);
801                self.stmt(*body);
802            }
803            ClassMember::Constructor { params, body, .. } => {
804                self.params(params);
805                self.stmt(*body);
806            }
807            ClassMember::Accessor {
808                param,
809                return_type,
810                body,
811                ..
812            } => {
813                self.params(param.as_deref().map_or(&[], std::slice::from_ref));
814                self.annotations(return_type);
815                self.stmt(*body);
816            }
817        }
818    }
819}
820
821/// Annotations are boxed values, measured with an explicit stack for the same
822/// reason as arena nodes.
823fn annotation_height(root: &TypeAnnotation) -> Result<u32, CompilerFailure> {
824    measure_annotation(root, u64::MAX, u32::MAX).map(|extent| extent.depth)
825}
826
827/// Nodes in `root`, counted without recursion; stops once past `max_nodes`.
828pub(crate) fn annotation_nodes(
829    root: &TypeAnnotation,
830    max_nodes: u64,
831) -> Result<u64, CompilerFailure> {
832    measure_annotation(root, max_nodes, u32::MAX).map(|extent| extent.nodes)
833}
834
835fn measure_annotation(
836    root: &TypeAnnotation,
837    max_nodes: u64,
838    max_depth: u32,
839) -> Result<crate::type_size::TypeExtent, CompilerFailure> {
840    crate::type_walk::measure(root, max_nodes, max_depth, AnnotationChildren::new).map_err(|_| {
841        CompilerFailure::Internal {
842            stage: CompilerStage::Parse,
843            span: Some(root.span),
844            message: "could not allocate annotation traversal frames".into(),
845        }
846    })
847}
848
849struct AnnotationChildren<'a> {
850    members: std::slice::Iter<'a, TypeAnnotation>,
851    fields: std::slice::Iter<'a, crate::ast::TypeAnnotationField>,
852    trailing: Option<&'a TypeAnnotation>,
853}
854
855impl<'a> AnnotationChildren<'a> {
856    fn new(ty: &'a TypeAnnotation) -> Self {
857        let mut children = Self {
858            members: [].iter(),
859            fields: [].iter(),
860            trailing: None,
861        };
862        match &ty.kind {
863            TypeAnnotationKind::Name { args, .. }
864            | TypeAnnotationKind::Qualified { args, .. }
865            | TypeAnnotationKind::Tuple(args)
866            | TypeAnnotationKind::Union(args) => {
867                children.members = args.iter();
868            }
869            TypeAnnotationKind::StringLiteral(_)
870            | TypeAnnotationKind::NumberLiteral(_)
871            | TypeAnnotationKind::BigIntLiteral(_)
872            | TypeAnnotationKind::BooleanLiteral(_)
873            | TypeAnnotationKind::TypeOf { .. } => {}
874            TypeAnnotationKind::Array(inner)
875            | TypeAnnotationKind::Optional(inner)
876            | TypeAnnotationKind::Readonly(inner)
877            | TypeAnnotationKind::KeyOf(inner) => children.trailing = Some(inner),
878            TypeAnnotationKind::Object { fields, index } => {
879                children.fields = fields.iter();
880                children.trailing = index.as_ref().map(|index| &index.value);
881            }
882            TypeAnnotationKind::Function {
883                params,
884                return_type,
885            } => {
886                children.fields = params.iter();
887                children.trailing = Some(return_type);
888            }
889        }
890        children
891    }
892}
893
894impl<'a> Iterator for AnnotationChildren<'a> {
895    type Item = &'a TypeAnnotation;
896
897    fn next(&mut self) -> Option<Self::Item> {
898        self.trailing
899            .take()
900            .or_else(|| self.fields.next_back().map(|field| &field.ty))
901            .or_else(|| self.members.next_back())
902    }
903}
904
905struct TypedTree<'a>(&'a TypedAst);
906
907impl Tree for TypedTree<'_> {
908    fn expr_ids(&self) -> Result<Range<u32>, CompilerFailure> {
909        let ids = self.0.expr_ids().map_err(typed_arena_error)?;
910        Ok(id_range(ids.map(|id| id.0)))
911    }
912
913    fn stmt_ids(&self) -> Result<Range<u32>, CompilerFailure> {
914        let ids = self.0.stmt_ids().map_err(typed_arena_error)?;
915        Ok(id_range(ids.map(|id| id.0)))
916    }
917
918    fn node(&self, node: Node) -> Result<NodeInfo, CompilerFailure> {
919        let mut children = TypedChildren::default();
920        let span = match node {
921            Node::Expr(id) => {
922                let expr = self.0.try_expr(id).map_err(typed_arena_error)?;
923                children.expr_kind(&expr.kind);
924                expr.span
925            }
926            Node::Stmt(id) => {
927                let stmt = self.0.try_stmt(id).map_err(typed_arena_error)?;
928                children.stmt_kind(&stmt.kind);
929                stmt.span
930            }
931        };
932        Ok(NodeInfo {
933            span,
934            children: children
935                .nodes
936                .into_iter()
937                .map(Child::direct)
938                .chain(children.behind_owned)
939                .collect(),
940            owned_height: children.owned_height,
941        })
942    }
943}
944
945#[derive(Default)]
946struct TypedChildren {
947    nodes: Vec<Node>,
948    /// Children reached through owned levels, measured with that offset.
949    behind_owned: Vec<Child>,
950    owned_height: u32,
951}
952
953impl TypedChildren {
954    fn expr(&mut self, id: ExprId) {
955        self.nodes.push(Node::Expr(id));
956    }
957
958    fn exprs(&mut self, ids: &[ExprId]) {
959        self.nodes.extend(ids.iter().copied().map(Node::Expr));
960    }
961
962    fn stmt(&mut self, id: StmtId) {
963        self.nodes.push(Node::Stmt(id));
964    }
965
966    /// Code generation nests each chain link inside the previous one, so the
967    /// link at depth `n` and the operands it owns sit `n` levels down.
968    fn chain_parts(&mut self, parts: &[TypedChainPart]) {
969        let mut depth = 0u32;
970        for part in parts {
971            depth = depth.saturating_add(1);
972            self.owned_height = self.owned_height.max(depth);
973            match part {
974                TypedChainPart::Field { .. }
975                | TypedChainPart::InterfaceProperty { .. }
976                | TypedChainPart::NonNull { .. } => {}
977                TypedChainPart::Index { idx, .. } => self.expr_at(*idx, depth),
978                TypedChainPart::Call { args, .. } | TypedChainPart::MethodCall { args, .. } => {
979                    for arg in args {
980                        self.expr_at(*arg, depth);
981                    }
982                }
983            }
984        }
985    }
986
987    fn expr_at(&mut self, id: ExprId, offset: u32) {
988        self.behind_owned.push(Child {
989            node: Node::Expr(id),
990            offset,
991        });
992    }
993
994    /// Spread fallbacks form a boxed chain inside one object literal field; a
995    /// walk descends the chain before reaching its terminal expression.
996    fn field_source(&mut self, source: &TypedObjectFieldSource) {
997        let mut chain_height = 0u32;
998        let mut next = Some(source);
999        while let Some(source) = next {
1000            chain_height = chain_height.saturating_add(1);
1001            next = match source {
1002                TypedObjectFieldSource::Literal(id) | TypedObjectFieldSource::Absent(id) => {
1003                    self.expr_at(*id, chain_height);
1004                    None
1005                }
1006                TypedObjectFieldSource::Spread { fallback, .. } => fallback.as_deref(),
1007            };
1008        }
1009        self.owned_height = self.owned_height.max(chain_height);
1010    }
1011
1012    fn expr_kind(&mut self, kind: &TypedExprKind) {
1013        match kind {
1014            TypedExprKind::Number(_)
1015            | TypedExprKind::BigInt(_)
1016            | TypedExprKind::String(_)
1017            | TypedExprKind::Boolean(_)
1018            | TypedExprKind::Null
1019            | TypedExprKind::Undefined
1020            | TypedExprKind::This
1021            | TypedExprKind::Regex { .. }
1022            | TypedExprKind::LocalRef { .. }
1023            | TypedExprKind::LocalNarrowRef { .. }
1024            | TypedExprKind::GlobalRef { .. }
1025            | TypedExprKind::FunctionRef { .. }
1026            | TypedExprKind::NumberEnumMember { .. }
1027            | TypedExprKind::StringEnumMember { .. } => {}
1028            TypedExprKind::EffectThen { effect, result } => self.exprs(&[*effect, *result]),
1029            TypedExprKind::Sequence { stmts, result } => {
1030                self.nodes.extend(stmts.iter().copied().map(Node::Stmt));
1031                self.expr(*result);
1032            }
1033            TypedExprKind::Binary { lhs, rhs, .. }
1034            | TypedExprKind::NullishCoalesce { lhs, rhs } => self.exprs(&[*lhs, *rhs]),
1035            TypedExprKind::Unary { operand, .. } => self.expr(*operand),
1036            TypedExprKind::Call { args, .. }
1037            | TypedExprKind::VirtualCall { args, .. }
1038            | TypedExprKind::IntrinsicCall { args, .. }
1039            | TypedExprKind::SuperCtorCall { args, .. }
1040            | TypedExprKind::SuperMethodCall { args, .. } => self.exprs(args),
1041            TypedExprKind::CallClosure { callee, args } => {
1042                self.expr(*callee);
1043                self.exprs(args);
1044            }
1045            TypedExprKind::GenericCall { args, .. } => {
1046                self.nodes
1047                    .extend(args.iter().map(|argument| Node::Expr(argument.expr)));
1048            }
1049            TypedExprKind::MethodCall { receiver, args, .. } => {
1050                self.expr(*receiver);
1051                self.exprs(args);
1052            }
1053            TypedExprKind::GenericMethodCall { receiver, args, .. } => {
1054                self.expr(*receiver);
1055                self.nodes
1056                    .extend(args.iter().map(|argument| Node::Expr(argument.expr)));
1057            }
1058            TypedExprKind::ObjectLiteral { members, fields } => {
1059                for member in members {
1060                    self.nodes.extend(member.expressions().map(Node::Expr));
1061                }
1062                for field in fields {
1063                    self.field_source(&field.source);
1064                }
1065            }
1066            TypedExprKind::ArrayLiteral { elements, .. } => {
1067                self.nodes
1068                    .extend(elements.iter().map(|element| Node::Expr(element.expr_id())));
1069            }
1070            TypedExprKind::TupleLiteral { elements, .. } => self.exprs(elements),
1071            TypedExprKind::FieldAccess { receiver, .. }
1072            | TypedExprKind::InterfacePropertyAccess { receiver, .. } => self.expr(*receiver),
1073            TypedExprKind::IndexAccess { receiver, index } => self.exprs(&[*receiver, *index]),
1074            TypedExprKind::Closure { body, .. } => match body {
1075                ClosureBody::Expr(id) => self.expr(*id),
1076                ClosureBody::Block(id) => self.stmt(*id),
1077            },
1078            TypedExprKind::TypeofTag { value, .. }
1079            | TypedExprKind::NonNullAssert { value }
1080            | TypedExprKind::Cast { value, .. }
1081            | TypedExprKind::InstanceOf { value, .. } => self.expr(*value),
1082            TypedExprKind::Narrowed { source, inner, .. } => self.exprs(&[*source, *inner]),
1083            TypedExprKind::Ternary { cond, then_, else_ } => self.exprs(&[*cond, *then_, *else_]),
1084            TypedExprKind::OptionalChain { base, parts } => {
1085                self.expr(*base);
1086                self.chain_parts(parts);
1087            }
1088            TypedExprKind::PostfixUnary { target, .. } => match target {
1089                PostfixTarget::Local { .. } | PostfixTarget::Global { .. } => {}
1090                PostfixTarget::Field { receiver, .. } => self.expr(*receiver),
1091                PostfixTarget::Index {
1092                    receiver, index, ..
1093                } => self.exprs(&[*receiver, *index]),
1094            },
1095        }
1096    }
1097
1098    fn stmt_kind(&mut self, kind: &TypedStmtKind) {
1099        match kind {
1100            TypedStmtKind::Let { value, .. }
1101            | TypedStmtKind::Const { value, .. }
1102            | TypedStmtKind::Throw { value }
1103            | TypedStmtKind::Expr(value)
1104            | TypedStmtKind::AssignLocal { value, .. }
1105            | TypedStmtKind::AssignGlobal { value, .. } => self.expr(*value),
1106            TypedStmtKind::ReboxLocal { .. } | TypedStmtKind::Break | TypedStmtKind::Continue => {}
1107            TypedStmtKind::If {
1108                condition,
1109                then_block,
1110                else_block,
1111            } => {
1112                self.expr(*condition);
1113                self.stmt(*then_block);
1114                self.nodes.extend(else_block.map(Node::Stmt));
1115            }
1116            TypedStmtKind::While { condition, body }
1117            | TypedStmtKind::DoWhile { body, condition } => {
1118                self.expr(*condition);
1119                self.stmt(*body);
1120            }
1121            TypedStmtKind::For {
1122                init,
1123                condition,
1124                update,
1125                body,
1126            } => {
1127                self.nodes.extend(init.map(Node::Stmt));
1128                self.nodes.extend(condition.map(Node::Expr));
1129                self.nodes.extend(update.map(Node::Stmt));
1130                self.stmt(*body);
1131            }
1132            TypedStmtKind::ForOf { iter, body, .. } => {
1133                self.expr(*iter);
1134                self.stmt(*body);
1135            }
1136            TypedStmtKind::Switch {
1137                discriminant,
1138                cases,
1139                default,
1140                ..
1141            } => {
1142                self.expr(*discriminant);
1143                self.nodes.extend(
1144                    cases
1145                        .iter()
1146                        .flat_map(crate::TypedSwitchCase::label_comparisons)
1147                        .map(Node::Expr),
1148                );
1149                self.nodes
1150                    .extend(cases.iter().map(|case| Node::Stmt(case.body)));
1151                self.nodes.extend(default.map(Node::Stmt));
1152            }
1153            TypedStmtKind::Return(value) => self.nodes.extend(value.map(Node::Expr)),
1154            TypedStmtKind::Try {
1155                body,
1156                catches,
1157                finally,
1158            } => {
1159                self.stmt(*body);
1160                self.nodes
1161                    .extend(catches.iter().map(|catch| Node::Stmt(catch.body)));
1162                self.nodes.extend(finally.map(Node::Stmt));
1163            }
1164            TypedStmtKind::Block(stmts) => self.nodes.extend(stmts.iter().copied().map(Node::Stmt)),
1165            TypedStmtKind::AssignField {
1166                receiver, value, ..
1167            } => self.exprs(&[*receiver, *value]),
1168            TypedStmtKind::AssignIndex {
1169                receiver,
1170                index,
1171                value,
1172                ..
1173            } => self.exprs(&[*receiver, *index, *value]),
1174            TypedStmtKind::NarrowRegion { source, body, .. } => {
1175                self.expr(*source);
1176                self.stmt(*body);
1177            }
1178        }
1179    }
1180}
1181
1182#[cfg(test)]
1183mod tests {
1184    use super::*;
1185    use crate::compile::parse_script;
1186    use crate::{Expr, FileId, Type, TypedExpr};
1187
1188    fn annotation(kind: TypeAnnotationKind) -> TypeAnnotation {
1189        TypeAnnotation {
1190            kind,
1191            span: Span::at(FileId(3)),
1192        }
1193    }
1194
1195    fn annotation_field(ty: TypeAnnotation) -> crate::ast::TypeAnnotationField {
1196        crate::ast::TypeAnnotationField {
1197            name: crate::Ident {
1198                name: "field".into(),
1199                span: ty.span,
1200            },
1201            ty,
1202            optional: false,
1203            readonly: false,
1204            rest: false,
1205            method: false,
1206        }
1207    }
1208
1209    #[test]
1210    fn wide_annotations_use_only_ancestor_frames() {
1211        let root = annotation(TypeAnnotationKind::Tuple(vec![
1212            annotation(
1213                TypeAnnotationKind::BooleanLiteral(true)
1214            );
1215            100_000
1216        ]));
1217        for budget in [0, 1, 7, 100_001] {
1218            let (nodes, observed) =
1219                crate::type_walk::tests::observe(None, || annotation_nodes(&root, budget).unwrap());
1220            assert_eq!(nodes, 100_001.min(budget + 1));
1221            assert!(observed.peak_frames <= 1);
1222            assert!(observed.reservations <= 1);
1223        }
1224        let (height, observed) =
1225            crate::type_walk::tests::observe(None, || annotation_height(&root).unwrap());
1226        assert_eq!(height, 2);
1227        assert_eq!(observed.peak_frames, 1);
1228    }
1229
1230    #[test]
1231    fn mixed_annotations_keep_depth_counts_and_lifo_order() {
1232        let leaf = annotation(TypeAnnotationKind::BooleanLiteral(true));
1233        let deep = (1..20).fold(leaf.clone(), |inner, _| {
1234            annotation(TypeAnnotationKind::Array(Box::new(inner)))
1235        });
1236        let root = annotation(TypeAnnotationKind::Tuple(vec![
1237            annotation(TypeAnnotationKind::Tuple(vec![leaf; 100_000])),
1238            deep,
1239        ]));
1240        let (extent, observed) = crate::type_walk::tests::observe(None, || {
1241            measure_annotation(&root, u64::MAX, u32::MAX).unwrap()
1242        });
1243        assert_eq!(
1244            extent,
1245            crate::type_size::TypeExtent {
1246                nodes: 100_022,
1247                depth: 21
1248            }
1249        );
1250        assert_eq!(observed.peak_frames, 20);
1251        assert_eq!(
1252            measure_annotation(&root, 3, u32::MAX).unwrap(),
1253            crate::type_size::TypeExtent { nodes: 4, depth: 4 }
1254        );
1255        let (failure, _) = crate::type_walk::tests::observe(Some(1), || annotation_height(&root));
1256        assert!(matches!(failure, Err(CompilerFailure::Internal { .. })));
1257    }
1258
1259    #[test]
1260    fn annotation_variants_preserve_children_and_measurement() {
1261        let first = annotation(TypeAnnotationKind::StringLiteral("first".into()));
1262        let second = annotation(TypeAnnotationKind::BooleanLiteral(false));
1263        let pair = vec![first.clone(), second.clone()];
1264        let name = crate::Ident {
1265            name: "T".into(),
1266            span: first.span,
1267        };
1268        let cases = vec![
1269            (
1270                TypeAnnotationKind::Name {
1271                    name: name.clone(),
1272                    args: pair.clone(),
1273                },
1274                pair.clone(),
1275            ),
1276            (
1277                TypeAnnotationKind::Qualified {
1278                    path: vec![name.clone(), name],
1279                    args: pair.clone(),
1280                },
1281                pair.clone(),
1282            ),
1283            (TypeAnnotationKind::Tuple(pair.clone()), pair.clone()),
1284            (TypeAnnotationKind::Union(pair.clone()), pair.clone()),
1285            (
1286                TypeAnnotationKind::Array(Box::new(first.clone())),
1287                vec![first.clone()],
1288            ),
1289            (
1290                TypeAnnotationKind::Readonly(Box::new(first.clone())),
1291                vec![first.clone()],
1292            ),
1293            (
1294                TypeAnnotationKind::KeyOf(Box::new(first.clone())),
1295                vec![first.clone()],
1296            ),
1297            (
1298                TypeAnnotationKind::Object {
1299                    fields: vec![annotation_field(first.clone())],
1300                    index: Some(Box::new(crate::IndexSignatureAnnotation {
1301                        value: second.clone(),
1302                        readonly: false,
1303                        span: second.span,
1304                    })),
1305                },
1306                pair.clone(),
1307            ),
1308            (
1309                TypeAnnotationKind::Function {
1310                    params: vec![annotation_field(first.clone())],
1311                    return_type: Box::new(second.clone()),
1312                },
1313                pair,
1314            ),
1315            (TypeAnnotationKind::TypeOf { path: Vec::new() }, Vec::new()),
1316            (TypeAnnotationKind::StringLiteral("leaf".into()), Vec::new()),
1317            (
1318                TypeAnnotationKind::NumberLiteral(crate::types::LiteralF64(1.0)),
1319                Vec::new(),
1320            ),
1321            (TypeAnnotationKind::BooleanLiteral(true), Vec::new()),
1322        ];
1323        for (kind, mut expected) in cases {
1324            let root = annotation(kind);
1325            let nodes = expected.len() as u64 + 1;
1326            let height = if expected.is_empty() { 1 } else { 2 };
1327            expected.reverse();
1328            assert_eq!(
1329                AnnotationChildren::new(&root).cloned().collect::<Vec<_>>(),
1330                expected
1331            );
1332            assert_eq!(annotation_nodes(&root, u64::MAX).unwrap(), nodes);
1333            assert_eq!(annotation_height(&root).unwrap(), height);
1334        }
1335    }
1336
1337    #[test]
1338    fn annotation_failure_terminates_syntax_validation() {
1339        let root = annotation(TypeAnnotationKind::Array(Box::new(annotation(
1340            TypeAnnotationKind::BooleanLiteral(true),
1341        ))));
1342        let (count, _) = crate::type_walk::tests::observe(Some(0), || annotation_nodes(&root, 10));
1343        assert!(
1344            matches!(count, Err(CompilerFailure::Internal { span: Some(s), .. }) if s == root.span)
1345        );
1346        // Parse before injection, so the assertion targets the public syntax check.
1347        let source = "type T = [number, string];";
1348        let mut lexer = crate::asi::Asi::new(source, FileId(3));
1349        let mut tokens = Vec::new();
1350        loop {
1351            let token = lexer.next_token();
1352            let eof = matches!(token.kind, crate::TokenKind::Eof);
1353            tokens.push(token);
1354            if eof {
1355                break;
1356            }
1357        }
1358        let (ast, diagnostics) = crate::parser::parse_checked(source, tokens, FileId(3)).unwrap();
1359        assert!(diagnostics.is_empty());
1360        let (result, _) = crate::type_walk::tests::observe(Some(0), || check_syntax(&ast));
1361        assert!(matches!(
1362            result,
1363            Err(CompilerFailure::Internal {
1364                stage: CompilerStage::Parse,
1365                span: Some(_),
1366                ..
1367            })
1368        ));
1369        assert_eq!(check_syntax(&ast), Ok(()));
1370    }
1371
1372    /// The syntax-height diagnostic `parse_script` reports for `source`, if any.
1373    fn syntax_limit(source: &str) -> Option<crate::Diagnostic> {
1374        parse_script(source, FileId(0))
1375            .diagnostics()
1376            .iter()
1377            .find(|d| d.message.contains("syntax nesting"))
1378            .cloned()
1379    }
1380
1381    fn sum(operands: usize) -> String {
1382        // Function, block and return statements add three levels to the chain.
1383        format!(
1384            "function main(): number {{ return {}; }}",
1385            vec!["1"; operands].join(" + ")
1386        )
1387    }
1388
1389    #[test]
1390    fn syntax_height_accepts_the_limit_and_rejects_one_more_level() {
1391        let at_limit = MAX_SYNTAX_HEIGHT as usize - 3;
1392        let chain_start = "function main(): number { return ".len() as u32;
1393        assert!(syntax_limit(&sum(at_limit)).is_none());
1394        // One level over, the function statement is the tallest node over the
1395        // limit; the report follows the deepest path to the chain itself.
1396        let diagnostic = syntax_limit(&sum(at_limit + 1)).expect("one level over is rejected");
1397        assert_eq!(diagnostic.span.start, chain_start);
1398        assert!(!diagnostic.help.is_empty());
1399        let diagnostic = syntax_limit(&sum(10 * at_limit)).expect("a long chain is rejected");
1400        assert_eq!(diagnostic.span.start, chain_start);
1401    }
1402
1403    #[test]
1404    fn syntax_height_counts_postfix_chains_and_annotations() {
1405        let calls = format!(
1406            "function main(): number {{ const f = (): unknown => f; f{}; return 1; }}",
1407            "()".repeat(MAX_SYNTAX_HEIGHT as usize)
1408        );
1409        assert!(syntax_limit(&calls).is_some());
1410        let nested_type = format!(
1411            "function main(): number {{ const x: {}number{} | null = null; return 1; }}",
1412            "{ v: ".repeat(40),
1413            " }".repeat(40)
1414        );
1415        let parsed = parse_script(&nested_type, FileId(0));
1416        assert!(!parsed.has_errors(), "{:?}", parsed.diagnostics());
1417    }
1418
1419    /// `const y = <base>?.a.a…<tail>;` with `links` field links before `tail`.
1420    fn optional_fields(base: &str, links: usize, tail: &str) -> String {
1421        format!(
1422            "function main(): number {{ const y = {base}?.a{}{tail}; return 1; }}",
1423            ".a".repeat(links.saturating_sub(1))
1424        )
1425    }
1426
1427    fn longest_accepted_field_chain() -> usize {
1428        let longest = (1..=MAX_SYNTAX_HEIGHT as usize)
1429            .take_while(|&links| syntax_limit(&optional_fields("x", links, "")).is_none())
1430            .last()
1431            .expect("a one-link chain is accepted");
1432        assert!(longest < MAX_SYNTAX_HEIGHT as usize - 1);
1433        longest
1434    }
1435
1436    #[test]
1437    fn syntax_height_counts_each_optional_chain_link() {
1438        let longest = longest_accepted_field_chain();
1439        let over = optional_fields("x", longest + 1, "");
1440        let diagnostic = syntax_limit(&over).expect("one more link is rejected");
1441        assert_eq!(diagnostic.span.start as usize, over.find("x?.").unwrap());
1442        assert!(syntax_limit(&optional_fields("x", 20_000, "")).is_some());
1443    }
1444
1445    #[test]
1446    fn optional_chain_operands_sit_below_their_link() {
1447        let longest = longest_accepted_field_chain();
1448        // `.f(…)` is two links; the argument is measured under the second.
1449        assert!(syntax_limit(&optional_fields("x", longest - 3, ".f(1)")).is_none());
1450        assert!(syntax_limit(&optional_fields("x", longest - 3, ".f(- 1)")).is_some());
1451        assert!(syntax_limit(&optional_fields("x", longest - 2, "[0]")).is_none());
1452        assert!(syntax_limit(&optional_fields("x", longest - 2, "[- 0]")).is_some());
1453    }
1454
1455    #[test]
1456    fn a_long_expression_is_reported_rather_than_a_closure_it_contains() {
1457        let longest = longest_accepted_field_chain();
1458        let closure = "((): number => { const q = 1; return q; })()";
1459        let annotated = "((): number => { const q: number = 1; return q; })()";
1460        let plus_chain = |operands: usize| vec!["1"; operands].join(" + ");
1461        let closure_led_sum = |inner: usize, outer: usize| {
1462            format!(
1463                "function main(): number {{ return ((): number => {{ {{ return {}; }} }})(){}; }}",
1464                plus_chain(inner),
1465                " + 1".repeat(outer)
1466            )
1467        };
1468        // Each case pairs a source with the text its caret must start at.
1469        let cases = [
1470            // The chain's links make the function statement too tall.
1471            (optional_fields(closure, longest + 1, ""), closure),
1472            // The chain itself is over the limit.
1473            (optional_fields(closure, 20_000, ""), closure),
1474            // Block closures as the last call argument and index.
1475            (
1476                optional_fields("x", longest + 1, ".f(() => { return 1; })"),
1477                "x?.",
1478            ),
1479            (
1480                optional_fields("x", 20_000, "[(() => { return 1; })()]"),
1481                "x?.",
1482            ),
1483            // Sums led by a closure start where the closure does, with or
1484            // without an annotation on the closure's local.
1485            (
1486                format!(
1487                    "function main(): number {{ return {closure}{}; }}",
1488                    " + 1".repeat(400)
1489                ),
1490                closure,
1491            ),
1492            (
1493                format!(
1494                    "function main(): number {{ return {annotated}{}; }}",
1495                    " + 1".repeat(400)
1496                ),
1497                annotated,
1498            ),
1499            // A long outer sum wins over a shorter one inside its closure.
1500            (closure_led_sum(130, 10_000), "((): number"),
1501            // 203 inner operands tie with the outer sum, which wins; one more
1502            // and the inner sum is the longer.
1503            (closure_led_sum(203, 200), "((): number"),
1504            (closure_led_sum(204, 200), "1 + 1"),
1505            // The chain's links push the function over the limit, so its
1506            // segment counts them all rather than stopping at the sum inside
1507            // its base.
1508            (
1509                optional_fields(
1510                    &format!("((): number => {{ {{ return {}; }} }})()", plus_chain(200)),
1511                    longest + 1,
1512                    "",
1513                ),
1514                "((): number",
1515            ),
1516            // The chain's links tie with its base's height; they still decide
1517            // it, so the chain is reported.
1518            (
1519                optional_fields(
1520                    &format!(
1521                        "((): number => {{ {{ return {}; }} }})()",
1522                        plus_chain(longest - 5)
1523                    ),
1524                    longest + 1,
1525                    "",
1526                ),
1527                "((): number",
1528            ),
1529            // Of two equally tall statements, the first is reported.
1530            (
1531                format!(
1532                    "function main(): number {{ const a = {0}; const b = {0}; return 1; }}",
1533                    plus_chain(300)
1534                ),
1535                "1 + 1",
1536            ),
1537            // Of two equally tall functions, the first is reported.
1538            (
1539                format!(
1540                    "function f(): number {{ return {0}; }} function g(): number {{ return {0}; }}",
1541                    plus_chain(400)
1542                ),
1543                "1 + 1",
1544            ),
1545        ];
1546        for (case, (source, marker)) in cases.iter().enumerate() {
1547            let diagnostic = syntax_limit(source).expect("the long expression is rejected");
1548            let start = source.find(marker).unwrap();
1549            assert_eq!(diagnostic.span.start as usize, start, "case {case}");
1550        }
1551    }
1552
1553    #[test]
1554    fn syntax_cycles_are_internal_failures() {
1555        let mut ast = Ast::new();
1556        let span = Span::at(FileId(0));
1557        ast.try_push_expr(Expr {
1558            kind: ExprKind::Paren(ExprId(0)),
1559            span,
1560        })
1561        .unwrap();
1562        assert!(matches!(
1563            check_syntax(&ast),
1564            Err(CompilerFailure::Internal {
1565                stage: CompilerStage::Parse,
1566                ..
1567            })
1568        ));
1569        let mut ast = Ast::new();
1570        ast.try_push_expr(Expr {
1571            kind: ExprKind::Paren(ExprId(7)),
1572            span,
1573        })
1574        .unwrap();
1575        assert!(matches!(
1576            check_syntax(&ast),
1577            Err(CompilerFailure::Internal { .. })
1578        ));
1579    }
1580
1581    #[test]
1582    fn typed_height_is_measured_iteratively_and_rejects_cycles() {
1583        let span = Span::at(FileId(0));
1584        let mut ta = TypedAst::new();
1585        let mut previous = ta
1586            .try_push_expr(TypedExpr {
1587                kind: TypedExprKind::Number(1.0),
1588                span,
1589                ty: Type::Number,
1590            })
1591            .unwrap();
1592        for _ in 1..MAX_TYPED_HEIGHT {
1593            previous = ta
1594                .try_push_expr(TypedExpr {
1595                    kind: TypedExprKind::Unary {
1596                        op: crate::UnOp::Neg,
1597                        operand: previous,
1598                    },
1599                    span,
1600                    ty: Type::Number,
1601                })
1602                .unwrap();
1603        }
1604        check_typed(&ta, CompilerStage::Codegen).unwrap();
1605        ta.try_push_expr(TypedExpr {
1606            kind: TypedExprKind::Unary {
1607                op: crate::UnOp::Neg,
1608                operand: previous,
1609            },
1610            span,
1611            ty: Type::Number,
1612        })
1613        .unwrap();
1614        assert!(matches!(
1615            check_typed(&ta, CompilerStage::Codegen),
1616            Err(CompilerFailure::Limit {
1617                stage: CompilerStage::Codegen,
1618                ..
1619            })
1620        ));
1621
1622        let mut ta = TypedAst::new();
1623        ta.try_push_expr(TypedExpr {
1624            kind: TypedExprKind::NonNullAssert { value: ExprId(0) },
1625            span,
1626            ty: Type::Number,
1627        })
1628        .unwrap();
1629        assert!(matches!(
1630            check_typed(&ta, CompilerStage::Infer),
1631            Err(CompilerFailure::Internal {
1632                stage: CompilerStage::Infer,
1633                ..
1634            })
1635        ));
1636    }
1637
1638    /// A negation chain of `levels` typed nodes, returning its root.
1639    fn negations(ta: &mut TypedAst, levels: u32) -> ExprId {
1640        let span = Span::at(FileId(0));
1641        let mut expr = ta
1642            .try_push_expr(TypedExpr {
1643                kind: TypedExprKind::Number(1.0),
1644                span,
1645                ty: Type::Number,
1646            })
1647            .unwrap();
1648        for _ in 1..levels {
1649            expr = ta
1650                .try_push_expr(TypedExpr {
1651                    kind: TypedExprKind::Unary {
1652                        op: crate::UnOp::Neg,
1653                        operand: expr,
1654                    },
1655                    span,
1656                    ty: Type::Number,
1657                })
1658                .unwrap();
1659        }
1660        expr
1661    }
1662
1663    fn object_with_fallback_chain(links: u32, terminal_levels: u32) -> TypedAst {
1664        let mut ta = TypedAst::new();
1665        let terminal = negations(&mut ta, terminal_levels);
1666        let mut source = TypedObjectFieldSource::Literal(terminal);
1667        for _ in 1..links {
1668            source = TypedObjectFieldSource::Spread {
1669                source_index: 0,
1670                field_name: "a".into(),
1671                source_ty: Type::Number,
1672                fallback: Some(Box::new(source)),
1673            };
1674        }
1675        ta.try_push_expr(TypedExpr {
1676            kind: TypedExprKind::ObjectLiteral {
1677                members: Vec::new(),
1678                fields: vec![crate::TypedObjectFieldOrigin {
1679                    name: crate::Ident {
1680                        name: "a".into(),
1681                        span: Span::at(FileId(0)),
1682                    },
1683                    source,
1684                    optional: true,
1685                    ty: Type::Number,
1686                }],
1687            },
1688            span: Span::at(FileId(0)),
1689            ty: Type::Unknown,
1690        })
1691        .unwrap();
1692        ta
1693    }
1694
1695    #[test]
1696    fn spread_fallback_links_add_to_the_terminal_expression_height() {
1697        // Object node + fallback links + terminal chain.
1698        let within = MAX_TYPED_HEIGHT - 1 - 600;
1699        check_typed(
1700            &object_with_fallback_chain(600, within),
1701            CompilerStage::Codegen,
1702        )
1703        .unwrap();
1704        assert!(matches!(
1705            check_typed(
1706                &object_with_fallback_chain(600, within + 1),
1707                CompilerStage::Codegen
1708            ),
1709            Err(CompilerFailure::Limit { .. })
1710        ));
1711    }
1712}