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