Skip to main content

submilli_engine/
parser.rs

1use crate::compiler_error::{CompileError, CompilerFailure, CompilerStage};
2use crate::{
3    ArrayLiteralElement, ArrowBody, Ast, BinOp, Binding, CatchClause, Diagnostic, EnumInitializer,
4    EnumMember, ExportedDecl, Expr, ExprId, ExprKind, FileId, Ident, ImportKind, ImportSpecifier,
5    ObjectLiteralField, ObjectLiteralMember, ObjectPatternField, ParamDecl, PostfixOp, Severity,
6    Span, Stmt, StmtId, StmtKind, SwitchCase, SwitchDefault, Token, TokenKind, TypeAnnotation,
7    TypeAnnotationField, TypeAnnotationKind, UnOp,
8};
9
10const MAX_ERRORS: usize = 20;
11// Counts recursive grammar entries, not delimiters: expression precedence and
12// mixed statement/type nesting share this budget on a 2 MiB worker stack.
13const MAX_PARSE_DEPTH: usize = 128;
14
15/// Names a type declaration may not take, as in TypeScript.
16const BUILT_IN_TYPE_NAMES: &[&str] = &[
17    "any",
18    "bigint",
19    "boolean",
20    "never",
21    "number",
22    "object",
23    "string",
24    "symbol",
25    "undefined",
26    "unknown",
27    "void",
28];
29
30/// Where a type is being parsed. Only one production depends on it: `(T)`, spelled
31/// identically as a grouped type and as v1's rejected bare parameter list, is decided by
32/// whether a `=>` follows the matching `)`. In an arrow's return annotation that `=>` is
33/// the *body's*, so the tie-break has no signal there and `(T)` groups — which loses
34/// nothing, since a bare parameter list is rejected outright in every position.
35#[derive(Clone, Copy, PartialEq)]
36enum TypePos {
37    Anywhere,
38    ArrowReturn,
39}
40
41/// Compatibility adapter for tools that consume parser diagnostics.
42pub fn parse(source: &str, tokens: Vec<Token>, file: FileId) -> (Ast, Vec<Diagnostic>) {
43    match parse_checked(source, tokens, file) {
44        Ok(result) => result,
45        Err(error) => (Ast::new(), error.into_diagnostics(file)),
46    }
47}
48
49pub fn parse_checked(
50    source: &str,
51    tokens: Vec<Token>,
52    file: FileId,
53) -> Result<(Ast, Vec<Diagnostic>), CompileError> {
54    crate::source::SourceError::check_source_len(source.len())
55        .map_err(|error| error.into_compiler_failure(CompilerStage::Parse))?;
56    validate_token_stream(source, &tokens, file).map_err(|message| CompilerFailure::Internal {
57        stage: CompilerStage::Parse,
58        span: None,
59        message: message.into(),
60    })?;
61    let eof = tokens
62        .last()
63        .cloned()
64        .unwrap_or_else(|| Token::new(TokenKind::Eof, Span::at(file)));
65    let mut p = Parser {
66        source,
67        tokens,
68        file,
69        pos: 0,
70        ast: Ast::new(),
71        diagnostics: Vec::new(),
72        block_depth: 0,
73        class_member_body_depth: 0,
74        function_expression_body_depth: 0,
75        recursion_depth: 0,
76        recursion_limit_span: None,
77        last_as_type_end: None,
78        eof,
79        fatal: None,
80    };
81    p.parse_program();
82    if let Some(span) = p.recursion_limit_span {
83        p.fatal = Some(CompilerFailure::Limit {
84            stage: CompilerStage::Parse,
85            span: Some(span),
86            message: "parser recursion limit exceeded".into(),
87            help: vec!["simplify nested syntax or split it into separate declarations".into()],
88        });
89    }
90    if let Some(fatal) = p.fatal {
91        return Err(CompileError {
92            diagnostics: p.diagnostics,
93            fatal: Some(fatal),
94        });
95    }
96    p.ast
97        .validate_source(source, file)
98        .map_err(|error| CompileError {
99            diagnostics: p.diagnostics.clone(),
100            fatal: Some(error.into_compiler_failure(CompilerStage::Parse)),
101        })?;
102    crate::tree_height::check_syntax(&p.ast).map_err(|fatal| CompileError {
103        diagnostics: p.diagnostics.clone(),
104        fatal: Some(fatal),
105    })?;
106    Ok((p.ast, p.diagnostics))
107}
108
109fn validate_token_stream(source: &str, tokens: &[Token], file: FileId) -> Result<(), &'static str> {
110    if !matches!(tokens.last().map(|token| &token.kind), Some(TokenKind::Eof)) {
111        return Err("token stream must end with EOF");
112    }
113    let mut previous = 0;
114    for (index, token) in tokens.iter().enumerate() {
115        let span = token.span;
116        if span.file != file
117            || span.start < previous
118            || span.start > span.end
119            || source.get(span.start as usize..span.end as usize).is_none()
120        {
121            return Err("token span is outside its source or out of order");
122        }
123        if matches!(token.kind, TokenKind::Eof) && index != tokens.len() - 1 {
124            return Err("tokens follow EOF");
125        }
126        if let Some(doc) = &token.leading_doc {
127            let span = doc.span;
128            if span.file != file
129                || span.start > span.end
130                || span.end > token.span.start
131                || source.get(span.start as usize..span.end as usize) != Some(doc.text.as_str())
132                || !doc.text.starts_with("/**")
133                || !doc.text.ends_with("*/")
134            {
135                return Err("documentation metadata does not match its source");
136            }
137        }
138        previous = span.start;
139    }
140    Ok(())
141}
142
143pub(crate) struct Parser<'a> {
144    source: &'a str,
145    tokens: Vec<Token>,
146    file: FileId,
147    pos: usize,
148    ast: Ast,
149    diagnostics: Vec<Diagnostic>,
150    /// Zero at top level; used to reject `import` inside nested blocks.
151    block_depth: u32,
152    /// Nonzero while parsing a class method or constructor body; gates `this`/`super`.
153    class_member_body_depth: u32,
154    function_expression_body_depth: u32,
155    recursion_depth: usize,
156    recursion_limit_span: Option<Span>,
157    /// End offset of the type in the most recent `x as T`. A statement ending there
158    /// may end at a line break, since the type stops at one (see
159    /// [`Parser::at_statement_end`]).
160    last_as_type_end: Option<u32>,
161    eof: Token,
162    fatal: Option<CompilerFailure>,
163}
164
165impl<'a> Parser<'a> {
166    fn span(&self, start: u32, end: u32) -> Span {
167        Span {
168            file: self.file,
169            start,
170            end,
171        }
172    }
173
174    fn type_name(&mut self, span: Span) -> Option<Ident> {
175        match span.text(self.source, self.file) {
176            Ok(name) => Some(Ident {
177                name: name.to_string(),
178                span,
179            }),
180            Err(error) => {
181                self.fatal = Some(error.into_compiler_failure(CompilerStage::Parse));
182                None
183            }
184        }
185    }
186
187    fn parse_program(&mut self) {
188        while !self.is_at_eof() {
189            if self.fatal.is_some() || self.error_count() >= MAX_ERRORS {
190                break;
191            }
192            if matches!(self.peek().kind, TokenKind::Semicolon) {
193                self.advance();
194                continue;
195            }
196            let pos_before = self.pos;
197            if let Some(id) = self.parse_statement() {
198                self.ast.top_level.push(id);
199            } else {
200                if self.fatal.is_some() {
201                    break;
202                }
203                self.recover();
204                if self.pos != pos_before && matches!(self.peek().kind, TokenKind::RightBrace) {
205                    self.advance();
206                    continue;
207                }
208                // Forward-progress guarantee: advance if recover didn't move.
209                if self.pos == pos_before && !self.is_at_eof() {
210                    self.advance();
211                }
212            }
213        }
214    }
215
216    fn parse_statement(&mut self) -> Option<StmtId> {
217        self.with_recursion_limit(Self::parse_statement_inner)
218    }
219
220    fn parse_statement_inner(&mut self) -> Option<StmtId> {
221        match self.peek().kind {
222            TokenKind::Let => self.parse_let_or_const(false),
223            TokenKind::Const => self.parse_let_or_const(true),
224            TokenKind::Function => self.parse_function_decl(),
225            TokenKind::Class => self.parse_class_decl(),
226            TokenKind::Interface => self.parse_interface_decl(),
227            TokenKind::Enum => self.parse_enum_decl(),
228            TokenKind::Identifier if self.at_type_alias_head() => self.parse_type_alias_decl(),
229            TokenKind::Export => self.parse_export(),
230            TokenKind::Import => self.parse_import_decl(),
231            TokenKind::If => self.parse_if(),
232            TokenKind::While => self.parse_while(),
233            TokenKind::Do => self.parse_do_while(),
234            TokenKind::For => self.parse_for(),
235            TokenKind::Switch => self.parse_switch(),
236            TokenKind::Break => self.parse_break(),
237            TokenKind::Continue => self.parse_continue(),
238            TokenKind::Return => self.parse_return(),
239            TokenKind::Throw => self.parse_throw(),
240            TokenKind::Try => self.parse_try(),
241            TokenKind::LeftBrace => self.parse_block(),
242            _ => self.parse_expression_statement(),
243        }
244    }
245
246    fn parse_expression_statement(&mut self) -> Option<StmtId> {
247        let expr_id = self.parse_expression()?;
248        let expr_span = parse_arena_result(self.ast.try_expr(expr_id), &mut self.fatal)?.span;
249        if !self.at_statement_end() {
250            let what = if matches!(
251                parse_arena_result(self.ast.try_expr(expr_id), &mut self.fatal)?.kind,
252                ExprKind::Assign { .. }
253            ) {
254                "assignment"
255            } else {
256                "expression"
257            };
258            self.error_at_peek(format!("expected `;` after {what}"));
259            return None;
260        }
261        let end = self.finish_statement();
262        let span = self.span(expr_span.start, end);
263        let kind = self.statement_kind_for(expr_id)?;
264        parse_arena_result(self.ast.try_push_stmt(Stmt { kind, span }), &mut self.fatal)
265    }
266
267    /// An assignment in statement position becomes an assignment statement;
268    /// only one used as a value stays an `ExprKind::Assign`.
269    fn statement_kind_for(&mut self, expr_id: ExprId) -> Option<StmtKind> {
270        let ExprKind::Assign {
271            target,
272            op,
273            op_span,
274            value,
275        } = parse_arena_result(self.ast.try_expr(expr_id), &mut self.fatal)?
276            .kind
277            .clone()
278        else {
279            return Some(StmtKind::Expr(expr_id));
280        };
281        Some(
282            match (
283                parse_arena_result(self.ast.try_expr(target), &mut self.fatal)?
284                    .kind
285                    .clone(),
286                op,
287            ) {
288                (ExprKind::Identifier(target), None) => StmtKind::Assign { target, value },
289                (ExprKind::Identifier(target), Some(op)) => StmtKind::CompoundAssign {
290                    target,
291                    op,
292                    op_span,
293                    value,
294                },
295                (ExprKind::FieldAccess { receiver, name }, None) => StmtKind::AssignField {
296                    receiver,
297                    field_name: name,
298                    value,
299                },
300                (ExprKind::FieldAccess { receiver, name }, Some(op)) => {
301                    StmtKind::CompoundAssignField {
302                        receiver,
303                        field_name: name,
304                        op,
305                        op_span,
306                        value,
307                    }
308                }
309                (ExprKind::IndexAccess { receiver, index }, None) => StmtKind::AssignIndex {
310                    receiver,
311                    index,
312                    value,
313                },
314                (ExprKind::IndexAccess { receiver, index }, Some(op)) => {
315                    StmtKind::CompoundAssignIndex {
316                        receiver,
317                        index,
318                        op,
319                        op_span,
320                        value,
321                    }
322                }
323                _ => return self.invariant_failure("invalid assignment target"),
324            },
325        )
326    }
327
328    fn parse_let_or_const(&mut self, is_const: bool) -> Option<StmtId> {
329        let doc = self.take_leading_doc();
330        let kw = self.advance();
331
332        let binding = match self.peek().kind {
333            TokenKind::LeftBrace | TokenKind::LeftBracket => Some(self.parse_binding()?),
334            _ => None,
335        };
336
337        let name = if binding.is_none() {
338            let name_tok =
339                self.expect_binding_identifier("expected identifier after `let`/`const`")?;
340            Some(self.ident_from_token(&name_tok))
341        } else {
342            None
343        };
344
345        let ty = if matches!(self.peek().kind, TokenKind::Colon) {
346            self.advance();
347            Some(self.parse_type_annotation()?)
348        } else {
349            None
350        };
351
352        let implicit = !is_const
353            && ty.is_some()
354            && !matches!(self.peek().kind, TokenKind::Equals)
355            && matches!(self.peek().kind, TokenKind::Semicolon);
356        let value = if let (true, Some(name)) = (implicit, &name) {
357            // `let x: T;` starts as `undefined`; the typechecker requires `T` to allow it.
358            let value = parse_arena_result(
359                self.ast.try_push_expr(Expr {
360                    kind: ExprKind::Identifier(Ident {
361                        name: "undefined".to_string(),
362                        span: name.span,
363                    }),
364                    span: name.span,
365                }),
366                &mut self.fatal,
367            )?;
368            self.ast.implicit_initializers.insert(value);
369            self.ast.synthetic_undefined.insert(value);
370            value
371        } else {
372            if !matches!(self.peek().kind, TokenKind::Equals) {
373                let (msg, help) = if is_const {
374                    (
375                        "`const` declaration requires an initializer",
376                        vec!["const x: T = expr;".to_string()],
377                    )
378                } else {
379                    (
380                        "`let` declaration requires an initializer",
381                        vec![
382                            "let x: T = expr;".to_string(),
383                            "or declare one that starts empty: `let x: T | undefined;`".to_string(),
384                        ],
385                    )
386                };
387                self.error_at_peek_with_help(msg, help);
388                return None;
389            }
390            self.advance();
391            self.parse_expression()?
392        };
393
394        if !self.at_statement_end() {
395            self.error_at_peek("expected `;` after declaration");
396            return None;
397        }
398        let end = self.finish_statement();
399
400        let span = self.span(kw.span.start, end);
401        let kind = match (binding, name) {
402            (Some(binding), _) => {
403                if is_const {
404                    StmtKind::ConstPattern {
405                        binding,
406                        ty,
407                        value,
408                        doc,
409                    }
410                } else {
411                    StmtKind::LetPattern {
412                        binding,
413                        ty,
414                        value,
415                        doc,
416                    }
417                }
418            }
419            (None, Some(name)) => {
420                if is_const {
421                    StmtKind::Const {
422                        name,
423                        ty,
424                        value,
425                        doc,
426                    }
427                } else {
428                    StmtKind::Let {
429                        name,
430                        ty,
431                        value,
432                        doc,
433                    }
434                }
435            }
436            (None, None) => return self.invariant_failure("either binding or name must be Some"),
437        };
438        parse_arena_result(self.ast.try_push_stmt(Stmt { kind, span }), &mut self.fatal)
439    }
440
441    // One level only; nested patterns are rejected.
442    fn parse_binding(&mut self) -> Option<Binding> {
443        match self.peek().kind {
444            TokenKind::LeftBrace => self.parse_object_binding(),
445            TokenKind::LeftBracket => self.parse_array_binding(),
446            _ => {
447                self.error_at_peek("expected `{` or `[` to start a destructuring pattern");
448                None
449            }
450        }
451    }
452
453    fn parse_object_binding(&mut self) -> Option<Binding> {
454        let open = self.advance();
455        let mut fields: Vec<ObjectPatternField> = Vec::new();
456        let mut rest: Option<Ident> = None;
457
458        if matches!(self.peek().kind, TokenKind::RightBrace) {
459            self.error_at_peek("empty object destructuring pattern");
460            return None;
461        }
462
463        loop {
464            if matches!(self.peek().kind, TokenKind::DotDotDot) {
465                let dots = self.advance();
466                let name_tok = self.expect_binding_identifier("expected identifier after `...`")?;
467                let name = self.ident_from_token(&name_tok);
468                if !matches!(self.peek().kind, TokenKind::RightBrace) {
469                    self.error_at_peek_with_help(
470                        "rest element must be the last element in a destructuring pattern",
471                        vec!["move `...` after every other binding".to_string()],
472                    );
473                    return None;
474                }
475                rest = Some(Ident {
476                    name: name.name,
477                    span: self.span(dots.span.start, name.span.end),
478                });
479                break;
480            }
481
482            let source_tok = self.expect_property_name("expected field name in object pattern")?;
483            let source = self.ident_from_token(&source_tok);
484            let field_span_start = source.span.start;
485
486            let (local, field_end) = if matches!(self.peek().kind, TokenKind::Colon) {
487                self.advance();
488                match self.peek().kind {
489                    TokenKind::LeftBrace | TokenKind::LeftBracket => {
490                        self.error_at_peek_with_help(
491                                "nested destructuring is not supported",
492                                vec![
493                                    "destructure once and access nested fields explicitly: const { a } = obj; const inner = a.b;"
494                                        .to_string(),
495                                ],
496                            );
497                        return None;
498                    }
499                    _ => {
500                        let local_tok = self.expect_binding_identifier(
501                            "expected identifier after `:` in pattern",
502                        )?;
503                        let local = self.ident_from_token(&local_tok);
504                        let end = local.span.end;
505                        (local, end)
506                    }
507                }
508            } else {
509                if !matches!(source_tok.kind, TokenKind::Identifier) {
510                    self.error_at_peek("expected `:` after keyword field name in object pattern");
511                    return None;
512                }
513                self.reject_strict_mode_reserved_word(&source_tok);
514                self.reject_eval_or_arguments(source_tok.span);
515                let end = source.span.end;
516                (source.clone(), end)
517            };
518
519            let default = if matches!(self.peek().kind, TokenKind::Equals) {
520                self.advance();
521                Some(self.parse_expression()?)
522            } else {
523                None
524            };
525            let field_end = match default {
526                Some(value) => {
527                    parse_arena_result(self.ast.try_expr(value), &mut self.fatal)?
528                        .span
529                        .end
530                }
531                None => field_end,
532            };
533
534            fields.push(ObjectPatternField {
535                source,
536                local,
537                default,
538                span: self.span(field_span_start, field_end),
539            });
540
541            match self.peek().kind {
542                TokenKind::Comma => {
543                    self.advance();
544                    if matches!(self.peek().kind, TokenKind::RightBrace) {
545                        break;
546                    }
547                }
548                TokenKind::RightBrace => break,
549                _ => {
550                    self.error_at_peek("expected `,` or `}` in object pattern");
551                    return None;
552                }
553            }
554        }
555
556        if !matches!(self.peek().kind, TokenKind::RightBrace) {
557            self.error_at_peek("expected `}` to close object pattern");
558            return None;
559        }
560        let close = self.advance();
561
562        Some(Binding::Object {
563            fields,
564            rest,
565            span: self.span(open.span.start, close.span.end),
566        })
567    }
568
569    fn parse_array_binding(&mut self) -> Option<Binding> {
570        let open = self.advance();
571        let mut elems: Vec<Option<Ident>> = Vec::new();
572        let mut defaults = Vec::new();
573        let mut rest: Option<Ident> = None;
574
575        if matches!(self.peek().kind, TokenKind::RightBracket) {
576            self.error_at_peek("empty array destructuring pattern");
577            return None;
578        }
579
580        loop {
581            match self.peek().kind {
582                TokenKind::Comma => {
583                    elems.push(None);
584                    defaults.push(None);
585                    self.advance();
586                    if matches!(self.peek().kind, TokenKind::RightBracket) {
587                        break;
588                    }
589                    continue;
590                }
591                TokenKind::RightBracket => break,
592                TokenKind::DotDotDot => {
593                    let dots = self.advance();
594                    let name_tok =
595                        self.expect_binding_identifier("expected identifier after `...`")?;
596                    let name = self.ident_from_token(&name_tok);
597                    if !matches!(self.peek().kind, TokenKind::RightBracket) {
598                        self.error_at_peek_with_help(
599                            "rest element must be the last element in a destructuring pattern",
600                            vec!["move `...` after every other binding".to_string()],
601                        );
602                        return None;
603                    }
604                    rest = Some(Ident {
605                        name: name.name,
606                        span: self.span(dots.span.start, name.span.end),
607                    });
608                    break;
609                }
610                TokenKind::LeftBrace | TokenKind::LeftBracket => {
611                    self.error_at_peek_with_help(
612                        "nested destructuring is not supported",
613                        vec![
614                            "destructure once and access nested elements explicitly: const [a] = arr; const inner = a[0];"
615                                .to_string(),
616                        ],
617                    );
618                    return None;
619                }
620                _ => {
621                    let name_tok =
622                        self.expect_binding_identifier("expected identifier in array pattern")?;
623                    let name = self.ident_from_token(&name_tok);
624
625                    let default = if matches!(self.peek().kind, TokenKind::Equals) {
626                        self.advance();
627                        Some(self.parse_expression()?)
628                    } else {
629                        None
630                    };
631                    defaults.push(default);
632                    elems.push(Some(name));
633
634                    match self.peek().kind {
635                        TokenKind::Comma => {
636                            self.advance();
637                            if matches!(self.peek().kind, TokenKind::RightBracket) {
638                                break;
639                            }
640                        }
641                        TokenKind::RightBracket => break,
642                        _ => {
643                            self.error_at_peek("expected `,` or `]` in array pattern");
644                            return None;
645                        }
646                    }
647                }
648            }
649        }
650
651        if !matches!(self.peek().kind, TokenKind::RightBracket) {
652            self.error_at_peek("expected `]` to close array pattern");
653            return None;
654        }
655        let close = self.advance();
656
657        Some(Binding::Array {
658            elems,
659            defaults,
660            rest,
661            span: self.span(open.span.start, close.span.end),
662        })
663    }
664
665    fn parse_function_decl(&mut self) -> Option<StmtId> {
666        let doc = self.take_leading_doc();
667        let kw = self.advance();
668
669        let name_tok = self.expect_binding_identifier("expected function name")?;
670        let name = self.ident_from_token(&name_tok);
671
672        let generics = if matches!(self.peek().kind, TokenKind::LessThan) {
673            self.parse_generic_param_list()?
674        } else {
675            Vec::new()
676        };
677
678        if !matches!(self.peek().kind, TokenKind::LeftParen) {
679            self.error_at_peek("expected `(` after function name");
680            return None;
681        }
682        self.advance();
683
684        let params = self.parse_param_list(false)?;
685        self.advance(); // `)` — parse_param_list left us on it
686
687        if !matches!(self.peek().kind, TokenKind::Colon) {
688            self.error_at_peek_with_help(
689                "expected `:` and return type",
690                vec!["function name(): T { … }".to_string()],
691            );
692            return None;
693        }
694        self.advance();
695        let (return_type, type_predicate) =
696            self.parse_predicate_or_return_type(TypePos::Anywhere)?;
697
698        // A declaration nested in a method has no receiver of its own, and
699        // must not see the method's.
700        let body = self.parse_outer_this_boundary(Self::parse_block)?;
701        let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
702            .span
703            .end;
704
705        parse_arena_result(
706            self.ast.try_push_stmt(Stmt {
707                kind: StmtKind::Function {
708                    name,
709                    generics,
710                    params,
711                    return_type,
712                    type_predicate,
713                    body,
714                    doc,
715                },
716                span: self.span(kw.span.start, body_end),
717            }),
718            &mut self.fatal,
719        )
720    }
721
722    fn parse_class_decl(&mut self) -> Option<StmtId> {
723        let doc = self.take_leading_doc();
724        let kw = self.advance();
725        // Classes are collected from the module's top level only; a nested one
726        // would otherwise be dropped unchecked.
727        if self.block_depth > 0 {
728            self.error_at_with_help(
729                kw.span,
730                "a `class` must be declared at the top level of the module",
731                vec!["move this `class` outside any block or function body".to_string()],
732            );
733        }
734        let name = self.parse_type_decl_name("a", "class")?;
735
736        let generics = if matches!(self.peek().kind, TokenKind::LessThan) {
737            self.parse_generic_param_list()?
738        } else {
739            Vec::new()
740        };
741
742        let extends = if matches!(self.peek().kind, TokenKind::Extends) {
743            self.advance();
744            Some(self.parse_type_annotation()?)
745        } else {
746            None
747        };
748
749        let mut implements: Vec<crate::TypeAnnotation> = Vec::new();
750        if matches!(self.peek().kind, TokenKind::Implements) {
751            self.advance();
752            loop {
753                implements.push(self.parse_type_annotation()?);
754                if matches!(self.peek().kind, TokenKind::Comma) {
755                    self.advance();
756                    continue;
757                }
758                break;
759            }
760        }
761
762        if !matches!(self.peek().kind, TokenKind::LeftBrace) {
763            self.error_at_peek("expected `{` after class header");
764            return None;
765        }
766        self.advance();
767
768        let mut members: Vec<crate::ClassMember> = Vec::new();
769        while !matches!(self.peek().kind, TokenKind::RightBrace | TokenKind::Eof) {
770            // Stray `;` between members is accepted (TS allows it).
771            if matches!(self.peek().kind, TokenKind::Semicolon) {
772                self.advance();
773                continue;
774            }
775            members.push(self.parse_class_member()?);
776        }
777
778        if !matches!(self.peek().kind, TokenKind::RightBrace) {
779            self.error_at_peek("expected `}` to close class");
780            return None;
781        }
782        let close = self.advance();
783
784        parse_arena_result(
785            self.ast.try_push_stmt(Stmt {
786                kind: StmtKind::ClassDecl {
787                    name,
788                    generics,
789                    extends,
790                    implements,
791                    members,
792                    doc,
793                },
794                span: self.span(kw.span.start, close.span.end),
795            }),
796            &mut self.fatal,
797        )
798    }
799
800    fn parse_class_member(&mut self) -> Option<crate::ClassMember> {
801        let doc = self.take_leading_doc();
802        let member_start = self.peek().span.start;
803
804        // Excluded modifiers — reject before anything else, naming the fix.
805        if self.peek_identifier_text_is("protected") {
806            let tok = self.advance();
807            self.error_at_with_help(
808                tok.span,
809                "`protected` is not supported",
810                vec!["use `private` or `public`".to_string()],
811            );
812            return None;
813        }
814        if self.peek_identifier_text_is("abstract") {
815            let tok = self.advance();
816            self.error_at_with_help(
817                tok.span,
818                "abstract classes are not supported",
819                vec!["provide a concrete implementation instead of `abstract`".to_string()],
820            );
821            return None;
822        }
823
824        let modifiers = self.parse_class_modifiers();
825
826        // Getter/setter: `get`/`set` <name> ( … ). A member literally named `get`/`set`
827        // (followed directly by `(`) is an ordinary method and stays accepted.
828        for (kw, kind) in [
829            ("get", crate::AccessorKind::Get),
830            ("set", crate::AccessorKind::Set),
831        ] {
832            if self.peek_identifier_text_is(kw)
833                && is_property_name(&self.peek_at(1).kind)
834                && matches!(self.peek_at(2).kind, TokenKind::LeftParen)
835            {
836                if let Some(span) = modifiers.readonly {
837                    self.reject_readonly_modifier(span);
838                }
839                if let Some(span) = modifiers.static_span {
840                    self.error_at_with_help(
841                        span,
842                        "static accessors are not supported",
843                        vec!["use a static method: `static name(): T { … }`".to_string()],
844                    );
845                    return None;
846                }
847                return self.parse_accessor(member_start, modifiers, kind, doc);
848            }
849        }
850
851        // Constructor.
852        if self.peek_identifier_text_is("constructor")
853            && matches!(self.peek_at(1).kind, TokenKind::LeftParen)
854        {
855            if let Some(span) = modifiers.static_span {
856                self.error_at_with_help(
857                    span,
858                    "a constructor cannot be `static`",
859                    vec![
860                        "remove `static` — the constructor already belongs to the class, not \
861                         instances"
862                            .to_string(),
863                    ],
864                );
865                return None;
866            }
867            if let Some(span) = modifiers.readonly {
868                self.error_at_with_help(
869                    span,
870                    "a constructor cannot be `readonly`",
871                    vec!["remove `readonly`; it applies to fields".to_string()],
872                );
873                return None;
874            }
875            return self.parse_constructor(member_start, modifiers.visibility, doc);
876        }
877
878        let name = self.expect_property_ident("expected class member name")?;
879        let optional_span = if matches!(self.peek().kind, TokenKind::Question) {
880            Some(self.advance().span)
881        } else {
882            None
883        };
884        let optional = optional_span.is_some();
885
886        // Method.
887        if matches!(self.peek().kind, TokenKind::LessThan | TokenKind::LeftParen) {
888            if let Some(span) = modifiers.readonly {
889                self.reject_readonly_modifier(span);
890            }
891            let generics = if matches!(self.peek().kind, TokenKind::LessThan) {
892                self.parse_generic_param_list()?
893            } else {
894                Vec::new()
895            };
896            if !matches!(self.peek().kind, TokenKind::LeftParen) {
897                self.error_at_peek("expected `(` to start the method parameter list");
898                return None;
899            }
900            self.advance();
901            let params = self.parse_class_member_params(false)?;
902            self.advance(); // `)` — parse_param_list left us on it
903            if !matches!(self.peek().kind, TokenKind::Colon) {
904                self.error_at_peek_with_help(
905                    "expected `:` and return type",
906                    vec!["method name(): T { … }".to_string()],
907                );
908                return None;
909            }
910            self.advance();
911            let return_type = self.parse_type_annotation()?;
912            if self.at_inserted_semicolon() && matches!(self.peek_at(1).kind, TokenKind::LeftBrace)
913            {
914                self.advance();
915            }
916            if optional
917                && matches!(
918                    self.peek().kind,
919                    TokenKind::Semicolon | TokenKind::RightBrace
920                )
921            {
922                if !generics.is_empty() {
923                    self.error_at(
924                        name.span,
925                        "generic optional method declarations are not supported",
926                    );
927                    return None;
928                }
929                let end = if matches!(self.peek().kind, TokenKind::Semicolon) {
930                    self.advance().span.end
931                } else {
932                    return_type.span.end
933                };
934                let ty = self.method_declaration_type(
935                    params,
936                    return_type,
937                    self.span(name.span.start, end),
938                )?;
939                return Some(crate::ClassMember::Field {
940                    name,
941                    modifiers,
942                    optional: true,
943                    method_signature: true,
944                    ty,
945                    initializer: None,
946                    span: self.span(member_start, end),
947                    doc,
948                });
949            }
950            let body = self.parse_class_member_body()?;
951            let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
952                .span
953                .end;
954            return Some(crate::ClassMember::Method {
955                name,
956                optional,
957                modifiers,
958                generics,
959                params,
960                return_type,
961                body,
962                span: self.span(member_start, body_end),
963                doc,
964            });
965        }
966
967        // Field.
968        if let Some(span) = optional_span
969            && modifiers.static_span.is_some()
970        {
971            self.error_at_with_help(
972                span,
973                "a static field cannot be optional",
974                vec!["a static field must be initialized; give it a value with `= …`".to_string()],
975            );
976            return None;
977        }
978        if !matches!(self.peek().kind, TokenKind::Colon) {
979            if matches!(self.peek().kind, TokenKind::Equals) {
980                self.error_at_peek_with_help(
981                    "class fields require a type annotation",
982                    vec!["add `: T` before the initializer, e.g. `name: string = …`".to_string()],
983                );
984            } else {
985                self.error_at_peek("expected `:` and a type for the class field");
986            }
987            return None;
988        }
989        self.advance();
990        let ty = self.parse_type_annotation()?;
991        let initializer = if matches!(self.peek().kind, TokenKind::Equals) {
992            self.advance();
993            // A field initializer may read `this` (the typechecker binds it); gate
994            // it like a member body so `this.x` parses.
995            self.class_member_body_depth += 1;
996            let init = self.parse_expression();
997            self.class_member_body_depth -= 1;
998            Some(init?)
999        } else {
1000            None
1001        };
1002        if !matches!(self.peek().kind, TokenKind::Semicolon) {
1003            self.error_at_peek("expected `;` after class field");
1004            return None;
1005        }
1006        let semi = self.advance();
1007        Some(crate::ClassMember::Field {
1008            name,
1009            modifiers,
1010            optional,
1011            method_signature: false,
1012            ty,
1013            initializer,
1014            span: self.span(member_start, semi.span.end),
1015            doc,
1016        })
1017    }
1018
1019    fn parse_constructor(
1020        &mut self,
1021        member_start: u32,
1022        visibility: crate::Visibility,
1023        doc: Option<crate::DocComment>,
1024    ) -> Option<crate::ClassMember> {
1025        self.advance(); // `constructor`
1026        self.advance(); // `(` — guaranteed by the caller
1027        let params = self.parse_class_member_params(true)?;
1028        self.advance(); // `)` — parse_param_list left us on it
1029        if matches!(self.peek().kind, TokenKind::Colon) {
1030            let colon = self.advance();
1031            let _ = self.parse_type_annotation();
1032            self.error_at_with_help(
1033                colon.span,
1034                "a constructor cannot declare a return type",
1035                vec![
1036                    "drop the `: T` — a constructor always returns the class instance".to_string(),
1037                ],
1038            );
1039        }
1040        let body = self.parse_class_member_body()?;
1041        let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
1042            .span
1043            .end;
1044        Some(crate::ClassMember::Constructor {
1045            visibility,
1046            params,
1047            body,
1048            span: self.span(member_start, body_end),
1049            doc,
1050        })
1051    }
1052
1053    /// `get name(): T { … }` / `set name(v: T) { … }`. The caller has confirmed the
1054    /// `get`/`set` keyword, a property name, and a `(` follow.
1055    fn parse_accessor(
1056        &mut self,
1057        member_start: u32,
1058        modifiers: crate::ClassModifiers,
1059        kind: crate::AccessorKind,
1060        doc: Option<crate::DocComment>,
1061    ) -> Option<crate::ClassMember> {
1062        self.advance(); // `get` / `set`
1063        let name = self.expect_property_ident("expected accessor name")?;
1064        self.advance(); // `(` — guaranteed by the caller
1065        let mut params = self.parse_class_member_params(false)?;
1066        self.advance(); // `)`
1067        let param = match kind {
1068            crate::AccessorKind::Get => {
1069                if !params.is_empty() {
1070                    self.error_at(name.span, "a getter cannot declare parameters");
1071                }
1072                None
1073            }
1074            crate::AccessorKind::Set => {
1075                if params.len() != 1 {
1076                    self.error_at(name.span, "a setter must declare exactly one parameter");
1077                    return None;
1078                }
1079                Some(Box::new(params.remove(0)))
1080            }
1081        };
1082        let return_type = if matches!(self.peek().kind, TokenKind::Colon) {
1083            self.advance();
1084            Some(self.parse_type_annotation()?)
1085        } else {
1086            // A getter's type is its return type, which, as for a method, is
1087            // written rather than inferred. The body still parses, so the
1088            // rest of the class is checked.
1089            if kind == crate::AccessorKind::Get {
1090                self.error_at_peek_with_help(
1091                    "expected `:` and return type",
1092                    vec!["get name(): T { … }".to_string()],
1093                );
1094            }
1095            None
1096        };
1097        let body = self.parse_class_member_body()?;
1098        let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
1099            .span
1100            .end;
1101        Some(crate::ClassMember::Accessor {
1102            name,
1103            modifiers,
1104            kind,
1105            param,
1106            return_type,
1107            body,
1108            span: self.span(member_start, body_end),
1109            doc,
1110        })
1111    }
1112
1113    /// `public`/`private`/`static`/`readonly`. Duplicate or conflicting modifiers are
1114    /// diagnosed but parsing continues so the member shape is still recovered.
1115    /// Visibility must precede `static` and `readonly` (the TypeScript order);
1116    /// `readonly` is accepted on either side of `static` — no semantic difference
1117    /// under our subset.
1118    fn parse_class_modifiers(&mut self) -> crate::ClassModifiers {
1119        let mut visibility = crate::Visibility::Public;
1120        let mut visibility_span: Option<Span> = None;
1121        let mut readonly: Option<Span> = None;
1122        let mut static_span: Option<Span> = None;
1123        loop {
1124            if self.peek_word_is_class_modifier("public")
1125                || self.peek_word_is_class_modifier("private")
1126            {
1127                let (kind, word) = if self.peek_identifier_text_is("private") {
1128                    (crate::Visibility::Private, "private")
1129                } else {
1130                    (crate::Visibility::Public, "public")
1131                };
1132                let tok = self.advance();
1133                if visibility_span.is_some() {
1134                    self.error_at_with_help(
1135                        tok.span,
1136                        "a class member may have at most one visibility modifier",
1137                        vec!["keep a single `public` or `private`".to_string()],
1138                    );
1139                    continue;
1140                }
1141                // TypeScript reports one misplaced modifier per member; `static` is
1142                // named first when both precede.
1143                let preceding = static_span
1144                    .map(|_| "static")
1145                    .or(readonly.map(|_| "readonly"));
1146                if let Some(preceding) = preceding {
1147                    self.error_at_with_help(
1148                        tok.span,
1149                        format!("`{word}` must come before `{preceding}`"),
1150                        vec![format!("write `{word} {preceding} <name>`")],
1151                    );
1152                }
1153                visibility = kind;
1154                visibility_span = Some(tok.span);
1155                continue;
1156            }
1157            if self.peek_word_is_class_modifier("static") {
1158                let tok = self.advance();
1159                if static_span.is_some() {
1160                    self.error_at(tok.span, "duplicate `static` modifier");
1161                } else {
1162                    static_span = Some(tok.span);
1163                }
1164                continue;
1165            }
1166            if self.peek_word_is_class_modifier("readonly") {
1167                let tok = self.advance();
1168                if readonly.is_some() {
1169                    self.error_at(tok.span, "duplicate `readonly` modifier");
1170                } else {
1171                    readonly = Some(tok.span);
1172                }
1173                continue;
1174            }
1175            break;
1176        }
1177        crate::ClassModifiers {
1178            visibility,
1179            visibility_span,
1180            readonly,
1181            static_span,
1182        }
1183    }
1184
1185    /// A contextual modifier keyword counts as a modifier only when another member token
1186    /// (the real name, or a further modifier) follows — otherwise the word is the member
1187    /// name itself (e.g. a field named `private`). Mirrors `eat_readonly_property_modifier`.
1188    /// As in TypeScript, only `static` may be followed by a line break; in a class body
1189    /// ASI already ends the member there, but a constructor parameter list has no ASI.
1190    fn peek_word_is_class_modifier(&self, word: &str) -> bool {
1191        if !self.peek_identifier_text_is(word) {
1192            return false;
1193        }
1194        if word != "static" && self.line_break_after_peek() {
1195            return false;
1196        }
1197        let next = &self.peek_at(1).kind;
1198        is_property_name(next) || matches!(next, TokenKind::StringLiteral(_))
1199    }
1200
1201    fn parse_class_member_params(
1202        &mut self,
1203        allow_param_properties: bool,
1204    ) -> Option<Vec<ParamDecl>> {
1205        let saved = self.class_member_body_depth;
1206        self.class_member_body_depth = 1;
1207        let params = self.parse_param_list(allow_param_properties);
1208        self.class_member_body_depth = saved;
1209        params
1210    }
1211
1212    fn parse_class_member_body(&mut self) -> Option<StmtId> {
1213        self.class_member_body_depth += 1;
1214        let body = self.parse_block();
1215        self.class_member_body_depth -= 1;
1216        body
1217    }
1218
1219    fn parse_interface_decl(&mut self) -> Option<StmtId> {
1220        let doc = self.take_leading_doc();
1221        let kw = self.advance();
1222        let name = self.parse_type_decl_name("an", "interface")?;
1223
1224        let generics = if matches!(self.peek().kind, TokenKind::LessThan) {
1225            self.parse_generic_param_list()?
1226        } else {
1227            Vec::new()
1228        };
1229
1230        let mut extends = Vec::new();
1231        if matches!(self.peek().kind, TokenKind::Extends) {
1232            self.advance();
1233            loop {
1234                extends.push(self.parse_type_annotation()?);
1235                if !matches!(self.peek().kind, TokenKind::Comma) {
1236                    break;
1237                }
1238                self.advance();
1239            }
1240        }
1241        if !matches!(self.peek().kind, TokenKind::LeftBrace) {
1242            self.error_at_peek("expected `{` after interface name");
1243            return None;
1244        }
1245        self.advance();
1246
1247        let mut members: Vec<crate::InterfaceMember> = Vec::new();
1248        while !matches!(self.peek().kind, TokenKind::RightBrace | TokenKind::Eof)
1249            && !self.peek_starts_declaration()
1250        {
1251            // Call signatures `(params): ret;` are stored under the sentinel name `@call`.
1252            // The `@` prefix is not a valid identifier start, so collisions with user methods
1253            // are impossible.
1254            let member_doc = self.take_leading_doc();
1255            if matches!(self.peek().kind, TokenKind::LeftParen) {
1256                let open = self.advance();
1257                let params = self.parse_param_list(false)?;
1258                self.advance();
1259                if !matches!(self.peek().kind, TokenKind::Colon) {
1260                    self.error_at_peek("expected `:` and return type");
1261                    return None;
1262                }
1263                self.advance();
1264                let return_type = self.parse_type_annotation()?;
1265                let end = self.finish_interface_member(return_type.span.end)?;
1266                let span = self.span(open.span.start, end);
1267                let sentinel = Ident {
1268                    name: "@call".to_string(),
1269                    span: open.span,
1270                };
1271                members.push(crate::InterfaceMember::Method {
1272                    name: sentinel,
1273                    optional: false,
1274                    generics: Vec::new(),
1275                    params,
1276                    return_type,
1277                    span,
1278                    doc: member_doc,
1279                });
1280                continue;
1281            }
1282            if matches!(self.peek().kind, TokenKind::LessThan) {
1283                self.error_at_peek("generic call signatures are not yet supported");
1284                return None;
1285            }
1286            let member_start = self.peek().span.start;
1287            let readonly = self.eat_readonly_property_modifier();
1288            if self.peek_is_parameterless_index_signature() {
1289                self.skip_parameterless_index_signature(member_start)?;
1290                self.finish_interface_member(self.prev_token_end())?;
1291                continue;
1292            }
1293            if matches!(self.peek().kind, TokenKind::LeftBracket) {
1294                let signature = self.parse_index_signature(readonly)?;
1295                self.finish_interface_member(signature.span.end)?;
1296                members.push(crate::InterfaceMember::IndexSignature(signature));
1297                continue;
1298            }
1299            let member_name = self.expect_property_ident("expected interface member name")?;
1300            let optional = matches!(self.peek().kind, TokenKind::Question);
1301            if optional {
1302                self.advance();
1303            }
1304            if matches!(self.peek().kind, TokenKind::Colon) {
1305                self.advance();
1306                let ty = self.parse_type_annotation()?;
1307                let end = self.finish_interface_member(ty.span.end)?;
1308                let span = self.span(member_name.span.start, end);
1309                members.push(crate::InterfaceMember::Property {
1310                    name: member_name,
1311                    ty,
1312                    optional,
1313                    readonly,
1314                    span,
1315                    doc: member_doc,
1316                });
1317                continue;
1318            }
1319            if readonly {
1320                self.reject_readonly_modifier(self.readonly_modifier_span(member_start));
1321            }
1322            let m_generics = if matches!(self.peek().kind, TokenKind::LessThan) {
1323                self.parse_generic_param_list()?
1324            } else {
1325                Vec::new()
1326            };
1327            if !matches!(self.peek().kind, TokenKind::LeftParen) {
1328                self.error_at_peek(
1329                    "expected `(` to start a method signature or `:` to start a property",
1330                );
1331                return None;
1332            }
1333            self.advance();
1334            let params = self.parse_param_list(false)?;
1335            self.advance();
1336            if !matches!(self.peek().kind, TokenKind::Colon) {
1337                self.error_at_peek("expected `:` and return type");
1338                return None;
1339            }
1340            self.advance();
1341            let return_type = self.parse_type_annotation()?;
1342            let end = self.finish_interface_member(return_type.span.end)?;
1343            let span = self.span(member_name.span.start, end);
1344            if optional {
1345                if !m_generics.is_empty() {
1346                    self.error_at(
1347                        member_name.span,
1348                        "generic optional method declarations are not supported",
1349                    );
1350                    return None;
1351                }
1352                let ty = self.method_declaration_type(params, return_type, span)?;
1353                members.push(crate::InterfaceMember::Property {
1354                    name: member_name,
1355                    ty,
1356                    optional: true,
1357                    readonly,
1358                    span,
1359                    doc: member_doc,
1360                });
1361            } else {
1362                members.push(crate::InterfaceMember::Method {
1363                    name: member_name,
1364                    optional,
1365                    generics: m_generics,
1366                    params,
1367                    return_type,
1368                    span,
1369                    doc: member_doc,
1370                });
1371            }
1372        }
1373
1374        if !matches!(self.peek().kind, TokenKind::RightBrace) {
1375            self.error_at_peek("expected `}` to close interface");
1376            return None;
1377        }
1378        let close = self.advance();
1379
1380        parse_arena_result(
1381            self.ast.try_push_stmt(Stmt {
1382                kind: StmtKind::InterfaceDecl {
1383                    name,
1384                    generics,
1385                    extends,
1386                    members,
1387                    doc,
1388                },
1389                span: self.span(kw.span.start, close.span.end),
1390            }),
1391            &mut self.fatal,
1392        )
1393    }
1394
1395    fn method_declaration_type(
1396        &mut self,
1397        params: Vec<ParamDecl>,
1398        return_type: TypeAnnotation,
1399        span: Span,
1400    ) -> Option<TypeAnnotation> {
1401        let mut fields = Vec::new();
1402        for param in params {
1403            if param.pattern.is_some() || param.default.is_some() {
1404                self.error_at(
1405                    param.name.span,
1406                    "a method declaration cannot contain a parameter pattern or default value",
1407                );
1408                return None;
1409            }
1410            let Some(ty) = param.ty else {
1411                return self.invariant_failure("method parameter annotation was not retained");
1412            };
1413            fields.push(TypeAnnotationField {
1414                name: param.name,
1415                ty,
1416                optional: param.optional,
1417                readonly: false,
1418                rest: param.rest,
1419                method: false,
1420            });
1421        }
1422        Some(TypeAnnotation {
1423            kind: TypeAnnotationKind::Function {
1424                params: fields,
1425                return_type: Box::new(return_type),
1426            },
1427            span,
1428        })
1429    }
1430
1431    fn finish_interface_member(&mut self, body_end: u32) -> Option<u32> {
1432        self.finish_type_member(body_end, "expected `;`, `,`, or `}` after interface member")
1433    }
1434
1435    /// Consume the separator after an interface or type-literal member and return
1436    /// where the member ends. `;` and `,` are interchangeable and the last member's
1437    /// is optional. A line break also ends a member, as in TypeScript: ASI inserts
1438    /// `;` there, but keeps some tokens, such as `[` and `(`, attached. `body_end` is
1439    /// where the member ends when it carries no separator of its own.
1440    fn finish_type_member(&mut self, body_end: u32, message: &str) -> Option<u32> {
1441        match self.peek().kind {
1442            TokenKind::Semicolon | TokenKind::Comma => Some(self.advance().span.end),
1443            TokenKind::RightBrace => Some(body_end),
1444            _ if self.line_break_before_peek() => Some(body_end),
1445            _ => {
1446                self.error_at_peek(message);
1447                None
1448            }
1449        }
1450    }
1451
1452    fn parse_type_alias_decl(&mut self) -> Option<StmtId> {
1453        let doc = self.take_leading_doc();
1454        let kw = self.advance();
1455        let name = self.parse_type_decl_name("a", "type alias")?;
1456
1457        let generics: Vec<Ident> = if matches!(self.peek().kind, TokenKind::LessThan) {
1458            self.parse_generic_param_list()?
1459        } else {
1460            Vec::new()
1461        };
1462
1463        if !matches!(self.peek().kind, TokenKind::Equals) {
1464            self.error_at_peek("expected `=` after type alias name");
1465            return None;
1466        }
1467        self.advance();
1468
1469        let ty = self.parse_type_annotation()?;
1470
1471        // ASI keeps a `[` on the next line attached, but the type already ended at
1472        // the line break (see `parse_type_array_inner`), so the break ends the alias.
1473        let end = if matches!(self.peek().kind, TokenKind::Semicolon) {
1474            self.advance().span.end
1475        } else if self.line_break_before_peek() {
1476            self.prev_token_end()
1477        } else {
1478            self.error_at_peek("expected `;` after type alias body");
1479            return None;
1480        };
1481
1482        parse_arena_result(
1483            self.ast.try_push_stmt(Stmt {
1484                kind: StmtKind::TypeAliasDecl {
1485                    name,
1486                    generics,
1487                    ty,
1488                    doc,
1489                },
1490                span: self.span(kw.span.start, end),
1491            }),
1492            &mut self.fatal,
1493        )
1494    }
1495
1496    // Parser enforces kind-coherence: an enum cannot mix numeric and string initializers.
1497    fn parse_enum_decl(&mut self) -> Option<StmtId> {
1498        #[derive(Copy, Clone)]
1499        enum Kind {
1500            Number,
1501            String,
1502        }
1503        let doc = self.take_leading_doc();
1504        let kw = self.advance();
1505        let name = self.parse_type_decl_name("an", "enum")?;
1506
1507        if !matches!(self.peek().kind, TokenKind::LeftBrace) {
1508            self.error_at_peek("expected `{` after enum name");
1509            return None;
1510        }
1511        self.advance();
1512
1513        let mut members: Vec<EnumMember> = Vec::new();
1514        let mut first_explicit: Option<(Kind, Span)> = None;
1515        while !matches!(self.peek().kind, TokenKind::RightBrace | TokenKind::Eof) {
1516            // ASI inserts `;` after `,` on a newline — tolerate it so multi-line enums parse.
1517            if matches!(self.peek().kind, TokenKind::Semicolon) {
1518                self.advance();
1519                continue;
1520            }
1521            let member_doc = self.take_leading_doc();
1522            let member_name_tok = self.expect_identifier_name("expected enum member name")?;
1523            let member_name = self.ident_from_token(&member_name_tok);
1524
1525            let value = if matches!(self.peek().kind, TokenKind::Equals) {
1526                self.advance();
1527                let init = self.parse_enum_initializer()?;
1528                let (this_kind, this_span) = match &init {
1529                    EnumInitializer::Number { span, .. } => (Kind::Number, *span),
1530                    EnumInitializer::String { span, .. } => (Kind::String, *span),
1531                };
1532                match first_explicit {
1533                    None => first_explicit = Some((this_kind, this_span)),
1534                    Some((prev, _)) => {
1535                        let mismatched = matches!(
1536                            (prev, this_kind),
1537                            (Kind::Number, Kind::String) | (Kind::String, Kind::Number)
1538                        );
1539                        if mismatched {
1540                            self.error_at(
1541                                this_span,
1542                                "mixed numeric and string enum members are not allowed",
1543                            );
1544                        }
1545                    }
1546                }
1547                Some(init)
1548            } else {
1549                None
1550            };
1551
1552            let end_span = value
1553                .as_ref()
1554                .map_or(member_name.span, super::ast::EnumInitializer::span);
1555            let member_span = self.span(member_name.span.start, end_span.end);
1556            members.push(EnumMember {
1557                name: member_name,
1558                value,
1559                span: member_span,
1560                doc: member_doc,
1561            });
1562
1563            match self.peek().kind {
1564                // ASI supplies the `;` before a closing `}` and after a newline, so an
1565                // enum body sees one wherever a comma was omitted.
1566                TokenKind::Comma | TokenKind::Semicolon => {
1567                    self.advance();
1568                }
1569                TokenKind::RightBrace | TokenKind::Eof => {}
1570                _ => {
1571                    self.error_at_peek("expected `,` or `}` after enum member");
1572                    return None;
1573                }
1574            }
1575        }
1576
1577        if !matches!(self.peek().kind, TokenKind::RightBrace) {
1578            self.error_at_peek("expected `}` to close enum");
1579            return None;
1580        }
1581        let close = self.advance();
1582
1583        parse_arena_result(
1584            self.ast.try_push_stmt(Stmt {
1585                kind: StmtKind::EnumDecl { name, members, doc },
1586                span: self.span(kw.span.start, close.span.end),
1587            }),
1588            &mut self.fatal,
1589        )
1590    }
1591
1592    /// `export` on a top-level declaration. Two forms (multi-file.md §3):
1593    /// Form 1 marks a declaration (`export function …`) — the declaration is
1594    /// parsed normally and recorded in `exported_decls`; Form 2 is a re-export
1595    /// (`export { a, b as c } from "./util";` / `export { x };`) parsed into an
1596    /// `ExportFrom` node. `export` is top-level only; inside a function body it
1597    /// is a parse error. `export default` stays rejected.
1598    fn parse_export(&mut self) -> Option<StmtId> {
1599        let export_tok = self.advance();
1600        let export_span = export_tok.span;
1601
1602        let nested = self.block_depth > 0;
1603        if nested {
1604            self.error_at_with_help(
1605                export_span,
1606                "`export` statements must appear at the top of the file",
1607                vec!["move this `export` to the top level of the module".to_string()],
1608            );
1609            // Fall through to parse the declaration / list anyway so recovery
1610            // stays in sync, but return None below so it isn't recorded.
1611        }
1612
1613        // `export type { … }` — TS's type-only re-export, accepted as a no-op
1614        // synonym: exports carry both value and type spaces here.
1615        if self.peek_identifier_text_is("type")
1616            && matches!(self.peek_at(1).kind, TokenKind::LeftBrace)
1617        {
1618            self.advance();
1619        }
1620
1621        let declares = matches!(
1622            self.peek().kind,
1623            TokenKind::Function
1624                | TokenKind::Class
1625                | TokenKind::Const
1626                | TokenKind::Let
1627                | TokenKind::Interface
1628                | TokenKind::Enum
1629        ) || self.at_type_alias_head();
1630        if declares {
1631            // Carry any doc comment that attached to `export` onto the
1632            // declaration token so the declaration parser still sees it.
1633            if export_tok.leading_doc.is_some() && self.peek().leading_doc.is_none() {
1634                if let Some(token) = self.tokens.get_mut(self.pos) {
1635                    token.leading_doc = export_tok.leading_doc;
1636                } else {
1637                    return self.invariant_failure("export cursor is outside token stream");
1638                }
1639            }
1640            let id = self.parse_statement()?;
1641            if nested {
1642                return None;
1643            }
1644            self.ast.exported_decls.push(ExportedDecl {
1645                stmt: id,
1646                export_span,
1647            });
1648            return Some(id);
1649        }
1650
1651        match self.peek().kind {
1652            TokenKind::LeftBrace => {
1653                // A doc comment attaches to the `export` keyword, which we've
1654                // already consumed — carry it onto the re-export node.
1655                let doc = export_tok
1656                    .leading_doc
1657                    .as_ref()
1658                    .and_then(|raw| self.parse_doc(raw))
1659                    .or_else(|| self.take_leading_doc());
1660                self.parse_export_from(export_span, nested, doc)
1661            }
1662            TokenKind::Default => {
1663                self.error_at_peek_with_help(
1664                    "`export default` is not supported",
1665                    vec![
1666                        "default exports are out of scope; drop `default` — a named \
1667                         `export` marks the declaration visible to sibling modules"
1668                            .to_string(),
1669                    ],
1670                );
1671                None
1672            }
1673            _ => {
1674                self.error_at_peek_with_help(
1675                    "expected a declaration after `export`",
1676                    vec![
1677                        "`export` may precede `function`, `class`, `const`, `let`, `type`, \
1678                         `interface`, or `enum`, or take the form `export { … }`"
1679                            .to_string(),
1680                    ],
1681                );
1682                None
1683            }
1684        }
1685    }
1686
1687    /// Parse Form 2 re-export: `export { a, b as c } from "./util";` or the
1688    /// bare `export { x };`. The leading `export` has already been consumed.
1689    fn parse_export_from(
1690        &mut self,
1691        export_span: Span,
1692        nested: bool,
1693        doc: Option<crate::DocComment>,
1694    ) -> Option<StmtId> {
1695        let (specs, open_span) = self.parse_specifier_list(SpecifierList::Export)?;
1696        if specs.is_empty() {
1697            self.error_at_with_help(
1698                open_span,
1699                "empty export specifier list",
1700                vec!["name what you re-export: `export { name } from \"./util\";`".to_string()],
1701            );
1702            return None;
1703        }
1704
1705        let source = if self.peek_identifier_text_is("from") {
1706            self.advance();
1707            let module_tok = self.peek().clone();
1708            let module = if let TokenKind::StringLiteral(s) = &module_tok.kind {
1709                s.clone()
1710            } else {
1711                self.error_at_peek("expected module specifier string after `from`");
1712                return None;
1713            };
1714            self.advance();
1715            Some((module, module_tok.span))
1716        } else {
1717            None
1718        };
1719
1720        if !matches!(self.peek().kind, TokenKind::Semicolon) {
1721            self.error_at_peek("expected `;` after export statement");
1722            return None;
1723        }
1724        let semi = self.advance();
1725
1726        if nested {
1727            return None;
1728        }
1729
1730        parse_arena_result(
1731            self.ast.try_push_stmt(Stmt {
1732                kind: StmtKind::ExportFrom { specs, source, doc },
1733                span: self.span(export_span.start, semi.span.end),
1734            }),
1735            &mut self.fatal,
1736        )
1737    }
1738
1739    fn parse_import_decl(&mut self) -> Option<StmtId> {
1740        let doc = self.take_leading_doc();
1741        let kw = self.advance();
1742        let kw_span = kw.span;
1743
1744        let nested = self.block_depth > 0;
1745        if nested {
1746            self.error_at_with_help(
1747                kw_span,
1748                "`import` statements must appear at the top of the file",
1749                vec!["move this `import` outside any block / function body".to_string()],
1750            );
1751            // Continue parsing to consume the whole import so recovery isn't confused.
1752        }
1753
1754        let kind = self.parse_import_kind()?;
1755
1756        if !self.peek_identifier_text_is("from") {
1757            self.error_at_peek("expected `from` after import specifier list");
1758            return None;
1759        }
1760        self.advance();
1761
1762        let module_tok = self.peek().clone();
1763        let module = if let TokenKind::StringLiteral(s) = &module_tok.kind {
1764            s.clone()
1765        } else {
1766            self.error_at_peek("expected module specifier string after `from`");
1767            return None;
1768        };
1769        let module_span = module_tok.span;
1770        self.advance();
1771
1772        if !matches!(self.peek().kind, TokenKind::Semicolon) {
1773            self.error_at_peek("expected `;` after import statement");
1774            return None;
1775        }
1776        let semi = self.advance();
1777
1778        if nested {
1779            return None;
1780        }
1781
1782        parse_arena_result(
1783            self.ast.try_push_stmt(Stmt {
1784                kind: StmtKind::Import {
1785                    module,
1786                    module_span,
1787                    kind,
1788                    doc,
1789                },
1790                span: self.span(kw_span.start, semi.span.end),
1791            }),
1792            &mut self.fatal,
1793        )
1794    }
1795
1796    fn parse_import_kind(&mut self) -> Option<ImportKind> {
1797        // `import type …` — TS's type-only import, accepted as a no-op synonym:
1798        // imports carry both value and type spaces here. `import type from "m"`
1799        // stays a namespace import named `type`, so `type` is a modifier only
1800        // when what follows can't be a complete import clause without it.
1801        if self.peek_identifier_text_is("type")
1802            && match self.peek_at(1).kind {
1803                TokenKind::LeftBrace | TokenKind::Star => true,
1804                TokenKind::Identifier => {
1805                    !(self.peek_at_is_word(1, "from")
1806                        && matches!(self.peek_at(2).kind, TokenKind::StringLiteral(_)))
1807                }
1808                _ => false,
1809            }
1810        {
1811            self.advance();
1812        }
1813
1814        match self.peek().kind {
1815            TokenKind::LeftBrace => self.parse_named_imports(),
1816            TokenKind::Identifier => {
1817                let local_tok = self.advance();
1818                let local_name = self.ident_from_token(&local_tok);
1819                if matches!(self.peek().kind, TokenKind::Comma) {
1820                    self.error_at_peek_with_help(
1821                        "combining default and named imports is not supported",
1822                        vec!["use two separate `import` statements".to_string()],
1823                    );
1824                    return None;
1825                }
1826                Some(ImportKind::Namespace { local_name })
1827            }
1828            // `import * as ns from "pkg"` — forgiveness synonym for `import ns from "pkg"`.
1829            // Both are identical since we have no default exports.
1830            TokenKind::Star => {
1831                self.advance();
1832                if !self.peek_identifier_text_is("as") {
1833                    self.error_at_peek_with_help(
1834                        "expected `as <name>` after `import *`",
1835                        vec![
1836                            "name the namespace binding: \
1837                             `import * as ns from \"pkg\";` (or use the \
1838                             short form: `import ns from \"pkg\";`)"
1839                                .to_string(),
1840                        ],
1841                    );
1842                    return None;
1843                }
1844                self.advance();
1845                let local_tok = self.expect_identifier("expected namespace name after `as`")?;
1846                let local_name = self.ident_from_token(&local_tok);
1847                Some(ImportKind::Namespace { local_name })
1848            }
1849            TokenKind::StringLiteral(_) => {
1850                self.error_at_peek_with_help(
1851                    "side-effect-only imports are not supported",
1852                    vec![
1853                        "name what you import: `import { name } from \"pkg\";` \
1854                         or `import ns from \"pkg\";`"
1855                            .to_string(),
1856                    ],
1857                );
1858                None
1859            }
1860            _ => {
1861                self.error_at_peek("expected `{` or namespace name after `import`");
1862                None
1863            }
1864        }
1865    }
1866
1867    fn parse_named_imports(&mut self) -> Option<ImportKind> {
1868        let (specs, open_span) = self.parse_specifier_list(SpecifierList::Import)?;
1869        if specs.is_empty() {
1870            self.error_at_with_help(
1871                open_span,
1872                "empty import specifier list",
1873                vec!["name what you import: `import { name } from \"pkg\";`".to_string()],
1874            );
1875            return None;
1876        }
1877        Some(ImportKind::Named(specs))
1878    }
1879
1880    /// Parse a `{ a, b as c }` specifier list shared by `import` and re-`export`.
1881    /// `list` says whether the specifiers bind local names (an import) and names the
1882    /// construct in diagnostics. Returns the specifiers (possibly
1883    /// empty — callers reject empty with construct-specific help) and the span of
1884    /// the opening brace.
1885    fn parse_specifier_list(
1886        &mut self,
1887        list: SpecifierList,
1888    ) -> Option<(Vec<ImportSpecifier>, Span)> {
1889        let what = list.noun();
1890        let open = self.advance();
1891        let mut specs: Vec<ImportSpecifier> = Vec::new();
1892        while !matches!(self.peek().kind, TokenKind::RightBrace | TokenKind::Eof) {
1893            // Inline type-only modifier (`{ type X }`), accepted as a no-op
1894            // synonym. `{ type as t }` instead names the binding `type`.
1895            if self.peek_identifier_text_is("type")
1896                && matches!(self.peek_at(1).kind, TokenKind::Identifier)
1897                && !self.peek_at_is_word(1, "as")
1898            {
1899                self.advance();
1900            }
1901            let imported_tok =
1902                self.expect_identifier_name(&format!("expected {what} specifier name"))?;
1903            let imported_name = self.ident_from_token(&imported_tok);
1904
1905            let binds_local = list == SpecifierList::Import;
1906            let local_name = if self.peek_identifier_text_is("as") {
1907                self.advance();
1908                let local_tok = self.expect_identifier_name("expected local name after `as`")?;
1909                if binds_local {
1910                    self.reject_strict_mode_reserved_word(&local_tok);
1911                }
1912                self.ident_from_token(&local_tok)
1913            } else {
1914                if binds_local {
1915                    self.reject_strict_mode_reserved_word(&imported_tok);
1916                }
1917                imported_name.clone()
1918            };
1919
1920            specs.push(ImportSpecifier {
1921                imported_name,
1922                local_name,
1923            });
1924
1925            match self.peek().kind {
1926                TokenKind::Comma => {
1927                    self.advance();
1928                }
1929                TokenKind::RightBrace | TokenKind::Eof => {}
1930                _ => {
1931                    self.error_at_peek(format!("expected `,` or `}}` after {what} specifier"));
1932                    return None;
1933                }
1934            }
1935        }
1936
1937        if !matches!(self.peek().kind, TokenKind::RightBrace) {
1938            self.error_at_peek(format!("expected `}}` to close {what} specifier list"));
1939            return None;
1940        }
1941        self.advance();
1942
1943        Some((specs, open.span))
1944    }
1945
1946    fn parse_enum_initializer(&mut self) -> Option<EnumInitializer> {
1947        let neg = if matches!(self.peek().kind, TokenKind::Minus) {
1948            Some(self.advance())
1949        } else {
1950            None
1951        };
1952        let tok = self.advance();
1953        match &tok.kind {
1954            TokenKind::NumberLiteral(n) => {
1955                let value = if neg.is_some() { -n } else { *n };
1956                // tsc constant-folds enum initializers, which turns `-0` into `0`.
1957                let value = if value == 0.0 { 0.0 } else { value };
1958                let start = neg.as_ref().map_or(tok.span.start, |t| t.span.start);
1959                Some(EnumInitializer::Number {
1960                    value,
1961                    span: self.span(start, tok.span.end),
1962                })
1963            }
1964            TokenKind::StringLiteral(s) => {
1965                if let Some(neg_tok) = &neg {
1966                    self.error_at(
1967                        self.span(neg_tok.span.start, tok.span.end),
1968                        "cannot negate a string enum initializer",
1969                    );
1970                    return None;
1971                }
1972                Some(EnumInitializer::String {
1973                    value: s.clone(),
1974                    span: tok.span,
1975                })
1976            }
1977            _ => {
1978                let span = neg.map_or(tok.span, |t| self.span(t.span.start, tok.span.end));
1979                self.error_at(
1980                    span,
1981                    "expected a number or string literal for enum initializer",
1982                );
1983                None
1984            }
1985        }
1986    }
1987
1988    /// Whether the token at `index` is `operator` (`keyof`, `readonly`) used as a
1989    /// prefix type operator.
1990    ///
1991    /// Both are contextual keywords — valid as ordinary identifiers in TypeScript and
1992    /// Submilli — so they are only operators when a type follows. A bare type name is
1993    /// never followed by another type name, so requiring one separates `keyof T` from
1994    /// a type literally named `keyof`.
1995    fn is_contextual_type_operator(&self, index: usize, operator: &str) -> bool {
1996        let Some(tok) = self.tokens.get(index) else {
1997            return false;
1998        };
1999        if !self.token_is_word(tok, operator) {
2000            return false;
2001        }
2002        matches!(
2003            self.tokens.get(index + 1).map(|t| &t.kind),
2004            Some(
2005                TokenKind::Identifier
2006                    | TokenKind::Void
2007                    | TokenKind::LeftBrace
2008                    | TokenKind::LeftParen
2009                    | TokenKind::LeftBracket
2010                    | TokenKind::StringLiteral(_)
2011                    | TokenKind::NumberLiteral(_)
2012                    | TokenKind::BooleanLiteral(_)
2013                    | TokenKind::NullLiteral
2014            )
2015        )
2016    }
2017
2018    fn parse_type_argument_list(&mut self) -> Option<(Vec<TypeAnnotation>, u32)> {
2019        let lt = self.advance();
2020        if matches!(self.peek().kind, TokenKind::GreaterThan) {
2021            self.error_at(lt.span, "empty generic argument list `<>` is not allowed");
2022            return None;
2023        }
2024        let mut args = Vec::new();
2025        loop {
2026            args.push(self.parse_type_annotation()?);
2027            match self.peek().kind {
2028                TokenKind::Comma => {
2029                    self.advance();
2030                    if matches!(self.peek().kind, TokenKind::GreaterThan) {
2031                        let gt = self.advance();
2032                        return Some((args, gt.span.end));
2033                    }
2034                }
2035                TokenKind::GreaterThan => {
2036                    let gt = self.advance();
2037                    return Some((args, gt.span.end));
2038                }
2039                _ => {
2040                    self.error_at(
2041                        lt.span,
2042                        "expected `,` or `>` to close generic argument list",
2043                    );
2044                    return None;
2045                }
2046            }
2047        }
2048    }
2049
2050    /// The name a `class`, `interface`, `type` or `enum` declares, `noun` naming
2051    /// which. A built-in type's name is reported, as in TypeScript: `class number {}`
2052    /// would read as the built-in in some positions and the class in others.
2053    fn parse_type_decl_name(&mut self, article: &str, noun: &str) -> Option<Ident> {
2054        let name_tok = self.expect_identifier(&format!("expected {noun} name"))?;
2055        let name = self.ident_from_token(&name_tok);
2056        if BUILT_IN_TYPE_NAMES.contains(&name.name.as_str()) {
2057            self.error_at_with_help(
2058                name.span,
2059                format!(
2060                    "`{}` is a built-in type and can't be used as {article} {noun} name",
2061                    name.name
2062                ),
2063                vec![format!("rename the {noun}")],
2064            );
2065        }
2066        Some(name)
2067    }
2068
2069    fn parse_generic_param_list(&mut self) -> Option<Vec<Ident>> {
2070        let lt = self.advance();
2071        if matches!(self.peek().kind, TokenKind::GreaterThan) {
2072            self.error_at(lt.span, "empty generic parameter list `<>` is not allowed");
2073            return None;
2074        }
2075        let mut generics = Vec::new();
2076        loop {
2077            let name_tok = self.expect_identifier("expected generic parameter name")?;
2078            let name = self.ident_from_token(&name_tok);
2079            // A duplicate is reported and dropped, as `tsc` drops it: the first
2080            // parameter keeps the name, and the type arguments count against the
2081            // parameters that remain.
2082            if generics.iter().any(|g: &Ident| g.name == name.name) {
2083                self.error_at_with_help(
2084                    name.span,
2085                    format!("duplicate type parameter `{}`", name.name),
2086                    vec!["give each type parameter its own name".to_string()],
2087                );
2088            } else {
2089                generics.push(name);
2090            }
2091            match self.peek().kind {
2092                TokenKind::Comma => {
2093                    self.advance();
2094                    if matches!(self.peek().kind, TokenKind::GreaterThan) {
2095                        self.advance();
2096                        return Some(generics);
2097                    }
2098                }
2099                TokenKind::GreaterThan => {
2100                    self.advance();
2101                    return Some(generics);
2102                }
2103                _ => {
2104                    self.error_at_peek("expected `,` or `>`");
2105                    return None;
2106                }
2107            }
2108        }
2109    }
2110
2111    fn parse_param_list(&mut self, allow_param_properties: bool) -> Option<Vec<ParamDecl>> {
2112        if matches!(self.peek().kind, TokenKind::RightParen) {
2113            return Some(Vec::new());
2114        }
2115        let mut params = Vec::new();
2116        loop {
2117            let param = self.parse_param(allow_param_properties)?;
2118            let is_rest = param.rest;
2119            params.push(param);
2120            match self.peek().kind {
2121                TokenKind::Comma => {
2122                    if is_rest {
2123                        let comma = self.advance();
2124                        self.error_at(comma.span, "rest parameter must be the last parameter");
2125                        return None;
2126                    }
2127                    self.advance();
2128                    if matches!(self.peek().kind, TokenKind::RightParen) {
2129                        break;
2130                    }
2131                }
2132                TokenKind::RightParen => break,
2133                _ => {
2134                    self.error_at_peek("expected `,` or `)`");
2135                    return None;
2136                }
2137            }
2138        }
2139        self.check_parameter_order(params.iter().map(ParamOrder::of_param));
2140        Some(params)
2141    }
2142
2143    fn parse_param(&mut self, allow_param_properties: bool) -> Option<ParamDecl> {
2144        // Parameter properties: `constructor(public x: T)` declares + assigns a
2145        // field. A modifier keyword counts only when a parameter name follows it
2146        // (so a param literally named `public`/`readonly` still parses).
2147        let modifiers = if self.peek_word_is_class_modifier("public")
2148            || self.peek_word_is_class_modifier("private")
2149            || self.peek_word_is_class_modifier("readonly")
2150        {
2151            let span = self.peek().span;
2152            let parsed = self.parse_class_modifiers();
2153            if allow_param_properties {
2154                Some(parsed)
2155            } else {
2156                self.error_at_with_help(
2157                    span,
2158                    "parameter properties are only allowed in a constructor",
2159                    vec![
2160                        "move the `public`/`private`/`readonly` modifier to a constructor parameter"
2161                            .to_string(),
2162                    ],
2163                );
2164                None
2165            }
2166        } else {
2167            None
2168        };
2169
2170        let rest = if matches!(self.peek().kind, TokenKind::DotDotDot) {
2171            self.advance();
2172            true
2173        } else {
2174            false
2175        };
2176        if modifiers.is_some() && rest {
2177            self.error_at_peek("a parameter property cannot be a rest parameter");
2178            return None;
2179        }
2180
2181        // Placeholder `name` is the empty identifier at the pattern's span;
2182        // the pre-infer lowering pass replaces it with a fresh `#pattern_p_N`.
2183        let (name, pattern) = match self.peek().kind {
2184            TokenKind::LeftBrace | TokenKind::LeftBracket => {
2185                if rest {
2186                    self.error_at_peek("rest parameter cannot be destructured");
2187                    return None;
2188                }
2189                if modifiers.is_some() {
2190                    self.error_at_peek("a parameter property cannot be destructured");
2191                    return None;
2192                }
2193                let binding = self.parse_binding()?;
2194                let placeholder = Ident {
2195                    name: String::new(),
2196                    span: binding.span(),
2197                };
2198                (placeholder, Some(binding))
2199            }
2200            _ => {
2201                let name_tok = self.expect_binding_identifier(if rest {
2202                    "expected identifier after `...`"
2203                } else {
2204                    "expected parameter name"
2205                })?;
2206                let name = self.ident_from_token(&name_tok);
2207                (name, None)
2208            }
2209        };
2210
2211        let optional = self.parse_parameter_optional(rest, pattern.is_some())?;
2212        let ty = if matches!(self.peek().kind, TokenKind::Colon) {
2213            self.advance();
2214            Some(self.parse_type_annotation()?)
2215        } else if !rest && matches!(self.peek().kind, TokenKind::Equals) {
2216            None
2217        } else {
2218            if rest {
2219                self.error_at_peek_with_help(
2220                    "rest parameter requires a type annotation",
2221                    vec!["function name(...args: T[]): R { … }".to_string()],
2222                );
2223            } else {
2224                self.error_at_peek_with_help(
2225                    "parameter requires a type annotation",
2226                    vec!["add a type (`x: T`) or a default initializer (`x = value`)".to_string()],
2227                );
2228            }
2229            return None;
2230        };
2231        let default = self.parse_parameter_default(rest, optional)?;
2232        Some(ParamDecl {
2233            name,
2234            ty,
2235            default,
2236            optional,
2237            pattern,
2238            rest,
2239            modifiers,
2240        })
2241    }
2242
2243    fn parse_parameter_optional(&mut self, rest: bool, pattern: bool) -> Option<bool> {
2244        if !matches!(self.peek().kind, TokenKind::Question) {
2245            return Some(false);
2246        }
2247        let question = self.advance();
2248        if rest || pattern {
2249            self.error_at(
2250                question.span,
2251                if rest {
2252                    "a rest parameter cannot be optional"
2253                } else {
2254                    "a destructured parameter cannot be optional; use a default value"
2255                },
2256            );
2257            return None;
2258        }
2259        Some(true)
2260    }
2261
2262    /// The `= value` after a parameter, if any. An optional parameter's default is
2263    /// reported but still parsed, so the rest of the list is checked too.
2264    fn parse_parameter_default(&mut self, rest: bool, optional: bool) -> Option<Option<ExprId>> {
2265        if !matches!(self.peek().kind, TokenKind::Equals) {
2266            return Some(None);
2267        }
2268        if rest {
2269            self.error_rest_parameter_default();
2270            return None;
2271        }
2272        if optional {
2273            self.error_at_peek_with_help(
2274                "an optional parameter cannot have a default value",
2275                vec!["drop the `?`: a parameter with a default is already optional".to_string()],
2276            );
2277        }
2278        self.advance();
2279        Some(Some(self.parse_expression()?))
2280    }
2281
2282    /// Rest's default is the empty array, so an explicit one is rejected.
2283    fn error_rest_parameter_default(&mut self) {
2284        self.error_at_peek_with_help(
2285            "rest parameter cannot have a default value",
2286            vec!["omit the `= …`; an unspecified rest defaults to `[]`".to_string()],
2287        );
2288    }
2289
2290    /// A caller cannot skip an optional argument and still pass a later one, so a
2291    /// required parameter after an optional one is reported. Only the first is, and
2292    /// parsing continues.
2293    fn check_parameter_order<'p>(&mut self, params: impl IntoIterator<Item = ParamOrder<'p>>) {
2294        let mut follows_optional = false;
2295        for param in params {
2296            if param.optional {
2297                follows_optional = true;
2298                continue;
2299            }
2300            if !follows_optional || !param.required {
2301                continue;
2302            }
2303            let label = match param.name {
2304                Some(name) => format!("parameter `{name}`"),
2305                None => "destructured parameter".to_string(),
2306            };
2307            self.error_at_with_help(
2308                param.span,
2309                format!("required {label} cannot follow an optional parameter"),
2310                vec!["make it optional too (`x?: T`), give it a default (`x: T = …`), or move it before the optional parameters".to_string()],
2311            );
2312            return;
2313        }
2314    }
2315
2316    /// Drop a `;` that ASI inserted directly before `next`, where the grammar requires
2317    /// `next` and so no statement can have ended. ASI is a token-stream pre-pass with no
2318    /// parser feedback, so it cannot see that a `}` ends a `do` body rather than a
2319    /// statement, or that the `{` after a signature's return annotation opens its body.
2320    fn eat_asi_semicolon_before(&mut self, next: TokenKind) {
2321        if matches!(self.peek().kind, TokenKind::Semicolon) && self.peek_at(1).kind == next {
2322            self.advance();
2323        }
2324    }
2325
2326    fn parse_block(&mut self) -> Option<StmtId> {
2327        self.eat_asi_semicolon_before(TokenKind::LeftBrace);
2328        if !matches!(self.peek().kind, TokenKind::LeftBrace) {
2329            self.error_at_peek("expected `{`");
2330            return None;
2331        }
2332        let open = self.advance();
2333        self.block_depth += 1;
2334
2335        let mut stmts = Vec::new();
2336        while !matches!(self.peek().kind, TokenKind::RightBrace | TokenKind::Eof) {
2337            if matches!(self.peek().kind, TokenKind::Semicolon) {
2338                self.advance();
2339                continue;
2340            }
2341            let pos_before = self.pos;
2342            if let Some(id) = self.parse_statement() {
2343                stmts.push(id);
2344            } else {
2345                self.recover();
2346                if self.pos == pos_before && !self.is_at_eof() {
2347                    self.advance();
2348                }
2349            }
2350        }
2351
2352        self.block_depth -= 1;
2353        if !matches!(self.peek().kind, TokenKind::RightBrace) {
2354            self.error_at_peek("expected `}`");
2355            return None;
2356        }
2357        let close = self.advance();
2358
2359        let span = self.span(open.span.start, close.span.end);
2360        parse_arena_result(
2361            self.ast.try_push_stmt(Stmt {
2362                kind: StmtKind::Block(stmts),
2363                span,
2364            }),
2365            &mut self.fatal,
2366        )
2367    }
2368
2369    // Control-flow bodies accept either a braced block or a single statement;
2370    // `if (c) return x;` is identical to `if (c) { return x; }` under our type
2371    // system (forgiveness principle). Downstream passes assume a `Block`, so a
2372    // braceless statement is wrapped in one.
2373    fn parse_control_body(&mut self) -> Option<StmtId> {
2374        if matches!(self.peek().kind, TokenKind::LeftBrace) {
2375            return self.parse_block();
2376        }
2377        // A binding here would go out of scope as soon as it was made. TypeScript
2378        // or JavaScript rejects each of these, so it is reported and then parsed
2379        // as usual. Only a declaration is reported: a `let` followed by
2380        // anything else already fails to parse.
2381        let keyword = match self.peek().kind {
2382            TokenKind::Let => Some(("a", "let")),
2383            TokenKind::Const => Some(("a", "const")),
2384            TokenKind::Function => Some(("a", "function")),
2385            // A nested `class` is reported wherever it is: see `parse_class_decl`.
2386            TokenKind::Interface => Some(("an", "interface")),
2387            TokenKind::Enum => Some(("an", "enum")),
2388            _ if self.peek_identifier_text_is("type") => Some(("a", "type")),
2389            _ => None,
2390        };
2391        // After `let`/`const`, a bracket or brace starts a destructuring
2392        // pattern; after `type`, which is also an ordinary identifier, it is an
2393        // index or nothing a declaration can be.
2394        let declares = match self.peek_at(1).kind {
2395            TokenKind::Identifier => true,
2396            TokenKind::LeftBracket | TokenKind::LeftBrace => !matches!(keyword, Some((_, "type"))),
2397            _ => false,
2398        };
2399        if let Some((article, keyword)) = keyword
2400            && declares
2401        {
2402            self.error_at_peek_with_help(
2403                format!(
2404                    "{article} `{keyword}` declaration can't be the body of a statement without braces"
2405                ),
2406                vec![format!("wrap it in braces: `{{ {keyword} … }}`")],
2407            );
2408        }
2409        self.block_depth += 1;
2410        let stmt = self.parse_statement();
2411        self.block_depth -= 1;
2412        let stmt = stmt?;
2413        let span = parse_arena_result(self.ast.try_stmt(stmt), &mut self.fatal)?.span;
2414        parse_arena_result(
2415            self.ast.try_push_stmt(Stmt {
2416                kind: StmtKind::Block(vec![stmt]),
2417                span,
2418            }),
2419            &mut self.fatal,
2420        )
2421    }
2422
2423    /// A loop body may also be a lone `;`: `while (advance());` does all its work in
2424    /// the header. Branches do not accept it; see `parse_branch_body`.
2425    fn parse_loop_body(&mut self) -> Option<StmtId> {
2426        if !matches!(self.peek().kind, TokenKind::Semicolon) {
2427            return self.parse_control_body();
2428        }
2429        self.empty_body_at_semicolon()
2430    }
2431
2432    /// An `if` or `else` body. Unlike a loop, a branch cannot be a lone `;`: `if (c);`
2433    /// silently detaches the block meant to follow it. The `;` is reported, then
2434    /// consumed as an empty body so an `else` after it doesn't cascade into a second error.
2435    fn parse_branch_body(&mut self, keyword: &str) -> Option<StmtId> {
2436        if !matches!(self.peek().kind, TokenKind::Semicolon) {
2437            return self.parse_control_body();
2438        }
2439        self.error_at_peek_with_help(
2440            format!("`{keyword}` has an empty body"),
2441            vec![format!("remove the `;` after `{keyword}`")],
2442        );
2443        self.empty_body_at_semicolon()
2444    }
2445
2446    /// Consumes a lone `;` as an empty block spanning it.
2447    fn empty_body_at_semicolon(&mut self) -> Option<StmtId> {
2448        let semi = self.advance();
2449        parse_arena_result(
2450            self.ast.try_push_stmt(Stmt {
2451                kind: StmtKind::Block(vec![]),
2452                span: semi.span,
2453            }),
2454            &mut self.fatal,
2455        )
2456    }
2457
2458    fn parse_if(&mut self) -> Option<StmtId> {
2459        self.with_recursion_limit(Self::parse_if_inner)
2460    }
2461
2462    fn parse_if_inner(&mut self) -> Option<StmtId> {
2463        let kw = self.advance();
2464        let condition = self.parse_paren_condition()?;
2465        let then_block = self.parse_branch_body("if")?;
2466
2467        let else_block = if matches!(self.peek().kind, TokenKind::Else) {
2468            self.advance();
2469            if matches!(self.peek().kind, TokenKind::If) {
2470                Some(self.parse_if()?)
2471            } else {
2472                Some(self.parse_branch_body("else")?)
2473            }
2474        } else {
2475            None
2476        };
2477
2478        let end = match else_block {
2479            Some(id) => {
2480                parse_arena_result(self.ast.try_stmt(id), &mut self.fatal)?
2481                    .span
2482                    .end
2483            }
2484            None => {
2485                parse_arena_result(self.ast.try_stmt(then_block), &mut self.fatal)?
2486                    .span
2487                    .end
2488            }
2489        };
2490
2491        parse_arena_result(
2492            self.ast.try_push_stmt(Stmt {
2493                kind: StmtKind::If {
2494                    condition,
2495                    then_block,
2496                    else_block,
2497                },
2498                span: self.span(kw.span.start, end),
2499            }),
2500            &mut self.fatal,
2501        )
2502    }
2503
2504    fn parse_while(&mut self) -> Option<StmtId> {
2505        let kw = self.advance();
2506        let condition = self.parse_paren_condition()?;
2507        let body = self.parse_loop_body()?;
2508        let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
2509            .span
2510            .end;
2511
2512        parse_arena_result(
2513            self.ast.try_push_stmt(Stmt {
2514                kind: StmtKind::While { condition, body },
2515                span: self.span(kw.span.start, body_end),
2516            }),
2517            &mut self.fatal,
2518        )
2519    }
2520
2521    // Disambiguates C-style for and for-of via non-consuming lookahead for `of`.
2522    fn parse_for(&mut self) -> Option<StmtId> {
2523        let kw = self.advance();
2524        if !matches!(self.peek().kind, TokenKind::LeftParen) {
2525            self.error_at_peek("expected `(` after `for`");
2526            return None;
2527        }
2528        self.advance();
2529
2530        if self.scan_for_of_shape() {
2531            self.parse_for_of_tail(kw)
2532        } else {
2533            self.parse_c_for_tail(kw)
2534        }
2535    }
2536
2537    // Counts bracket/angle depth to skip past optional type annotations.
2538    fn scan_for_of_shape(&self) -> bool {
2539        if !matches!(self.peek().kind, TokenKind::Let | TokenKind::Const) {
2540            return false;
2541        }
2542        // Object patterns are tentatively accepted here; `parse_for_of_tail` rejects them.
2543        let (start, mut depth) = match self.peek_at(1).kind {
2544            TokenKind::Identifier => match self.peek_at(2).kind {
2545                TokenKind::Identifier if self.peek_at_is_word(2, "of") => return true,
2546                TokenKind::Colon => (3, 0i32),
2547                _ => return false,
2548            },
2549            TokenKind::LeftBracket | TokenKind::LeftBrace => (2, 1i32),
2550            _ => return false,
2551        };
2552        let mut i = start;
2553        loop {
2554            match self.peek_at(i).kind {
2555                TokenKind::Eof => return false,
2556                TokenKind::LessThan
2557                | TokenKind::LeftParen
2558                | TokenKind::LeftBracket
2559                | TokenKind::LeftBrace => {
2560                    depth += 1;
2561                }
2562                TokenKind::GreaterThan
2563                | TokenKind::RightParen
2564                | TokenKind::RightBracket
2565                | TokenKind::RightBrace => {
2566                    if depth == 0 {
2567                        return false;
2568                    }
2569                    depth -= 1;
2570                }
2571                // A user type literally named `of` in the annotation would
2572                // misclassify here and error loudly downstream — acceptable.
2573                TokenKind::Identifier if depth == 0 && self.peek_at_is_word(i, "of") => {
2574                    return true;
2575                }
2576                TokenKind::Equals | TokenKind::Semicolon if depth == 0 => return false,
2577                _ => {}
2578            }
2579            i += 1;
2580        }
2581    }
2582
2583    // The trailing `;` of init-statement parsers doubles as the first `;` of the for header.
2584    fn parse_c_for_tail(&mut self, kw: Token) -> Option<StmtId> {
2585        let init = if matches!(self.peek().kind, TokenKind::Semicolon) {
2586            self.advance();
2587            None
2588        } else {
2589            let init_id = match self.peek().kind {
2590                TokenKind::Let => self.parse_let_or_const(false)?,
2591                TokenKind::Const => self.parse_let_or_const(true)?,
2592                _ => self.parse_expression_statement()?,
2593            };
2594            Some(init_id)
2595        };
2596        let for_stmt = self.finish_c_for(kw, init);
2597        if for_stmt.is_none()
2598            && let Some(init) = init
2599        {
2600            self.discard_detached_pattern(init);
2601        }
2602        for_stmt
2603    }
2604
2605    fn finish_c_for(&mut self, kw: Token, init: Option<StmtId>) -> Option<StmtId> {
2606        let condition = if matches!(self.peek().kind, TokenKind::Semicolon) {
2607            None
2608        } else {
2609            Some(self.parse_expression()?)
2610        };
2611        if !matches!(self.peek().kind, TokenKind::Semicolon) {
2612            self.error_at_peek("expected `;` after `for` condition");
2613            return None;
2614        }
2615        self.advance();
2616
2617        let update = if matches!(self.peek().kind, TokenKind::RightParen) {
2618            None
2619        } else {
2620            Some(self.parse_for_update_stmt()?)
2621        };
2622        if !matches!(self.peek().kind, TokenKind::RightParen) {
2623            self.error_at_peek("expected `)` after `for` update");
2624            return None;
2625        }
2626        self.advance();
2627
2628        let body = self.parse_loop_body()?;
2629        let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
2630            .span
2631            .end;
2632        parse_arena_result(
2633            self.ast.try_push_stmt(Stmt {
2634                kind: StmtKind::For {
2635                    init,
2636                    condition,
2637                    update,
2638                    body,
2639                },
2640                span: self.span(kw.span.start, body_end),
2641            }),
2642            &mut self.fatal,
2643        )
2644    }
2645
2646    /// A destructuring `for` initializer is lowered with its loop, so one whose
2647    /// loop failed to parse would otherwise reach inference unlowered.
2648    fn discard_detached_pattern(&mut self, init: StmtId) {
2649        let Some(stmt) = parse_arena_result(self.ast.try_stmt_mut(init), &mut self.fatal) else {
2650            return;
2651        };
2652        if matches!(
2653            stmt.kind,
2654            StmtKind::LetPattern { .. } | StmtKind::ConstPattern { .. }
2655        ) {
2656            stmt.kind = StmtKind::Block(Vec::new());
2657        }
2658    }
2659
2660    fn parse_for_update_stmt(&mut self) -> Option<StmtId> {
2661        let expr_id = self.parse_expression()?;
2662        let span = parse_arena_result(self.ast.try_expr(expr_id), &mut self.fatal)?.span;
2663        let kind = self.statement_kind_for(expr_id)?;
2664        parse_arena_result(self.ast.try_push_stmt(Stmt { kind, span }), &mut self.fatal)
2665    }
2666
2667    fn parse_for_of_tail(&mut self, kw: Token) -> Option<StmtId> {
2668        let binding_tok = self.advance();
2669        let binding_kind = match binding_tok.kind {
2670            TokenKind::Let => crate::BindingKind::Let,
2671            TokenKind::Const => crate::BindingKind::Const,
2672            _ => return self.invariant_failure("scan_for_of_shape gated this"),
2673        };
2674        let (name, binding) = match self.peek().kind {
2675            TokenKind::LeftBracket => {
2676                let b = self.parse_binding()?;
2677                (None, Some(b))
2678            }
2679            TokenKind::LeftBrace => {
2680                let b = self.parse_binding()?;
2681                self.error_at(
2682                    b.span(),
2683                    "object destructuring is not supported in `for-of`; use array destructuring or unpack inside the loop body",
2684                );
2685                return None;
2686            }
2687            _ => {
2688                let name_tok = self.expect_binding_identifier(
2689                    "expected identifier after `let`/`const` in `for-of`",
2690                )?;
2691                (Some(self.ident_from_token(&name_tok)), None)
2692            }
2693        };
2694        let ty = if matches!(self.peek().kind, TokenKind::Colon) {
2695            self.advance();
2696            Some(self.parse_type_annotation()?)
2697        } else {
2698            None
2699        };
2700        if !self.peek_identifier_text_is("of") {
2701            self.error_at_peek("expected `of` in `for-of`");
2702            return None;
2703        }
2704        self.advance();
2705
2706        let iter = self.parse_expression()?;
2707        if !matches!(self.peek().kind, TokenKind::RightParen) {
2708            self.error_at_peek("expected `)` after `for-of` iterable");
2709            return None;
2710        }
2711        self.advance();
2712
2713        let body = self.parse_loop_body()?;
2714        let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
2715            .span
2716            .end;
2717        let kind = match (binding, name) {
2718            (Some(binding), _) => StmtKind::ForOfPattern {
2719                binding_kind,
2720                binding,
2721                ty,
2722                iter,
2723                body,
2724            },
2725            (None, Some(name)) => StmtKind::ForOf {
2726                binding_kind,
2727                name,
2728                ty,
2729                iter,
2730                body,
2731            },
2732            (None, None) => return self.invariant_failure("either binding or name must be Some"),
2733        };
2734        parse_arena_result(
2735            self.ast.try_push_stmt(Stmt {
2736                kind,
2737                span: self.span(kw.span.start, body_end),
2738            }),
2739            &mut self.fatal,
2740        )
2741    }
2742
2743    fn parse_do_while(&mut self) -> Option<StmtId> {
2744        let kw = self.advance();
2745        let body = self.parse_loop_body()?;
2746        self.eat_asi_semicolon_before(TokenKind::While);
2747        if !matches!(self.peek().kind, TokenKind::While) {
2748            self.error_at_peek("expected `while` after `do` body");
2749            return None;
2750        }
2751        self.advance();
2752        let condition = self.parse_paren_condition()?;
2753        // ECMAScript inserts the terminator after a do-while's `)` unconditionally, so the
2754        // `;` is optional here — ASI can't supply it, since that `)` closes a `while`
2755        // header everywhere else.
2756        let end = if matches!(self.peek().kind, TokenKind::Semicolon) {
2757            self.advance().span.end
2758        } else {
2759            parse_arena_result(self.ast.try_expr(condition), &mut self.fatal)?
2760                .span
2761                .end
2762        };
2763        parse_arena_result(
2764            self.ast.try_push_stmt(Stmt {
2765                kind: StmtKind::DoWhile { body, condition },
2766                span: self.span(kw.span.start, end),
2767            }),
2768            &mut self.fatal,
2769        )
2770    }
2771
2772    // Multiple consecutive `case`/`default` labels share the next body.
2773    fn parse_switch(&mut self) -> Option<StmtId> {
2774        let kw = self.advance();
2775        if !matches!(self.peek().kind, TokenKind::LeftParen) {
2776            self.error_at_peek("expected `(` after `switch`");
2777            return None;
2778        }
2779        self.advance();
2780        let discriminant = self.parse_expression()?;
2781        if !matches!(self.peek().kind, TokenKind::RightParen) {
2782            self.error_at_peek("expected `)` after `switch` discriminant");
2783            return None;
2784        }
2785        self.advance();
2786
2787        if !matches!(self.peek().kind, TokenKind::LeftBrace) {
2788            self.error_at_peek("expected `{` to start `switch` body");
2789            return None;
2790        }
2791        let open = self.advance();
2792        self.block_depth += 1;
2793
2794        let mut cases: Vec<SwitchCase> = Vec::new();
2795        let mut default: Option<SwitchDefault> = None;
2796        let mut pending_values: Vec<ExprId> = Vec::new();
2797        let mut pending_label_start: Option<u32> = None;
2798        let mut pending_default: Option<Span> = None;
2799
2800        loop {
2801            match self.peek().kind {
2802                TokenKind::Eof => break,
2803                // ASI inserts `;` after `:` on a newline — treat it as a no-op here.
2804                TokenKind::Semicolon => {
2805                    self.advance();
2806                }
2807                TokenKind::RightBrace => {
2808                    // Case bodies are required (typechecker enforces break/return termination).
2809                    if pending_label_start.is_some() {
2810                        self.error_at_peek(
2811                            "expected case body before `}` — case labels must be followed by statements",
2812                        );
2813                        return None;
2814                    }
2815                    break;
2816                }
2817                TokenKind::Case => {
2818                    let case_kw = self.advance();
2819                    if pending_label_start.is_none() {
2820                        pending_label_start = Some(case_kw.span.start);
2821                    }
2822                    let value = self.parse_expression()?;
2823                    if !matches!(self.peek().kind, TokenKind::Colon) {
2824                        self.error_at_peek("expected `:` after `case` label");
2825                        return None;
2826                    }
2827                    self.advance();
2828                    pending_values.push(value);
2829                }
2830                TokenKind::Default => {
2831                    let kw_tok = self.advance();
2832                    if default.is_some() {
2833                        self.error_at(kw_tok.span, "duplicate `default` clause in `switch`");
2834                        return None;
2835                    }
2836                    if !matches!(self.peek().kind, TokenKind::Colon) {
2837                        self.error_at_peek("expected `:` after `default`");
2838                        return None;
2839                    }
2840                    self.advance();
2841                    if pending_label_start.is_none() {
2842                        pending_label_start = Some(kw_tok.span.start);
2843                    }
2844                    pending_default = Some(kw_tok.span);
2845                }
2846                _ => {
2847                    if pending_label_start.is_none() {
2848                        self.error_at_peek(
2849                            "expected `case` or `default` at the start of a `switch` body",
2850                        );
2851                        return None;
2852                    }
2853                    let Some(label_start) = pending_label_start else {
2854                        return self.invariant_failure("switch arm has no label");
2855                    };
2856                    let body = self.parse_switch_arm_body(label_start)?;
2857                    let arm_span =
2858                        parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?.span;
2859                    if !pending_values.is_empty() {
2860                        let first = parse_arena_result(
2861                            self.ast.try_expr(pending_values[0]),
2862                            &mut self.fatal,
2863                        )?
2864                        .span
2865                        .start;
2866                        let case_span = self.span(first, arm_span.end);
2867                        cases.push(SwitchCase {
2868                            values: std::mem::take(&mut pending_values),
2869                            body,
2870                            span: case_span,
2871                        });
2872                    }
2873                    if let Some(def_kw_span) = pending_default.take() {
2874                        default = Some(SwitchDefault {
2875                            body,
2876                            span: self.span(def_kw_span.start, arm_span.end),
2877                        });
2878                    }
2879                    pending_label_start = None;
2880                }
2881            }
2882        }
2883
2884        self.block_depth -= 1;
2885        if !matches!(self.peek().kind, TokenKind::RightBrace) {
2886            self.error_at_peek("expected `}` to close `switch` body");
2887            return None;
2888        }
2889        let close = self.advance();
2890
2891        let _ = open;
2892        parse_arena_result(
2893            self.ast.try_push_stmt(Stmt {
2894                kind: StmtKind::Switch {
2895                    discriminant,
2896                    cases,
2897                    default,
2898                },
2899                span: self.span(kw.span.start, close.span.end),
2900            }),
2901            &mut self.fatal,
2902        )
2903    }
2904
2905    // `body_start` is the label keyword's offset so diagnostics point at the label.
2906    fn parse_switch_arm_body(&mut self, body_start: u32) -> Option<StmtId> {
2907        let mut stmts: Vec<StmtId> = Vec::new();
2908        let mut end_offset = body_start;
2909        loop {
2910            if matches!(
2911                self.peek().kind,
2912                TokenKind::Case | TokenKind::Default | TokenKind::RightBrace | TokenKind::Eof
2913            ) {
2914                break;
2915            }
2916            if matches!(self.peek().kind, TokenKind::Semicolon) {
2917                end_offset = self.peek().span.end;
2918                self.advance();
2919                continue;
2920            }
2921            let pos_before = self.pos;
2922            if let Some(id) = self.parse_statement() {
2923                end_offset = parse_arena_result(self.ast.try_stmt(id), &mut self.fatal)?
2924                    .span
2925                    .end;
2926                stmts.push(id);
2927            } else {
2928                self.recover();
2929                if self.pos == pos_before && !self.is_at_eof() {
2930                    self.advance();
2931                }
2932                // Recovery consumed tokens but produced no statement; advance the body
2933                // end past them so the arm span can't end before its own case value.
2934                end_offset = end_offset.max(self.prev_token_end());
2935            }
2936        }
2937        let span = self.span(body_start, end_offset);
2938        parse_arena_result(
2939            self.ast.try_push_stmt(Stmt {
2940                kind: StmtKind::Block(stmts),
2941                span,
2942            }),
2943            &mut self.fatal,
2944        )
2945    }
2946
2947    fn parse_break(&mut self) -> Option<StmtId> {
2948        let kw = self.advance();
2949        if !matches!(self.peek().kind, TokenKind::Semicolon) {
2950            self.error_at_peek("expected `;` after `break`");
2951            return None;
2952        }
2953        let semi = self.advance();
2954        parse_arena_result(
2955            self.ast.try_push_stmt(Stmt {
2956                kind: StmtKind::Break,
2957                span: self.span(kw.span.start, semi.span.end),
2958            }),
2959            &mut self.fatal,
2960        )
2961    }
2962
2963    fn parse_continue(&mut self) -> Option<StmtId> {
2964        let kw = self.advance();
2965        if !matches!(self.peek().kind, TokenKind::Semicolon) {
2966            self.error_at_peek("expected `;` after `continue`");
2967            return None;
2968        }
2969        let semi = self.advance();
2970        parse_arena_result(
2971            self.ast.try_push_stmt(Stmt {
2972                kind: StmtKind::Continue,
2973                span: self.span(kw.span.start, semi.span.end),
2974            }),
2975            &mut self.fatal,
2976        )
2977    }
2978
2979    fn parse_return(&mut self) -> Option<StmtId> {
2980        let kw = self.advance();
2981
2982        let value = if matches!(self.peek().kind, TokenKind::Semicolon) {
2983            None
2984        } else {
2985            Some(self.parse_expression()?)
2986        };
2987
2988        if !self.at_statement_end() {
2989            self.error_at_peek("expected `;` after return");
2990            return None;
2991        }
2992        let end = self.finish_statement();
2993
2994        parse_arena_result(
2995            self.ast.try_push_stmt(Stmt {
2996                kind: StmtKind::Return(value),
2997                span: self.span(kw.span.start, end),
2998            }),
2999            &mut self.fatal,
3000        )
3001    }
3002
3003    fn parse_throw(&mut self) -> Option<StmtId> {
3004        let kw = self.advance();
3005
3006        if matches!(self.peek().kind, TokenKind::Semicolon) {
3007            self.error_at_peek_with_help(
3008                "expected expression after `throw`",
3009                vec!["throw new Error(\"message\");".to_string()],
3010            );
3011            return None;
3012        }
3013
3014        let value = self.parse_expression()?;
3015
3016        if !self.at_statement_end() {
3017            self.error_at_peek("expected `;` after `throw`");
3018            return None;
3019        }
3020        let end = self.finish_statement();
3021
3022        parse_arena_result(
3023            self.ast.try_push_stmt(Stmt {
3024                kind: StmtKind::Throw { value },
3025                span: self.span(kw.span.start, end),
3026            }),
3027            &mut self.fatal,
3028        )
3029    }
3030
3031    fn parse_try(&mut self) -> Option<StmtId> {
3032        let kw = self.advance();
3033
3034        let body = self.parse_block()?;
3035
3036        let mut catches: Vec<CatchClause> = Vec::new();
3037        let mut finally: Option<StmtId> = None;
3038        let mut tail_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
3039            .span
3040            .end;
3041
3042        loop {
3043            match self.peek().kind {
3044                TokenKind::Catch => {
3045                    if finally.is_some() {
3046                        self.error_at_peek("`catch` clause must appear before `finally`");
3047                        return None;
3048                    }
3049                    let clause = self.parse_catch_clause()?;
3050                    tail_end = clause.span.end;
3051                    catches.push(clause);
3052                }
3053                TokenKind::Finally => {
3054                    if finally.is_some() {
3055                        self.error_at_peek("duplicate `finally` clause");
3056                        return None;
3057                    }
3058                    self.advance();
3059                    let block = self.parse_block()?;
3060                    tail_end = parse_arena_result(self.ast.try_stmt(block), &mut self.fatal)?
3061                        .span
3062                        .end;
3063                    finally = Some(block);
3064                }
3065                _ => break,
3066            }
3067        }
3068
3069        if catches.is_empty() && finally.is_none() {
3070            self.error_at(
3071                self.span(kw.span.start, tail_end),
3072                "try requires at least one of `catch` or `finally`",
3073            );
3074            return None;
3075        }
3076
3077        parse_arena_result(
3078            self.ast.try_push_stmt(Stmt {
3079                kind: StmtKind::Try {
3080                    body,
3081                    catches,
3082                    finally,
3083                },
3084                span: self.span(kw.span.start, tail_end),
3085            }),
3086            &mut self.fatal,
3087        )
3088    }
3089
3090    fn parse_catch_clause(&mut self) -> Option<CatchClause> {
3091        let kw = self.advance();
3092
3093        let (binding, ty) = if matches!(self.peek().kind, TokenKind::LeftBrace) {
3094            // This binding is inaccessible to source code, including nested catches.
3095            (
3096                Ident {
3097                    name: "#catch".into(),
3098                    span: kw.span,
3099                },
3100                None,
3101            )
3102        } else {
3103            self.parse_catch_binding()?
3104        };
3105
3106        let body = self.parse_block()?;
3107        let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
3108            .span
3109            .end;
3110
3111        Some(CatchClause {
3112            binding,
3113            ty,
3114            body,
3115            span: self.span(kw.span.start, body_end),
3116        })
3117    }
3118
3119    fn parse_catch_binding(&mut self) -> Option<(Ident, Option<TypeAnnotation>)> {
3120        if !matches!(self.peek().kind, TokenKind::LeftParen) {
3121            self.error_at_peek("expected `(` after `catch`");
3122            return None;
3123        }
3124        self.advance();
3125        let name_tok = self.expect_binding_identifier("expected catch binding name")?;
3126        let binding = self.ident_from_token(&name_tok);
3127        let ty = if matches!(self.peek().kind, TokenKind::Colon) {
3128            self.advance();
3129            if self.reject_any_catch_annotation() {
3130                // The error is reported; parsing continues as an unannotated catch.
3131                None
3132            } else {
3133                Some(self.parse_type_annotation()?)
3134            }
3135        } else {
3136            None
3137        };
3138        if !matches!(self.peek().kind, TokenKind::RightParen) {
3139            self.error_at_peek("expected `)` after catch binding");
3140            return None;
3141        }
3142        self.advance();
3143        Some((binding, ty))
3144    }
3145
3146    /// Reports `catch (e: any)` and consumes the `any`, returning whether it did.
3147    /// Only exactly `any)` is handled; compound types keep the general diagnostic.
3148    fn reject_any_catch_annotation(&mut self) -> bool {
3149        let is_bare_any = self.peek_identifier_text_is("any")
3150            && matches!(self.peek_at(1).kind, TokenKind::RightParen);
3151        if !is_bare_any {
3152            return false;
3153        }
3154        let any_tok = self.advance();
3155        // The general `any` help suggests `unknown`, which a catch binding refuses.
3156        self.error_at_with_help(
3157            any_tok.span,
3158            "`any` is not supported",
3159            vec![
3160                "write `catch (e)` to catch every thrown error, or name an error class, \
3161                 as in `catch (e: RangeError)`, to catch only that type"
3162                    .to_string(),
3163            ],
3164        );
3165        true
3166    }
3167
3168    fn parse_paren_condition(&mut self) -> Option<ExprId> {
3169        if !matches!(self.peek().kind, TokenKind::LeftParen) {
3170            self.error_at_peek("expected `(`");
3171            return None;
3172        }
3173        self.advance();
3174        let expr = self.parse_expression()?;
3175        if !matches!(self.peek().kind, TokenKind::RightParen) {
3176            self.error_at_peek("expected `)`");
3177            return None;
3178        }
3179        self.advance();
3180        Some(expr)
3181    }
3182
3183    fn parse_predicate_or_return_type(
3184        &mut self,
3185        type_pos: TypePos,
3186    ) -> Option<(
3187        Option<TypeAnnotation>,
3188        Option<crate::TypePredicateAnnotation>,
3189    )> {
3190        if matches!(self.peek().kind, TokenKind::Identifier) && self.peek_at_is_word(1, "is") {
3191            let param_tok = self.advance();
3192            let param = self.ident_from_token(&param_tok);
3193            self.advance();
3194            let asserted = self.parse_type_annotation_in(type_pos)?;
3195            let span = self.span(param.span.start, asserted.span.end);
3196            return Some((
3197                None,
3198                Some(crate::TypePredicateAnnotation {
3199                    param,
3200                    asserted,
3201                    span,
3202                }),
3203            ));
3204        }
3205        Some((Some(self.parse_type_annotation_in(type_pos)?), None))
3206    }
3207
3208    fn parse_type_annotation(&mut self) -> Option<TypeAnnotation> {
3209        self.parse_type_annotation_in(TypePos::Anywhere)
3210    }
3211
3212    fn parse_type_annotation_in(&mut self, type_pos: TypePos) -> Option<TypeAnnotation> {
3213        // A leading `|` (prettier's multiline format) is accepted and discarded.
3214        let leading_pipe = matches!(self.peek().kind, TokenKind::Pipe);
3215        if leading_pipe {
3216            self.advance();
3217        }
3218        let first = self.parse_type_array(type_pos)?;
3219        if !matches!(self.peek().kind, TokenKind::Pipe) {
3220            return Some(first);
3221        }
3222        let start = first.span.start;
3223        let mut end = first.span.end;
3224        let mut members = vec![first];
3225        while matches!(self.peek().kind, TokenKind::Pipe) {
3226            self.advance();
3227            let member = self.parse_type_array(type_pos)?;
3228            end = member.span.end;
3229            members.push(member);
3230        }
3231        Some(TypeAnnotation {
3232            kind: TypeAnnotationKind::Union(members),
3233            span: self.span(start, end),
3234        })
3235    }
3236
3237    fn parse_type_array(&mut self, type_pos: TypePos) -> Option<TypeAnnotation> {
3238        self.with_recursion_limit(|parser| parser.parse_type_array_inner(type_pos))
3239    }
3240
3241    fn parse_type_array_inner(&mut self, type_pos: TypePos) -> Option<TypeAnnotation> {
3242        // `keyof` is contextual, not reserved: TypeScript allows it as an ordinary
3243        // identifier, and so does Submilli. It is an operator only when another type
3244        // follows it, which an identifier in type position never does.
3245        if self.is_contextual_type_operator(self.pos, "keyof") {
3246            let kw = self.advance();
3247            let operand = self.parse_type_array(type_pos)?;
3248            let span = self.span(kw.span.start, operand.span.end);
3249            return Some(TypeAnnotation {
3250                kind: TypeAnnotationKind::KeyOf(Box::new(operand)),
3251                span,
3252            });
3253        }
3254        if self.is_contextual_type_operator(self.pos, "readonly") {
3255            return self.parse_readonly_type(type_pos);
3256        }
3257
3258        let mut ty = match self.peek().kind {
3259            // `typeof x` names the type of a *value*. Unlike `keyof` this is already a
3260            // reserved word (the `typeof x === "string"` guard uses it), so it needs no
3261            // contextual check — in type position it is only ever the operator. Built
3262            // here rather than returned early so the `[]` suffix loop below still runs,
3263            // making `typeof a[]` an array of it, as TypeScript parses it too.
3264            TokenKind::Typeof => {
3265                let kw = self.advance();
3266                let first = self.expect_identifier("expected a value name after `typeof`")?;
3267                let mut path = vec![self.type_name(first.span)?];
3268                let mut end = first.span.end;
3269                while matches!(self.peek().kind, TokenKind::Dot) {
3270                    self.advance();
3271                    let seg = self.expect_identifier_name("expected a property name after `.`")?;
3272                    end = seg.span.end;
3273                    path.push(self.type_name(seg.span)?);
3274                }
3275                TypeAnnotation {
3276                    kind: TypeAnnotationKind::TypeOf { path },
3277                    span: self.span(kw.span.start, end),
3278                }
3279            }
3280            TokenKind::Identifier | TokenKind::Void => {
3281                // `void` never starts a qualified path.
3282                let leading_is_identifier = matches!(self.peek().kind, TokenKind::Identifier);
3283                let tok = self.advance();
3284                let first_span = tok.span;
3285                let mut path_end = first_span.end;
3286                let first_name = self.type_name(first_span)?;
3287                let mut path = vec![first_name.clone()];
3288                if leading_is_identifier {
3289                    while matches!(self.peek().kind, TokenKind::Dot) {
3290                        self.advance();
3291                        if !matches!(self.peek().kind, TokenKind::Identifier) {
3292                            self.error_at_peek("expected identifier after `.` in type name");
3293                            return None;
3294                        }
3295                        let seg = self.advance();
3296                        path_end = seg.span.end;
3297                        path.push(self.type_name(seg.span)?);
3298                    }
3299                }
3300                // Type arguments, like the `[]` suffix, must start on the type's line.
3301                let (args, end) = if matches!(self.peek().kind, TokenKind::LessThan)
3302                    && !self.line_break_before_peek()
3303                {
3304                    self.parse_type_argument_list()?
3305                } else {
3306                    (Vec::new(), path_end)
3307                };
3308                let outer_span = self.span(first_span.start, end);
3309                let kind = if path.len() == 1 {
3310                    if first_name.name == "any" {
3311                        self.error_at_with_help(
3312                            first_span,
3313                            "`any` is not supported",
3314                            vec![
3315                                "annotate a concrete type instead — an object shape \
3316                                 like `{ id: string }`, a named `interface`, or \
3317                                 `unknown` (a safe dynamic type you narrow before use)"
3318                                    .to_string(),
3319                            ],
3320                        );
3321                    }
3322                    TypeAnnotationKind::Name {
3323                        name: first_name,
3324                        args,
3325                    }
3326                } else {
3327                    TypeAnnotationKind::Qualified { path, args }
3328                };
3329                TypeAnnotation {
3330                    kind,
3331                    span: outer_span,
3332                }
3333            }
3334            // `null<T>` is meaningless — no type args after `null`.
3335            TokenKind::NullLiteral => {
3336                let tok = self.advance();
3337                TypeAnnotation {
3338                    kind: TypeAnnotationKind::Name {
3339                        name: self.type_name(tok.span)?,
3340                        args: Vec::new(),
3341                    },
3342                    span: tok.span,
3343                }
3344            }
3345            TokenKind::StringLiteral(_) => {
3346                let tok = self.advance();
3347                let span = tok.span;
3348                let TokenKind::StringLiteral(s) = tok.kind else {
3349                    // advance returns the token just matched, before moving the cursor.
3350                    unreachable!("advance preserves the matched StringLiteral variant");
3351                };
3352                TypeAnnotation {
3353                    kind: TypeAnnotationKind::StringLiteral(s),
3354                    span,
3355                }
3356            }
3357            TokenKind::NumberLiteral(_) => {
3358                let tok = self.advance();
3359                let span = tok.span;
3360                let TokenKind::NumberLiteral(v) = tok.kind else {
3361                    // advance returns the token just matched, before moving the cursor.
3362                    unreachable!("advance preserves the matched NumberLiteral variant");
3363                };
3364                // Canonicalize `-0.0` → `0.0` so literal type `0` matches both signs.
3365                let canonical = if v == 0.0 { 0.0 } else { v };
3366                TypeAnnotation {
3367                    kind: TypeAnnotationKind::NumberLiteral(crate::types::LiteralF64(canonical)),
3368                    span,
3369                }
3370            }
3371            TokenKind::BigIntLiteral(_) => {
3372                let tok = self.advance();
3373                let TokenKind::BigIntLiteral(digits) = tok.kind else {
3374                    // advance returns the token just matched, before moving the cursor.
3375                    unreachable!("advance preserves the matched BigIntLiteral variant");
3376                };
3377                TypeAnnotation {
3378                    kind: TypeAnnotationKind::BigIntLiteral(digits),
3379                    span: tok.span,
3380                }
3381            }
3382            TokenKind::Minus
3383                if matches!(
3384                    self.peek_at(1).kind,
3385                    TokenKind::NumberLiteral(_) | TokenKind::BigIntLiteral(_)
3386                ) =>
3387            {
3388                let minus = self.advance().span;
3389                let tok = self.advance();
3390                let span = self.span(minus.start, tok.span.end);
3391                let kind =
3392                    match tok.kind {
3393                        TokenKind::NumberLiteral(v) => TypeAnnotationKind::NumberLiteral(
3394                            crate::types::LiteralF64(if v == 0.0 { 0.0 } else { -v }),
3395                        ),
3396                        TokenKind::BigIntLiteral(digits) => TypeAnnotationKind::BigIntLiteral(
3397                            crate::types::negate_bigint_digits(&digits),
3398                        ),
3399                        // The guard admits only the two numeric literal tokens.
3400                        _ => unreachable!("guarded to a numeric literal after `-`"),
3401                    };
3402                TypeAnnotation { kind, span }
3403            }
3404            TokenKind::BooleanLiteral(value) => {
3405                let span = self.advance().span;
3406                TypeAnnotation {
3407                    kind: TypeAnnotationKind::BooleanLiteral(value),
3408                    span,
3409                }
3410            }
3411            TokenKind::LeftBrace => self.parse_object_type_annotation()?,
3412            TokenKind::LeftParen if self.paren_at_opens_function_type(self.pos, type_pos) => {
3413                self.parse_function_type_annotation()?
3414            }
3415            TokenKind::LeftParen => self.parse_grouped_or_function_type()?,
3416            // Leading `[` is unambiguously a tuple (postfix `[]` can only follow a base type).
3417            TokenKind::LeftBracket => self.parse_tuple_type_annotation()?,
3418            _ => {
3419                self.error_at_peek("expected type");
3420                return None;
3421            }
3422        };
3423        // Array suffixes nest boxed annotations without recursing, so each one
3424        // spends the recursive grammar budget to keep the chain's drop bounded.
3425        let mut array_suffixes = 0usize;
3426        // As in TypeScript, a line break ends the type before `[`: in an object type,
3427        // `a: B` ⏎ `[k: string]: V` is a field followed by an index signature.
3428        while matches!(self.peek().kind, TokenKind::LeftBracket) && !self.line_break_before_peek() {
3429            array_suffixes += 1;
3430            if self.recursion_depth.saturating_add(array_suffixes) >= MAX_PARSE_DEPTH {
3431                self.stop_at_recursion_limit();
3432                return None;
3433            }
3434            let open = self.advance();
3435            if !matches!(self.peek().kind, TokenKind::RightBracket) {
3436                self.error_at(open.span, "expected `]` to close array type");
3437                return None;
3438            }
3439            let close = self.advance();
3440            let span = self.span(ty.span.start, close.span.end);
3441            ty = TypeAnnotation {
3442                kind: TypeAnnotationKind::Array(Box::new(ty)),
3443                span,
3444            };
3445        }
3446        Some(ty)
3447    }
3448
3449    /// `readonly T[]` or `readonly [A, B]`. The operand binds like `keyof`'s, so
3450    /// `readonly T[][]` is a readonly array of mutable arrays and
3451    /// `readonly T[] | null` a union with a readonly member.
3452    fn parse_readonly_type(&mut self, type_pos: TypePos) -> Option<TypeAnnotation> {
3453        let kw = self.advance();
3454        let opens_group = matches!(self.peek().kind, TokenKind::LeftParen);
3455        let operand = self.parse_type_array(type_pos)?;
3456        // `readonly (T[])` is rejected as TypeScript rejects it: the operand must be
3457        // written as an array or tuple type, not grouped into one.
3458        let grouped = opens_group
3459            && matches!(
3460                self.pos
3461                    .checked_sub(1)
3462                    .and_then(|index| self.tokens.get(index))
3463                    .map(|token| &token.kind),
3464                Some(TokenKind::RightParen)
3465            );
3466        if grouped
3467            || !matches!(
3468                operand.kind,
3469                TypeAnnotationKind::Array(_) | TypeAnnotationKind::Tuple(_)
3470            )
3471        {
3472            self.error_at_with_help(
3473                kw.span,
3474                "`readonly` only applies to array and tuple types",
3475                vec![
3476                    "write `readonly T[]` or `readonly [A, B]`; to protect an object's \
3477                     properties, mark each one `readonly` instead"
3478                        .to_string(),
3479                ],
3480            );
3481            return Some(operand);
3482        }
3483        let span = self.span(kw.span.start, operand.span.end);
3484        Some(TypeAnnotation {
3485            kind: TypeAnnotationKind::Readonly(Box::new(operand)),
3486            span,
3487        })
3488    }
3489
3490    /// In type position `(` is ambiguous: a function type's parameter list, or a
3491    /// grouping paren the postfix `[]` / `|` then composes with. Decided by what the
3492    /// parens *contain*, not by what follows them — a trailing `=>` can belong to an
3493    /// enclosing arrow function's body (`(): (string | null) => null`), so looking
3494    /// past the `)` would read that return type as a parameter list. The exception is
3495    /// `(T)` / `(T, U)`, spelled identically either way, which only a trailing `=>`
3496    /// can break.
3497    ///
3498    /// Empty parens count as a parameter list: `()` groups nothing, so the
3499    /// function-type parse's "expected `=>`" is the message that names the fix.
3500    fn paren_at_opens_function_type(&self, index: usize, type_pos: TypePos) -> bool {
3501        let after = |offset: usize| self.tokens.get(index + offset).map(|t| &t.kind);
3502        match (after(1), after(2)) {
3503            // Only a parameter list is empty or starts with a rest element.
3504            (Some(TokenKind::RightParen | TokenKind::DotDotDot), _) => true,
3505            // A named, annotated parameter — no type starts this way.
3506            (Some(TokenKind::Identifier), Some(TokenKind::Colon | TokenKind::Question)) => true,
3507            // `(T)` and `(T, U)` are spelled exactly like a grouped type. v1
3508            // rejects bare parameter names, and a trailing `=>` is the only sign
3509            // the writer meant one — routing those to the function-type parse
3510            // keeps its "params require named annotations" message reachable in
3511            // declaration position (`type F = (T) => R`). In an arrow's *return*
3512            // annotation the trailing `=>` is the body's, so `(): (T) => x` loses
3513            // that message and falls back to the expression parse; dropping the
3514            // redundant parens is the fix there.
3515            (Some(TokenKind::Identifier), Some(TokenKind::Comma | TokenKind::RightParen)) => {
3516                type_pos == TypePos::Anywhere
3517                    && self.index_after_matching_paren(index).is_some_and(|i| {
3518                        matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Arrow))
3519                    })
3520            }
3521            // Anything else can only begin a type, so the parens group.
3522            _ => false,
3523        }
3524    }
3525
3526    /// Parse a `(` the contents rule called a group. If that fails and a `=>` follows
3527    /// the matching `)`, the author was writing a function type after all — a typo in
3528    /// its parameter list is what made it look like a group — so re-parse that way to
3529    /// recover the diagnostic naming the parameter rule instead of a stray-`)` error.
3530    fn parse_grouped_or_function_type(&mut self) -> Option<TypeAnnotation> {
3531        let open = self.pos;
3532        let diagnostics_before = self.diagnostics.len();
3533        if let Some(ty) = self.parse_grouped_type_annotation() {
3534            return Some(ty);
3535        }
3536        let arrow_follows = self
3537            .index_after_matching_paren(open)
3538            .is_some_and(|i| matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Arrow)));
3539        if !arrow_follows {
3540            return None;
3541        }
3542        self.pos = open;
3543        self.diagnostics.truncate(diagnostics_before);
3544        self.parse_function_type_annotation()
3545    }
3546
3547    /// A parenthesized type is its inner type — the parens only group, so no node
3548    /// records them. The span covers them so a diagnostic points at what was written.
3549    fn parse_grouped_type_annotation(&mut self) -> Option<TypeAnnotation> {
3550        let open = self.advance();
3551        let inner = self.parse_type_annotation()?;
3552        if !matches!(self.peek().kind, TokenKind::RightParen) {
3553            self.error_at_peek("expected `)` to close a parenthesized type");
3554            return None;
3555        }
3556        let close = self.advance();
3557        Some(TypeAnnotation {
3558            span: self.span(open.span.start, close.span.end),
3559            ..inner
3560        })
3561    }
3562
3563    // Param names are required — bare `(T, U) => R` is rejected.
3564    fn parse_function_type_annotation(&mut self) -> Option<TypeAnnotation> {
3565        let open = self.advance();
3566        let params = self.parse_function_type_params()?;
3567        if !matches!(self.peek().kind, TokenKind::Arrow) {
3568            self.error_at_peek("expected `=>` in function-type annotation");
3569            return None;
3570        }
3571        self.advance();
3572        let return_type = self.parse_type_annotation()?;
3573        let end = return_type.span.end;
3574        Some(TypeAnnotation {
3575            kind: TypeAnnotationKind::Function {
3576                params,
3577                return_type: Box::new(return_type),
3578            },
3579            span: self.span(open.span.start, end),
3580        })
3581    }
3582
3583    /// The `(…)` of a function type, its `(` already consumed and its `)` consumed here.
3584    /// Shared with a type literal's method members, which spell the same parameter list
3585    /// before a `:` rather than a `=>`.
3586    fn parse_function_type_params(&mut self) -> Option<Vec<TypeAnnotationField>> {
3587        let mut params: Vec<TypeAnnotationField> = Vec::new();
3588        if !matches!(self.peek().kind, TokenKind::RightParen) {
3589            loop {
3590                let rest = if matches!(self.peek().kind, TokenKind::DotDotDot) {
3591                    self.advance();
3592                    true
3593                } else {
3594                    false
3595                };
3596                let name_tok = self.expect_binding_identifier(if rest {
3597                    "expected identifier after `...` in function-type annotation"
3598                } else {
3599                    "expected parameter name in function-type annotation"
3600                })?;
3601                let name = self.ident_from_token(&name_tok);
3602                let optional = self.parse_parameter_optional(rest, false)?;
3603                if !matches!(self.peek().kind, TokenKind::Colon) {
3604                    if rest {
3605                        self.error_at_peek_with_help(
3606                            "rest parameter requires a type annotation",
3607                            vec!["(...args: T[]) => R".to_string()],
3608                        );
3609                    } else {
3610                        self.error_at_peek(
3611                            "expected `:` after parameter name (function-type params \
3612                             require named annotations in v1)",
3613                        );
3614                    }
3615                    return None;
3616                }
3617                self.advance();
3618                let ty = self.parse_type_annotation()?;
3619                if rest && !is_rest_array_annotation(&ty) {
3620                    self.error_at(ty.span, "rest parameter type must be an array");
3621                    return None;
3622                }
3623                if matches!(self.peek().kind, TokenKind::Equals) {
3624                    if rest {
3625                        self.error_rest_parameter_default();
3626                    } else {
3627                        self.error_at_peek(
3628                            "default values in function-type annotations are not yet supported",
3629                        );
3630                    }
3631                    return None;
3632                }
3633                params.push(TypeAnnotationField {
3634                    name,
3635                    ty,
3636                    optional,
3637                    readonly: false,
3638                    rest,
3639                    method: false,
3640                });
3641                match self.peek().kind {
3642                    TokenKind::Comma => {
3643                        if rest {
3644                            let comma = self.advance();
3645                            self.error_at(comma.span, "rest parameter must be the last parameter");
3646                            return None;
3647                        }
3648                        self.advance();
3649                        if matches!(self.peek().kind, TokenKind::RightParen) {
3650                            break;
3651                        }
3652                    }
3653                    TokenKind::RightParen => break,
3654                    _ => {
3655                        self.error_at_peek("expected `,` or `)`");
3656                        return None;
3657                    }
3658                }
3659            }
3660            self.check_parameter_order(params.iter().map(ParamOrder::of_field));
3661        }
3662        if !matches!(self.peek().kind, TokenKind::RightParen) {
3663            self.error_at_peek("expected `)`");
3664            return None;
3665        }
3666        self.advance();
3667        Some(params)
3668    }
3669
3670    fn parse_tuple_type_annotation(&mut self) -> Option<TypeAnnotation> {
3671        let open = self.advance();
3672        if matches!(self.peek().kind, TokenKind::RightBracket) {
3673            let close = self.peek().span;
3674            self.error_at(
3675                self.span(open.span.start, close.end),
3676                "tuple types must have at least one element",
3677            );
3678            return None;
3679        }
3680        let mut elements: Vec<TypeAnnotation> = Vec::new();
3681        loop {
3682            if matches!(self.peek().kind, TokenKind::DotDotDot) {
3683                self.error_at_with_help(
3684                    self.peek().span,
3685                    "rest elements in tuple types are not supported",
3686                    vec!["use an array type `T[]` for a list of varying length".to_string()],
3687                );
3688                return None;
3689            }
3690            // Labels may be mixed with unlabeled elements, as TypeScript allows
3691            // since 5.2.
3692            let label = self.skip_tuple_element_label();
3693            let mut ty = self.parse_type_annotation()?;
3694            let suffix_optional = matches!(self.peek().kind, TokenKind::Question);
3695            if suffix_optional && label != TupleElementLabel::None {
3696                self.error_at_peek_with_help(
3697                    "put the optional marker after the tuple element label",
3698                    vec!["write `name?: T` instead of `name: T?`".to_string()],
3699                );
3700                return None;
3701            }
3702            let optional = label == TupleElementLabel::Optional || suffix_optional;
3703            if optional {
3704                let end = if suffix_optional {
3705                    self.advance().span.end
3706                } else {
3707                    ty.span.end
3708                };
3709                let span = self.span(ty.span.start, end);
3710                ty = TypeAnnotation {
3711                    kind: TypeAnnotationKind::Optional(Box::new(ty)),
3712                    span,
3713                };
3714            } else if elements
3715                .iter()
3716                .any(|element| matches!(element.kind, TypeAnnotationKind::Optional(_)))
3717            {
3718                self.error_at(
3719                    ty.span,
3720                    "a required tuple element cannot follow an optional element",
3721                );
3722                return None;
3723            }
3724            elements.push(ty);
3725            match self.peek().kind {
3726                TokenKind::Comma => {
3727                    self.advance();
3728                    if matches!(self.peek().kind, TokenKind::RightBracket) {
3729                        break;
3730                    }
3731                }
3732                TokenKind::RightBracket => break,
3733                _ => {
3734                    self.error_at_peek("expected `,` or `]`");
3735                    return None;
3736                }
3737            }
3738        }
3739        if !matches!(self.peek().kind, TokenKind::RightBracket) {
3740            self.error_at_peek("expected `]`");
3741            return None;
3742        }
3743        let close = self.advance();
3744        Some(TypeAnnotation {
3745            kind: TypeAnnotationKind::Tuple(elements),
3746            span: self.span(open.span.start, close.span.end),
3747        })
3748    }
3749
3750    /// Labels document positions; retain only whether the label was optional.
3751    fn skip_tuple_element_label(&mut self) -> TupleElementLabel {
3752        if !is_property_name(&self.peek().kind) {
3753            return TupleElementLabel::None;
3754        }
3755        match self.peek_at(1).kind {
3756            TokenKind::Colon => {
3757                self.advance();
3758                self.advance();
3759                TupleElementLabel::Required
3760            }
3761            TokenKind::Question if matches!(self.peek_at(2).kind, TokenKind::Colon) => {
3762                self.advance();
3763                self.advance();
3764                self.advance();
3765                TupleElementLabel::Optional
3766            }
3767            _ => TupleElementLabel::None,
3768        }
3769    }
3770
3771    /// `[]` or `[]: V` — an index signature with no parameter, which TypeScript
3772    /// reads and rejects.
3773    fn peek_is_parameterless_index_signature(&self) -> bool {
3774        matches!(self.peek().kind, TokenKind::LeftBracket)
3775            && matches!(self.peek_at(1).kind, TokenKind::RightBracket)
3776    }
3777
3778    /// Diagnoses and skips a parameterless index signature (see
3779    /// `peek_is_parameterless_index_signature`) so the members after it still parse.
3780    /// `member_start` is where the member begins, at a `readonly` modifier if any.
3781    fn skip_parameterless_index_signature(&mut self, member_start: u32) -> Option<()> {
3782        self.advance();
3783        let close = self.advance();
3784        self.error_at_with_help(
3785            self.span(member_start, close.span.end),
3786            "an index signature must declare exactly one parameter",
3787            vec!["write `[key: string]: V`".to_string()],
3788        );
3789        if matches!(self.peek().kind, TokenKind::Colon) {
3790            self.advance();
3791            self.parse_type_annotation()?;
3792        }
3793        Some(())
3794    }
3795
3796    fn parse_index_signature(&mut self, readonly: bool) -> Option<crate::IndexSignatureAnnotation> {
3797        let open = self.advance();
3798        let parameter = self.expect_property_ident("expected index parameter name")?;
3799        self.reject_eval_or_arguments(parameter.span);
3800        if !matches!(self.peek().kind, TokenKind::Colon) {
3801            self.error_at_peek("expected `:` after index parameter name");
3802            return None;
3803        }
3804        self.advance();
3805        if !self.peek_identifier_text_is("string") {
3806            self.error_at_peek("index signatures require string keys; use `[key: string]: V`");
3807            return None;
3808        }
3809        self.advance();
3810        if !matches!(self.peek().kind, TokenKind::RightBracket) {
3811            self.error_at_peek("expected `]` after string index key type");
3812            return None;
3813        }
3814        self.advance();
3815        if !matches!(self.peek().kind, TokenKind::Colon) {
3816            self.error_at_peek("expected `:` before index value type");
3817            return None;
3818        }
3819        self.advance();
3820        let value = self.parse_type_annotation()?;
3821        Some(crate::IndexSignatureAnnotation {
3822            span: self.span(open.span.start, value.span.end),
3823            value,
3824            readonly,
3825        })
3826    }
3827
3828    fn parse_object_type_annotation(&mut self) -> Option<TypeAnnotation> {
3829        let open = self.advance();
3830        let mut fields: Vec<TypeAnnotationField> = Vec::new();
3831        let mut index = None;
3832        while !matches!(self.peek().kind, TokenKind::RightBrace | TokenKind::Eof)
3833            && !self.peek_starts_declaration()
3834        {
3835            let member_start = self.peek().span.start;
3836            let readonly = self.eat_readonly_property_modifier();
3837            if self.peek_is_parameterless_index_signature() {
3838                self.skip_parameterless_index_signature(member_start)?;
3839            } else if self.peek_is_construct_signature() {
3840                self.reject_construct_signature()?;
3841            } else if matches!(self.peek().kind, TokenKind::LeftBracket) {
3842                let signature = self.parse_index_signature(readonly)?;
3843                if index.is_some() {
3844                    self.error_at_with_help(
3845                        signature.span,
3846                        "duplicate string index signature",
3847                        vec![],
3848                    );
3849                }
3850                index = Some(Box::new(signature));
3851            } else {
3852                let field = self.parse_object_type_field(readonly, member_start)?;
3853                if let Some(existing) = fields.iter().find(|f| f.name.name == field.name.name) {
3854                    self.diagnostics.push(Diagnostic {
3855                        severity: Severity::Error,
3856                        span: field.name.span,
3857                        message: format!("duplicate field `{}` in object type", field.name.name),
3858                        help: vec![],
3859                        notes: vec![(existing.name.span, "first defined here".into())],
3860                    });
3861                }
3862                fields.push(field);
3863            }
3864            self.finish_type_member(self.prev_token_end(), "expected `;`, `,`, or `}`")?;
3865        }
3866        if !matches!(self.peek().kind, TokenKind::RightBrace) {
3867            self.error_at_peek("expected `}`");
3868            return None;
3869        }
3870        let close = self.advance();
3871        Some(TypeAnnotation {
3872            kind: TypeAnnotationKind::Object { index, fields },
3873            span: self.span(open.span.start, close.span.end),
3874        })
3875    }
3876
3877    /// Whether the next tokens start a declaration (`function f`, `class C`, `type T`,
3878    /// …, optionally after `export`, `declare`, `async` or `abstract`) or an export
3879    /// statement (`export {`, `export *`, `export default`), which no type member can:
3880    /// a keyword is a member name only before `:`, `?` or `(`. A member list that reaches one was left unclosed, so it stops there
3881    /// and leaves the declaration to be parsed.
3882    fn peek_starts_declaration(&self) -> bool {
3883        if matches!(self.peek().kind, TokenKind::Export)
3884            && matches!(
3885                self.peek_at(1).kind,
3886                TokenKind::Default | TokenKind::LeftBrace | TokenKind::Star
3887            )
3888        {
3889            return true;
3890        }
3891        let mut offset = 0;
3892        while self.peek_at_is_declaration_modifier(offset) {
3893            offset += 1;
3894        }
3895        let declaration_keyword = matches!(
3896            self.peek_at(offset).kind,
3897            TokenKind::Function
3898                | TokenKind::Class
3899                | TokenKind::Let
3900                | TokenKind::Const
3901                | TokenKind::Enum
3902                | TokenKind::Interface
3903        ) || self.peek_at_is_word(offset, "type");
3904        declaration_keyword && matches!(self.peek_at(offset + 1).kind, TokenKind::Identifier)
3905    }
3906
3907    /// `export`, `declare`, `async` or `abstract` before a declaration keyword.
3908    fn peek_at_is_declaration_modifier(&self, offset: usize) -> bool {
3909        matches!(self.peek_at(offset).kind, TokenKind::Export)
3910            || ["declare", "async", "abstract"]
3911                .iter()
3912                .any(|word| self.peek_at_is_word(offset, word))
3913    }
3914
3915    /// `new (params): T` or `new <T>(params): T` inside a type literal: a construct
3916    /// signature, which TypeScript reads where it would otherwise see a method named
3917    /// `new`. A quoted `"new"()`, an optional `new?()` and a field `new: T` are
3918    /// ordinary members, as in TypeScript.
3919    fn peek_is_construct_signature(&self) -> bool {
3920        matches!(self.peek().kind, TokenKind::New)
3921            && matches!(
3922                self.peek_at(1).kind,
3923                TokenKind::LeftParen | TokenKind::LessThan
3924            )
3925    }
3926
3927    /// Only interfaces declare construct signatures, so one in a type literal (see
3928    /// `peek_is_construct_signature`) is diagnosed, then skipped rather than misread
3929    /// as a method named `new`.
3930    fn reject_construct_signature(&mut self) -> Option<()> {
3931        let keyword = self.advance();
3932        self.error_at_with_help(
3933            keyword.span,
3934            "construct signatures are not supported in object types",
3935            vec![
3936                "declare an `interface` with the `new (…)` signature, or pass a factory \
3937                 function such as `() => T`"
3938                    .to_string(),
3939            ],
3940        );
3941        if matches!(self.peek().kind, TokenKind::LessThan) {
3942            self.parse_generic_param_list()?;
3943        }
3944        if !matches!(self.peek().kind, TokenKind::LeftParen) {
3945            self.error_at_peek("expected `(` after construct signature type parameters");
3946            return None;
3947        }
3948        self.parse_object_type_method_signature(keyword.span.start)?;
3949        Some(())
3950    }
3951
3952    /// `name: T`, `name?: T` or the method signature `name(params): T`.
3953    fn parse_object_type_field(
3954        &mut self,
3955        readonly: bool,
3956        member_start: u32,
3957    ) -> Option<TypeAnnotationField> {
3958        let name = self.expect_property_ident("expected field name in object type")?;
3959        // `name?: T` — omittable at construction; reads widen to `T | undefined`.
3960        let optional = matches!(self.peek().kind, TokenKind::Question);
3961        if optional {
3962            self.advance();
3963        }
3964        let method = matches!(self.peek().kind, TokenKind::LeftParen);
3965        let ty = if method {
3966            if readonly {
3967                self.reject_readonly_modifier(self.readonly_modifier_span(member_start));
3968            }
3969            self.parse_object_type_method_signature(name.span.start)?
3970        } else {
3971            if !matches!(self.peek().kind, TokenKind::Colon) {
3972                self.error_at_peek("expected `:` after field name");
3973                return None;
3974            }
3975            self.advance();
3976            self.parse_type_annotation()?
3977        };
3978        Some(TypeAnnotationField {
3979            name,
3980            ty,
3981            optional,
3982            readonly,
3983            rest: false,
3984            method,
3985        })
3986    }
3987
3988    /// `m(params): T` inside a type literal, spelled as the function-typed field
3989    /// `m: (params) => T` — the two are interchangeable in a structural shape, and
3990    /// interfaces already accept both spellings for the same member. `start` is the
3991    /// member name's offset, so the annotation's span covers the name too.
3992    fn parse_object_type_method_signature(&mut self, start: u32) -> Option<TypeAnnotation> {
3993        self.advance();
3994        let params = self.parse_function_type_params()?;
3995        if !matches!(self.peek().kind, TokenKind::Colon) {
3996            self.error_at_peek("expected `:` and return type after method parameters");
3997            return None;
3998        }
3999        self.advance();
4000        let return_type = self.parse_type_annotation()?;
4001        let end = return_type.span.end;
4002        Some(TypeAnnotation {
4003            kind: TypeAnnotationKind::Function {
4004                params,
4005                return_type: Box::new(return_type),
4006            },
4007            span: self.span(start, end),
4008        })
4009    }
4010
4011    /// An identifier that names a binding or a type, which rejects the words strict
4012    /// mode reserves (see [`STRICT_MODE_RESERVED_WORDS`]).
4013    fn expect_identifier(&mut self, message: &str) -> Option<Token> {
4014        let tok = self.expect_identifier_name(message)?;
4015        self.reject_strict_mode_reserved_word(&tok);
4016        Some(tok)
4017    }
4018
4019    /// The name a variable, parameter, function or `catch` binds. Strict mode also
4020    /// forbids `eval` and `arguments` there, though not as a class, enum or type
4021    /// name (TypeScript's TS1100 covers the same positions).
4022    fn expect_binding_identifier(&mut self, message: &str) -> Option<Token> {
4023        let tok = self.expect_identifier(message)?;
4024        self.reject_eval_or_arguments(tok.span);
4025        Some(tok)
4026    }
4027
4028    /// An identifier in a position where strict mode's reserved words are allowed: an
4029    /// enum member, a property, or the name an import specifier refers to.
4030    fn expect_identifier_name(&mut self, message: &str) -> Option<Token> {
4031        if matches!(self.peek().kind, TokenKind::Identifier) {
4032            return Some(self.advance());
4033        }
4034        if is_reserved_identifier_word(&self.peek().kind) {
4035            let tok = self.advance();
4036            let keyword = &self.source[tok.span.start as usize..tok.span.end as usize];
4037            self.error_at_with_help(
4038                tok.span,
4039                format!("`{keyword}` is a reserved keyword and can't be used as a name"),
4040                vec![format!(
4041                    "rename it, for example: `{}`",
4042                    reserved_keyword_rename_example(keyword)
4043                )],
4044            );
4045            return None;
4046        }
4047        self.error_at_peek(message.to_string());
4048        None
4049    }
4050
4051    /// Reports a binding named by a word strict mode reserves. Parsing continues
4052    /// with the name: nothing else about it is wrong.
4053    fn reject_strict_mode_reserved_word(&mut self, tok: &Token) {
4054        let word = &self.source[tok.span.start as usize..tok.span.end as usize];
4055        if !STRICT_MODE_RESERVED_WORDS.contains(&word) {
4056            return;
4057        }
4058        self.error_at_with_help(
4059            tok.span,
4060            format!("`{word}` is a reserved word in strict mode and can't be used as a name"),
4061            vec![format!(
4062                "rename it, for example: `{}`",
4063                reserved_keyword_rename_example(word)
4064            )],
4065        );
4066    }
4067
4068    /// Reports a binding named `eval` or `arguments`. Parsing continues with the
4069    /// name: nothing else about it is wrong.
4070    fn reject_eval_or_arguments(&mut self, span: Span) {
4071        let word = &self.source[span.start as usize..span.end as usize];
4072        if !matches!(word, "eval" | "arguments") {
4073            return;
4074        }
4075        self.error_at_with_help(
4076            span,
4077            format!("`{word}` can't name a binding in strict mode"),
4078            vec![format!(
4079                "rename it, for example: `{}`",
4080                reserved_keyword_rename_example(word)
4081            )],
4082        );
4083    }
4084
4085    fn ident_from_token(&self, tok: &Token) -> Ident {
4086        Ident {
4087            name: self.source[tok.span.start as usize..tok.span.end as usize].to_string(),
4088            span: tok.span,
4089        }
4090    }
4091
4092    fn property_ident_from_token(&self, tok: &Token) -> Ident {
4093        if let TokenKind::StringLiteral(s) = &tok.kind {
4094            Ident {
4095                name: s.clone(),
4096                span: tok.span,
4097            }
4098        } else {
4099            self.ident_from_token(tok)
4100        }
4101    }
4102
4103    fn expect_property_name(&mut self, message: &str) -> Option<Token> {
4104        if !is_property_name(&self.peek().kind) {
4105            self.error_at_peek(message.to_string());
4106            return None;
4107        }
4108        Some(self.advance())
4109    }
4110
4111    fn expect_property_ident(&mut self, message: &str) -> Option<Ident> {
4112        if !is_property_name(&self.peek().kind)
4113            && !matches!(self.peek().kind, TokenKind::StringLiteral(_))
4114        {
4115            self.error_at_peek(message.to_string());
4116            return None;
4117        }
4118        let tok = self.advance();
4119        Some(self.property_ident_from_token(&tok))
4120    }
4121
4122    /// The span of a `readonly` modifier that [`Parser::eat_readonly_property_modifier`]
4123    /// consumed at `member_start`.
4124    fn readonly_modifier_span(&self, member_start: u32) -> Span {
4125        self.span(member_start, member_start + "readonly".len() as u32)
4126    }
4127
4128    /// Reports `readonly` on a method, accessor or method signature, as TypeScript
4129    /// does (TS1024). Parsing continues: the modifier changes nothing else.
4130    fn reject_readonly_modifier(&mut self, span: Span) {
4131        self.error_at_with_help(
4132            span,
4133            "`readonly` can only modify a property or index signature",
4134            vec!["remove `readonly`".to_string()],
4135        );
4136    }
4137
4138    fn eat_readonly_property_modifier(&mut self) -> bool {
4139        if self.peek_identifier_text_is("readonly")
4140            && !self.line_break_after_peek()
4141            && (self.peek_starts_property_after_readonly()
4142                || matches!(self.peek_at(1).kind, TokenKind::LeftBracket))
4143        {
4144            self.advance();
4145            return true;
4146        }
4147        false
4148    }
4149
4150    fn peek_starts_property_after_readonly(&self) -> bool {
4151        let name = &self.peek_at(1).kind;
4152        let after_name = &self.peek_at(2).kind;
4153        let after_optional = &self.peek_at(3).kind;
4154        // A member starts its type with `:`, or its parameter list with `(` — otherwise
4155        // `readonly` is the member's own name.
4156        let starts_member_type =
4157            |kind: &TokenKind| matches!(kind, TokenKind::Colon | TokenKind::LeftParen);
4158        (is_property_name(name) || matches!(name, TokenKind::StringLiteral(_)))
4159            && (starts_member_type(after_name)
4160                || matches!(after_name, TokenKind::Question) && starts_member_type(after_optional))
4161    }
4162
4163    fn peek_identifier_text_is(&self, expected: &str) -> bool {
4164        self.token_is_word(self.peek(), expected)
4165    }
4166
4167    fn peek_at_is_word(&self, offset: usize, expected: &str) -> bool {
4168        self.token_is_word(self.peek_at(offset), expected)
4169    }
4170
4171    fn token_is_word(&self, tok: &Token, expected: &str) -> bool {
4172        matches!(tok.kind, TokenKind::Identifier)
4173            && &self.source[tok.span.start as usize..tok.span.end as usize] == expected
4174    }
4175
4176    /// `delete` is lexed as an identifier: `map.delete(k)`, a `delete()` method and
4177    /// `submilli:http`'s `delete(url)` function all use the name. So it is the
4178    /// operator only when an operand that can't be an argument list follows it, and
4179    /// `delete (o.x)` stays a call. `as` and `satisfies` are lexed as identifiers too:
4180    /// `delete as unknown` casts the function value, while `delete as.x` deletes from a
4181    /// binding named `as`.
4182    fn at_delete_operator(&self) -> bool {
4183        if !self.peek_identifier_text_is("delete") {
4184            return false;
4185        }
4186        if !matches!(
4187            self.peek_at(1).kind,
4188            TokenKind::Identifier | TokenKind::This
4189        ) {
4190            return false;
4191        }
4192        let next_is_cast_keyword =
4193            self.peek_at_is_word(1, "as") || self.peek_at_is_word(1, "satisfies");
4194        !next_is_cast_keyword || cannot_start_type(&self.peek_at(2).kind)
4195    }
4196
4197    /// `type X …` commits to a type-alias declaration only when followed by an
4198    /// identifier, mirroring TypeScript; `type = 5`, `type;`, `type(x)` stay
4199    /// expression statements over a binding named `type`.
4200    fn at_type_alias_head(&self) -> bool {
4201        self.peek_identifier_text_is("type")
4202            && matches!(self.peek_at(1).kind, TokenKind::Identifier)
4203    }
4204
4205    /// Assignment is the lowest-precedence expression and right-associative:
4206    /// `a = b = c` assigns `c` to both, and yields it.
4207    fn parse_expression(&mut self) -> Option<ExprId> {
4208        self.with_recursion_limit(Self::parse_expression_inner)
4209    }
4210
4211    fn parse_expression_inner(&mut self) -> Option<ExprId> {
4212        let written = self.parse_conditional()?;
4213        let shift_assignment = self.peek_shift().filter(|shift| shift.assignment);
4214        if !is_assign_lookahead(&self.peek().kind) && shift_assignment.is_none() {
4215            return Some(written);
4216        }
4217        let target_span = parse_arena_result(self.ast.try_expr(written), &mut self.fatal)?.span;
4218        let target = self.assignment_target(written)?;
4219        let op_tok = self.advance();
4220        let mut op_span = op_tok.span;
4221        let op = if let Some(ShiftToken { op, tokens, .. }) = shift_assignment {
4222            for _ in 1..tokens {
4223                op_span.end = self.advance().span.end;
4224            }
4225            Some(op)
4226        } else {
4227            compound_op_for_token(&op_tok.kind)
4228        };
4229        let value = self.parse_expression()?;
4230        let value_span = parse_arena_result(self.ast.try_expr(value), &mut self.fatal)?.span;
4231        parse_arena_result(
4232            self.ast.try_push_expr(Expr {
4233                kind: ExprKind::Assign {
4234                    target,
4235                    op,
4236                    op_span,
4237                    value,
4238                },
4239                span: self.span(target_span.start, value_span.end),
4240            }),
4241            &mut self.fatal,
4242        )
4243    }
4244
4245    /// The binding, field, or element `written` names, through any parentheses:
4246    /// `(a) = 1` assigns `a`, as in JavaScript.
4247    fn assignment_target(&mut self, written: ExprId) -> Option<ExprId> {
4248        let mut target = written;
4249        while let ExprKind::Paren(inner) =
4250            parse_arena_result(self.ast.try_expr(target), &mut self.fatal)?.kind
4251        {
4252            target = inner;
4253        }
4254        if matches!(
4255            parse_arena_result(self.ast.try_expr(target), &mut self.fatal)?.kind,
4256            ExprKind::Identifier(_) | ExprKind::FieldAccess { .. } | ExprKind::IndexAccess { .. }
4257        ) {
4258            return Some(target);
4259        }
4260        let error_span = parse_arena_result(self.ast.try_expr(written), &mut self.fatal)?.span;
4261        self.error_at_with_help(
4262            error_span,
4263            "invalid assignment target",
4264            vec![
4265                "assign to a variable, a field (`o.f = …`), or an element (`a[i] = …`)".to_string(),
4266            ],
4267        );
4268        None
4269    }
4270
4271    fn parse_conditional(&mut self) -> Option<ExprId> {
4272        if self.is_arrow_start() {
4273            return self.parse_arrow();
4274        }
4275        let cond = self.parse_binary(0)?;
4276        // Ternary sits below every binary op; right-associative.
4277        if !matches!(self.peek().kind, TokenKind::Question) {
4278            return Some(cond);
4279        }
4280        self.advance();
4281        let then_ = self.parse_expression()?;
4282        if !matches!(self.peek().kind, TokenKind::Colon) {
4283            self.error_at_peek_with_help(
4284                "expected `:` to complete ternary expression",
4285                vec!["const x = cond ? then : else;".to_string()],
4286            );
4287            return None;
4288        }
4289        self.advance();
4290        let else_ = self.parse_expression()?;
4291        let cond_span = parse_arena_result(self.ast.try_expr(cond), &mut self.fatal)?.span;
4292        let else_span = parse_arena_result(self.ast.try_expr(else_), &mut self.fatal)?.span;
4293        parse_arena_result(
4294            self.ast.try_push_expr(Expr {
4295                kind: ExprKind::Ternary { cond, then_, else_ },
4296                span: self.span(cond_span.start, else_span.end),
4297            }),
4298            &mut self.fatal,
4299        )
4300    }
4301
4302    fn is_arrow_start(&self) -> bool {
4303        match self.peek().kind {
4304            TokenKind::Identifier => matches!(self.peek_at(1).kind, TokenKind::Arrow),
4305            TokenKind::LeftParen => self.find_arrow_after_matching_paren(),
4306            _ => false,
4307        }
4308    }
4309
4310    /// Index just past the `)` matching the `(` at `index`; `None` when the parens
4311    /// never balance. Pure lookahead — emits no diagnostics.
4312    fn index_after_matching_paren(&self, index: usize) -> Option<usize> {
4313        if !matches!(
4314            self.tokens.get(index).map(|t| &t.kind),
4315            Some(TokenKind::LeftParen)
4316        ) {
4317            return None;
4318        }
4319        self.scan_past_balanced(
4320            index,
4321            |k| matches!(k, TokenKind::LeftParen),
4322            |k| matches!(k, TokenKind::RightParen),
4323        )
4324    }
4325
4326    fn find_arrow_after_matching_paren(&self) -> bool {
4327        let Some(i) = self.index_after_matching_paren(self.pos) else {
4328            return false;
4329        };
4330        let after_type = if matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Colon)) {
4331            match self.scan_past_type_annotation(i + 1, TypePos::ArrowReturn) {
4332                Some(p) => p,
4333                None => return false,
4334            }
4335        } else {
4336            i
4337        };
4338        matches!(
4339            self.tokens.get(after_type).map(|t| &t.kind),
4340            Some(TokenKind::Arrow),
4341        )
4342    }
4343
4344    // Pure lookahead used only by the arrow-disambiguator. No diagnostics.
4345    // Must accept every form `parse_type_annotation` accepts: a form missing
4346    // here makes the disambiguator reject a valid arrow, so the expression
4347    // parser reports a misleading `expected expression` at the return type.
4348    // `arrow_return_type_scanner_matches_type_grammar` pins the two together.
4349    fn scan_past_type_annotation(&self, start: usize, type_pos: TypePos) -> Option<usize> {
4350        self.scan_past_type_annotation_at_depth(start, type_pos, 0)
4351    }
4352
4353    fn scan_past_type_annotation_at_depth(
4354        &self,
4355        start: usize,
4356        type_pos: TypePos,
4357        depth: usize,
4358    ) -> Option<usize> {
4359        if depth >= MAX_PARSE_DEPTH {
4360            return None;
4361        }
4362        if matches!(
4363            self.tokens.get(start).map(|t| &t.kind),
4364            Some(TokenKind::Identifier)
4365        ) && self
4366            .tokens
4367            .get(start + 1)
4368            .is_some_and(|t| self.token_is_word(t, "is"))
4369        {
4370            return self.scan_past_type_annotation_at_depth(start + 2, type_pos, depth + 1);
4371        }
4372        // Leading `|` (prettier's multiline union format).
4373        let start = if matches!(
4374            self.tokens.get(start).map(|t| &t.kind),
4375            Some(TokenKind::Pipe)
4376        ) {
4377            start + 1
4378        } else {
4379            start
4380        };
4381        let mut i = self.scan_past_single_type_member(start, type_pos, depth + 1)?;
4382        while matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Pipe)) {
4383            i = self.scan_past_single_type_member(i + 1, type_pos, depth + 1)?;
4384        }
4385        Some(i)
4386    }
4387
4388    fn scan_past_single_type_member(
4389        &self,
4390        start: usize,
4391        type_pos: TypePos,
4392        depth: usize,
4393    ) -> Option<usize> {
4394        if depth >= MAX_PARSE_DEPTH {
4395            return None;
4396        }
4397        if self.is_contextual_type_operator(start, "keyof")
4398            || self.is_contextual_type_operator(start, "readonly")
4399        {
4400            return self.scan_past_single_type_member(start + 1, type_pos, depth + 1);
4401        }
4402        let mut i = start;
4403        match self.tokens.get(i)?.kind {
4404            TokenKind::Identifier | TokenKind::Void => {
4405                i += 1;
4406                while matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Dot)) {
4407                    if !matches!(
4408                        self.tokens.get(i + 1).map(|t| &t.kind),
4409                        Some(TokenKind::Identifier)
4410                    ) {
4411                        return None;
4412                    }
4413                    i += 2;
4414                }
4415                if matches!(
4416                    self.tokens.get(i).map(|t| &t.kind),
4417                    Some(TokenKind::LessThan)
4418                ) {
4419                    i = self.scan_past_balanced(
4420                        i,
4421                        |k| matches!(k, TokenKind::LessThan),
4422                        |k| matches!(k, TokenKind::GreaterThan),
4423                    )?;
4424                }
4425            }
4426            TokenKind::NullLiteral
4427            | TokenKind::StringLiteral(_)
4428            | TokenKind::NumberLiteral(_)
4429            | TokenKind::BooleanLiteral(_) => {
4430                i += 1;
4431            }
4432            TokenKind::LeftBrace => {
4433                i = self.scan_past_balanced(
4434                    i,
4435                    |k| matches!(k, TokenKind::LeftBrace),
4436                    |k| matches!(k, TokenKind::RightBrace),
4437                )?;
4438            }
4439            TokenKind::LeftBracket => {
4440                i = self.scan_past_balanced(
4441                    i,
4442                    |k| matches!(k, TokenKind::LeftBracket),
4443                    |k| matches!(k, TokenKind::RightBracket),
4444                )?;
4445            }
4446            // Same `(`-disambiguation `parse_type_array` uses, and it has to agree:
4447            // a function type carries its own `=>`, so everything after it is the
4448            // return type and the scan ends with that. Grouping parens instead fall
4449            // through to the postfix `[]` loop below.
4450            TokenKind::LeftParen => {
4451                let is_function_type = self.paren_at_opens_function_type(i, type_pos);
4452                i = self.scan_past_balanced(
4453                    i,
4454                    |k| matches!(k, TokenKind::LeftParen),
4455                    |k| matches!(k, TokenKind::RightParen),
4456                )?;
4457                if is_function_type {
4458                    if !matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Arrow)) {
4459                        return None;
4460                    }
4461                    return self.scan_past_type_annotation_at_depth(
4462                        i + 1,
4463                        TypePos::Anywhere,
4464                        depth + 1,
4465                    );
4466                }
4467            }
4468            _ => return None,
4469        }
4470        while matches!(
4471            self.tokens.get(i).map(|t| &t.kind),
4472            Some(TokenKind::LeftBracket)
4473        ) {
4474            i += 1;
4475            if !matches!(
4476                self.tokens.get(i).map(|t| &t.kind),
4477                Some(TokenKind::RightBracket)
4478            ) {
4479                return None;
4480            }
4481            i += 1;
4482        }
4483        Some(i)
4484    }
4485
4486    // `start` must index the opening token; returns the index just past the
4487    // matching close, or None if the tokens run out first.
4488    fn scan_past_balanced(
4489        &self,
4490        start: usize,
4491        is_open: fn(&TokenKind) -> bool,
4492        is_close: fn(&TokenKind) -> bool,
4493    ) -> Option<usize> {
4494        let mut depth: i32 = 1;
4495        let mut i = start + 1;
4496        while i < self.tokens.len() && depth > 0 {
4497            let token = self.tokens.get(i)?;
4498            let kind = &token.kind;
4499            if is_open(kind) {
4500                depth += 1;
4501            } else if is_close(kind) {
4502                depth -= 1;
4503            } else if matches!(kind, TokenKind::Eof) {
4504                return None;
4505            }
4506            i += 1;
4507        }
4508        if depth == 0 { Some(i) } else { None }
4509    }
4510
4511    fn parse_arrow(&mut self) -> Option<ExprId> {
4512        let start_span = self.peek().span;
4513
4514        let params = match self.peek().kind {
4515            TokenKind::Identifier => {
4516                let name_tok = self.advance();
4517                self.reject_eval_or_arguments(name_tok.span);
4518                let name = self.ident_from_token(&name_tok);
4519                vec![ParamDecl {
4520                    name,
4521                    ty: None,
4522                    default: None,
4523                    optional: false,
4524                    pattern: None,
4525                    rest: false,
4526                    modifiers: None,
4527                }]
4528            }
4529            TokenKind::LeftParen => {
4530                self.advance();
4531                let params = if matches!(self.peek().kind, TokenKind::RightParen) {
4532                    Vec::new()
4533                } else {
4534                    self.parse_arrow_param_list()?
4535                };
4536                if !matches!(self.peek().kind, TokenKind::RightParen) {
4537                    self.error_at_peek("expected `)`");
4538                    return None;
4539                }
4540                self.advance();
4541                params
4542            }
4543            _ => return self.invariant_failure("is_arrow_start guarded the entry"),
4544        };
4545
4546        let (return_type, type_predicate) = if matches!(self.peek().kind, TokenKind::Colon) {
4547            self.advance();
4548            self.parse_predicate_or_return_type(TypePos::ArrowReturn)?
4549        } else {
4550            (None, None)
4551        };
4552
4553        if !matches!(self.peek().kind, TokenKind::Arrow) {
4554            self.error_at_peek("expected `=>` in arrow function");
4555            return None;
4556        }
4557        self.advance();
4558
4559        let (body, end_pos) = if matches!(self.peek().kind, TokenKind::LeftBrace) {
4560            let block = self.parse_block()?;
4561            let span = parse_arena_result(self.ast.try_stmt(block), &mut self.fatal)?.span;
4562            (ArrowBody::Block(block), span.end)
4563        } else {
4564            let expr = self.parse_expression()?;
4565            let span = parse_arena_result(self.ast.try_expr(expr), &mut self.fatal)?.span;
4566            (ArrowBody::Expr(expr), span.end)
4567        };
4568
4569        parse_arena_result(
4570            self.ast.try_push_expr(Expr {
4571                kind: ExprKind::Arrow {
4572                    params,
4573                    return_type,
4574                    type_predicate,
4575                    body,
4576                },
4577                span: self.span(start_span.start, end_pos),
4578            }),
4579            &mut self.fatal,
4580        )
4581    }
4582
4583    /// Function expressions share parameter/body parsing with arrows, but retain
4584    /// their own receiver and optional recursive-name binding.
4585    fn parse_function_expression(&mut self) -> Option<ExprId> {
4586        let kw = self.advance();
4587
4588        if matches!(self.peek().kind, TokenKind::LessThan) {
4589            self.error_at_peek("generic function expressions are not supported");
4590            return None;
4591        }
4592
4593        let self_name = if matches!(self.peek().kind, TokenKind::Identifier) {
4594            let name_tok = self.advance();
4595            self.reject_eval_or_arguments(name_tok.span);
4596            Some(self.ident_from_token(&name_tok))
4597        } else {
4598            None
4599        };
4600
4601        if !matches!(self.peek().kind, TokenKind::LeftParen) {
4602            self.error_at_peek("expected `(` after `function`");
4603            return None;
4604        }
4605        self.advance();
4606
4607        let this_type = if matches!(self.peek().kind, TokenKind::This) {
4608            self.advance();
4609            if !matches!(self.peek().kind, TokenKind::Colon) {
4610                self.error_at_peek("expected a type annotation after `this`");
4611                return None;
4612            }
4613            self.advance();
4614            let ty = self.parse_type_annotation()?;
4615            if matches!(self.peek().kind, TokenKind::Comma) {
4616                self.advance();
4617            } else if !matches!(self.peek().kind, TokenKind::RightParen) {
4618                self.error_at_peek("expected `,` or `)` after the `this` parameter");
4619                return None;
4620            }
4621            Some(ty)
4622        } else {
4623            None
4624        };
4625        let params = if matches!(self.peek().kind, TokenKind::RightParen) {
4626            Vec::new()
4627        } else {
4628            let saved_class = self.class_member_body_depth;
4629            let saved_function = self.function_expression_body_depth;
4630            self.class_member_body_depth = 0;
4631            self.function_expression_body_depth = 1;
4632            let params = self.parse_arrow_param_list();
4633            self.class_member_body_depth = saved_class;
4634            self.function_expression_body_depth = saved_function;
4635            params?
4636        };
4637        if !matches!(self.peek().kind, TokenKind::RightParen) {
4638            self.error_at_peek("expected `)`");
4639            return None;
4640        }
4641        self.advance();
4642
4643        let (return_type, type_predicate) = if matches!(self.peek().kind, TokenKind::Colon) {
4644            self.advance();
4645            self.parse_predicate_or_return_type(TypePos::ArrowReturn)?
4646        } else {
4647            (None, None)
4648        };
4649
4650        if !matches!(self.peek().kind, TokenKind::LeftBrace) {
4651            self.error_at_peek("expected `{` to open the function body");
4652            return None;
4653        }
4654        let saved = self.class_member_body_depth;
4655        self.class_member_body_depth = 0;
4656        self.function_expression_body_depth += 1;
4657        let block = self.parse_block();
4658        self.function_expression_body_depth -= 1;
4659        self.class_member_body_depth = saved;
4660        let block = block?;
4661        let end = parse_arena_result(self.ast.try_stmt(block), &mut self.fatal)?
4662            .span
4663            .end;
4664
4665        let function = parse_arena_result(
4666            self.ast.try_push_expr(Expr {
4667                kind: ExprKind::Arrow {
4668                    params,
4669                    return_type,
4670                    type_predicate,
4671                    body: ArrowBody::Block(block),
4672                },
4673                span: self.span(kw.span.start, end),
4674            }),
4675            &mut self.fatal,
4676        )?;
4677        parse_arena_result(
4678            self.ast.try_push_expr(Expr {
4679                kind: ExprKind::FunctionExpression {
4680                    name: self_name,
4681                    this_type,
4682                    function,
4683                },
4684                span: self.span(kw.span.start, end),
4685            }),
4686            &mut self.fatal,
4687        )
4688    }
4689
4690    /// Shorthand methods still use arrow lowering. Reject their receiver syntax
4691    /// so an enclosing class or function receiver cannot leak into the body.
4692    fn parse_outer_this_boundary<T>(
4693        &mut self,
4694        parse: impl FnOnce(&mut Self) -> Option<T>,
4695    ) -> Option<T> {
4696        let saved = self.class_member_body_depth;
4697        self.class_member_body_depth = 0;
4698        let saved_function = self.function_expression_body_depth;
4699        self.function_expression_body_depth = 0;
4700        let parsed = parse(self);
4701        self.class_member_body_depth = saved;
4702        self.function_expression_body_depth = saved_function;
4703        parsed
4704    }
4705
4706    fn parse_arrow_param_list(&mut self) -> Option<Vec<ParamDecl>> {
4707        let mut params = Vec::new();
4708        loop {
4709            let rest = if matches!(self.peek().kind, TokenKind::DotDotDot) {
4710                self.advance();
4711                true
4712            } else {
4713                false
4714            };
4715            // The lowering pass rewrites expression-body arrows to block form when any param
4716            // has a destructuring pattern.
4717            let (name, pattern) = match self.peek().kind {
4718                TokenKind::LeftBrace | TokenKind::LeftBracket => {
4719                    if rest {
4720                        self.error_at_peek("rest parameter cannot be destructured");
4721                        return None;
4722                    }
4723                    let binding = self.parse_binding()?;
4724                    let placeholder = Ident {
4725                        name: String::new(),
4726                        span: binding.span(),
4727                    };
4728                    (placeholder, Some(binding))
4729                }
4730                _ => {
4731                    let name_tok = self.expect_binding_identifier(if rest {
4732                        "expected identifier after `...`"
4733                    } else {
4734                        "expected parameter name"
4735                    })?;
4736                    let name = self.ident_from_token(&name_tok);
4737                    (name, None)
4738                }
4739            };
4740            let optional = self.parse_parameter_optional(rest, pattern.is_some())?;
4741            let ty = if matches!(self.peek().kind, TokenKind::Colon) {
4742                self.advance();
4743                Some(self.parse_type_annotation()?)
4744            } else {
4745                // Rest without annotation is OK at parse time; the typechecker fills it
4746                // from a contextual function-type hint if available.
4747                None
4748            };
4749            let default = self.parse_parameter_default(rest, optional)?;
4750            params.push(ParamDecl {
4751                name,
4752                ty,
4753                default,
4754                optional,
4755                pattern,
4756                rest,
4757                modifiers: None,
4758            });
4759            match self.peek().kind {
4760                TokenKind::Comma => {
4761                    if rest {
4762                        let comma = self.advance();
4763                        self.error_at(comma.span, "rest parameter must be the last parameter");
4764                        return None;
4765                    }
4766                    self.advance();
4767                    if matches!(self.peek().kind, TokenKind::RightParen) {
4768                        break;
4769                    }
4770                }
4771                TokenKind::RightParen => break,
4772                _ => {
4773                    self.error_at_peek("expected `,` or `)`");
4774                    return None;
4775                }
4776            }
4777        }
4778        self.check_parameter_order(params.iter().map(ParamOrder::of_param));
4779        Some(params)
4780    }
4781
4782    fn parse_binary(&mut self, min_prec: u8) -> Option<ExprId> {
4783        self.with_recursion_limit(|parser| parser.parse_binary_inner(min_prec))
4784    }
4785
4786    /// Angle tokens stay separate for nested type arguments. Only adjacent
4787    /// source tokens form a shift; whitespace and comments cannot join them.
4788    fn peek_shift(&self) -> Option<ShiftToken> {
4789        let first = self.peek();
4790        let second = self.peek_at(1);
4791        if first.span.end != second.span.start {
4792            return None;
4793        }
4794        match (&first.kind, &second.kind) {
4795            (TokenKind::LessThan, TokenKind::LessThan) => {
4796                Some(ShiftToken::new(BinOp::Shl, 2, false))
4797            }
4798            (TokenKind::LessThan, TokenKind::LessEquals) => {
4799                Some(ShiftToken::new(BinOp::Shl, 2, true))
4800            }
4801            (TokenKind::GreaterThan, TokenKind::GreaterEquals) => {
4802                Some(ShiftToken::new(BinOp::Shr, 2, true))
4803            }
4804            (TokenKind::GreaterThan, TokenKind::GreaterThan) => {
4805                let third = self.peek_at(2);
4806                if second.span.end == third.span.start {
4807                    match third.kind {
4808                        TokenKind::GreaterThan => {
4809                            return Some(ShiftToken::new(BinOp::UnsignedShr, 3, false));
4810                        }
4811                        TokenKind::GreaterEquals => {
4812                            return Some(ShiftToken::new(BinOp::UnsignedShr, 3, true));
4813                        }
4814                        _ => {}
4815                    }
4816                }
4817                Some(ShiftToken::new(BinOp::Shr, 2, false))
4818            }
4819            _ => None,
4820        }
4821    }
4822
4823    fn peek_binary(&self) -> Option<BinaryToken> {
4824        if let Some(shift) = self.peek_shift() {
4825            return (!shift.assignment).then_some(BinaryToken {
4826                op: shift.op,
4827                precedence: SHIFT_PRECEDENCE,
4828                tokens: shift.tokens,
4829            });
4830        }
4831        peek_binop(&self.peek().kind).map(|(op, precedence)| BinaryToken {
4832            op,
4833            precedence,
4834            tokens: 1,
4835        })
4836    }
4837
4838    fn parse_binary_inner(&mut self, min_prec: u8) -> Option<ExprId> {
4839        let mut lhs = self.parse_cast()?;
4840        // Track the previous op to reject `a || b ?? c` / `a ?? b || c` mixes. Both
4841        // reach this loop: `??` parses its right operand above `&&` (see
4842        // `right_operand_min_prec`), so `||` / `&&` after it is left for this check.
4843        let mut last_op: Option<BinOp> = None;
4844        let mut reported_mixing = false;
4845        while let Some(BinaryToken {
4846            op,
4847            precedence: prec,
4848            tokens: count,
4849        }) = self.peek_binary()
4850        {
4851            if prec < min_prec {
4852                break;
4853            }
4854            if !reported_mixing
4855                && let Some(last) = last_op
4856                && mixed_logical(last, op)
4857            {
4858                reported_mixing = true;
4859                let op_span = self.peek().span;
4860                // Parsing continues as if the left side were grouped, so one error
4861                // inside an `if (...)` header doesn't cascade through the statement.
4862                // Later mixes in the same chain add nothing, so only the first is reported.
4863                self.error_at_with_help(
4864                    op_span,
4865                    "mixing `??` with `||` / `&&` requires parentheses",
4866                    vec![mixed_logical_help(last, op)],
4867                );
4868            }
4869            if op == BinOp::Pow
4870                && matches!(
4871                    parse_arena_result(self.ast.try_expr(lhs), &mut self.fatal)?.kind,
4872                    ExprKind::Unary { .. }
4873                        | ExprKind::Typeof { .. }
4874                        | ExprKind::Delete { .. }
4875                        | ExprKind::Void { .. }
4876                )
4877            {
4878                let error_span = parse_arena_result(self.ast.try_expr(lhs), &mut self.fatal)?.span;
4879                self.error_at_with_help(
4880                    error_span,
4881                    "an unparenthesized unary expression cannot be the left operand of `**`",
4882                    vec!["choose the grouping explicitly: `(-x) ** 2` or `-(x ** 2)`".into()],
4883                );
4884                return None;
4885            }
4886            let loose = matches!(self.peek().kind, TokenKind::EqEq | TokenKind::BangEq);
4887            for _ in 0..count {
4888                self.advance();
4889            }
4890            let rhs = self.parse_binary(right_operand_min_prec(op, prec))?;
4891            let lhs_span = parse_arena_result(self.ast.try_expr(lhs), &mut self.fatal)?.span;
4892            let rhs_span = parse_arena_result(self.ast.try_expr(rhs), &mut self.fatal)?.span;
4893            if loose {
4894                self.reject_loose_nullish_comparison(op, lhs, rhs)?;
4895            }
4896            lhs = parse_arena_result(
4897                self.ast.try_push_expr(Expr {
4898                    kind: ExprKind::Binary { op, lhs, rhs },
4899                    span: self.span(lhs_span.start, rhs_span.end),
4900                }),
4901                &mut self.fatal,
4902            )?;
4903            last_op = Some(op);
4904        }
4905        Some(lhs)
4906    }
4907
4908    // Higher than every binary op, lower than every unary prefix. Left-associative.
4909    fn parse_cast(&mut self) -> Option<ExprId> {
4910        let mut expr = self.parse_unary()?;
4911        while matches!(self.peek().kind, TokenKind::Instanceof)
4912            || self.peek_identifier_text_is("as")
4913        {
4914            let is_instanceof = matches!(self.peek().kind, TokenKind::Instanceof);
4915            self.advance();
4916            let ty = self.parse_type_annotation()?;
4917            let start = parse_arena_result(self.ast.try_expr(expr), &mut self.fatal)?
4918                .span
4919                .start;
4920            let end = ty.span.end;
4921            let kind = if is_instanceof {
4922                ExprKind::InstanceOf { value: expr, ty }
4923            } else {
4924                self.last_as_type_end = Some(end);
4925                ExprKind::As { expr, ty }
4926            };
4927            expr = parse_arena_result(
4928                self.ast.try_push_expr(Expr {
4929                    kind,
4930                    span: self.span(start, end),
4931                }),
4932                &mut self.fatal,
4933            )?;
4934        }
4935        Some(expr)
4936    }
4937
4938    fn parse_unary(&mut self) -> Option<ExprId> {
4939        self.with_recursion_limit(Self::parse_unary_inner)
4940    }
4941
4942    fn parse_unary_inner(&mut self) -> Option<ExprId> {
4943        // `typeof` is parsed as a unary prefix so `typeof x === "T"` works without a new layer;
4944        // the typechecker rejects `Typeof` outside the recognized equality fold position.
4945        if matches!(self.peek().kind, TokenKind::Typeof) {
4946            return self.parse_prefix_expr(|operand| ExprKind::Typeof { operand });
4947        }
4948        if matches!(self.peek().kind, TokenKind::Void) {
4949            return self.parse_prefix_expr(|operand| ExprKind::Void { operand });
4950        }
4951        if self.at_delete_operator() {
4952            return self.parse_prefix_expr(|operand| ExprKind::Delete { operand });
4953        }
4954        if matches!(self.peek().kind, TokenKind::New) {
4955            let op_tok = self.advance();
4956            let mut callee = self.parse_atom()?;
4957            while matches!(self.peek().kind, TokenKind::Dot) {
4958                self.advance();
4959                if !matches!(self.peek().kind, TokenKind::Identifier) {
4960                    self.error_at_peek("expected identifier after `.` in constructor name");
4961                    return None;
4962                }
4963                let ident_tok = self.advance();
4964                let name = self.ident_from_token(&ident_tok);
4965                let receiver_span =
4966                    parse_arena_result(self.ast.try_expr(callee), &mut self.fatal)?.span;
4967                let combined_span = self.span(receiver_span.start, ident_tok.span.end);
4968                callee = parse_arena_result(
4969                    self.ast.try_push_expr(Expr {
4970                        kind: ExprKind::FieldAccess {
4971                            receiver: callee,
4972                            name,
4973                        },
4974                        span: combined_span,
4975                    }),
4976                    &mut self.fatal,
4977                )?;
4978            }
4979            let type_args = if matches!(self.peek().kind, TokenKind::LessThan) {
4980                let saved_pos = self.pos;
4981                let saved_diag_len = self.diagnostics.len();
4982                if let Some(args) = self.try_parse_type_args_only() {
4983                    Some(args)
4984                } else {
4985                    self.pos = saved_pos;
4986                    self.diagnostics.truncate(saved_diag_len);
4987                    None
4988                }
4989            } else {
4990                None
4991            };
4992            if !matches!(self.peek().kind, TokenKind::LeftParen) {
4993                self.error_at_peek("expected `(` after constructor name in `new` expression");
4994                return None;
4995            }
4996            self.advance();
4997            let args = if matches!(self.peek().kind, TokenKind::RightParen) {
4998                Vec::new()
4999            } else {
5000                self.parse_call_args()?
5001            };
5002            if !matches!(self.peek().kind, TokenKind::RightParen) {
5003                self.error_at_peek("expected `)`");
5004                return None;
5005            }
5006            let close = self.advance();
5007            let new_expr = parse_arena_result(
5008                self.ast.try_push_expr(Expr {
5009                    kind: ExprKind::New {
5010                        callee,
5011                        type_args,
5012                        args,
5013                    },
5014                    span: self.span(op_tok.span.start, close.span.end),
5015                }),
5016                &mut self.fatal,
5017            )?;
5018            // `new Foo()` is a postfix primary: `new Map().set(...)`, `new Map().size`,
5019            // `new Foo()[i]` all continue the chain off the constructor result.
5020            return self.parse_postfix_from(new_expr);
5021        }
5022        let op = match self.peek().kind {
5023            TokenKind::Bang => UnOp::Not,
5024            TokenKind::Tilde => UnOp::BitNot,
5025            TokenKind::Minus => UnOp::Neg,
5026            TokenKind::Plus => UnOp::Pos,
5027            _ => return self.parse_postfix(),
5028        };
5029        self.parse_prefix_expr(|operand| ExprKind::Unary { op, operand })
5030    }
5031
5032    /// Consume a prefix operator and its operand, spanning both.
5033    fn parse_prefix_expr(&mut self, kind: impl FnOnce(ExprId) -> ExprKind) -> Option<ExprId> {
5034        let op_tok = self.advance();
5035        // ASI cannot distinguish the `void` type from the expression operator.
5036        // In expression position a newline after the keyword still awaits an operand.
5037        if matches!(op_tok.kind, TokenKind::Void) && self.at_inserted_semicolon() {
5038            self.advance();
5039        }
5040        let operand = self.parse_unary()?;
5041        let operand_span = parse_arena_result(self.ast.try_expr(operand), &mut self.fatal)?.span;
5042        parse_arena_result(
5043            self.ast.try_push_expr(Expr {
5044                kind: kind(operand),
5045                span: self.span(op_tok.span.start, operand_span.end),
5046            }),
5047            &mut self.fatal,
5048        )
5049    }
5050
5051    fn parse_postfix(&mut self) -> Option<ExprId> {
5052        let expr = self.parse_atom()?;
5053        self.parse_postfix_from(expr)
5054    }
5055
5056    fn parse_postfix_from(&mut self, mut expr: ExprId) -> Option<ExprId> {
5057        // OptionalChain is emitted once at end-of-loop so a speculative-`<` rewind
5058        // never strands a half-built chain. Everything before the first `?.` accumulates
5059        // in `expr` normally; from the first `?.` onward ops go into `parts`.
5060        let mut chain_parts: Option<(ExprId, Vec<crate::ChainPart>)> = None;
5061        loop {
5062            match self.peek().kind {
5063                TokenKind::LeftParen => {
5064                    if let Some((_, parts)) = &mut chain_parts {
5065                        let part = self.parse_chain_call_tail(/*optional=*/ false)?;
5066                        parts.push(part);
5067                    } else {
5068                        expr = self.parse_call_tail(expr, None)?;
5069                    }
5070                }
5071                TokenKind::Dot => {
5072                    if let Some((_, parts)) = &mut chain_parts {
5073                        let part = self.parse_chain_field_tail(/*optional=*/ false)?;
5074                        parts.push(part);
5075                    } else {
5076                        expr = self.parse_field_access_tail(expr)?;
5077                    }
5078                }
5079                TokenKind::LeftBracket => {
5080                    if let Some((_, parts)) = &mut chain_parts {
5081                        let part = self.parse_chain_index_tail(/*optional=*/ false)?;
5082                        parts.push(part);
5083                    } else {
5084                        expr = self.parse_index_access_tail(expr)?;
5085                    }
5086                }
5087                TokenKind::QuestionDot => {
5088                    if chain_parts.is_none() {
5089                        chain_parts = Some((expr, Vec::new()));
5090                    }
5091                    self.advance();
5092                    let part = match self.peek().kind {
5093                        TokenKind::LeftParen => self.parse_chain_call_tail(true)?,
5094                        TokenKind::LeftBracket => self.parse_chain_index_tail(true)?,
5095                        _ if is_property_name(&self.peek().kind) => {
5096                            self.parse_chain_field_tail(true)?
5097                        }
5098                        _ => {
5099                            self.error_at_peek(
5100                                "expected field name, `(` for call, or `[` for index after `?.`",
5101                            );
5102                            return None;
5103                        }
5104                    };
5105                    if let Some((_, parts)) = &mut chain_parts {
5106                        parts.push(part);
5107                    }
5108                }
5109                // `<` after a callable expression might be a generic call `f<T>(args)`.
5110                // Speculatively parse; on failure restore state and let `parse_binary`
5111                // treat `<` as a comparison.
5112                TokenKind::LessThan => {
5113                    if chain_parts.is_some() {
5114                        // `a?.<T>()` not yet supported — terminate the chain.
5115                        break;
5116                    }
5117                    let saved_pos = self.pos;
5118                    let saved_diag_len = self.diagnostics.len();
5119                    if let Some(call_id) = self.try_parse_generic_call_tail(expr) {
5120                        expr = call_id;
5121                    } else {
5122                        self.pos = saved_pos;
5123                        self.diagnostics.truncate(saved_diag_len);
5124                        break;
5125                    }
5126                }
5127                TokenKind::Bang => {
5128                    if let Some((_, parts)) = &mut chain_parts {
5129                        let tok = self.advance();
5130                        parts.push(crate::ChainPart::NonNull { span: tok.span });
5131                        continue;
5132                    }
5133                    let tok = self.advance();
5134                    let start = parse_arena_result(self.ast.try_expr(expr), &mut self.fatal)?
5135                        .span
5136                        .start;
5137                    expr = parse_arena_result(
5138                        self.ast.try_push_expr(Expr {
5139                            kind: ExprKind::PostfixUnary {
5140                                op: PostfixOp::NonNullAssert,
5141                                operand: expr,
5142                            },
5143                            span: self.span(start, tok.span.end),
5144                        }),
5145                        &mut self.fatal,
5146                    )?;
5147                }
5148                // Postfix `++`/`--` terminates the chain; `a?.b++` is rejected.
5149                TokenKind::PlusPlus | TokenKind::MinusMinus => {
5150                    if chain_parts.is_some() {
5151                        break;
5152                    }
5153                    let tok = self.advance();
5154                    let op = if matches!(tok.kind, TokenKind::PlusPlus) {
5155                        PostfixOp::Inc
5156                    } else {
5157                        PostfixOp::Dec
5158                    };
5159                    let start = parse_arena_result(self.ast.try_expr(expr), &mut self.fatal)?
5160                        .span
5161                        .start;
5162                    expr = parse_arena_result(
5163                        self.ast.try_push_expr(Expr {
5164                            kind: ExprKind::PostfixUnary { op, operand: expr },
5165                            span: self.span(start, tok.span.end),
5166                        }),
5167                        &mut self.fatal,
5168                    )?;
5169                    break;
5170                }
5171                _ => break,
5172            }
5173        }
5174        if let Some((base, parts)) = chain_parts {
5175            let base_span = parse_arena_result(self.ast.try_expr(base), &mut self.fatal)?.span;
5176            let end = parts.last().map_or(base_span.end, |p| p.span().end);
5177            Some(parse_arena_result(
5178                self.ast.try_push_expr(Expr {
5179                    kind: ExprKind::OptionalChain { base, parts },
5180                    span: self.span(base_span.start, end),
5181                }),
5182                &mut self.fatal,
5183            )?)
5184        } else {
5185            Some(expr)
5186        }
5187    }
5188
5189    // `.` is consumed here for the non-optional case; `?.` was consumed by the caller.
5190    fn parse_chain_field_tail(&mut self, optional: bool) -> Option<crate::ChainPart> {
5191        let start_pos = self.peek().span.start;
5192        if !optional {
5193            self.advance();
5194        }
5195        let name_tok = if is_property_name(&self.peek().kind) {
5196            self.advance()
5197        } else {
5198            let what = if optional { "?." } else { "." };
5199            self.error_at_peek(format!("expected field name after `{what}`"));
5200            return None;
5201        };
5202        let name = self.ident_from_token(&name_tok);
5203        let end = name.span.end;
5204        Some(crate::ChainPart::Field {
5205            name,
5206            optional,
5207            span: self.span(start_pos, end),
5208        })
5209    }
5210
5211    fn parse_chain_index_tail(&mut self, optional: bool) -> Option<crate::ChainPart> {
5212        let open = self.advance();
5213        let idx = self.parse_expression()?;
5214        if !matches!(self.peek().kind, TokenKind::RightBracket) {
5215            self.error_at_peek("expected `]`");
5216            return None;
5217        }
5218        let close = self.advance();
5219        Some(crate::ChainPart::Index {
5220            idx,
5221            optional,
5222            span: self.span(open.span.start, close.span.end),
5223        })
5224    }
5225
5226    fn parse_chain_call_tail(&mut self, optional: bool) -> Option<crate::ChainPart> {
5227        let open = self.advance();
5228        let args = if matches!(self.peek().kind, TokenKind::RightParen) {
5229            Vec::new()
5230        } else {
5231            self.parse_call_args()?
5232        };
5233        if !matches!(self.peek().kind, TokenKind::RightParen) {
5234            self.error_at_peek("expected `)`");
5235            return None;
5236        }
5237        let close = self.advance();
5238        Some(crate::ChainPart::Call {
5239            args,
5240            type_args: None,
5241            optional,
5242            span: self.span(open.span.start, close.span.end),
5243        })
5244    }
5245
5246    fn parse_field_access_tail(&mut self, receiver: ExprId) -> Option<ExprId> {
5247        self.advance();
5248        // Keywords are valid field names — `Foo.new(args)` is the canonical constructor call.
5249        let name_tok = if is_property_name(&self.peek().kind) {
5250            self.advance()
5251        } else {
5252            self.error_at_peek("expected field name after `.`");
5253            return None;
5254        };
5255        let name = self.ident_from_token(&name_tok);
5256        let receiver_span = parse_arena_result(self.ast.try_expr(receiver), &mut self.fatal)?.span;
5257        parse_arena_result(
5258            self.ast.try_push_expr(Expr {
5259                kind: ExprKind::FieldAccess {
5260                    receiver,
5261                    name: name.clone(),
5262                },
5263                span: self.span(receiver_span.start, name.span.end),
5264            }),
5265            &mut self.fatal,
5266        )
5267    }
5268
5269    fn parse_index_access_tail(&mut self, receiver: ExprId) -> Option<ExprId> {
5270        self.advance();
5271        let index = self.parse_expression()?;
5272        if !matches!(self.peek().kind, TokenKind::RightBracket) {
5273            self.error_at_peek("expected `]`");
5274            return None;
5275        }
5276        let close = self.advance();
5277        let receiver_span = parse_arena_result(self.ast.try_expr(receiver), &mut self.fatal)?.span;
5278        parse_arena_result(
5279            self.ast.try_push_expr(Expr {
5280                kind: ExprKind::IndexAccess { receiver, index },
5281                span: self.span(receiver_span.start, close.span.end),
5282            }),
5283            &mut self.fatal,
5284        )
5285    }
5286
5287    fn parse_call_tail(
5288        &mut self,
5289        callee: ExprId,
5290        type_args: Option<Vec<TypeAnnotation>>,
5291    ) -> Option<ExprId> {
5292        self.advance();
5293        let args = if matches!(self.peek().kind, TokenKind::RightParen) {
5294            Vec::new()
5295        } else {
5296            self.parse_call_args()?
5297        };
5298        if !matches!(self.peek().kind, TokenKind::RightParen) {
5299            self.error_at_peek("expected `)`");
5300            return None;
5301        }
5302        let close = self.advance();
5303
5304        let callee_span = parse_arena_result(self.ast.try_expr(callee), &mut self.fatal)?.span;
5305        parse_arena_result(
5306            self.ast.try_push_expr(Expr {
5307                kind: ExprKind::Call {
5308                    callee,
5309                    type_args,
5310                    args,
5311                },
5312                span: self.span(callee_span.start, close.span.end),
5313            }),
5314            &mut self.fatal,
5315        )
5316    }
5317
5318    // Caller restores position + diagnostics on `None` so `<` is re-interpreted as comparison.
5319    fn try_parse_generic_call_tail(&mut self, callee: ExprId) -> Option<ExprId> {
5320        let type_args = self.try_parse_type_args_only()?;
5321        if !matches!(self.peek().kind, TokenKind::LeftParen) {
5322            return None;
5323        }
5324        self.parse_call_tail(callee, Some(type_args))
5325    }
5326
5327    fn try_parse_type_args_only(&mut self) -> Option<Vec<TypeAnnotation>> {
5328        if !matches!(self.peek().kind, TokenKind::LessThan) {
5329            return self.invariant_failure("type argument parser expected `<`");
5330        }
5331        self.advance();
5332
5333        if matches!(self.peek().kind, TokenKind::GreaterThan) {
5334            return None;
5335        }
5336
5337        let mut type_args = Vec::new();
5338        loop {
5339            let ta = self.parse_type_annotation()?;
5340            type_args.push(ta);
5341            match self.peek().kind {
5342                TokenKind::Comma => {
5343                    self.advance();
5344                    if matches!(self.peek().kind, TokenKind::GreaterThan) {
5345                        self.advance();
5346                        break;
5347                    }
5348                }
5349                TokenKind::GreaterThan => {
5350                    self.advance();
5351                    break;
5352                }
5353                _ => return None,
5354            }
5355        }
5356        Some(type_args)
5357    }
5358
5359    fn parse_call_args(&mut self) -> Option<Vec<ExprId>> {
5360        let mut args = Vec::new();
5361        loop {
5362            let arg = self.parse_expression()?;
5363            args.push(arg);
5364            match self.peek().kind {
5365                TokenKind::Comma => {
5366                    self.advance();
5367                    if matches!(self.peek().kind, TokenKind::RightParen) {
5368                        return Some(args);
5369                    }
5370                }
5371                TokenKind::RightParen => return Some(args),
5372                _ => {
5373                    self.error_at_peek("expected `,` or `)`");
5374                    return None;
5375                }
5376            }
5377        }
5378    }
5379
5380    /// `<T>expr`, TypeScript's original cast syntax, lowered to the same
5381    /// [`ExprKind::As`] node `expr as T` produces — so it is checked and emitted
5382    /// identically, including the runtime `ref.test` our `as` performs.
5383    ///
5384    /// The operand is a unary expression, which is what binds `<T>-1` as a cast of
5385    /// `-1` rather than a cast of `1` that is then negated.
5386    fn parse_angle_cast(&mut self) -> Option<ExprId> {
5387        let open = self.advance();
5388        let ty = self.parse_type_annotation()?;
5389        if !matches!(self.peek().kind, TokenKind::GreaterThan) {
5390            self.error_at_peek("expected `>` to close a type assertion");
5391            return None;
5392        }
5393        self.advance();
5394        let expr = self.parse_unary()?;
5395        let end = parse_arena_result(self.ast.try_expr(expr), &mut self.fatal)?
5396            .span
5397            .end;
5398        parse_arena_result(
5399            self.ast.try_push_expr(Expr {
5400                kind: ExprKind::As { expr, ty },
5401                span: self.span(open.span.start, end),
5402            }),
5403            &mut self.fatal,
5404        )
5405    }
5406
5407    fn parse_atom(&mut self) -> Option<ExprId> {
5408        match self.peek().kind {
5409            // `<T>expr` — the older spelling of `expr as T`, lowered to the same node.
5410            // Unambiguous only because this is an atom's first token: a `<` that means
5411            // comparison always has a left operand, and one that opens a generic
5412            // argument list always follows a callee, so neither reaches here.
5413            TokenKind::LessThan => return self.parse_angle_cast(),
5414            TokenKind::LeftParen => return self.parse_paren(),
5415            // `{` here is an object literal; statement-start `{` is dispatched to
5416            // `parse_block` before `parse_atom` runs, so the two stay disjoint.
5417            TokenKind::LeftBrace => return self.parse_object_literal(),
5418            TokenKind::LeftBracket => return self.parse_array_literal(),
5419            TokenKind::TemplateHead(_) => return self.parse_template_literal(),
5420            TokenKind::This => return self.parse_this_or_super(true),
5421            TokenKind::Super => return self.parse_this_or_super(false),
5422            TokenKind::Function => return self.parse_function_expression(),
5423            _ => {}
5424        }
5425        if !matches!(
5426            self.peek().kind,
5427            TokenKind::NumberLiteral(_)
5428                | TokenKind::BigIntLiteral(_)
5429                | TokenKind::StringLiteral(_)
5430                | TokenKind::TemplateNoSubstitution(_)
5431                | TokenKind::BooleanLiteral(_)
5432                | TokenKind::NullLiteral
5433                | TokenKind::Identifier
5434                | TokenKind::RegexLiteral { .. }
5435        ) {
5436            self.error_at_peek("expected expression");
5437            return None;
5438        }
5439        let tok = self.advance();
5440        let span = tok.span;
5441        let kind = match tok.kind {
5442            TokenKind::NumberLiteral(v) => ExprKind::Number(v),
5443            TokenKind::BigIntLiteral(s) => ExprKind::BigInt(s),
5444            TokenKind::StringLiteral(s) => ExprKind::String(s),
5445            TokenKind::TemplateNoSubstitution(s) => ExprKind::String(s),
5446            TokenKind::BooleanLiteral(b) => ExprKind::Boolean(b),
5447            TokenKind::NullLiteral => ExprKind::Null,
5448            TokenKind::Identifier => ExprKind::Identifier(Ident {
5449                name: self.source[span.start as usize..span.end as usize].to_string(),
5450                span,
5451            }),
5452            TokenKind::RegexLiteral { source, flags } => ExprKind::Regex { source, flags },
5453            // No cursor movement occurs between the accepted-kind check and advance.
5454            _ => unreachable!("advance preserves the accepted expression token"),
5455        };
5456        parse_arena_result(self.ast.try_push_expr(Expr { kind, span }), &mut self.fatal)
5457    }
5458
5459    /// Ordinary functions establish a `this` boundary and exclude an enclosing
5460    /// class's `super`; arrows inherit the current parsing context.
5461    fn parse_this_or_super(&mut self, is_this: bool) -> Option<ExprId> {
5462        let tok = self.advance();
5463        let span = tok.span;
5464        if !is_this && self.class_member_body_depth == 0 {
5465            self.error_at_with_help(
5466                span,
5467                "`super` is only valid inside a class method or constructor body",
5468                vec![
5469                    "call `super(...)` in a subclass constructor or `super.method(...)` \
5470                     in a subclass method"
5471                        .to_string(),
5472                ],
5473            );
5474        }
5475        let kind = if is_this
5476            && self.class_member_body_depth == 0
5477            && self.function_expression_body_depth == 0
5478        {
5479            ExprKind::ThisOutsideReceiver
5480        } else if is_this {
5481            ExprKind::This
5482        } else {
5483            ExprKind::Super
5484        };
5485        parse_arena_result(self.ast.try_push_expr(Expr { kind, span }), &mut self.fatal)
5486    }
5487
5488    // parse_atom dispatches immediately after matching TemplateHead.
5489    // The remaining template tokens are still parsed and diagnosed normally.
5490    fn parse_template_literal(&mut self) -> Option<ExprId> {
5491        let head_tok = self.advance();
5492        let TokenKind::TemplateHead(head) = head_tok.kind else {
5493            unreachable!("template dispatch requires TemplateHead");
5494        };
5495        let start = head_tok.span.start;
5496        let mut parts: Vec<String> = vec![head];
5497        let mut exprs: Vec<ExprId> = Vec::new();
5498        let mut substitution_spans: Vec<Span> = Vec::new();
5499        // The token before a substitution ends just past its `${`; the one after
5500        // starts at its `}`.
5501        let mut substitution_start = head_tok.span.end.saturating_sub(2);
5502        loop {
5503            let expr = self.parse_expression()?;
5504            exprs.push(expr);
5505            let closing = self.peek().span.start;
5506            let substitution_span = self.span(substitution_start, closing.saturating_add(1));
5507            match self.peek().kind.clone() {
5508                TokenKind::TemplateMiddle(s) => {
5509                    let tok = self.advance();
5510                    substitution_spans.push(substitution_span);
5511                    substitution_start = tok.span.end.saturating_sub(2);
5512                    parts.push(s);
5513                }
5514                TokenKind::TemplateTail(s) => {
5515                    let tok = self.advance();
5516                    substitution_spans.push(substitution_span);
5517                    parts.push(s);
5518                    return parse_arena_result(
5519                        self.ast.try_push_expr(Expr {
5520                            kind: ExprKind::TemplateLiteral {
5521                                parts,
5522                                exprs,
5523                                substitution_spans,
5524                            },
5525                            span: self.span(start, tok.span.end),
5526                        }),
5527                        &mut self.fatal,
5528                    );
5529                }
5530                _ => {
5531                    self.error_at_peek("expected `}` to close template interpolation");
5532                    return None;
5533                }
5534            }
5535        }
5536    }
5537
5538    fn parse_object_literal(&mut self) -> Option<ExprId> {
5539        let open = self.advance();
5540
5541        let mut members: Vec<ObjectLiteralMember> = Vec::new();
5542        if !matches!(self.peek().kind, TokenKind::RightBrace) {
5543            loop {
5544                if matches!(self.peek().kind, TokenKind::DotDotDot) {
5545                    let dots = self.advance();
5546                    let value = self.parse_expression()?;
5547                    let value_span =
5548                        parse_arena_result(self.ast.try_expr(value), &mut self.fatal)?.span;
5549                    members.push(ObjectLiteralMember::Spread {
5550                        value,
5551                        span: self.span(dots.span.start, value_span.end),
5552                    });
5553                } else if matches!(self.peek().kind, TokenKind::LeftBracket) {
5554                    self.advance();
5555                    let key = self.parse_expression()?;
5556                    if !matches!(self.peek().kind, TokenKind::RightBracket) {
5557                        self.error_at_peek("expected `]` after computed property key");
5558                        return None;
5559                    }
5560                    self.advance();
5561                    if !matches!(self.peek().kind, TokenKind::Colon) {
5562                        self.error_at_peek("expected `:` after computed property key; computed methods are not supported");
5563                        return None;
5564                    }
5565                    self.advance();
5566                    let value = self.parse_expression()?;
5567                    members.push(ObjectLiteralMember::Computed { key, value });
5568                } else {
5569                    let field = self.parse_object_literal_field()?;
5570                    // Spread members are dynamic — only literal fields participate in
5571                    // the duplicate-key check.
5572                    if let Some(existing) = members.iter().find_map(|m| match m {
5573                        ObjectLiteralMember::Field(f) if f.name.name == field.name.name => Some(f),
5574                        _ => None,
5575                    }) {
5576                        self.diagnostics.push(Diagnostic {
5577                            severity: Severity::Error,
5578                            span: field.name.span,
5579                            message: format!(
5580                                "duplicate field `{}` in object literal",
5581                                field.name.name
5582                            ),
5583                            help: vec![],
5584                            notes: vec![(existing.name.span, "first defined here".into())],
5585                        });
5586                        // Keep parsing so the user sees all duplicates at once.
5587                    }
5588                    members.push(ObjectLiteralMember::Field(field));
5589                }
5590                if self.at_inserted_semicolon()
5591                    && matches!(self.peek_at(1).kind, TokenKind::RightBrace)
5592                {
5593                    self.advance();
5594                }
5595                match self.peek().kind {
5596                    TokenKind::Comma => {
5597                        self.advance();
5598                        if matches!(self.peek().kind, TokenKind::RightBrace) {
5599                            break;
5600                        }
5601                    }
5602                    TokenKind::RightBrace => break,
5603                    _ => {
5604                        self.error_at_peek("expected `,` or `}`");
5605                        return None;
5606                    }
5607                }
5608            }
5609        }
5610
5611        if !matches!(self.peek().kind, TokenKind::RightBrace) {
5612            self.error_at_peek("expected `}`");
5613            return None;
5614        }
5615        let close = self.advance();
5616
5617        parse_arena_result(
5618            self.ast.try_push_expr(Expr {
5619                kind: ExprKind::ObjectLiteral { members },
5620                span: self.span(open.span.start, close.span.end),
5621            }),
5622            &mut self.fatal,
5623        )
5624    }
5625
5626    fn parse_object_literal_field(&mut self) -> Option<ObjectLiteralField> {
5627        let (name, shorthandable) = match self.peek().kind.clone() {
5628            TokenKind::Identifier => {
5629                let tok = self.advance();
5630                (self.ident_from_token(&tok), true)
5631            }
5632            kind if is_property_name(&kind) => {
5633                let tok = self.advance();
5634                (self.ident_from_token(&tok), false)
5635            }
5636            TokenKind::StringLiteral(_) => {
5637                let tok = self.advance();
5638                (self.property_ident_from_token(&tok), false)
5639            }
5640            _ => {
5641                self.error_at_peek("expected field name");
5642                return None;
5643            }
5644        };
5645
5646        // `{ m(x: T): R { … } }` is shorthand for `{ m: (x: T): R => { … } }`. Method
5647        // shorthand carries no `this` of its own here, for the same reason a function
5648        // expression does not, so the arrow is an exact lowering. Checked before the
5649        // `:` branch and independently of `shorthandable`, because a keyword or string
5650        // key can name a method too.
5651        if matches!(self.peek().kind, TokenKind::LeftParen) {
5652            let value = self.parse_method_shorthand_body(name.span)?;
5653            return Some(ObjectLiteralField { name, value });
5654        }
5655
5656        if !matches!(self.peek().kind, TokenKind::Colon) {
5657            // `{ x }` is shorthand for `{ x: x }`. Only identifier keys can use
5658            // it — a string key like `{ "x" }` has no binding to reference.
5659            if shorthandable {
5660                let value = parse_arena_result(
5661                    self.ast.try_push_expr(Expr {
5662                        kind: ExprKind::Identifier(name.clone()),
5663                        span: name.span,
5664                    }),
5665                    &mut self.fatal,
5666                )?;
5667                return Some(ObjectLiteralField { name, value });
5668            }
5669            self.error_at_peek("expected `:` after field name");
5670            return None;
5671        }
5672        self.advance();
5673
5674        let value = self.parse_expression()?;
5675        Some(ObjectLiteralField { name, value })
5676    }
5677
5678    /// The `(params): R { … }` tail of an object-literal method, lowered to an arrow.
5679    /// `name_span` is the key's span, so the arrow spans the whole member.
5680    fn parse_method_shorthand_body(&mut self, name_span: Span) -> Option<ExprId> {
5681        self.advance();
5682
5683        let params = if matches!(self.peek().kind, TokenKind::RightParen) {
5684            Vec::new()
5685        } else {
5686            self.parse_arrow_param_list()?
5687        };
5688        if !matches!(self.peek().kind, TokenKind::RightParen) {
5689            self.error_at_peek("expected `)`");
5690            return None;
5691        }
5692        self.advance();
5693
5694        let (return_type, type_predicate) = if matches!(self.peek().kind, TokenKind::Colon) {
5695            self.advance();
5696            self.parse_predicate_or_return_type(TypePos::ArrowReturn)?
5697        } else {
5698            (None, None)
5699        };
5700
5701        if !matches!(self.peek().kind, TokenKind::LeftBrace) {
5702            self.error_at_peek("expected `{` to open the method body");
5703            return None;
5704        }
5705        let block = self.parse_outer_this_boundary(Self::parse_block)?;
5706        let end = parse_arena_result(self.ast.try_stmt(block), &mut self.fatal)?
5707            .span
5708            .end;
5709
5710        parse_arena_result(
5711            self.ast.try_push_expr(Expr {
5712                kind: ExprKind::Arrow {
5713                    params,
5714                    return_type,
5715                    type_predicate,
5716                    body: ArrowBody::Block(block),
5717                },
5718                span: self.span(name_span.start, end),
5719            }),
5720            &mut self.fatal,
5721        )
5722    }
5723
5724    fn parse_array_literal(&mut self) -> Option<ExprId> {
5725        let open = self.advance();
5726
5727        let mut elements: Vec<ArrayLiteralElement> = Vec::new();
5728        if !matches!(self.peek().kind, TokenKind::RightBracket) {
5729            loop {
5730                let element = if matches!(self.peek().kind, TokenKind::DotDotDot) {
5731                    let dots = self.advance();
5732                    let value = self.parse_expression()?;
5733                    let value_span =
5734                        parse_arena_result(self.ast.try_expr(value), &mut self.fatal)?.span;
5735                    ArrayLiteralElement::Spread {
5736                        value,
5737                        span: self.span(dots.span.start, value_span.end),
5738                    }
5739                } else {
5740                    ArrayLiteralElement::Value(self.parse_expression()?)
5741                };
5742                elements.push(element);
5743                match self.peek().kind {
5744                    TokenKind::Comma => {
5745                        self.advance();
5746                        if matches!(self.peek().kind, TokenKind::RightBracket) {
5747                            break;
5748                        }
5749                    }
5750                    TokenKind::RightBracket => break,
5751                    _ => {
5752                        self.error_at_peek("expected `,` or `]`");
5753                        return None;
5754                    }
5755                }
5756            }
5757        }
5758
5759        if !matches!(self.peek().kind, TokenKind::RightBracket) {
5760            self.error_at_peek("expected `]`");
5761            return None;
5762        }
5763        let close = self.advance();
5764
5765        parse_arena_result(
5766            self.ast.try_push_expr(Expr {
5767                kind: ExprKind::ArrayLiteral { elements },
5768                span: self.span(open.span.start, close.span.end),
5769            }),
5770            &mut self.fatal,
5771        )
5772    }
5773
5774    fn parse_paren(&mut self) -> Option<ExprId> {
5775        let open = self.advance();
5776        let inner = self.parse_expression()?;
5777        if !matches!(self.peek().kind, TokenKind::RightParen) {
5778            self.error_at_peek("expected `)`");
5779            return None;
5780        }
5781        let close = self.advance();
5782        let span = self.span(open.span.start, close.span.end);
5783        parse_arena_result(
5784            self.ast.try_push_expr(Expr {
5785                kind: ExprKind::Paren(inner),
5786                span,
5787            }),
5788            &mut self.fatal,
5789        )
5790    }
5791
5792    fn with_recursion_limit<T>(&mut self, parse: impl FnOnce(&mut Self) -> Option<T>) -> Option<T> {
5793        if self.fatal.is_some() {
5794            return None;
5795        }
5796        if self.recursion_limit_span.is_some() || self.recursion_depth >= MAX_PARSE_DEPTH {
5797            self.stop_at_recursion_limit();
5798            return None;
5799        }
5800        self.recursion_depth += 1;
5801        let result = parse(self);
5802        self.recursion_depth -= 1;
5803        result
5804    }
5805
5806    fn stop_at_recursion_limit(&mut self) {
5807        self.recursion_limit_span.get_or_insert(self.peek().span);
5808        // Stop recovery and enclosing block loops as well as recursive calls.
5809        self.pos = self.tokens.len().saturating_sub(1);
5810    }
5811
5812    fn recover(&mut self) {
5813        while !self.is_at_eof() {
5814            match self.peek().kind {
5815                TokenKind::Semicolon => {
5816                    self.advance();
5817                    return;
5818                }
5819                TokenKind::RightBrace
5820                | TokenKind::Let
5821                | TokenKind::Const
5822                | TokenKind::Function
5823                | TokenKind::Export
5824                | TokenKind::If
5825                | TokenKind::While
5826                | TokenKind::Return
5827                | TokenKind::Try
5828                | TokenKind::Throw => return,
5829                _ => {
5830                    self.advance();
5831                }
5832            }
5833        }
5834    }
5835
5836    fn peek(&self) -> &Token {
5837        self.tokens.get(self.pos).unwrap_or(&self.eof)
5838    }
5839
5840    fn peek_at(&self, offset: usize) -> &Token {
5841        self.pos
5842            .checked_add(offset)
5843            .and_then(|index| self.tokens.get(index))
5844            .unwrap_or(&self.eof)
5845    }
5846
5847    /// ASI inserts its `;` as a zero-width token; a written `;` always has width.
5848    fn at_inserted_semicolon(&self) -> bool {
5849        let token = self.peek();
5850        matches!(token.kind, TokenKind::Semicolon) && token.span.start == token.span.end
5851    }
5852
5853    fn advance(&mut self) -> Token {
5854        let tok = self.peek().clone();
5855        if self.pos < self.tokens.len().saturating_sub(1) {
5856            self.pos += 1;
5857        }
5858        tok
5859    }
5860
5861    fn is_at_eof(&self) -> bool {
5862        self.fatal.is_some() || matches!(self.peek().kind, TokenKind::Eof)
5863    }
5864
5865    /// Whether a line break separates the next token from the one before it.
5866    fn line_break_before_peek(&self) -> bool {
5867        self.source_has_line_break(self.prev_token_end(), self.peek().span.start)
5868    }
5869
5870    /// Whether a line break separates the next token from the one after it.
5871    fn line_break_after_peek(&self) -> bool {
5872        self.source_has_line_break(self.peek().span.end, self.peek_at(1).span.start)
5873    }
5874
5875    /// The ASI pass drops newline tokens, so line breaks between tokens are read
5876    /// from the source.
5877    fn source_has_line_break(&self, start: u32, end: u32) -> bool {
5878        self.source
5879            .get(start as usize..end as usize)
5880            .is_some_and(|gap| gap.contains(['\n', '\r']))
5881    }
5882
5883    /// Whether the statement being parsed ends here: at a `;`, or at a line break
5884    /// right after an `as T`. ASI keeps a `[` or `(` on the next line attached, but
5885    /// the type already stopped at the break (see `parse_type_array_inner`), and no
5886    /// expression continues past a type, so TypeScript ends the statement there.
5887    fn at_statement_end(&self) -> bool {
5888        matches!(self.peek().kind, TokenKind::Semicolon)
5889            || (self.last_as_type_end == Some(self.prev_token_end())
5890                && self.line_break_before_peek())
5891    }
5892
5893    /// Consumes the `;` that [`Parser::at_statement_end`] found, if there is one, and
5894    /// returns the statement's end offset.
5895    fn finish_statement(&mut self) -> u32 {
5896        if matches!(self.peek().kind, TokenKind::Semicolon) {
5897            return self.advance().span.end;
5898        }
5899        self.prev_token_end()
5900    }
5901
5902    /// End offset of the most recently consumed token (the body of the file's first
5903    /// token if nothing has been consumed yet).
5904    fn prev_token_end(&self) -> u32 {
5905        self.tokens
5906            .get(self.pos.saturating_sub(1))
5907            .unwrap_or(&self.eof)
5908            .span
5909            .end
5910    }
5911
5912    fn take_leading_doc(&mut self) -> Option<crate::DocComment> {
5913        let parsed = self
5914            .peek()
5915            .leading_doc
5916            .as_ref()
5917            .map(crate::parse_doc_comment)
5918            .transpose();
5919        match parsed {
5920            Ok(doc) => doc,
5921            Err(error) => {
5922                self.fatal = Some(error.into_compiler_failure(CompilerStage::Parse));
5923                None
5924            }
5925        }
5926    }
5927
5928    fn parse_doc(&mut self, raw: &crate::RawDoc) -> Option<crate::DocComment> {
5929        match crate::parse_doc_comment(raw) {
5930            Ok(doc) => Some(doc),
5931            Err(error) => {
5932                self.fatal = Some(error.into_compiler_failure(CompilerStage::Parse));
5933                None
5934            }
5935        }
5936    }
5937
5938    fn error_at(&mut self, span: Span, message: impl Into<String>) {
5939        self.diagnostics.push(Diagnostic {
5940            severity: Severity::Error,
5941            span,
5942            message: message.into(),
5943            help: vec![],
5944            notes: vec![],
5945        });
5946    }
5947
5948    /// `==` is `===` here, so `x == null` would silently miss `undefined` — the
5949    /// one case where TypeScript's loose equality differs in a way code relies on.
5950    fn reject_loose_nullish_comparison(
5951        &mut self,
5952        op: BinOp,
5953        lhs: ExprId,
5954        rhs: ExprId,
5955    ) -> Option<()> {
5956        let lhs_span = parse_arena_result(self.ast.try_expr(lhs), &mut self.fatal)?.span;
5957        let rhs_span = parse_arena_result(self.ast.try_expr(rhs), &mut self.fatal)?.span;
5958        let (nullish, operand, nullish_first) =
5959            match (self.nullish_operand(lhs)?, self.nullish_operand(rhs)?) {
5960                (Some(left), Some(right)) => {
5961                    self.reject_nullish_against_nullish(op, lhs_span, rhs_span, [left, right]);
5962                    return Some(());
5963                }
5964                (None, Some(nullish)) => (nullish, lhs, false),
5965                (Some(nullish), None) => (nullish, rhs, true),
5966                (None, None) => return Some(()),
5967            };
5968        let comparison = LooseComparison {
5969            nullish,
5970            nullish_first,
5971            negated: op == BinOp::NotEq,
5972        };
5973        let operand_span = parse_arena_result(self.ast.try_expr(operand), &mut self.fatal)?.span;
5974        let x = self.single_line_source(operand_span).unwrap_or("x");
5975        let strict = self.strict_suggestion(&comparison, x);
5976        let either = self.either_suggestion(&comparison, operand, x)?;
5977        let literal = comparison.nullish.literal.text();
5978        // Quote the comparison as written, so `null == z` stays in that order.
5979        let written = self
5980            .single_line_source(self.span(lhs_span.start, rhs_span.end))
5981            .map_or_else(
5982                || {
5983                    let written_op = if comparison.negated { "!=" } else { "==" };
5984                    comparison.ordered(x, written_op, literal)
5985                },
5986                str::to_string,
5987            );
5988        self.error_at_with_help(
5989            self.span(lhs_span.start, rhs_span.end),
5990            format!(
5991                "`{written}` does not also match `{}`: `==` compares like `===`",
5992                comparison.nullish.literal.other().text()
5993            ),
5994            vec![
5995                either,
5996                format!("to test for `{literal}` alone, write `{strict}`"),
5997            ],
5998        );
5999        Some(())
6000    }
6001
6002    /// Both sides are always nullish. TypeScript's `==` treats `null` and
6003    /// `undefined` as equal, so the comparison has one value, which the help
6004    /// names; a `void` operand with effects still runs, as a statement.
6005    fn reject_nullish_against_nullish(
6006        &mut self,
6007        op: BinOp,
6008        lhs: Span,
6009        rhs: Span,
6010        sides: [NullishOperand; 2],
6011    ) {
6012        let span = self.span(lhs.start, rhs.end);
6013        // A comparison written over several lines can't be quoted on one, so
6014        // the message names it without the source.
6015        let subject = self.single_line_source(span).map_or_else(
6016            || "this comparison".to_string(),
6017            |written| format!("`{written}`"),
6018        );
6019        let value = if op == BinOp::NotEq { "false" } else { "true" };
6020        let effects: Vec<String> = sides
6021            .iter()
6022            .filter_map(|side| side.effect)
6023            .map(|effect| {
6024                self.single_line_source(effect.operand).map_or_else(
6025                    || "the `void` operand's expression".to_string(),
6026                    |statement| format!("`{statement};`"),
6027                )
6028            })
6029            .collect();
6030        let help = if effects.is_empty() {
6031            format!("in TypeScript this is always `{value}`; write `{value}`")
6032        } else {
6033            format!(
6034                "in TypeScript this is always `{value}`; run {} {}, then use `{value}`",
6035                effects.join(" and "),
6036                if effects.len() > 1 {
6037                    "each as its own statement"
6038                } else {
6039                    "as its own statement"
6040                }
6041            )
6042        };
6043        self.error_at_with_help(
6044            span,
6045            format!("{subject} compares two nullish values: `==` compares like `===`"),
6046            vec![help],
6047        );
6048    }
6049
6050    /// The strict comparison against the written nullish value. A `void`
6051    /// operand with effects stays as written, so the operand runs once.
6052    fn strict_suggestion(&self, comparison: &LooseComparison, x: &str) -> String {
6053        let eq = comparison.strict_op();
6054        match comparison.nullish.effect {
6055            Some(effect) => {
6056                let void = self.single_line_source(effect.void).unwrap_or("void expr");
6057                comparison.ordered(x, eq, void)
6058            }
6059            None => format!("{x} {eq} {}", comparison.nullish.literal.text()),
6060        }
6061    }
6062
6063    /// "To test for either" names the operand twice, so one with effects is
6064    /// bound to a `const` first, and a `void` operand's expression runs as a
6065    /// statement of its own.
6066    fn either_suggestion(
6067        &mut self,
6068        comparison: &LooseComparison,
6069        operand: ExprId,
6070        x: &str,
6071    ) -> Option<String> {
6072        let mut steps = Vec::new();
6073        let tested = if self.repeats_without_effect(operand)? {
6074            x
6075        } else {
6076            steps.push(format!("bind `{x}` to a `const` (`const value = {x};`)"));
6077            "value"
6078        };
6079        if let Some(effect) = comparison.nullish.effect {
6080            let statement = self.single_line_source(effect.operand).unwrap_or("expr");
6081            let step = format!("run `{statement};` as its own statement");
6082            if comparison.nullish_first {
6083                steps.insert(0, step);
6084            } else {
6085                steps.push(step);
6086            }
6087        }
6088        let eq = comparison.strict_op();
6089        let joiner = if comparison.negated { "&&" } else { "||" };
6090        let both = format!("{tested} {eq} null {joiner} {tested} {eq} undefined");
6091        Some(if steps.is_empty() {
6092            format!("to test for either, write `{both}`")
6093        } else {
6094            format!(
6095                "to test for either, first {}, then write `{both}`",
6096                steps.join(", then ")
6097            )
6098        })
6099    }
6100
6101    /// Whether naming `id` twice evaluates it twice with no effect: a literal,
6102    /// or a reference path.
6103    fn repeats_without_effect(&mut self, id: ExprId) -> Option<bool> {
6104        let unwrapped = self.unparenthesized(id)?;
6105        let expr = parse_arena_result(self.ast.try_expr(unwrapped), &mut self.fatal)?;
6106        if is_literal(&expr.kind) {
6107            return Some(true);
6108        }
6109        if self
6110            .nullish_operand(unwrapped)?
6111            .is_some_and(|nullish| nullish.effect.is_none())
6112        {
6113            return Some(true);
6114        }
6115        self.is_reference_path(id)
6116    }
6117
6118    /// `null`, `undefined` or `void e`, looking through parentheses.
6119    fn nullish_operand(&mut self, id: ExprId) -> Option<Option<NullishOperand>> {
6120        let id = self.unparenthesized(id)?;
6121        let expr = parse_arena_result(self.ast.try_expr(id), &mut self.fatal)?;
6122        let span = expr.span;
6123        Some(match &expr.kind {
6124            ExprKind::Null => Some(NullishOperand {
6125                literal: Nullish::Null,
6126                effect: None,
6127            }),
6128            ExprKind::Identifier(ident) if ident.name == "undefined" => Some(NullishOperand {
6129                literal: Nullish::Undefined,
6130                effect: None,
6131            }),
6132            ExprKind::Void { operand } => {
6133                let operand_id = self.unparenthesized(*operand)?;
6134                let operand =
6135                    parse_arena_result(self.ast.try_expr(operand_id), &mut self.fatal)?.clone();
6136                // A literal or a reference path reads with no effect to keep.
6137                let effect_free =
6138                    is_literal(&operand.kind) || self.is_reference_path(operand_id)?;
6139                let effect = (!effect_free).then_some(VoidEffect {
6140                    void: span,
6141                    operand: operand.span,
6142                });
6143                Some(NullishOperand {
6144                    literal: Nullish::Undefined,
6145                    effect,
6146                })
6147            }
6148            _ => None,
6149        })
6150    }
6151
6152    /// An identifier, `this`, or a `.field` chain on one: naming it twice evaluates
6153    /// it twice with no effect.
6154    fn is_reference_path(&mut self, id: ExprId) -> Option<bool> {
6155        let mut id = id;
6156        loop {
6157            match &parse_arena_result(self.ast.try_expr(id), &mut self.fatal)?.kind {
6158                ExprKind::Identifier(_) | ExprKind::This => return Some(true),
6159                ExprKind::FieldAccess { receiver, .. } => id = *receiver,
6160                ExprKind::Paren(inner) => id = *inner,
6161                _ => return Some(false),
6162            }
6163        }
6164    }
6165
6166    fn unparenthesized(&mut self, id: ExprId) -> Option<ExprId> {
6167        let mut id = id;
6168        loop {
6169            match parse_arena_result(self.ast.try_expr(id), &mut self.fatal)?.kind {
6170                ExprKind::Paren(inner) => id = inner,
6171                _ => return Some(id),
6172            }
6173        }
6174    }
6175
6176    fn single_line_source(&self, span: Span) -> Option<&'a str> {
6177        self.source
6178            .get(span.start as usize..span.end as usize)
6179            .filter(|text| !text.contains('\n'))
6180    }
6181
6182    fn error_at_with_help(&mut self, span: Span, message: impl Into<String>, help: Vec<String>) {
6183        self.diagnostics.push(Diagnostic {
6184            severity: Severity::Error,
6185            span,
6186            message: message.into(),
6187            help,
6188            notes: vec![],
6189        });
6190    }
6191
6192    fn invariant_failure<T>(&mut self, message: &str) -> Option<T> {
6193        self.fatal.get_or_insert_with(|| CompilerFailure::Internal {
6194            stage: CompilerStage::Parse,
6195            span: None,
6196            message: message.into(),
6197        });
6198        self.pos = self.tokens.len();
6199        None
6200    }
6201
6202    fn error_at_peek(&mut self, message: impl Into<String>) {
6203        let span = self.peek().span;
6204        self.error_at(span, message);
6205    }
6206
6207    fn error_at_peek_with_help(&mut self, message: impl Into<String>, help: Vec<String>) {
6208        let span = self.peek().span;
6209        self.error_at_with_help(span, message, help);
6210    }
6211
6212    fn error_count(&self) -> usize {
6213        self.diagnostics
6214            .iter()
6215            .filter(|d| d.severity == Severity::Error)
6216            .count()
6217    }
6218}
6219
6220fn is_property_name(kind: &TokenKind) -> bool {
6221    matches!(
6222        kind,
6223        TokenKind::Identifier
6224            | TokenKind::BooleanLiteral(_)
6225            | TokenKind::NullLiteral
6226            | TokenKind::Let
6227            | TokenKind::Const
6228            | TokenKind::Function
6229            | TokenKind::If
6230            | TokenKind::Else
6231            | TokenKind::While
6232            | TokenKind::Do
6233            | TokenKind::For
6234            | TokenKind::Break
6235            | TokenKind::Continue
6236            | TokenKind::Return
6237            | TokenKind::Switch
6238            | TokenKind::Case
6239            | TokenKind::Default
6240            | TokenKind::Void
6241            | TokenKind::Interface
6242            | TokenKind::Enum
6243            | TokenKind::In
6244            | TokenKind::Typeof
6245            | TokenKind::Import
6246            | TokenKind::Export
6247            | TokenKind::New
6248            | TokenKind::Try
6249            | TokenKind::Catch
6250            | TokenKind::Finally
6251            | TokenKind::Throw
6252            | TokenKind::Class
6253            | TokenKind::Extends
6254            | TokenKind::Implements
6255            | TokenKind::Super
6256            | TokenKind::This
6257    )
6258}
6259
6260fn is_reserved_identifier_word(kind: &TokenKind) -> bool {
6261    matches!(
6262        kind,
6263        TokenKind::BooleanLiteral(_)
6264            | TokenKind::NullLiteral
6265            | TokenKind::Let
6266            | TokenKind::Const
6267            | TokenKind::Function
6268            | TokenKind::If
6269            | TokenKind::Else
6270            | TokenKind::While
6271            | TokenKind::Do
6272            | TokenKind::For
6273            | TokenKind::Break
6274            | TokenKind::Continue
6275            | TokenKind::Return
6276            | TokenKind::Switch
6277            | TokenKind::Case
6278            | TokenKind::Default
6279            | TokenKind::Export
6280            | TokenKind::Void
6281            | TokenKind::Interface
6282            | TokenKind::Enum
6283            | TokenKind::In
6284            | TokenKind::Typeof
6285            | TokenKind::Import
6286            | TokenKind::New
6287            | TokenKind::Try
6288            | TokenKind::Catch
6289            | TokenKind::Finally
6290            | TokenKind::Throw
6291            | TokenKind::Class
6292            | TokenKind::Extends
6293            | TokenKind::Implements
6294            | TokenKind::Super
6295            | TokenKind::This
6296    )
6297}
6298
6299/// Which statement a `{ … }` specifier list belongs to. An import binds local
6300/// names, which strict mode restricts; an export's are module export names,
6301/// which it doesn't.
6302#[derive(Clone, Copy, PartialEq, Eq)]
6303enum SpecifierList {
6304    Import,
6305    Export,
6306}
6307
6308impl SpecifierList {
6309    fn noun(self) -> &'static str {
6310        match self {
6311            SpecifierList::Import => "import",
6312            SpecifierList::Export => "export",
6313        }
6314    }
6315}
6316
6317/// Words strict mode reserves that Submilli otherwise lexes as identifiers: they
6318/// can't name a binding or a type, but stay valid as property names and modifiers.
6319/// (`implements`, `interface` and `let` are keywords already.)
6320const STRICT_MODE_RESERVED_WORDS: [&str; 6] = [
6321    "package",
6322    "private",
6323    "protected",
6324    "public",
6325    "static",
6326    "yield",
6327];
6328
6329fn reserved_keyword_rename_example(keyword: &str) -> &'static str {
6330    match keyword {
6331        "default" => "defaultValue",
6332        _ => "valueName",
6333    }
6334}
6335
6336fn mixed_logical(prev: BinOp, next: BinOp) -> bool {
6337    let prev_logical = matches!(prev, BinOp::Or | BinOp::And);
6338    let next_logical = matches!(next, BinOp::Or | BinOp::And);
6339    let prev_nullish = matches!(prev, BinOp::NullishCoalesce);
6340    let next_nullish = matches!(next, BinOp::NullishCoalesce);
6341    (prev_logical && next_nullish) || (prev_nullish && next_logical)
6342}
6343
6344/// Spells out both groupings in the order the operators were written. Called
6345/// only for pairs `mixed_logical` accepts, so the non-`??` operator is `||` or `&&`.
6346fn mixed_logical_help(prev: BinOp, next: BinOp) -> String {
6347    let symbol = |op: BinOp| if op == BinOp::And { "&&" } else { "||" };
6348    let (first, second) = if prev == BinOp::NullishCoalesce {
6349        ("??", symbol(next))
6350    } else {
6351        (symbol(prev), "??")
6352    };
6353    format!(
6354        "add parentheses to choose the grouping: `(a {first} b) {second} c` or `a {first} (b {second} c)`"
6355    )
6356}
6357
6358const LOGICAL_AND_PREC: u8 = 2;
6359
6360/// One side of a loose comparison that is always nullish.
6361#[derive(Clone, Copy)]
6362struct NullishOperand {
6363    literal: Nullish,
6364    /// Set for `void e` when `e` is not a literal and may have effects.
6365    effect: Option<VoidEffect>,
6366}
6367
6368#[derive(Clone, Copy, PartialEq, Eq)]
6369enum Nullish {
6370    Null,
6371    Undefined,
6372}
6373
6374impl Nullish {
6375    fn text(self) -> &'static str {
6376        match self {
6377            Nullish::Null => "null",
6378            Nullish::Undefined => "undefined",
6379        }
6380    }
6381
6382    /// The nullish value a loose comparison against `self` fails to match.
6383    fn other(self) -> Self {
6384        match self {
6385            Nullish::Null => Nullish::Undefined,
6386            Nullish::Undefined => Nullish::Null,
6387        }
6388    }
6389}
6390
6391/// A rejected `x == null`-style comparison, as written.
6392struct LooseComparison {
6393    nullish: NullishOperand,
6394    /// Whether the nullish side is written first, as in `null == x`.
6395    nullish_first: bool,
6396    /// `!=` rather than `==`.
6397    negated: bool,
6398}
6399
6400impl LooseComparison {
6401    fn strict_op(&self) -> &'static str {
6402        if self.negated { "!==" } else { "===" }
6403    }
6404
6405    /// `x op nullish`, in the order the comparison was written.
6406    fn ordered(&self, x: &str, op: &str, nullish: &str) -> String {
6407        if self.nullish_first {
6408            format!("{nullish} {op} {x}")
6409        } else {
6410            format!("{x} {op} {nullish}")
6411        }
6412    }
6413}
6414
6415#[derive(Clone, Copy)]
6416struct VoidEffect {
6417    /// The whole `void e`.
6418    void: Span,
6419    /// The `e`, without enclosing parentheses.
6420    operand: Span,
6421}
6422
6423fn is_literal(kind: &ExprKind) -> bool {
6424    matches!(
6425        kind,
6426        ExprKind::Number(_)
6427            | ExprKind::BigInt(_)
6428            | ExprKind::String(_)
6429            | ExprKind::Boolean(_)
6430            | ExprKind::Null
6431    )
6432}
6433
6434/// What `Parser::check_parameter_order` needs from either kind of parameter list.
6435struct ParamOrder<'p> {
6436    optional: bool,
6437    /// Neither optional, defaulted, nor rest.
6438    required: bool,
6439    /// `None` for a destructuring pattern.
6440    name: Option<&'p str>,
6441    span: Span,
6442}
6443
6444impl<'p> ParamOrder<'p> {
6445    fn of_param(param: &'p ParamDecl) -> Self {
6446        let (name, span) = match &param.pattern {
6447            Some(binding) => (None, binding.span()),
6448            None => (Some(param.name.name.as_str()), param.name.span),
6449        };
6450        Self {
6451            optional: param.optional,
6452            required: !param.is_omittable() && !param.rest,
6453            name,
6454            span,
6455        }
6456    }
6457
6458    fn of_field(field: &'p TypeAnnotationField) -> Self {
6459        Self {
6460            optional: field.optional,
6461            required: !field.optional && !field.rest,
6462            name: Some(field.name.name.as_str()),
6463            span: field.name.span,
6464        }
6465    }
6466}
6467
6468/// A binary operator at the cursor, which may span several tokens.
6469struct BinaryToken {
6470    op: BinOp,
6471    precedence: u8,
6472    tokens: usize,
6473}
6474
6475/// `<<`, `>>`, `>>>` or their compound assignments, read from adjacent angle tokens.
6476struct ShiftToken {
6477    op: BinOp,
6478    tokens: usize,
6479    /// The operator ends in `=`: `<<=`, `>>=`, `>>>=`.
6480    assignment: bool,
6481}
6482
6483impl ShiftToken {
6484    fn new(op: BinOp, tokens: usize, assignment: bool) -> Self {
6485        Self {
6486            op,
6487            tokens,
6488            assignment,
6489        }
6490    }
6491}
6492
6493/// Between relational (7) and additive (9) in the `peek_binop` table.
6494const SHIFT_PRECEDENCE: u8 = 8;
6495
6496#[derive(Clone, Copy, PartialEq)]
6497enum TupleElementLabel {
6498    None,
6499    /// `name: T`
6500    Required,
6501    /// `name?: T`
6502    Optional,
6503}
6504
6505fn peek_binop(kind: &TokenKind) -> Option<(BinOp, u8)> {
6506    Some(match kind {
6507        TokenKind::QuestionQuestion => (BinOp::NullishCoalesce, 0),
6508        TokenKind::PipePipe => (BinOp::Or, 1),
6509        TokenKind::AmpAmp => (BinOp::And, LOGICAL_AND_PREC),
6510        TokenKind::Pipe => (BinOp::BitOr, 3),
6511        TokenKind::Caret => (BinOp::BitXor, 4),
6512        TokenKind::Amp => (BinOp::BitAnd, 5),
6513        TokenKind::EqEqEq | TokenKind::EqEq => (BinOp::Eq, 6),
6514        TokenKind::BangEqEq | TokenKind::BangEq => (BinOp::NotEq, 6),
6515        TokenKind::LessThan => (BinOp::Lt, 7),
6516        TokenKind::GreaterThan => (BinOp::Gt, 7),
6517        TokenKind::LessEquals => (BinOp::Le, 7),
6518        TokenKind::GreaterEquals => (BinOp::Ge, 7),
6519        TokenKind::In => (BinOp::In, 7),
6520        // Shifts take `SHIFT_PRECEDENCE` (8); they span several angle tokens, so
6521        // `Parser::peek_shift` recognizes them rather than this table.
6522        TokenKind::Plus => (BinOp::Add, 9),
6523        TokenKind::Minus => (BinOp::Sub, 9),
6524        TokenKind::Star => (BinOp::Mul, 10),
6525        TokenKind::Slash => (BinOp::Div, 10),
6526        TokenKind::Percent => (BinOp::Rem, 10),
6527        // `**` recurses with `prec` not `prec + 1` — that's what makes it right-associative.
6528        TokenKind::StarStar => (BinOp::Pow, 11),
6529        _ => return None,
6530    })
6531}
6532
6533/// After `as` or `satisfies`, a token that can only extend or end an operand, never
6534/// begin a type. `[` counts, as TypeScript reads `delete as [0]` as an index, so a
6535/// tuple cast of a binding named `delete` needs parentheses.
6536fn cannot_start_type(kind: &TokenKind) -> bool {
6537    matches!(
6538        kind,
6539        TokenKind::Dot
6540            | TokenKind::QuestionDot
6541            | TokenKind::LeftBracket
6542            | TokenKind::Bang
6543            | TokenKind::Semicolon
6544            | TokenKind::RightParen
6545            | TokenKind::RightBrace
6546            | TokenKind::Comma
6547            | TokenKind::Eof
6548    )
6549}
6550
6551/// The lowest precedence the right operand of `op` may contain. `**` is
6552/// right-associative, so it recurses at its own level. An operand of `??` may
6553/// not be an unparenthesized `||` / `&&` expression, so it stops below them too.
6554fn right_operand_min_prec(op: BinOp, prec: u8) -> u8 {
6555    match op {
6556        BinOp::Pow => prec,
6557        BinOp::NullishCoalesce => LOGICAL_AND_PREC + 1,
6558        _ => prec + 1,
6559    }
6560}
6561
6562fn compound_op_for_token(kind: &TokenKind) -> Option<BinOp> {
6563    Some(match kind {
6564        TokenKind::PlusEquals => BinOp::Add,
6565        TokenKind::MinusEquals => BinOp::Sub,
6566        TokenKind::StarEquals => BinOp::Mul,
6567        TokenKind::SlashEquals => BinOp::Div,
6568        TokenKind::PercentEquals => BinOp::Rem,
6569        TokenKind::StarStarEquals => BinOp::Pow,
6570        TokenKind::AmpEquals => BinOp::BitAnd,
6571        TokenKind::PipeEquals => BinOp::BitOr,
6572        TokenKind::CaretEquals => BinOp::BitXor,
6573        _ => return None,
6574    })
6575}
6576
6577fn is_assign_lookahead(kind: &TokenKind) -> bool {
6578    matches!(kind, TokenKind::Equals) || compound_op_for_token(kind).is_some()
6579}
6580
6581/// Parser productions use `None` for recovery; arena errors also latch a fatal
6582/// cause so speculative parsing cannot turn them into ordinary syntax errors.
6583fn parse_arena_result<T>(
6584    result: Result<T, crate::arena::ArenaError>,
6585    fatal: &mut Option<CompilerFailure>,
6586) -> Option<T> {
6587    match result {
6588        Ok(value) => Some(value),
6589        Err(error) => {
6590            fatal.get_or_insert_with(|| error.into_compiler_failure(CompilerStage::Parse));
6591            None
6592        }
6593    }
6594}
6595
6596/// Whether a rest parameter's annotation names an array: `T[]`, or `readonly T[]`.
6597fn is_rest_array_annotation(ty: &crate::TypeAnnotation) -> bool {
6598    use crate::ast::TypeAnnotationKind;
6599    match &ty.kind {
6600        TypeAnnotationKind::Array(_) => true,
6601        TypeAnnotationKind::Readonly(inner) => matches!(inner.kind, TypeAnnotationKind::Array(_)),
6602        _ => false,
6603    }
6604}
6605
6606#[cfg(test)]
6607mod tests {
6608    use super::{MAX_ERRORS, MAX_PARSE_DEPTH, Parser, parse};
6609    use crate::source::Sources;
6610    use crate::{Asi, Ast, ExprKind, FileId, ImportKind, StmtKind, Token, TokenKind, diagnostics};
6611
6612    const F: FileId = FileId(0);
6613
6614    #[test]
6615    fn malformed_direct_token_streams_return_typed_failures() {
6616        use crate::compiler_error::CompilerFailure;
6617        for tokens in [
6618            Vec::new(),
6619            vec![Token::new(
6620                TokenKind::Identifier,
6621                crate::Span::new(F, 0, 2).unwrap(),
6622            )],
6623            vec![
6624                Token::new(
6625                    TokenKind::Identifier,
6626                    crate::Span {
6627                        file: F,
6628                        start: 1,
6629                        end: 2,
6630                    },
6631                ),
6632                Token::new(TokenKind::Eof, crate::Span::new(F, 2, 2).unwrap()),
6633            ],
6634            vec![
6635                Token::new(TokenKind::Eof, crate::Span::at(F)),
6636                Token::new(TokenKind::Eof, crate::Span::at(F)),
6637            ],
6638            vec![
6639                Token::new(
6640                    TokenKind::Identifier,
6641                    crate::Span {
6642                        file: F,
6643                        start: 2,
6644                        end: 0,
6645                    },
6646                ),
6647                Token::new(TokenKind::Eof, crate::Span::new(F, 2, 2).unwrap()),
6648            ],
6649            vec![Token::new(TokenKind::Eof, crate::Span::at(FileId(9)))],
6650        ] {
6651            let error = super::parse_checked("é", tokens, F).expect_err("invalid token metadata");
6652            assert!(matches!(
6653                error.fatal,
6654                Some(CompilerFailure::Internal { .. })
6655            ));
6656        }
6657    }
6658
6659    #[test]
6660    fn malformed_documentation_metadata_returns_a_fatal_error() {
6661        let source = "/** hello */ function main(): void {}";
6662        let valid = tokens_of(source);
6663        for doc in [
6664            crate::RawDoc {
6665                text: "/** hi */".into(),
6666                span: crate::Span {
6667                    file: F,
6668                    start: u32::MAX,
6669                    end: u32::MAX,
6670                },
6671            },
6672            crate::RawDoc {
6673                text: "/** different */".into(),
6674                span: crate::Span::new(F, 0, 12).unwrap(),
6675            },
6676            crate::RawDoc {
6677                text: "/** hello */".into(),
6678                span: crate::Span::new(FileId(9), 0, 12).unwrap(),
6679            },
6680        ] {
6681            let mut tokens = valid.clone();
6682            tokens[0].leading_doc = Some(doc);
6683            let error = super::parse_checked(source, tokens, F).expect_err("invalid doc metadata");
6684            assert!(error.to_string().contains("documentation metadata"));
6685        }
6686        assert!(super::parse_checked(source, valid, F).is_ok());
6687    }
6688
6689    #[test]
6690    fn invalid_parser_dispatch_survives_diagnostic_rollback() {
6691        let mut parser = parser_from_source("let x = 1;");
6692        assert!(parser.try_parse_type_args_only().is_none());
6693        parser.diagnostics.clear();
6694        assert!(parser.fatal.is_some());
6695        assert!(parser.parse_expression().is_none());
6696    }
6697
6698    #[test]
6699    fn invalid_arena_read_stops_parser_and_survives_diagnostic_rollback() {
6700        use crate::{
6701            ExprId,
6702            compiler_error::{CompilerFailure, CompilerStage},
6703        };
6704        let mut parser = parser_from_source("let x = 1;");
6705        assert!(parser.statement_kind_for(ExprId(u32::MAX)).is_none());
6706        assert!(matches!(
6707            parser.fatal,
6708            Some(CompilerFailure::Internal {
6709                stage: CompilerStage::Parse,
6710                span: None,
6711                ..
6712            })
6713        ));
6714        parser.diagnostics.clear();
6715        let position = parser.pos;
6716        assert!(parser.parse_statement().is_none());
6717        parser.recover();
6718        assert_eq!(parser.pos, position);
6719        assert!(parser.ast.expr_ids().unwrap().next().is_none());
6720    }
6721
6722    fn tokens_of(source: &str) -> Vec<Token> {
6723        let mut asi = Asi::new(source, crate::FileId(0));
6724        let mut tokens = Vec::new();
6725        loop {
6726            let tok = asi.next_token();
6727            let is_eof = matches!(tok.kind, TokenKind::Eof);
6728            tokens.push(tok);
6729            if is_eof {
6730                break;
6731            }
6732        }
6733        let diags = asi.into_diagnostics();
6734        assert!(diags.is_empty(), "unexpected lexer diagnostics: {diags:?}");
6735        tokens
6736    }
6737
6738    fn parse_str(source: &str) -> (Ast, Vec<crate::Diagnostic>) {
6739        parse(source, tokens_of(source), crate::FileId(0))
6740    }
6741
6742    fn parser_from_source(source: &str) -> Parser<'_> {
6743        Parser {
6744            source,
6745            tokens: tokens_of(source),
6746            file: F,
6747            pos: 0,
6748            ast: Ast::new(),
6749            diagnostics: Vec::new(),
6750            block_depth: 0,
6751            class_member_body_depth: 0,
6752            function_expression_body_depth: 0,
6753            recursion_depth: 0,
6754            recursion_limit_span: None,
6755            last_as_type_end: None,
6756            eof: Token::new(
6757                TokenKind::Eof,
6758                crate::Span::new(F, source.len() as u32, source.len() as u32).unwrap(),
6759            ),
6760            fatal: None,
6761        }
6762    }
6763
6764    #[test]
6765    fn recursion_budget_accepts_boundary_and_restores_depth() {
6766        let source = format!("{}true", "!".repeat(MAX_PARSE_DEPTH - 1));
6767        let mut parser = parser_from_source(&source);
6768        assert!(parser.parse_unary().is_some());
6769        assert_eq!(parser.recursion_depth, 0);
6770        assert!(parser.recursion_limit_span.is_none());
6771
6772        let source = format!("{}true", "!".repeat(MAX_PARSE_DEPTH));
6773        let mut parser = parser_from_source(&source);
6774        assert!(parser.parse_unary().is_none());
6775        assert_eq!(parser.recursion_depth, 0);
6776        assert!(parser.recursion_limit_span.is_some());
6777    }
6778
6779    #[test]
6780    fn parse_empty_input() {
6781        let (ast, diags) = parse_str("");
6782        assert!(diags.is_empty());
6783        assert!(ast.top_level.is_empty());
6784    }
6785
6786    #[test]
6787    fn any_type_is_rejected() {
6788        // `any` is not supported — the parser rejects it in every type position
6789        // (annotation, cast target, array/generic element) and points to the fix.
6790        for src in [
6791            "function main(): void { const x: any = 1; }",
6792            "function main(): void { const x = 1 as any; }",
6793            "function main(): void { const x: any[] = []; }",
6794        ] {
6795            let (_ast, diags) = parse_str(src);
6796            assert!(
6797                diags
6798                    .iter()
6799                    .any(|d| d.message.contains("`any` is not supported")),
6800                "expected an `any` rejection for {src:?}, got: {diags:?}",
6801            );
6802            assert!(
6803                diags
6804                    .iter()
6805                    .any(|d| d.help.iter().any(|h| h.contains("`unknown`"))),
6806                "expected the fix-shape help for {src:?}, got: {diags:?}",
6807            );
6808        }
6809    }
6810
6811    #[test]
6812    fn any_catch_binding_help_suggests_catch_forms() {
6813        // `unknown` is not a valid catch binding type, so the general `any`
6814        // help would send the reader to a second error.
6815        let (_ast, diags) = parse_str("function main(): void { try { } catch (e: any) { } }");
6816        let any_diags: Vec<_> = diags
6817            .iter()
6818            .filter(|d| d.message.contains("`any` is not supported"))
6819            .collect();
6820        assert_eq!(any_diags.len(), 1, "got: {diags:?}");
6821        assert!(
6822            any_diags[0].help.iter().any(|h| h.contains("`catch (e)`")),
6823            "got: {diags:?}"
6824        );
6825        assert!(
6826            !any_diags[0].help.iter().any(|h| h.contains("`unknown`")),
6827            "got: {diags:?}"
6828        );
6829    }
6830
6831    #[test]
6832    fn arrow_return_type_scanner_matches_type_grammar() {
6833        // Every form the annotation grammar accepts must also be accepted by
6834        // the arrow-disambiguator's lookahead (scan_past_type_annotation):
6835        // a miss there turns a valid arrow into `expected expression`.
6836        // When adding a type form to parse_type_array, add a row here.
6837        for ty in [
6838            "number",
6839            "void",
6840            "null",
6841            "ns.Foo",
6842            "Map<string, number>",
6843            "ns.Foo<string>[]",
6844            "number[]",
6845            "[number, number]",
6846            "[number, [string, boolean]]",
6847            "[number, string][]",
6848            "{ a: number }",
6849            "{ a: number }[]",
6850            "\"on\" | \"off\"",
6851            "1 | 2",
6852            "Point | null",
6853            "| number | string",
6854            "(n: number) => number",
6855            "(n: number) => [number, number]",
6856            "readonly number[]",
6857            "readonly number[][]",
6858            "readonly [number, string]",
6859            "readonly number[] | null",
6860            "[first: number, second: string]",
6861            "[new: number, string]",
6862            "keyof { a: number }",
6863        ] {
6864            let annotation = format!("function f(): void {{ const a: {ty} = x; }}");
6865            let (_ast, diags) = parse_str(&annotation);
6866            assert!(
6867                diags.is_empty(),
6868                "annotation grammar rejected {ty:?}: {diags:?}",
6869            );
6870
6871            let arrow = format!("function f(): void {{ const g = (): {ty} => x; }}");
6872            let (_ast, diags) = parse_str(&arrow);
6873            assert!(
6874                diags.is_empty(),
6875                "arrow lookahead rejected return type {ty:?}: {diags:?}",
6876            );
6877        }
6878
6879        // Type-predicate returns ride the same scanner.
6880        let (_ast, diags) =
6881            parse_str("function f(): void { const g = (v: unknown): v is string => x; }");
6882        assert!(
6883            diags.is_empty(),
6884            "arrow lookahead rejected predicate return: {diags:?}",
6885        );
6886    }
6887
6888    #[test]
6889    fn bare_semicolons_are_silent_empty_statements() {
6890        let (ast, diags) = parse_str(";");
6891        assert!(diags.is_empty());
6892        assert!(ast.top_level.is_empty());
6893    }
6894
6895    #[test]
6896    fn parse_single_non_expression_token_errors() {
6897        let (ast, diags) = parse_str("}");
6898        assert_eq!(diags.len(), 1);
6899        assert_eq!(diags[0].message, "expected expression");
6900        assert!(ast.top_level.is_empty());
6901    }
6902
6903    #[test]
6904    fn recovery_across_many_bad_starts() {
6905        let (ast, diags) = parse_str("} } } x;");
6906        assert_eq!(diags.len(), 3);
6907        for d in &diags {
6908            assert_eq!(d.message, "expected expression");
6909        }
6910        assert_eq!(ast.top_level.len(), 1);
6911    }
6912
6913    #[test]
6914    fn recovery_consumes_semicolon() {
6915        let mut p = parser_from_source("foo ;");
6916        p.error_at_peek("test");
6917        p.recover();
6918        assert!(matches!(
6919            p.peek().kind,
6920            TokenKind::Semicolon | TokenKind::Eof
6921        ));
6922        assert!(matches!(p.peek().kind, TokenKind::Eof));
6923    }
6924
6925    #[test]
6926    fn recovery_stops_at_right_brace() {
6927        let mut p = parser_from_source("foo }");
6928        p.error_at_peek("test");
6929        p.recover();
6930        assert!(matches!(p.peek().kind, TokenKind::RightBrace));
6931    }
6932
6933    #[test]
6934    fn recovery_stops_at_let_keyword() {
6935        let mut p = parser_from_source("foo let x");
6936        p.error_at_peek("test");
6937        p.recover();
6938        assert!(matches!(p.peek().kind, TokenKind::Let));
6939    }
6940
6941    #[test]
6942    fn recovery_stops_at_function_keyword() {
6943        let mut p = parser_from_source("foo function bar");
6944        p.error_at_peek("test");
6945        p.recover();
6946        assert!(matches!(p.peek().kind, TokenKind::Function));
6947    }
6948
6949    #[test]
6950    fn error_cap_at_20() {
6951        let source = "} ".repeat(25);
6952        let (_, diags) = parse_str(&source);
6953        assert_eq!(diags.len(), MAX_ERRORS);
6954    }
6955
6956    #[test]
6957    fn driver_makes_progress_past_unmatched_brace() {
6958        let (ast, diags) = parse_str("} x;");
6959        assert_eq!(diags.len(), 1);
6960        assert_eq!(diags[0].message, "expected expression");
6961        assert_eq!(ast.top_level.len(), 1);
6962    }
6963
6964    #[test]
6965    fn snapshot_multi_error_render() {
6966        let source = "foo bar;\nbaz qux;\nhello world;";
6967        let (_, diags) = parse_str(source);
6968        let (sources, _) = Sources::single("script.subm", source).unwrap();
6969        let rendered: String = diags
6970            .iter()
6971            .map(|d| diagnostics::render(d, &sources))
6972            .collect::<Vec<_>>()
6973            .join("\n");
6974        insta::assert_snapshot!(rendered);
6975    }
6976
6977    fn single_stmt(ast: &Ast) -> &crate::Stmt {
6978        assert_eq!(ast.top_level.len(), 1);
6979        ast.try_stmt(ast.top_level[0]).unwrap()
6980    }
6981
6982    fn expr_of_single_stmt(ast: &Ast) -> &crate::Expr {
6983        let stmt = single_stmt(ast);
6984        match stmt.kind {
6985            crate::StmtKind::Expr(id) => ast.try_expr(id).unwrap(),
6986            _ => panic!("expected expression statement, got {:?}", stmt.kind),
6987        }
6988    }
6989
6990    #[test]
6991    fn parse_number_literal() {
6992        let (ast, diags) = parse_str("42;");
6993        assert!(diags.is_empty());
6994        let expr = expr_of_single_stmt(&ast);
6995        assert_eq!(expr.kind, crate::ExprKind::Number(42.0));
6996        assert_eq!(expr.span, crate::Span::new(F, 0, 2).unwrap());
6997    }
6998
6999    #[test]
7000    fn parse_string_literal() {
7001        let (ast, diags) = parse_str(r#""s";"#);
7002        assert!(diags.is_empty());
7003        let expr = expr_of_single_stmt(&ast);
7004        assert_eq!(expr.kind, crate::ExprKind::String("s".to_string()));
7005        assert_eq!(expr.span, crate::Span::new(F, 0, 3).unwrap());
7006    }
7007
7008    #[test]
7009    fn parse_boolean_true() {
7010        let (ast, diags) = parse_str("true;");
7011        assert!(diags.is_empty());
7012        let expr = expr_of_single_stmt(&ast);
7013        assert_eq!(expr.kind, crate::ExprKind::Boolean(true));
7014        assert_eq!(expr.span, crate::Span::new(F, 0, 4).unwrap());
7015    }
7016
7017    #[test]
7018    fn parse_boolean_false() {
7019        let (ast, diags) = parse_str("false;");
7020        assert!(diags.is_empty());
7021        let expr = expr_of_single_stmt(&ast);
7022        assert_eq!(expr.kind, crate::ExprKind::Boolean(false));
7023        assert_eq!(expr.span, crate::Span::new(F, 0, 5).unwrap());
7024    }
7025
7026    #[test]
7027    fn parse_null_literal() {
7028        let (ast, diags) = parse_str("null;");
7029        assert!(diags.is_empty());
7030        let expr = expr_of_single_stmt(&ast);
7031        assert_eq!(expr.kind, crate::ExprKind::Null);
7032        assert_eq!(expr.span, crate::Span::new(F, 0, 4).unwrap());
7033    }
7034
7035    #[test]
7036    fn parse_identifier() {
7037        let (ast, diags) = parse_str("x;");
7038        assert!(diags.is_empty());
7039        let expr = expr_of_single_stmt(&ast);
7040        assert!(matches!(expr.kind, crate::ExprKind::Identifier(_)));
7041        assert_eq!(expr.span, crate::Span::new(F, 0, 1).unwrap());
7042    }
7043
7044    #[test]
7045    fn reserved_keywords_remain_invalid_declaration_names() {
7046        let (_ast, diags) = parse_str("function default(): void {}");
7047        assert_eq!(diags.len(), 1, "{diags:#?}");
7048        assert_eq!(
7049            diags[0].message,
7050            "`default` is a reserved keyword and can't be used as a name"
7051        );
7052        assert!(
7053            diags[0]
7054                .help
7055                .iter()
7056                .any(|h| h == "rename it, for example: `defaultValue`"),
7057            "expected rename help, got: {diags:?}"
7058        );
7059    }
7060
7061    #[test]
7062    fn eval_and_arguments_cannot_name_bindings() {
7063        let bindings = [
7064            ("let arguments = 1;", "arguments"),
7065            ("const [eval] = [1];", "eval"),
7066            ("const { a: eval } = { a: 1 };", "eval"),
7067            ("const { eval } = { eval: 1 };", "eval"),
7068            ("const [...arguments] = [1];", "arguments"),
7069            ("function eval(): void {}", "eval"),
7070            ("function f(arguments: number): void {}", "arguments"),
7071            ("const f = arguments => arguments;", "arguments"),
7072            ("const f = (...eval: number[]) => 1;", "eval"),
7073            ("const f = function eval(): void {};", "eval"),
7074            ("for (const eval of [1]) {}", "eval"),
7075            ("try {} catch (arguments) {}", "arguments"),
7076            ("type F = (eval: number) => void;", "eval"),
7077            ("type G = (...arguments: number[]) => void;", "arguments"),
7078            ("type H = { [eval: string]: number };", "eval"),
7079        ];
7080        for (source, word) in bindings {
7081            let (_ast, diags) = parse_str(source);
7082            assert!(
7083                diags
7084                    .iter()
7085                    .any(|d| d.message == format!("`{word}` can't name a binding in strict mode")),
7086                "{source}: {diags:#?}"
7087            );
7088        }
7089    }
7090
7091    #[test]
7092    fn eval_and_arguments_still_name_types_and_properties() {
7093        for source in [
7094            "class eval {}",
7095            "interface arguments { eval: number }",
7096            "type eval = number;",
7097            "enum arguments { eval }",
7098            "const o = { eval: 1, arguments: 2 };",
7099            "import { eval } from \"./m\";",
7100        ] {
7101            let (_ast, diags) = parse_str(source);
7102            assert!(diags.is_empty(), "{source}: {diags:#?}");
7103        }
7104    }
7105
7106    #[test]
7107    fn reserved_keywords_remain_invalid_parameter_names() {
7108        let (_ast, diags) = parse_str("function f(default: string): void {}");
7109        assert_eq!(diags.len(), 1, "{diags:#?}");
7110        assert_eq!(
7111            diags[0].message,
7112            "`default` is a reserved keyword and can't be used as a name"
7113        );
7114        assert!(
7115            diags[0]
7116                .help
7117                .iter()
7118                .any(|h| h == "rename it, for example: `defaultValue`"),
7119            "expected rename help, got: {diags:?}"
7120        );
7121    }
7122
7123    #[test]
7124    fn contextual_keywords_are_valid_names() {
7125        for src in [
7126            "const type = \"x\";",
7127            "const from = 1;",
7128            "let of = 2;",
7129            "const as = 3;",
7130            "const is = 4;",
7131            "function f(from: string): void {}",
7132        ] {
7133            let (_ast, diags) = parse_str(src);
7134            assert!(
7135                diags.is_empty(),
7136                "unexpected diagnostics for {src:?}: {diags:#?}"
7137            );
7138        }
7139    }
7140
7141    #[test]
7142    fn type_alias_dispatch_table() {
7143        let (ast, diags) = parse_str("type X = number;");
7144        assert!(diags.is_empty(), "{diags:#?}");
7145        assert!(matches!(
7146            ast.try_stmt(ast.top_level[0]).unwrap().kind,
7147            crate::StmtKind::TypeAliasDecl { .. }
7148        ));
7149
7150        // A binding named `type` keeps expression/assignment statements working.
7151        for src in ["type = 5;", "type;", "type(1);", "type < 3;", "type.foo;"] {
7152            let (ast, diags) = parse_str(src);
7153            assert!(
7154                diags.is_empty(),
7155                "unexpected diagnostics for {src:?}: {diags:#?}"
7156            );
7157            assert!(
7158                !matches!(
7159                    ast.try_stmt(ast.top_level[0]).unwrap().kind,
7160                    crate::StmtKind::TypeAliasDecl { .. }
7161                ),
7162                "{src:?} must not parse as a type alias"
7163            );
7164        }
7165
7166        let (_ast, diags) = parse_str("type X;");
7167        assert!(
7168            diags
7169                .iter()
7170                .any(|d| d.message == "expected `=` after type alias name"),
7171            "bare `type X` should commit to the alias diagnostic, got: {diags:#?}"
7172        );
7173    }
7174
7175    #[test]
7176    fn export_type_alias_dispatch() {
7177        let (ast, diags) = parse_str("export type X = number;");
7178        assert!(diags.is_empty(), "{diags:#?}");
7179        assert_eq!(ast.exported_decls.len(), 1);
7180        assert!(matches!(
7181            ast.try_stmt(ast.exported_decls[0].stmt).unwrap().kind,
7182            crate::StmtKind::TypeAliasDecl { .. }
7183        ));
7184
7185        let (_ast, diags) = parse_str("export type = 5;");
7186        assert!(
7187            diags
7188                .iter()
7189                .any(|d| d.message == "expected a declaration after `export`"),
7190            "`export type = 5` must not become an exported assignment, got: {diags:#?}"
7191        );
7192    }
7193
7194    #[test]
7195    fn parse_paren_simple() {
7196        let (ast, diags) = parse_str("(x);");
7197        assert!(diags.is_empty());
7198        let expr = expr_of_single_stmt(&ast);
7199        let crate::ExprKind::Paren(inner_id) = expr.kind else {
7200            panic!("expected Paren, got {:?}", expr.kind);
7201        };
7202        assert_eq!(expr.span, crate::Span::new(F, 0, 3).unwrap());
7203        let inner = ast.try_expr(inner_id).unwrap();
7204        assert!(matches!(inner.kind, crate::ExprKind::Identifier(_)));
7205        assert_eq!(inner.span, crate::Span::new(F, 1, 2).unwrap());
7206    }
7207
7208    #[test]
7209    fn parse_paren_nested() {
7210        let (ast, diags) = parse_str("((x));");
7211        assert!(diags.is_empty());
7212        let outer = expr_of_single_stmt(&ast);
7213        let crate::ExprKind::Paren(mid_id) = outer.kind else {
7214            panic!("expected outer Paren");
7215        };
7216        assert_eq!(outer.span, crate::Span::new(F, 0, 5).unwrap());
7217        let mid = ast.try_expr(mid_id).unwrap();
7218        let crate::ExprKind::Paren(inner_id) = mid.kind else {
7219            panic!("expected inner Paren");
7220        };
7221        assert_eq!(mid.span, crate::Span::new(F, 1, 4).unwrap());
7222        let inner = ast.try_expr(inner_id).unwrap();
7223        assert!(matches!(inner.kind, crate::ExprKind::Identifier(_)));
7224        assert_eq!(inner.span, crate::Span::new(F, 2, 3).unwrap());
7225    }
7226
7227    #[test]
7228    fn parse_sequence_of_atoms() {
7229        let (ast, diags) = parse_str(r#"42; "hi"; true; null; x; (x);"#);
7230        assert!(diags.is_empty());
7231        assert_eq!(ast.top_level.len(), 6);
7232        insta::assert_debug_snapshot!(ast);
7233    }
7234
7235    #[test]
7236    fn missing_semicolon_after_expression() {
7237        let (ast, diags) = parse_str("42 43");
7238        assert_eq!(diags.len(), 1);
7239        assert_eq!(diags[0].message, "expected `;` after expression");
7240        assert!(ast.top_level.is_empty());
7241    }
7242
7243    #[test]
7244    fn empty_paren_diagnoses() {
7245        let (ast, diags) = parse_str("();");
7246        assert_eq!(diags.len(), 1);
7247        assert_eq!(diags[0].message, "expected expression");
7248        assert!(ast.top_level.is_empty());
7249    }
7250
7251    #[test]
7252    fn unterminated_paren_diagnoses() {
7253        let (ast, diags) = parse_str("(x;");
7254        assert_eq!(diags.len(), 1);
7255        assert_eq!(diags[0].message, "expected `)`");
7256        assert!(ast.top_level.is_empty());
7257    }
7258
7259    fn binary(e: &crate::Expr) -> (crate::BinOp, crate::ExprId, crate::ExprId) {
7260        match e.kind {
7261            crate::ExprKind::Binary { op, lhs, rhs } => (op, lhs, rhs),
7262            _ => panic!("expected Binary, got {:?}", e.kind),
7263        }
7264    }
7265
7266    #[test]
7267    fn parse_binary_add() {
7268        let (ast, diags) = parse_str("a + b;");
7269        assert!(diags.is_empty());
7270        let outer = expr_of_single_stmt(&ast);
7271        let (op, lhs, rhs) = binary(outer);
7272        assert_eq!(op, crate::BinOp::Add);
7273        assert!(matches!(
7274            ast.try_expr(lhs).unwrap().kind,
7275            crate::ExprKind::Identifier(_)
7276        ));
7277        assert!(matches!(
7278            ast.try_expr(rhs).unwrap().kind,
7279            crate::ExprKind::Identifier(_)
7280        ));
7281        assert_eq!(outer.span, crate::Span::new(F, 0, 5).unwrap());
7282    }
7283
7284    #[test]
7285    fn bitwise_precedence_and_shift_adjacency() {
7286        let (ast, diags) = parse_str("a | b ^ c & d == e >> f + g;");
7287        assert!(diags.is_empty(), "{diags:?}");
7288        let (op, _, mut rhs) = binary(expr_of_single_stmt(&ast));
7289        assert_eq!(op, crate::BinOp::BitOr);
7290        for expected in [
7291            crate::BinOp::BitXor,
7292            crate::BinOp::BitAnd,
7293            crate::BinOp::Eq,
7294            crate::BinOp::Shr,
7295            crate::BinOp::Add,
7296        ] {
7297            let (op, _, next) = binary(ast.try_expr(rhs).unwrap());
7298            assert_eq!(op, expected);
7299            rhs = next;
7300        }
7301        for source in ["a > > b;", "a >/*gap*/> b;", "a < < b;"] {
7302            let (_, diags) = parse_str(source);
7303            assert!(!diags.is_empty(), "{source}");
7304        }
7305    }
7306
7307    #[test]
7308    fn bitwise_shift_assignment_span() {
7309        let (ast, diags) = parse_str("a >>>= b;");
7310        assert!(diags.is_empty(), "{diags:?}");
7311        let crate::StmtKind::CompoundAssign { op, op_span, .. } = single_stmt(&ast).kind else {
7312            panic!("expected assignment");
7313        };
7314        assert_eq!(op, crate::BinOp::UnsignedShr);
7315        assert_eq!(op_span, crate::Span::new(F, 2, 6).unwrap());
7316    }
7317
7318    #[test]
7319    fn precedence_mul_binds_tighter_than_add() {
7320        let (ast, diags) = parse_str("a + b * c;");
7321        assert!(diags.is_empty());
7322        let outer = expr_of_single_stmt(&ast);
7323        let (op, lhs, rhs) = binary(outer);
7324        assert_eq!(op, crate::BinOp::Add);
7325        assert!(matches!(
7326            ast.try_expr(lhs).unwrap().kind,
7327            crate::ExprKind::Identifier(_)
7328        ));
7329        let (rop, rlhs, rrhs) = binary(ast.try_expr(rhs).unwrap());
7330        assert_eq!(rop, crate::BinOp::Mul);
7331        assert!(matches!(
7332            ast.try_expr(rlhs).unwrap().kind,
7333            crate::ExprKind::Identifier(_)
7334        ));
7335        assert!(matches!(
7336            ast.try_expr(rrhs).unwrap().kind,
7337            crate::ExprKind::Identifier(_)
7338        ));
7339    }
7340
7341    #[test]
7342    fn precedence_mul_left_of_add() {
7343        let (ast, diags) = parse_str("a * b + c;");
7344        assert!(diags.is_empty());
7345        let outer = expr_of_single_stmt(&ast);
7346        let (op, lhs, rhs) = binary(outer);
7347        assert_eq!(op, crate::BinOp::Add);
7348        let (lop, ..) = binary(ast.try_expr(lhs).unwrap());
7349        assert_eq!(lop, crate::BinOp::Mul);
7350        assert!(matches!(
7351            ast.try_expr(rhs).unwrap().kind,
7352            crate::ExprKind::Identifier(_)
7353        ));
7354    }
7355
7356    #[test]
7357    fn left_associative_same_precedence() {
7358        let (ast, diags) = parse_str("a - b - c;");
7359        assert!(diags.is_empty());
7360        let outer = expr_of_single_stmt(&ast);
7361        let (op, lhs, rhs) = binary(outer);
7362        assert_eq!(op, crate::BinOp::Sub);
7363        let (lop, llhs, lrhs) = binary(ast.try_expr(lhs).unwrap());
7364        assert_eq!(lop, crate::BinOp::Sub);
7365        assert!(matches!(
7366            ast.try_expr(llhs).unwrap().kind,
7367            crate::ExprKind::Identifier(_)
7368        ));
7369        assert!(matches!(
7370            ast.try_expr(lrhs).unwrap().kind,
7371            crate::ExprKind::Identifier(_)
7372        ));
7373        assert!(matches!(
7374            ast.try_expr(rhs).unwrap().kind,
7375            crate::ExprKind::Identifier(_)
7376        ));
7377    }
7378
7379    #[test]
7380    fn equality_inside_logical() {
7381        let (ast, diags) = parse_str("a === b && c === d;");
7382        assert!(diags.is_empty());
7383        let outer = expr_of_single_stmt(&ast);
7384        let (op, lhs, rhs) = binary(outer);
7385        assert_eq!(op, crate::BinOp::And);
7386        let (lop, ..) = binary(ast.try_expr(lhs).unwrap());
7387        assert_eq!(lop, crate::BinOp::Eq);
7388        let (rop, ..) = binary(ast.try_expr(rhs).unwrap());
7389        assert_eq!(rop, crate::BinOp::Eq);
7390    }
7391
7392    #[test]
7393    fn double_equal_is_same_variant_as_triple() {
7394        let (ast, _) = parse_str("a == b;");
7395        let outer = expr_of_single_stmt(&ast);
7396        let (op, ..) = binary(outer);
7397        assert_eq!(op, crate::BinOp::Eq);
7398    }
7399
7400    #[test]
7401    fn bang_eq_is_not_eq() {
7402        let (ast, _) = parse_str("a != b;");
7403        let outer = expr_of_single_stmt(&ast);
7404        let (op, ..) = binary(outer);
7405        assert_eq!(op, crate::BinOp::NotEq);
7406    }
7407
7408    #[test]
7409    fn comparison_has_higher_prec_than_logical() {
7410        let (ast, _) = parse_str("a < b && c;");
7411        let outer = expr_of_single_stmt(&ast);
7412        let (op, lhs, _) = binary(outer);
7413        assert_eq!(op, crate::BinOp::And);
7414        let (lop, ..) = binary(ast.try_expr(lhs).unwrap());
7415        assert_eq!(lop, crate::BinOp::Lt);
7416    }
7417
7418    #[test]
7419    fn full_precedence_chain_snapshot() {
7420        let (ast, diags) = parse_str("a || b && c === d < e + f * g;");
7421        assert!(diags.is_empty());
7422        insta::assert_debug_snapshot!(ast);
7423    }
7424
7425    #[test]
7426    fn paren_overrides_precedence() {
7427        let (ast, _) = parse_str("(a + b) * c;");
7428        let outer = expr_of_single_stmt(&ast);
7429        let (op, lhs, rhs) = binary(outer);
7430        assert_eq!(op, crate::BinOp::Mul);
7431        let crate::ExprKind::Paren(inner_id) = ast.try_expr(lhs).unwrap().kind else {
7432            panic!("expected Paren on lhs");
7433        };
7434        let (inner_op, ..) = binary(ast.try_expr(inner_id).unwrap());
7435        assert_eq!(inner_op, crate::BinOp::Add);
7436        assert!(matches!(
7437            ast.try_expr(rhs).unwrap().kind,
7438            crate::ExprKind::Identifier(_)
7439        ));
7440    }
7441
7442    #[test]
7443    fn binary_missing_rhs_diagnoses() {
7444        let (ast, diags) = parse_str("a +;");
7445        assert_eq!(diags.len(), 1);
7446        assert_eq!(diags[0].message, "expected expression");
7447        assert!(ast.top_level.is_empty());
7448    }
7449
7450    fn unary(e: &crate::Expr) -> (crate::UnOp, crate::ExprId) {
7451        match e.kind {
7452            crate::ExprKind::Unary { op, operand } => (op, operand),
7453            _ => panic!("expected Unary, got {:?}", e.kind),
7454        }
7455    }
7456
7457    #[test]
7458    fn parse_unary_not() {
7459        let (ast, _) = parse_str("!x;");
7460        let outer = expr_of_single_stmt(&ast);
7461        let (op, operand) = unary(outer);
7462        assert_eq!(op, crate::UnOp::Not);
7463        assert!(matches!(
7464            ast.try_expr(operand).unwrap().kind,
7465            crate::ExprKind::Identifier(_)
7466        ));
7467        assert_eq!(outer.span, crate::Span::new(F, 0, 2).unwrap());
7468    }
7469
7470    #[test]
7471    fn parse_unary_neg() {
7472        let (ast, _) = parse_str("-x;");
7473        let (op, _) = unary(expr_of_single_stmt(&ast));
7474        assert_eq!(op, crate::UnOp::Neg);
7475    }
7476
7477    #[test]
7478    fn parse_unary_pos() {
7479        let (ast, _) = parse_str("+x;");
7480        let (op, _) = unary(expr_of_single_stmt(&ast));
7481        assert_eq!(op, crate::UnOp::Pos);
7482    }
7483
7484    #[test]
7485    fn repeated_unary_not() {
7486        let (ast, _) = parse_str("!!x;");
7487        let outer = expr_of_single_stmt(&ast);
7488        let (op, operand) = unary(outer);
7489        assert_eq!(op, crate::UnOp::Not);
7490        let (inner_op, inner_operand) = unary(ast.try_expr(operand).unwrap());
7491        assert_eq!(inner_op, crate::UnOp::Not);
7492        assert!(matches!(
7493            ast.try_expr(inner_operand).unwrap().kind,
7494            crate::ExprKind::Identifier(_)
7495        ));
7496    }
7497
7498    #[test]
7499    fn unary_binds_tighter_than_binary() {
7500        let (ast, _) = parse_str("-x + 1;");
7501        let outer = expr_of_single_stmt(&ast);
7502        let (op, lhs, rhs) = binary(outer);
7503        assert_eq!(op, crate::BinOp::Add);
7504        let (unop, _) = unary(ast.try_expr(lhs).unwrap());
7505        assert_eq!(unop, crate::UnOp::Neg);
7506        assert_eq!(
7507            ast.try_expr(rhs).unwrap().kind,
7508            crate::ExprKind::Number(1.0)
7509        );
7510    }
7511
7512    #[test]
7513    fn unary_over_paren() {
7514        let (ast, _) = parse_str("-(x + 1);");
7515        let outer = expr_of_single_stmt(&ast);
7516        let (op, operand) = unary(outer);
7517        assert_eq!(op, crate::UnOp::Neg);
7518        let crate::ExprKind::Paren(inner_id) = ast.try_expr(operand).unwrap().kind else {
7519            panic!("expected Paren");
7520        };
7521        let (inner_op, ..) = binary(ast.try_expr(inner_id).unwrap());
7522        assert_eq!(inner_op, crate::BinOp::Add);
7523    }
7524
7525    #[test]
7526    fn parse_is_as_expression_rejected() {
7527        let (_, diags) = parse_str("x is number;");
7528        assert!(!diags.is_empty(), "expected parse error for `x is T`");
7529    }
7530
7531    fn as_cast(e: &crate::Expr) -> (crate::ExprId, &crate::TypeAnnotation) {
7532        match &e.kind {
7533            crate::ExprKind::As { expr, ty } => (*expr, ty),
7534            other => panic!("expected ExprKind::As, got {other:?}"),
7535        }
7536    }
7537
7538    #[test]
7539    fn parse_instanceof_class() {
7540        let (ast, diags) = parse_str("x instanceof Foo;");
7541        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7542        let outer = expr_of_single_stmt(&ast);
7543        let crate::ExprKind::InstanceOf { value, ty } = &outer.kind else {
7544            panic!("expected ExprKind::InstanceOf, got {:?}", outer.kind);
7545        };
7546        assert!(matches!(
7547            ast.try_expr(*value).unwrap().kind,
7548            crate::ExprKind::Identifier(_)
7549        ));
7550        assert!(matches!(ty.kind, crate::TypeAnnotationKind::Name { .. }));
7551    }
7552
7553    #[test]
7554    fn parse_instanceof_binds_tighter_than_logical_and() {
7555        // `a instanceof B && c` parses as `(a instanceof B) && c`.
7556        let (ast, diags) = parse_str("a instanceof B && c;");
7557        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7558        let outer = expr_of_single_stmt(&ast);
7559        let (op, lhs, rhs) = binary(outer);
7560        assert_eq!(op, crate::BinOp::And);
7561        assert!(matches!(
7562            ast.try_expr(lhs).unwrap().kind,
7563            crate::ExprKind::InstanceOf { .. }
7564        ));
7565        assert!(matches!(
7566            ast.try_expr(rhs).unwrap().kind,
7567            crate::ExprKind::Identifier(_)
7568        ));
7569    }
7570
7571    /// `<T>x` produces the same node as `x as T`, so everything downstream of the
7572    /// parser sees one construct rather than two.
7573    #[test]
7574    fn parse_angle_cast_lowers_to_the_as_node() {
7575        let (ast, diags) = parse_str("<number>x;");
7576        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7577        let outer = expr_of_single_stmt(&ast);
7578        let (inner, ty) = as_cast(outer);
7579        assert!(matches!(
7580            ast.try_expr(inner).unwrap().kind,
7581            crate::ExprKind::Identifier(_)
7582        ));
7583        assert!(matches!(ty.kind, crate::TypeAnnotationKind::Name { .. }));
7584    }
7585
7586    /// A `<` that means comparison always has a left operand, so it never reaches
7587    /// the cast branch. This is the regression the cast syntax could most easily
7588    /// have caused.
7589    #[test]
7590    fn a_less_than_comparison_is_not_read_as_a_cast() {
7591        let (ast, diags) = parse_str("a < b;");
7592        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7593        let e = expr_of_single_stmt(&ast);
7594        assert!(matches!(e.kind, crate::ExprKind::Binary { .. }));
7595    }
7596
7597    #[test]
7598    fn parse_typeof_in_type_position() {
7599        let (_ast, diags) = parse_str("let a: typeof b = 1;");
7600        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7601    }
7602
7603    #[test]
7604    fn parse_typeof_with_a_dotted_path() {
7605        let (_ast, diags) = parse_str("let a: typeof b.c.d = 1;");
7606        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7607    }
7608
7609    #[test]
7610    fn parse_as_primitive() {
7611        let (ast, diags) = parse_str("x as number;");
7612        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7613        let outer = expr_of_single_stmt(&ast);
7614        let (inner, ty) = as_cast(outer);
7615        assert!(matches!(
7616            ast.try_expr(inner).unwrap().kind,
7617            crate::ExprKind::Identifier(_)
7618        ));
7619        match &ty.kind {
7620            crate::TypeAnnotationKind::Name { args, .. } => {
7621                assert!(args.is_empty());
7622            }
7623            other => panic!("expected Name target, got {other:?}"),
7624        }
7625        assert_eq!(outer.span, crate::Span::new(F, 0, 11).unwrap());
7626    }
7627
7628    #[test]
7629    fn parse_as_object_literal_type() {
7630        let (ast, diags) = parse_str("x as { a: number };");
7631        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7632        let outer = expr_of_single_stmt(&ast);
7633        let (_, ty) = as_cast(outer);
7634        assert!(matches!(ty.kind, crate::TypeAnnotationKind::Object { .. }));
7635    }
7636
7637    #[test]
7638    fn parse_as_array_short() {
7639        let (ast, diags) = parse_str("x as string[];");
7640        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7641        let outer = expr_of_single_stmt(&ast);
7642        let (_, ty) = as_cast(outer);
7643        assert!(matches!(ty.kind, crate::TypeAnnotationKind::Array { .. }));
7644    }
7645
7646    #[test]
7647    fn parse_as_array_generic() {
7648        let (ast, diags) = parse_str("x as T[];");
7649        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7650        let outer = expr_of_single_stmt(&ast);
7651        let (_, ty) = as_cast(outer);
7652        assert!(matches!(ty.kind, crate::TypeAnnotationKind::Array { .. }));
7653    }
7654
7655    #[test]
7656    fn parse_as_generic_instantiation() {
7657        let (ast, diags) = parse_str("x as Foo<Bar>;");
7658        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7659        let outer = expr_of_single_stmt(&ast);
7660        let (_, ty) = as_cast(outer);
7661        match &ty.kind {
7662            crate::TypeAnnotationKind::Name { args, .. } => {
7663                assert_eq!(args.len(), 1, "Foo<Bar> has one type arg");
7664            }
7665            other => panic!("expected Name with type args, got {other:?}"),
7666        }
7667    }
7668
7669    #[test]
7670    fn parse_as_precedence_higher_than_additive() {
7671        let (ast, diags) = parse_str("x as number + 1;");
7672        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7673        let outer = expr_of_single_stmt(&ast);
7674        let (op, lhs, rhs) = binary(outer);
7675        assert_eq!(op, crate::BinOp::Add);
7676        assert!(matches!(
7677            ast.try_expr(lhs).unwrap().kind,
7678            crate::ExprKind::As { .. }
7679        ));
7680        assert!(matches!(
7681            ast.try_expr(rhs).unwrap().kind,
7682            crate::ExprKind::Number(_)
7683        ));
7684    }
7685
7686    #[test]
7687    fn parse_as_precedence_lower_than_unary() {
7688        let (ast, diags) = parse_str("!x as boolean;");
7689        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7690        let outer = expr_of_single_stmt(&ast);
7691        let (inner, _) = as_cast(outer);
7692        assert!(matches!(
7693            ast.try_expr(inner).unwrap().kind,
7694            crate::ExprKind::Unary {
7695                op: crate::UnOp::Not,
7696                ..
7697            }
7698        ));
7699    }
7700
7701    #[test]
7702    fn parse_as_left_associative_chain() {
7703        let (ast, diags) = parse_str("x as A as B;");
7704        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7705        let outer = expr_of_single_stmt(&ast);
7706        let (inner, _outer_ty) = as_cast(outer);
7707        let (innermost, _inner_ty) = as_cast(ast.try_expr(inner).unwrap());
7708        assert!(matches!(
7709            ast.try_expr(innermost).unwrap().kind,
7710            crate::ExprKind::Identifier(_)
7711        ));
7712    }
7713
7714    #[test]
7715    fn parse_as_inside_call_arg() {
7716        let (ast, diags) = parse_str("f(x as T);");
7717        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7718        let outer = expr_of_single_stmt(&ast);
7719        match &outer.kind {
7720            crate::ExprKind::Call { args, .. } => {
7721                assert_eq!(args.len(), 1);
7722                assert!(matches!(
7723                    ast.try_expr(args[0]).unwrap().kind,
7724                    crate::ExprKind::As { .. }
7725                ));
7726            }
7727            other => panic!("expected Call, got {other:?}"),
7728        }
7729    }
7730
7731    #[test]
7732    fn parse_as_after_field_access() {
7733        let (ast, diags) = parse_str("obj.field as T;");
7734        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7735        let outer = expr_of_single_stmt(&ast);
7736        let (inner, _) = as_cast(outer);
7737        assert!(matches!(
7738            ast.try_expr(inner).unwrap().kind,
7739            crate::ExprKind::FieldAccess { .. }
7740        ));
7741    }
7742
7743    #[test]
7744    fn parse_type_guard_return_annotation_on_function() {
7745        let src = "function isCircle(s: Shape): s is Circle { return true; }";
7746        let (ast, diags) = parse_str(src);
7747        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7748        let stmt = single_stmt(&ast);
7749        match stmt.kind {
7750            crate::StmtKind::Function {
7751                ref return_type,
7752                ref type_predicate,
7753                ..
7754            } => {
7755                assert!(
7756                    return_type.is_none(),
7757                    "predicate form leaves return_type empty",
7758                );
7759                let tp = type_predicate.as_ref().expect("predicate parsed");
7760                assert_eq!(tp.param.name, "s");
7761                assert!(matches!(
7762                    tp.asserted.kind,
7763                    crate::TypeAnnotationKind::Name { .. }
7764                ));
7765                assert_eq!(tp.span, crate::Span::new(F, 29, 40).unwrap());
7766            }
7767            _ => panic!("expected Function"),
7768        }
7769    }
7770
7771    #[test]
7772    fn parse_type_guard_return_annotation_on_arrow() {
7773        let src = "const f = (s: Shape): s is Circle => true;";
7774        let (ast, diags) = parse_str(src);
7775        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7776        let value = match &ast.try_stmt(ast.top_level[0]).unwrap().kind {
7777            crate::StmtKind::Const { value, .. } => *value,
7778            _ => panic!("expected const"),
7779        };
7780        match &ast.try_expr(value).unwrap().kind {
7781            crate::ExprKind::Arrow {
7782                return_type,
7783                type_predicate,
7784                ..
7785            } => {
7786                assert!(return_type.is_none());
7787                let tp = type_predicate.as_ref().expect("predicate parsed");
7788                assert_eq!(tp.param.name, "s");
7789            }
7790            other => panic!("expected Arrow, got {other:?}"),
7791        }
7792    }
7793
7794    #[test]
7795    fn parse_plain_return_type_unaffected_by_predicate_parser() {
7796        let src = "function f(s: Shape): boolean { return true; }";
7797        let (ast, diags) = parse_str(src);
7798        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7799        let stmt = single_stmt(&ast);
7800        match stmt.kind {
7801            crate::StmtKind::Function {
7802                ref return_type,
7803                ref type_predicate,
7804                ..
7805            } => {
7806                assert!(return_type.is_some(), "plain form has return_type");
7807                assert!(type_predicate.is_none(), "plain form has no type_predicate",);
7808            }
7809            _ => panic!("expected Function"),
7810        }
7811    }
7812
7813    #[test]
7814    fn parse_type_guard_rejects_non_return_position() {
7815        let (_, diags) = parse_str("let x: y is Foo = null;");
7816        assert!(
7817            !diags.is_empty(),
7818            "expected parse error for `is` outside return-type position",
7819        );
7820    }
7821
7822    #[test]
7823    fn parse_typeof_prefix() {
7824        let (ast, diags) = parse_str("typeof x;");
7825        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7826        let outer = expr_of_single_stmt(&ast);
7827        let crate::ExprKind::Typeof { operand } = outer.kind else {
7828            panic!("expected Typeof, got {:?}", outer.kind);
7829        };
7830        assert!(matches!(
7831            ast.try_expr(operand).unwrap().kind,
7832            crate::ExprKind::Identifier(_)
7833        ));
7834        assert_eq!(outer.span, crate::Span::new(F, 0, 8).unwrap());
7835    }
7836
7837    #[test]
7838    fn parse_typeof_in_equality() {
7839        let (ast, diags) = parse_str(r#"typeof x === "number";"#);
7840        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7841        let outer = expr_of_single_stmt(&ast);
7842        let (op, lhs, rhs) = binary(outer);
7843        assert_eq!(op, crate::BinOp::Eq);
7844        assert!(matches!(
7845            ast.try_expr(lhs).unwrap().kind,
7846            crate::ExprKind::Typeof { .. }
7847        ));
7848        assert!(matches!(
7849            ast.try_expr(rhs).unwrap().kind,
7850            crate::ExprKind::String(ref s) if s == "number"
7851        ));
7852    }
7853
7854    #[test]
7855    fn parse_let_without_type() {
7856        let (ast, diags) = parse_str("let x = 1;");
7857        assert!(diags.is_empty());
7858        let stmt = single_stmt(&ast);
7859        assert_eq!(stmt.span, crate::Span::new(F, 0, 10).unwrap());
7860        match stmt.kind {
7861            crate::StmtKind::Let {
7862                ref name,
7863                ref ty,
7864                value,
7865                ..
7866            } => {
7867                assert_eq!(name.name, "x");
7868                assert_eq!(name.span, crate::Span::new(F, 4, 5).unwrap());
7869                assert!(ty.is_none());
7870                assert_eq!(
7871                    ast.try_expr(value).unwrap().kind,
7872                    crate::ExprKind::Number(1.0)
7873                );
7874            }
7875            _ => panic!("expected Let"),
7876        }
7877    }
7878
7879    #[test]
7880    fn parse_const_with_type_and_string() {
7881        let (ast, diags) = parse_str(r#"const y: string = "hi";"#);
7882        assert!(diags.is_empty());
7883        let stmt = single_stmt(&ast);
7884        match stmt.kind {
7885            crate::StmtKind::Const {
7886                ref name,
7887                ref ty,
7888                value,
7889                ..
7890            } => {
7891                assert_eq!(name.name, "y");
7892                assert_eq!(name.span, crate::Span::new(F, 6, 7).unwrap());
7893                let ty = ty.as_ref().expect("expected type annotation");
7894                assert!(matches!(ty.kind, crate::TypeAnnotationKind::Name { .. }));
7895                assert_eq!(ty.span, crate::Span::new(F, 9, 15).unwrap());
7896                assert_eq!(
7897                    ast.try_expr(value).unwrap().kind,
7898                    crate::ExprKind::String("hi".to_string())
7899                );
7900            }
7901            _ => panic!("expected Const"),
7902        }
7903    }
7904
7905    #[test]
7906    fn parse_let_with_type_and_binary_initializer() {
7907        let (ast, diags) = parse_str("let z: number = 1 + 2;");
7908        assert!(diags.is_empty());
7909        let stmt = single_stmt(&ast);
7910        match stmt.kind {
7911            crate::StmtKind::Let { ref ty, value, .. } => {
7912                assert!(ty.is_some());
7913                let (op, ..) = binary(ast.try_expr(value).unwrap());
7914                assert_eq!(op, crate::BinOp::Add);
7915            }
7916            _ => panic!("expected Let"),
7917        }
7918    }
7919
7920    #[test]
7921    fn void_type_annotation_is_accepted_syntactically() {
7922        let (ast, diags) = parse_str("let v: void = null;");
7923        assert!(diags.is_empty());
7924        let stmt = single_stmt(&ast);
7925        match stmt.kind {
7926            crate::StmtKind::Let { ref ty, .. } => {
7927                let ty = ty.as_ref().unwrap();
7928                assert!(matches!(ty.kind, crate::TypeAnnotationKind::Name { .. }));
7929                assert_eq!(ty.span, crate::Span::new(F, 7, 11).unwrap());
7930            }
7931            _ => panic!("expected Let"),
7932        }
7933    }
7934
7935    #[test]
7936    fn const_missing_initializer_diagnoses() {
7937        let (ast, diags) = parse_str("const x;");
7938        assert_eq!(diags.len(), 1);
7939        assert_eq!(
7940            diags[0].message,
7941            "`const` declaration requires an initializer"
7942        );
7943        assert!(ast.top_level.is_empty());
7944    }
7945
7946    #[test]
7947    fn parse_const_object_shorthand_pattern() {
7948        let (ast, diags) = parse_str("const { a, b } = obj;");
7949        assert!(diags.is_empty(), "unexpected: {diags:?}");
7950        let stmt = single_stmt(&ast);
7951        match stmt.kind {
7952            crate::StmtKind::ConstPattern {
7953                binding:
7954                    crate::Binding::Object {
7955                        ref fields,
7956                        ref rest,
7957                        ..
7958                    },
7959                ..
7960            } => {
7961                assert_eq!(fields.len(), 2);
7962                assert_eq!(fields[0].source.name, "a");
7963                assert_eq!(fields[0].local.name, "a");
7964                assert_eq!(fields[1].source.name, "b");
7965                assert_eq!(fields[1].local.name, "b");
7966                assert!(rest.is_none());
7967            }
7968            _ => panic!("expected ConstPattern Object, got {:?}", stmt.kind),
7969        }
7970    }
7971
7972    #[test]
7973    fn parse_const_object_pattern_keyword_source_key() {
7974        let (ast, diags) = parse_str("const { type: statusType, default: fallback } = obj;");
7975        assert!(diags.is_empty(), "unexpected: {diags:?}");
7976        let stmt = single_stmt(&ast);
7977        let crate::StmtKind::ConstPattern {
7978            binding: crate::Binding::Object { ref fields, .. },
7979            ..
7980        } = stmt.kind
7981        else {
7982            panic!("expected object const pattern");
7983        };
7984
7985        let names: Vec<(&str, &str)> = fields
7986            .iter()
7987            .map(|f| (f.source.name.as_str(), f.local.name.as_str()))
7988            .collect();
7989        assert_eq!(names, vec![("type", "statusType"), ("default", "fallback")]);
7990    }
7991
7992    #[test]
7993    fn parse_const_object_pattern_keyword_shorthand_rejected() {
7994        let (_ast, diags) = parse_str("const { default } = obj;");
7995        assert!(
7996            diags
7997                .iter()
7998                .any(|d| d.message == "expected `:` after keyword field name in object pattern"),
7999            "expected keyword-shorthand diagnostic, got: {diags:?}"
8000        );
8001    }
8002
8003    #[test]
8004    fn parse_const_object_renamed_pattern() {
8005        let (ast, diags) = parse_str("const { a: x, b: y } = obj;");
8006        assert!(diags.is_empty(), "unexpected: {diags:?}");
8007        match single_stmt(&ast).kind {
8008            crate::StmtKind::ConstPattern {
8009                binding: crate::Binding::Object { ref fields, .. },
8010                ..
8011            } => {
8012                assert_eq!(fields[0].source.name, "a");
8013                assert_eq!(fields[0].local.name, "x");
8014                assert_eq!(fields[1].source.name, "b");
8015                assert_eq!(fields[1].local.name, "y");
8016            }
8017            ref other => panic!("expected ConstPattern Object, got {other:?}"),
8018        }
8019    }
8020
8021    #[test]
8022    fn parse_const_object_with_rest() {
8023        let (ast, diags) = parse_str("const { a, ...rest } = obj;");
8024        assert!(diags.is_empty(), "unexpected: {diags:?}");
8025        match single_stmt(&ast).kind {
8026            crate::StmtKind::ConstPattern {
8027                binding:
8028                    crate::Binding::Object {
8029                        ref fields,
8030                        ref rest,
8031                        ..
8032                    },
8033                ..
8034            } => {
8035                assert_eq!(fields.len(), 1);
8036                assert_eq!(fields[0].source.name, "a");
8037                let r = rest.as_ref().expect("expected rest");
8038                assert_eq!(r.name, "rest");
8039            }
8040            ref other => panic!("expected ConstPattern Object, got {other:?}"),
8041        }
8042    }
8043
8044    #[test]
8045    fn parse_const_array_pattern() {
8046        let (ast, diags) = parse_str("const [x, y] = arr;");
8047        assert!(diags.is_empty(), "unexpected: {diags:?}");
8048        match single_stmt(&ast).kind {
8049            crate::StmtKind::ConstPattern {
8050                binding:
8051                    crate::Binding::Array {
8052                        ref elems,
8053                        ref rest,
8054                        ..
8055                    },
8056                ..
8057            } => {
8058                assert_eq!(elems.len(), 2);
8059                assert_eq!(elems[0].as_ref().unwrap().name, "x");
8060                assert_eq!(elems[1].as_ref().unwrap().name, "y");
8061                assert!(rest.is_none());
8062            }
8063            ref other => panic!("expected ConstPattern Array, got {other:?}"),
8064        }
8065    }
8066
8067    #[test]
8068    fn parse_const_array_with_hole_and_rest() {
8069        let (ast, diags) = parse_str("const [, x, ...rest] = arr;");
8070        assert!(diags.is_empty(), "unexpected: {diags:?}");
8071        match single_stmt(&ast).kind {
8072            crate::StmtKind::ConstPattern {
8073                binding:
8074                    crate::Binding::Array {
8075                        ref elems,
8076                        ref rest,
8077                        ..
8078                    },
8079                ..
8080            } => {
8081                assert_eq!(elems.len(), 2);
8082                assert!(elems[0].is_none(), "first slot should be a hole");
8083                assert_eq!(elems[1].as_ref().unwrap().name, "x");
8084                assert_eq!(rest.as_ref().unwrap().name, "rest");
8085            }
8086            ref other => panic!("expected ConstPattern Array, got {other:?}"),
8087        }
8088    }
8089
8090    #[test]
8091    fn parse_let_object_pattern() {
8092        let (ast, diags) = parse_str("let { a, b } = obj;");
8093        assert!(diags.is_empty(), "unexpected: {diags:?}");
8094        assert!(matches!(
8095            single_stmt(&ast).kind,
8096            crate::StmtKind::LetPattern {
8097                binding: crate::Binding::Object { .. },
8098                ..
8099            }
8100        ));
8101    }
8102
8103    #[test]
8104    fn parse_function_param_object_pattern() {
8105        let src = "function f({ a, b }: T): void { return; }";
8106        let (ast, diags) = parse_str(src);
8107        assert!(diags.is_empty(), "unexpected: {diags:?}");
8108        match single_stmt(&ast).kind {
8109            crate::StmtKind::Function { ref params, .. } => {
8110                assert_eq!(params.len(), 1);
8111                let p = &params[0];
8112                assert!(p.pattern.is_some(), "expected pattern param");
8113                assert_eq!(p.name.name, "", "pattern placeholder uses empty name");
8114                match p.pattern.as_ref().unwrap() {
8115                    crate::Binding::Object { fields, .. } => {
8116                        assert_eq!(fields.len(), 2);
8117                        assert_eq!(fields[0].source.name, "a");
8118                    }
8119                    other => panic!("expected Object binding, got {other:?}"),
8120                }
8121            }
8122            ref other => panic!("expected Function, got {other:?}"),
8123        }
8124    }
8125
8126    #[test]
8127    fn parse_arrow_param_array_pattern() {
8128        let src = "const g = ([x, y]: number[]): number => x + y;";
8129        let (_ast, diags) = parse_str(src);
8130        assert!(diags.is_empty(), "unexpected: {diags:?}");
8131    }
8132
8133    #[test]
8134    fn parse_destructure_nested_rejected() {
8135        let (_ast, diags) = parse_str("const { a: { b } } = obj;");
8136        assert!(
8137            diags
8138                .iter()
8139                .any(|d| d.message.contains("nested destructuring")),
8140            "expected nested-destructuring diagnostic, got: {diags:?}",
8141        );
8142    }
8143
8144    #[test]
8145    fn parse_destructure_array_nested_rejected() {
8146        let (_ast, diags) = parse_str("const [[a]] = arr;");
8147        assert!(
8148            diags
8149                .iter()
8150                .any(|d| d.message.contains("nested destructuring")),
8151            "expected nested-destructuring diagnostic, got: {diags:?}",
8152        );
8153    }
8154
8155    #[test]
8156    fn parse_destructure_pattern_defaults() {
8157        let (ast, diags) = parse_str("const { a = 1, b: c = 2 } = obj;");
8158        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8159        let crate::StmtKind::ConstPattern { ref binding, .. } = single_stmt(&ast).kind else {
8160            panic!("expected pattern")
8161        };
8162        let crate::Binding::Object { fields, .. } = binding else {
8163            panic!("expected object")
8164        };
8165        assert_eq!(fields.len(), 2);
8166        assert!(fields.iter().all(|field| field.default.is_some()));
8167        assert_eq!(fields[1].local.name, "c");
8168    }
8169
8170    #[test]
8171    fn parse_destructure_param_default() {
8172        let (ast, diags) = parse_str("function f({ a = 1 }: T = obj): void { return; }");
8173        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8174        let crate::StmtKind::Function { ref params, .. } = single_stmt(&ast).kind else {
8175            panic!("expected function")
8176        };
8177        assert!(params[0].default.is_some());
8178        assert!(params[0].pattern.is_some());
8179    }
8180
8181    #[test]
8182    fn parse_destructure_rest_not_last_rejected() {
8183        let (_ast, diags) = parse_str("const { ...rest, a } = obj;");
8184        assert!(
8185            diags
8186                .iter()
8187                .any(|d| d.message.contains("rest element must be the last element")),
8188            "expected rest-not-last diagnostic, got: {diags:?}",
8189        );
8190    }
8191
8192    #[test]
8193    fn parse_destructure_empty_object_rejected() {
8194        let (_ast, diags) = parse_str("const { } = obj;");
8195        assert!(
8196            diags
8197                .iter()
8198                .any(|d| d.message.contains("empty object destructuring pattern")),
8199            "expected empty-pattern diagnostic, got: {diags:?}",
8200        );
8201    }
8202
8203    #[test]
8204    fn parse_ternary_simple() {
8205        let (ast, diags) = parse_str("const x: number = a ? 1 : 2;");
8206        assert!(diags.is_empty(), "unexpected: {diags:?}");
8207        let stmt = single_stmt(&ast);
8208        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8209            panic!("expected Const, got {:?}", stmt.kind);
8210        };
8211        assert!(matches!(
8212            ast.try_expr(value).unwrap().kind,
8213            crate::ExprKind::Ternary { .. }
8214        ));
8215    }
8216
8217    #[test]
8218    fn parse_ternary_nested_right_assoc() {
8219        let (ast, diags) = parse_str("const x: number = a ? 1 : b ? 2 : 3;");
8220        assert!(diags.is_empty(), "unexpected: {diags:?}");
8221        let stmt = single_stmt(&ast);
8222        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8223            panic!("expected Const");
8224        };
8225        let crate::ExprKind::Ternary { else_, .. } = ast.try_expr(value).unwrap().kind else {
8226            panic!("expected outer Ternary");
8227        };
8228        assert!(matches!(
8229            ast.try_expr(else_).unwrap().kind,
8230            crate::ExprKind::Ternary { .. }
8231        ));
8232    }
8233
8234    #[test]
8235    fn parse_ternary_missing_colon_diagnoses() {
8236        let (_, diags) = parse_str("const x: number = a ? 1;");
8237        assert!(
8238            diags
8239                .iter()
8240                .any(|d| d.message.contains("expected `:` to complete ternary")),
8241            "expected colon diagnostic, got: {diags:?}",
8242        );
8243    }
8244
8245    #[test]
8246    fn parse_nullish_coalesce_binop() {
8247        let (ast, diags) = parse_str("const x: number = a ?? 1;");
8248        assert!(diags.is_empty(), "unexpected: {diags:?}");
8249        let stmt = single_stmt(&ast);
8250        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8251            panic!("expected Const");
8252        };
8253        match ast.try_expr(value).unwrap().kind {
8254            crate::ExprKind::Binary { op, .. } => {
8255                assert_eq!(op, crate::BinOp::NullishCoalesce);
8256            }
8257            ref other => panic!("expected Binary NullishCoalesce, got {other:?}"),
8258        }
8259    }
8260
8261    #[test]
8262    fn parse_nullish_left_associative() {
8263        let (ast, diags) = parse_str("const x: number = a ?? b ?? c;");
8264        assert!(diags.is_empty(), "unexpected: {diags:?}");
8265        let stmt = single_stmt(&ast);
8266        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8267            panic!("expected Const");
8268        };
8269        let crate::ExprKind::Binary { lhs, .. } = ast.try_expr(value).unwrap().kind else {
8270            panic!("expected outer Binary");
8271        };
8272        match ast.try_expr(lhs).unwrap().kind {
8273            crate::ExprKind::Binary { op, .. } => {
8274                assert_eq!(op, crate::BinOp::NullishCoalesce);
8275            }
8276            ref other => panic!("expected inner Binary, got {other:?}"),
8277        }
8278    }
8279
8280    #[test]
8281    fn parse_pow_is_right_associative() {
8282        let (ast, diags) = parse_str("const x: number = 2 ** 3 ** 2;");
8283        assert!(diags.is_empty(), "unexpected: {diags:?}");
8284        let stmt = single_stmt(&ast);
8285        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8286            panic!("expected Const");
8287        };
8288        let crate::ExprKind::Binary { op, rhs, .. } = ast.try_expr(value).unwrap().kind else {
8289            panic!("expected outer Binary");
8290        };
8291        assert_eq!(op, crate::BinOp::Pow);
8292        match ast.try_expr(rhs).unwrap().kind {
8293            crate::ExprKind::Binary { op: inner_op, .. } => {
8294                assert_eq!(inner_op, crate::BinOp::Pow);
8295            }
8296            ref other => panic!("expected inner Pow Binary on RHS, got {other:?}"),
8297        }
8298    }
8299
8300    #[test]
8301    fn parse_pow_higher_than_multiply() {
8302        let (ast, diags) = parse_str("const x: number = 2 * 3 ** 2;");
8303        assert!(diags.is_empty(), "unexpected: {diags:?}");
8304        let stmt = single_stmt(&ast);
8305        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8306            panic!("expected Const");
8307        };
8308        let crate::ExprKind::Binary { op, rhs, .. } = ast.try_expr(value).unwrap().kind else {
8309            panic!("expected outer Binary");
8310        };
8311        assert_eq!(op, crate::BinOp::Mul);
8312        match ast.try_expr(rhs).unwrap().kind {
8313            crate::ExprKind::Binary { op: inner_op, .. } => {
8314                assert_eq!(inner_op, crate::BinOp::Pow);
8315            }
8316            ref other => panic!("expected Pow on the RHS of *, got {other:?}"),
8317        }
8318    }
8319
8320    #[test]
8321    fn parse_compound_assign_ident() {
8322        let (ast, diags) = parse_str("function main(): void { let x: number = 0; x += 5; }");
8323        assert!(diags.is_empty(), "unexpected: {diags:?}");
8324        let func = ast.top_level.iter().find_map(|sid| {
8325            if let crate::StmtKind::Function { body, .. } = &ast.try_stmt(*sid).unwrap().kind {
8326                Some(*body)
8327            } else {
8328                None
8329            }
8330        });
8331        let crate::StmtKind::Block(stmts) = &ast.try_stmt(func.expect("main")).unwrap().kind else {
8332            panic!("expected function body to be a block");
8333        };
8334        let target = stmts
8335            .iter()
8336            .find_map(|sid| match &ast.try_stmt(*sid).unwrap().kind {
8337                crate::StmtKind::CompoundAssign { target, op, .. } => {
8338                    Some((target.name.clone(), *op))
8339                }
8340                _ => None,
8341            });
8342        let (name, op) = target.expect("expected a CompoundAssign in the body");
8343        assert_eq!(name, "x");
8344        assert_eq!(op, crate::BinOp::Add);
8345    }
8346
8347    #[test]
8348    fn parse_nullish_mixed_with_or_rejected() {
8349        let (_, diags) = parse_str("const x: number = a || b ?? c;");
8350        assert!(
8351            diags
8352                .iter()
8353                .any(|d| d.message.contains("mixing `??` with `||` / `&&`")),
8354            "expected mixing diagnostic, got: {diags:?}",
8355        );
8356    }
8357
8358    #[test]
8359    fn parse_nullish_mixed_with_and_rejected() {
8360        let (_, diags) = parse_str("const x: number = a && b ?? c;");
8361        assert!(
8362            diags
8363                .iter()
8364                .any(|d| d.message.contains("mixing `??` with `||` / `&&`")),
8365            "expected mixing diagnostic, got: {diags:?}",
8366        );
8367    }
8368
8369    #[test]
8370    fn parse_nullish_followed_by_logical_rejected() {
8371        for (source, help) in [
8372            (
8373                "const x: number = a ?? b || c;",
8374                "`(a ?? b) || c` or `a ?? (b || c)`",
8375            ),
8376            (
8377                "const x: number = a ?? b && c;",
8378                "`(a ?? b) && c` or `a ?? (b && c)`",
8379            ),
8380            (
8381                "const x: number = a ?? b ?? c || d;",
8382                "`(a ?? b) || c` or `a ?? (b || c)`",
8383            ),
8384            // One error per chain, however many mixes it has.
8385            (
8386                "const x: number = a || b ?? c || d;",
8387                "`(a || b) ?? c` or `a || (b ?? c)`",
8388            ),
8389        ] {
8390            let (_, diags) = parse_str(source);
8391            assert_eq!(diags.len(), 1, "diags for {source:?}: {diags:?}");
8392            assert!(
8393                diags[0].message.contains("mixing `??` with `||` / `&&`"),
8394                "message for {source:?}: {diags:?}",
8395            );
8396            assert!(
8397                diags[0].help.iter().any(|h| h.contains(help)),
8398                "help for {source:?}: {diags:?}",
8399            );
8400        }
8401    }
8402
8403    #[test]
8404    fn parse_nullish_operand_keeps_tighter_operators() {
8405        // `a ?? (b == c)`: only `||` and `&&` are excluded from a `??` operand.
8406        let (ast, diags) = parse_str("const x: boolean = a ?? b == c;");
8407        assert!(diags.is_empty(), "unexpected: {diags:?}");
8408        let stmt = single_stmt(&ast);
8409        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8410            panic!("expected Const");
8411        };
8412        let crate::ExprKind::Binary { op, rhs, .. } = ast.try_expr(value).unwrap().kind else {
8413            panic!("expected outer Binary");
8414        };
8415        assert_eq!(op, crate::BinOp::NullishCoalesce);
8416        assert!(matches!(
8417            ast.try_expr(rhs).unwrap().kind,
8418            crate::ExprKind::Binary {
8419                op: crate::BinOp::Eq,
8420                ..
8421            }
8422        ));
8423    }
8424
8425    #[test]
8426    fn parse_nullish_with_parens_ok() {
8427        let (_, diags) = parse_str("const x: number = (a || b) ?? c;");
8428        assert!(diags.is_empty(), "unexpected: {diags:?}");
8429    }
8430
8431    #[test]
8432    fn parse_optional_field_chain() {
8433        let (ast, diags) = parse_str("const x: number = a?.b;");
8434        assert!(diags.is_empty(), "unexpected: {diags:?}");
8435        let stmt = single_stmt(&ast);
8436        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8437            panic!("expected Const");
8438        };
8439        match ast.try_expr(value).unwrap().kind {
8440            crate::ExprKind::OptionalChain { ref parts, .. } => {
8441                assert_eq!(parts.len(), 1);
8442                match &parts[0] {
8443                    crate::ChainPart::Field { name, optional, .. } => {
8444                        assert_eq!(name.name, "b");
8445                        assert!(*optional);
8446                    }
8447                    other => panic!("expected Field, got {other:?}"),
8448                }
8449            }
8450            ref other => panic!("expected OptionalChain, got {other:?}"),
8451        }
8452    }
8453
8454    #[test]
8455    fn parse_optional_chain_continues_after_first_dot() {
8456        let (ast, diags) = parse_str("const x: number = a?.b.c;");
8457        assert!(diags.is_empty(), "unexpected: {diags:?}");
8458        let stmt = single_stmt(&ast);
8459        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8460            panic!("expected Const");
8461        };
8462        match ast.try_expr(value).unwrap().kind {
8463            crate::ExprKind::OptionalChain { ref parts, .. } => {
8464                assert_eq!(parts.len(), 2);
8465                if let crate::ChainPart::Field { optional, .. } = &parts[0] {
8466                    assert!(*optional);
8467                }
8468                if let crate::ChainPart::Field { optional, .. } = &parts[1] {
8469                    assert!(!*optional);
8470                }
8471            }
8472            ref other => panic!("expected OptionalChain, got {other:?}"),
8473        }
8474    }
8475
8476    #[test]
8477    fn parse_optional_chain_multiple_short_circuits() {
8478        let (ast, diags) = parse_str("const x: number = a?.b?.c;");
8479        assert!(diags.is_empty(), "unexpected: {diags:?}");
8480        let stmt = single_stmt(&ast);
8481        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8482            panic!("expected Const");
8483        };
8484        let crate::ExprKind::OptionalChain { parts, .. } = &ast.try_expr(value).unwrap().kind
8485        else {
8486            panic!("expected OptionalChain");
8487        };
8488        assert_eq!(parts.len(), 2);
8489        for p in parts {
8490            if let crate::ChainPart::Field { optional, .. } = p {
8491                assert!(*optional, "both parts should be optional");
8492            }
8493        }
8494    }
8495
8496    #[test]
8497    fn parse_optional_call_and_index() {
8498        let (_, diags) = parse_str("const x: number = f?.();");
8499        assert!(diags.is_empty(), "unexpected: {diags:?}");
8500        let (_, diags) = parse_str("const y: number = arr?.[0];");
8501        assert!(diags.is_empty(), "unexpected: {diags:?}");
8502    }
8503
8504    #[test]
8505    fn parse_optional_chain_terminates_at_questionmark_for_ternary() {
8506        let (ast, diags) = parse_str("const x: number = a?.b ? c : d;");
8507        assert!(diags.is_empty(), "unexpected: {diags:?}");
8508        let stmt = single_stmt(&ast);
8509        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8510            panic!("expected Const");
8511        };
8512        let crate::ExprKind::Ternary { cond, .. } = ast.try_expr(value).unwrap().kind else {
8513            panic!(
8514                "expected top-level Ternary, got {:?}",
8515                ast.try_expr(value).unwrap().kind
8516            );
8517        };
8518        assert!(matches!(
8519            ast.try_expr(cond).unwrap().kind,
8520            crate::ExprKind::OptionalChain { .. }
8521        ));
8522    }
8523
8524    #[test]
8525    fn parse_nullish_below_ternary() {
8526        let (ast, diags) = parse_str("const x: number = a ?? b ? c : d;");
8527        assert!(diags.is_empty(), "unexpected: {diags:?}");
8528        let stmt = single_stmt(&ast);
8529        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8530            panic!("expected Const");
8531        };
8532        let crate::ExprKind::Ternary { cond, .. } = ast.try_expr(value).unwrap().kind else {
8533            panic!("expected top-level Ternary");
8534        };
8535        match ast.try_expr(cond).unwrap().kind {
8536            crate::ExprKind::Binary { op, .. } => {
8537                assert_eq!(op, crate::BinOp::NullishCoalesce);
8538            }
8539            ref other => panic!("expected Binary ??, got {other:?}"),
8540        }
8541    }
8542
8543    #[test]
8544    fn let_missing_initializer_diagnoses() {
8545        let (ast, diags) = parse_str("let x;");
8546        assert_eq!(diags.len(), 1);
8547        assert_eq!(
8548            diags[0].message,
8549            "`let` declaration requires an initializer"
8550        );
8551        assert!(ast.top_level.is_empty());
8552    }
8553
8554    #[test]
8555    fn missing_identifier_after_let_diagnoses() {
8556        let (ast, diags) = parse_str("let = 1;");
8557        assert_eq!(diags.len(), 1);
8558        assert_eq!(diags[0].message, "expected identifier after `let`/`const`");
8559        assert!(ast.top_level.is_empty());
8560    }
8561
8562    #[test]
8563    fn missing_type_after_colon_diagnoses() {
8564        let (ast, diags) = parse_str("let x: = 1;");
8565        assert_eq!(diags.len(), 1);
8566        assert_eq!(diags[0].message, "expected type");
8567        assert!(ast.top_level.is_empty());
8568    }
8569
8570    #[test]
8571    fn missing_semicolon_after_declaration_diagnoses() {
8572        let (ast, diags) = parse_str("let x = 1 y;");
8573        assert_eq!(diags.len(), 1);
8574        assert_eq!(diags[0].message, "expected `;` after declaration");
8575        assert!(ast.top_level.is_empty());
8576    }
8577
8578    #[test]
8579    fn expression_statements_still_parse() {
8580        let (ast, diags) = parse_str("42;");
8581        assert!(diags.is_empty());
8582        assert_eq!(ast.top_level.len(), 1);
8583    }
8584
8585    #[test]
8586    fn snapshot_mixed_declarations() {
8587        let (ast, diags) =
8588            parse_str(r#"let x = 1; const y: string = "hi"; let z: number = 1 + 2;"#);
8589        assert!(diags.is_empty());
8590        assert_eq!(ast.top_level.len(), 3);
8591        insta::assert_debug_snapshot!(ast);
8592    }
8593
8594    #[test]
8595    fn parse_function_typical() {
8596        let (ast, diags) = parse_str("function f(a: number, b: string): boolean { }");
8597        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8598        let stmt = single_stmt(&ast);
8599        match stmt.kind {
8600            crate::StmtKind::Function {
8601                ref name,
8602                ref generics,
8603                ref params,
8604                ref return_type,
8605                body,
8606                ..
8607            } => {
8608                assert_eq!(name.name, "f");
8609                assert_eq!(name.span, crate::Span::new(F, 9, 10).unwrap());
8610                assert!(generics.is_empty());
8611                assert_eq!(params.len(), 2);
8612                assert_eq!(params[0].name.name, "a");
8613                assert_eq!(params[0].name.span, crate::Span::new(F, 11, 12).unwrap());
8614                let p0_ty = params[0]
8615                    .ty
8616                    .as_ref()
8617                    .expect("function-decl param requires annotation");
8618                assert!(matches!(p0_ty.kind, crate::TypeAnnotationKind::Name { .. }));
8619                assert_eq!(p0_ty.span, crate::Span::new(F, 14, 20).unwrap()); // `number`
8620                assert_eq!(params[1].name.name, "b");
8621                assert_eq!(params[1].name.span, crate::Span::new(F, 22, 23).unwrap());
8622                let p1_ty = params[1]
8623                    .ty
8624                    .as_ref()
8625                    .expect("function-decl param requires annotation");
8626                assert_eq!(p1_ty.span, crate::Span::new(F, 25, 31).unwrap()); // `string`
8627                let return_type = return_type.as_ref().expect("plain return type");
8628                assert!(matches!(
8629                    return_type.kind,
8630                    crate::TypeAnnotationKind::Name { .. }
8631                ));
8632                assert_eq!(return_type.span, crate::Span::new(F, 34, 41).unwrap()); // `boolean`
8633                let block = ast.try_stmt(body).unwrap();
8634                assert!(matches!(block.kind, crate::StmtKind::Block(ref v) if v.is_empty()));
8635            }
8636            _ => panic!("expected Function"),
8637        }
8638    }
8639
8640    #[test]
8641    fn parse_function_with_doc_comment() {
8642        let (ast, diags) = parse_str(
8643            "/**\n * Sum two numbers.\n * @param a First.\n * @param b Second.\n */\nfunction add(a: number, b: number): number { return a + b; }",
8644        );
8645        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8646        let stmt = single_stmt(&ast);
8647        match &stmt.kind {
8648            crate::StmtKind::Function { doc, .. } => {
8649                let d = doc.as_ref().expect("doc attached");
8650                assert_eq!(d.summary, "Sum two numbers.");
8651                assert_eq!(d.params.len(), 2);
8652                assert_eq!(d.params[0].name, "a");
8653                assert_eq!(d.params[1].name, "b");
8654            }
8655            _ => panic!("expected Function"),
8656        }
8657    }
8658
8659    #[test]
8660    fn parse_interface_member_docs() {
8661        let (ast, diags) = parse_str(
8662            "interface Foo {\n  /** First method. */ first(): void;\n  /** A field. */ field: number;\n}",
8663        );
8664        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8665        let stmt = single_stmt(&ast);
8666        match &stmt.kind {
8667            crate::StmtKind::InterfaceDecl { members, .. } => {
8668                assert_eq!(members.len(), 2);
8669                match &members[0] {
8670                    crate::InterfaceMember::Method { doc, .. } => {
8671                        assert_eq!(doc.as_ref().unwrap().summary, "First method.");
8672                    }
8673                    _ => panic!("expected Method first"),
8674                }
8675                match &members[1] {
8676                    crate::InterfaceMember::Property { doc, .. } => {
8677                        assert_eq!(doc.as_ref().unwrap().summary, "A field.");
8678                    }
8679                    _ => panic!("expected Property second"),
8680                }
8681            }
8682            _ => panic!("expected InterfaceDecl"),
8683        }
8684    }
8685
8686    #[test]
8687    fn parse_numeric_enum_all_implicit() {
8688        let (ast, diags) = parse_str("enum Direction { Up, Down, Left, Right }");
8689        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8690        let stmt = single_stmt(&ast);
8691        match &stmt.kind {
8692            crate::StmtKind::EnumDecl { name, members, doc } => {
8693                assert_eq!(name.name, "Direction");
8694                assert!(doc.is_none());
8695                assert_eq!(members.len(), 4);
8696                for (m, expected) in members.iter().zip(["Up", "Down", "Left", "Right"]) {
8697                    assert_eq!(m.name.name, expected);
8698                    assert!(m.value.is_none(), "expected no initializer on {expected}");
8699                }
8700            }
8701            _ => panic!("expected EnumDecl"),
8702        }
8703    }
8704
8705    #[test]
8706    fn parse_string_enum_all_explicit() {
8707        let (ast, diags) =
8708            parse_str("enum Status { Active = \"active\", Inactive = \"inactive\" }");
8709        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8710        let stmt = single_stmt(&ast);
8711        match &stmt.kind {
8712            crate::StmtKind::EnumDecl { name, members, .. } => {
8713                assert_eq!(name.name, "Status");
8714                assert_eq!(members.len(), 2);
8715                match &members[0].value {
8716                    Some(crate::EnumInitializer::String { value, .. }) => {
8717                        assert_eq!(value, "active");
8718                    }
8719                    other => panic!("expected String init, got {other:?}"),
8720                }
8721                match &members[1].value {
8722                    Some(crate::EnumInitializer::String { value, .. }) => {
8723                        assert_eq!(value, "inactive");
8724                    }
8725                    other => panic!("expected String init, got {other:?}"),
8726                }
8727            }
8728            _ => panic!("expected EnumDecl"),
8729        }
8730    }
8731
8732    #[test]
8733    fn parse_numeric_enum_with_negative_init() {
8734        let (ast, diags) = parse_str("enum D { Up = 1, Down = -1 }");
8735        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8736        let stmt = single_stmt(&ast);
8737        match &stmt.kind {
8738            crate::StmtKind::EnumDecl { members, .. } => {
8739                assert_eq!(members.len(), 2);
8740                match &members[0].value {
8741                    Some(crate::EnumInitializer::Number { value, .. }) => {
8742                        assert_eq!(*value, 1.0);
8743                    }
8744                    other => panic!("expected Number init, got {other:?}"),
8745                }
8746                match &members[1].value {
8747                    Some(crate::EnumInitializer::Number { value, .. }) => {
8748                        assert_eq!(*value, -1.0);
8749                    }
8750                    other => panic!("expected Number init, got {other:?}"),
8751                }
8752            }
8753            _ => panic!("expected EnumDecl"),
8754        }
8755    }
8756
8757    #[test]
8758    fn parse_enum_mixed_kinds_is_parse_error() {
8759        let (_ast, diags) = parse_str("enum Mix { A = 1, B = \"two\" }");
8760        assert!(
8761            diags
8762                .iter()
8763                .any(|d| d.message.contains("mixed numeric and string")),
8764            "expected mixed-kind diagnostic, got: {diags:?}"
8765        );
8766    }
8767
8768    #[test]
8769    fn parse_enum_member_docs() {
8770        let (ast, diags) = parse_str(
8771            "enum D {\n  /** The first one. */ A = 1,\n  /** The second one. */ B = 2,\n}",
8772        );
8773        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8774        let stmt = single_stmt(&ast);
8775        match &stmt.kind {
8776            crate::StmtKind::EnumDecl { members, .. } => {
8777                assert_eq!(members.len(), 2);
8778                assert_eq!(
8779                    members[0].doc.as_ref().expect("first doc").summary,
8780                    "The first one."
8781                );
8782                assert_eq!(
8783                    members[1].doc.as_ref().expect("second doc").summary,
8784                    "The second one."
8785                );
8786            }
8787            _ => panic!("expected EnumDecl"),
8788        }
8789    }
8790
8791    #[test]
8792    fn parse_enum_trailing_comma_ok() {
8793        let (ast, diags) = parse_str("enum D { A, B, }");
8794        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8795        let stmt = single_stmt(&ast);
8796        match &stmt.kind {
8797            crate::StmtKind::EnumDecl { members, .. } => assert_eq!(members.len(), 2),
8798            _ => panic!("expected EnumDecl"),
8799        }
8800    }
8801
8802    #[test]
8803    fn parse_function_empty_params() {
8804        let (ast, diags) = parse_str("function noop(): void { }");
8805        assert!(diags.is_empty());
8806        let stmt = single_stmt(&ast);
8807        match stmt.kind {
8808            crate::StmtKind::Function { ref params, .. } => assert!(params.is_empty()),
8809            _ => panic!("expected Function"),
8810        }
8811    }
8812
8813    #[test]
8814    fn parse_generic_function_single_param() {
8815        let (ast, diags) = parse_str("function identity<T>(x: T): T { return x; }");
8816        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8817        let stmt = single_stmt(&ast);
8818        match stmt.kind {
8819            crate::StmtKind::Function {
8820                ref name,
8821                ref generics,
8822                ref params,
8823                ..
8824            } => {
8825                assert_eq!(name.name, "identity");
8826                assert_eq!(generics.len(), 1);
8827                assert_eq!(generics[0].name, "T");
8828                assert_eq!(params.len(), 1);
8829                assert_eq!(params[0].name.name, "x");
8830            }
8831            _ => panic!("expected Function"),
8832        }
8833    }
8834
8835    #[test]
8836    fn parse_generic_function_multiple_params() {
8837        let (ast, diags) = parse_str("function pair<T, U>(a: T, b: U): T { return a; }");
8838        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8839        let stmt = single_stmt(&ast);
8840        match stmt.kind {
8841            crate::StmtKind::Function { ref generics, .. } => {
8842                assert_eq!(generics.len(), 2);
8843                assert_eq!(generics[0].name, "T");
8844                assert_eq!(generics[1].name, "U");
8845            }
8846            _ => panic!("expected Function"),
8847        }
8848    }
8849
8850    #[test]
8851    fn parse_non_generic_function_has_empty_generics() {
8852        let (ast, diags) = parse_str("function noop(): void { }");
8853        assert!(diags.is_empty());
8854        let stmt = single_stmt(&ast);
8855        match stmt.kind {
8856            crate::StmtKind::Function { ref generics, .. } => assert!(generics.is_empty()),
8857            _ => panic!("expected Function"),
8858        }
8859    }
8860
8861    #[test]
8862    fn parse_empty_generic_list_diagnoses() {
8863        let (_ast, diags) = parse_str("function f<>(): void { }");
8864        assert!(
8865            diags
8866                .iter()
8867                .any(|d| d.message.contains("empty generic parameter list")),
8868            "expected empty-generic-list diagnostic, got: {diags:?}",
8869        );
8870    }
8871
8872    #[test]
8873    fn parse_trailing_comma_in_generic_list_ok() {
8874        let (ast, diags) = parse_str("function f<T,>(): void { }");
8875        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8876        match single_stmt(&ast).kind {
8877            crate::StmtKind::Function { ref generics, .. } => assert_eq!(generics.len(), 1),
8878            _ => panic!("expected Function"),
8879        }
8880    }
8881
8882    #[test]
8883    fn parse_generic_call_single_type_arg() {
8884        let (ast, diags) = parse_str("identity<number>(42);");
8885        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8886        let stmt = single_stmt(&ast);
8887        let crate::StmtKind::Expr(call_id) = stmt.kind else {
8888            panic!("expected Expr stmt");
8889        };
8890        match &ast.try_expr(call_id).unwrap().kind {
8891            crate::ExprKind::Call {
8892                type_args, args, ..
8893            } => {
8894                let ta = type_args.as_ref().expect("type_args set for generic call");
8895                assert_eq!(ta.len(), 1);
8896                assert_eq!(args.len(), 1);
8897            }
8898            other => panic!("expected Call, got {other:?}"),
8899        }
8900    }
8901
8902    #[test]
8903    fn parse_generic_call_trailing_comma_ok() {
8904        let (ast, diags) = parse_str("identity<number,>(42);");
8905        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8906        let stmt = single_stmt(&ast);
8907        let crate::StmtKind::Expr(call_id) = stmt.kind else {
8908            panic!("expected Expr stmt");
8909        };
8910        match &ast.try_expr(call_id).unwrap().kind {
8911            crate::ExprKind::Call {
8912                type_args, args, ..
8913            } => {
8914                assert_eq!(type_args.as_ref().expect("type_args set").len(), 1);
8915                assert_eq!(args.len(), 1);
8916            }
8917            other => panic!("expected Call, got {other:?}"),
8918        }
8919    }
8920
8921    #[test]
8922    fn parse_generic_call_multi_type_args() {
8923        let (ast, diags) = parse_str("pair<number, string>(1, \"x\");");
8924        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8925        let stmt = single_stmt(&ast);
8926        let crate::StmtKind::Expr(call_id) = stmt.kind else {
8927            panic!("expected Expr stmt");
8928        };
8929        match &ast.try_expr(call_id).unwrap().kind {
8930            crate::ExprKind::Call { type_args, .. } => {
8931                let ta = type_args.as_ref().expect("type_args set for generic call");
8932                assert_eq!(ta.len(), 2);
8933            }
8934            other => panic!("expected Call, got {other:?}"),
8935        }
8936    }
8937
8938    #[test]
8939    fn parse_comparison_chain_unaffected_by_generic_lookahead() {
8940        let (ast, diags) = parse_str("let z = a < b > c;");
8941        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8942        assert!(
8943            !ast_contains_call_with_type_args(&ast),
8944            "should not have parsed any generic call",
8945        );
8946    }
8947
8948    #[test]
8949    fn parse_lt_with_paren_rhs_is_comparison_not_generic() {
8950        let (ast, diags) = parse_str("let z = a < (b);");
8951        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8952        assert!(!ast_contains_call_with_type_args(&ast));
8953    }
8954
8955    #[test]
8956    fn parse_generic_call_no_args() {
8957        let (ast, diags) = parse_str("none<number>();");
8958        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8959        let stmt = single_stmt(&ast);
8960        let crate::StmtKind::Expr(call_id) = stmt.kind else {
8961            panic!("expected Expr stmt");
8962        };
8963        match &ast.try_expr(call_id).unwrap().kind {
8964            crate::ExprKind::Call {
8965                type_args, args, ..
8966            } => {
8967                assert_eq!(type_args.as_ref().unwrap().len(), 1);
8968                assert!(args.is_empty());
8969            }
8970            other => panic!("expected Call, got {other:?}"),
8971        }
8972    }
8973
8974    fn ast_contains_call_with_type_args(ast: &crate::Ast) -> bool {
8975        for id in 0..ast.top_level.len() {
8976            let stmt = ast.try_stmt(ast.top_level[id]).unwrap();
8977            if walk_stmt_for_typed_call(ast, stmt) {
8978                return true;
8979            }
8980        }
8981        false
8982    }
8983
8984    fn walk_stmt_for_typed_call(ast: &crate::Ast, stmt: &crate::Stmt) -> bool {
8985        match &stmt.kind {
8986            crate::StmtKind::Let { value, .. } | crate::StmtKind::Const { value, .. } => {
8987                walk_expr_for_typed_call(ast, *value)
8988            }
8989            crate::StmtKind::Expr(e) => walk_expr_for_typed_call(ast, *e),
8990            crate::StmtKind::Block(stmts) => stmts
8991                .iter()
8992                .any(|sid| walk_stmt_for_typed_call(ast, ast.try_stmt(*sid).unwrap())),
8993            _ => false,
8994        }
8995    }
8996
8997    fn walk_expr_for_typed_call(ast: &crate::Ast, id: crate::ExprId) -> bool {
8998        match &ast.try_expr(id).unwrap().kind {
8999            crate::ExprKind::Call {
9000                type_args: Some(_), ..
9001            } => true,
9002            crate::ExprKind::Call { callee, args, .. } => {
9003                walk_expr_for_typed_call(ast, *callee)
9004                    || args.iter().any(|a| walk_expr_for_typed_call(ast, *a))
9005            }
9006            crate::ExprKind::Binary { lhs, rhs, .. } => {
9007                walk_expr_for_typed_call(ast, *lhs) || walk_expr_for_typed_call(ast, *rhs)
9008            }
9009            crate::ExprKind::Unary { operand, .. } => walk_expr_for_typed_call(ast, *operand),
9010            crate::ExprKind::Paren(e) => walk_expr_for_typed_call(ast, *e),
9011            crate::ExprKind::FieldAccess { receiver, .. } => {
9012                walk_expr_for_typed_call(ast, *receiver)
9013            }
9014            crate::ExprKind::IndexAccess { receiver, index } => {
9015                walk_expr_for_typed_call(ast, *receiver) || walk_expr_for_typed_call(ast, *index)
9016            }
9017            _ => false,
9018        }
9019    }
9020
9021    #[test]
9022    fn parse_function_body_with_statements() {
9023        let (ast, diags) = parse_str("function g(x: number): number { let y = x; y; }");
9024        assert!(diags.is_empty());
9025        let stmt = single_stmt(&ast);
9026        match stmt.kind {
9027            crate::StmtKind::Function { body, .. } => {
9028                let block = ast.try_stmt(body).unwrap();
9029                match block.kind {
9030                    crate::StmtKind::Block(ref v) => assert_eq!(v.len(), 2),
9031                    _ => panic!("expected Block"),
9032                }
9033            }
9034            _ => panic!("expected Function"),
9035        }
9036    }
9037
9038    #[test]
9039    fn function_missing_return_type_diagnoses() {
9040        let (ast, diags) = parse_str("function f() { }");
9041        assert!(!diags.is_empty());
9042        assert_eq!(diags[0].message, "expected `:` and return type");
9043        assert!(ast.top_level.is_empty());
9044    }
9045
9046    #[test]
9047    fn function_missing_param_type_diagnoses() {
9048        let (ast, diags) = parse_str("function f(a): void { }");
9049        assert!(!diags.is_empty());
9050        assert_eq!(diags[0].message, "parameter requires a type annotation");
9051        assert!(ast.top_level.is_empty());
9052    }
9053
9054    #[test]
9055    fn function_missing_name_diagnoses() {
9056        let (ast, diags) = parse_str("function (x: number): void { }");
9057        assert!(!diags.is_empty());
9058        assert_eq!(diags[0].message, "expected function name");
9059        assert!(ast.top_level.is_empty());
9060    }
9061
9062    #[test]
9063    fn function_trailing_param_comma_ok() {
9064        let (ast, diags) = parse_str("function f(a: number,): void { }");
9065        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9066        match single_stmt(&ast).kind {
9067            crate::StmtKind::Function { ref params, .. } => assert_eq!(params.len(), 1),
9068            _ => panic!("expected Function"),
9069        }
9070    }
9071
9072    #[test]
9073    fn function_missing_open_brace_diagnoses() {
9074        let (ast, diags) = parse_str("function f(): void");
9075        assert_eq!(diags.len(), 1);
9076        assert_eq!(diags[0].message, "expected `{`");
9077        assert!(ast.top_level.is_empty());
9078    }
9079
9080    #[test]
9081    fn snapshot_function_declaration() {
9082        let (ast, diags) =
9083            parse_str("function add(a: number, b: number): number { let sum = a + b; sum; }");
9084        assert!(diags.is_empty());
9085        insta::assert_debug_snapshot!(ast);
9086    }
9087
9088    #[test]
9089    fn rest_param_basic_parses() {
9090        let (_, diags) = parse_str("function sum(...nums: number[]): number { return 0; }");
9091        assert!(diags.is_empty(), "expected clean parse, got {diags:?}");
9092    }
9093
9094    #[test]
9095    fn rest_param_with_fixed_prefix_parses() {
9096        let (_, diags) =
9097            parse_str("function tag(label: string, ...vals: number[]): string { return label; }");
9098        assert!(diags.is_empty(), "expected clean parse, got {diags:?}");
9099    }
9100
9101    #[test]
9102    fn rest_param_in_arrow_parses() {
9103        let (_, diags) = parse_str("const f = (...n: number[]) => n.length;");
9104        assert!(diags.is_empty(), "expected clean parse, got {diags:?}");
9105    }
9106
9107    #[test]
9108    fn rest_param_in_function_type_annotation_parses() {
9109        let (_, diags) =
9110            parse_str("let f: (a: number, ...rest: string[]) => void = (a, ...r) => {};");
9111        assert!(diags.is_empty(), "expected clean parse, got {diags:?}");
9112    }
9113
9114    #[test]
9115    fn rest_param_not_last_rejected() {
9116        let (_, diags) = parse_str("function f(...xs: number[], y: number): void {}");
9117        assert!(
9118            diags.iter().any(|d| d
9119                .message
9120                .contains("rest parameter must be the last parameter")),
9121            "expected 'must be last' diagnostic, got {diags:?}",
9122        );
9123    }
9124
9125    #[test]
9126    fn rest_param_with_default_rejected() {
9127        let (_, diags) = parse_str("function f(...xs: number[] = []): void {}");
9128        assert!(
9129            diags.iter().any(|d| d
9130                .message
9131                .contains("rest parameter cannot have a default value")),
9132            "expected default-rejection diagnostic, got {diags:?}",
9133        );
9134    }
9135
9136    #[test]
9137    fn rest_param_without_annotation_rejected() {
9138        let (_, diags) = parse_str("function f(...xs): void {}");
9139        assert!(
9140            diags.iter().any(|d| d
9141                .message
9142                .contains("rest parameter requires a type annotation")),
9143            "expected missing-annotation diagnostic, got {diags:?}",
9144        );
9145    }
9146
9147    #[test]
9148    fn rest_param_destructured_rejected() {
9149        let (_, diags) = parse_str("function f(...{a, b}): void {}");
9150        assert!(
9151            diags
9152                .iter()
9153                .any(|d| d.message.contains("rest parameter cannot be destructured")),
9154            "expected destructured-rejection diagnostic, got {diags:?}",
9155        );
9156    }
9157
9158    #[test]
9159    fn rest_param_in_function_type_with_non_array_rejected() {
9160        let (_, diags) = parse_str("let f: (...xs: number) => void = () => {};");
9161        assert!(
9162            diags
9163                .iter()
9164                .any(|d| d.message.contains("rest parameter type must be an array")),
9165            "expected non-array-type diagnostic, got {diags:?}",
9166        );
9167    }
9168
9169    fn call(e: &crate::Expr) -> (crate::ExprId, &[crate::ExprId]) {
9170        match e.kind {
9171            crate::ExprKind::Call {
9172                callee, ref args, ..
9173            } => (callee, args.as_slice()),
9174            _ => panic!("expected Call, got {:?}", e.kind),
9175        }
9176    }
9177
9178    #[test]
9179    fn parse_call_no_args() {
9180        let (ast, diags) = parse_str("f();");
9181        assert!(diags.is_empty());
9182        let outer = expr_of_single_stmt(&ast);
9183        let (callee, args) = call(outer);
9184        assert!(matches!(
9185            ast.try_expr(callee).unwrap().kind,
9186            crate::ExprKind::Identifier(_)
9187        ));
9188        assert!(args.is_empty());
9189        assert_eq!(outer.span, crate::Span::new(F, 0, 3).unwrap());
9190    }
9191
9192    #[test]
9193    fn parse_call_one_arg() {
9194        let (ast, diags) = parse_str("f(1);");
9195        assert!(diags.is_empty());
9196        let outer = expr_of_single_stmt(&ast);
9197        let (callee, args) = call(outer);
9198        assert!(matches!(
9199            ast.try_expr(callee).unwrap().kind,
9200            crate::ExprKind::Identifier(_)
9201        ));
9202        assert_eq!(args.len(), 1);
9203        assert_eq!(
9204            ast.try_expr(args[0]).unwrap().kind,
9205            crate::ExprKind::Number(1.0)
9206        );
9207    }
9208
9209    #[test]
9210    fn parse_call_three_args() {
9211        let (ast, diags) = parse_str("f(1, 2, 3);");
9212        assert!(diags.is_empty());
9213        let outer = expr_of_single_stmt(&ast);
9214        let (_callee, args) = call(outer);
9215        assert_eq!(args.len(), 3);
9216        assert_eq!(
9217            ast.try_expr(args[0]).unwrap().kind,
9218            crate::ExprKind::Number(1.0)
9219        );
9220        assert_eq!(
9221            ast.try_expr(args[1]).unwrap().kind,
9222            crate::ExprKind::Number(2.0)
9223        );
9224        assert_eq!(
9225            ast.try_expr(args[2]).unwrap().kind,
9226            crate::ExprKind::Number(3.0)
9227        );
9228    }
9229
9230    #[test]
9231    fn parse_call_nested() {
9232        let (ast, diags) = parse_str("f(g(x));");
9233        assert!(diags.is_empty());
9234        let outer = expr_of_single_stmt(&ast);
9235        let (_callee, args) = call(outer);
9236        assert_eq!(args.len(), 1);
9237        let (_inner_callee, inner_args) = call(ast.try_expr(args[0]).unwrap());
9238        assert_eq!(inner_args.len(), 1);
9239        assert!(matches!(
9240            ast.try_expr(inner_args[0]).unwrap().kind,
9241            crate::ExprKind::Identifier(_)
9242        ));
9243    }
9244
9245    #[test]
9246    fn parse_curried_call() {
9247        let (ast, diags) = parse_str("f()(x);");
9248        assert!(diags.is_empty());
9249        let outer = expr_of_single_stmt(&ast);
9250        let (callee, args) = call(outer);
9251        assert_eq!(args.len(), 1);
9252        let (_inner_callee, inner_args) = call(ast.try_expr(callee).unwrap());
9253        assert!(inner_args.is_empty());
9254    }
9255
9256    #[test]
9257    fn unary_binds_looser_than_call() {
9258        let (ast, diags) = parse_str("-f();");
9259        assert!(diags.is_empty());
9260        let outer = expr_of_single_stmt(&ast);
9261        let (op, operand) = unary(outer);
9262        assert_eq!(op, crate::UnOp::Neg);
9263        let (_callee, args) = call(ast.try_expr(operand).unwrap());
9264        assert!(args.is_empty());
9265    }
9266
9267    #[test]
9268    fn call_inside_binary() {
9269        let (ast, diags) = parse_str("f() + 1;");
9270        assert!(diags.is_empty());
9271        let outer = expr_of_single_stmt(&ast);
9272        let (op, lhs, rhs) = binary(outer);
9273        assert_eq!(op, crate::BinOp::Add);
9274        let (_callee, args) = call(ast.try_expr(lhs).unwrap());
9275        assert!(args.is_empty());
9276        assert_eq!(
9277            ast.try_expr(rhs).unwrap().kind,
9278            crate::ExprKind::Number(1.0)
9279        );
9280    }
9281
9282    #[test]
9283    fn call_with_complex_args() {
9284        let (ast, diags) = parse_str("f(1 + 2, g(x));");
9285        assert!(diags.is_empty());
9286        let outer = expr_of_single_stmt(&ast);
9287        let (_callee, args) = call(outer);
9288        assert_eq!(args.len(), 2);
9289        let (op, ..) = binary(ast.try_expr(args[0]).unwrap());
9290        assert_eq!(op, crate::BinOp::Add);
9291        let (_, inner_args) = call(ast.try_expr(args[1]).unwrap());
9292        assert_eq!(inner_args.len(), 1);
9293    }
9294
9295    #[test]
9296    fn call_trailing_comma_ok() {
9297        let (ast, diags) = parse_str("f(1, 2,);");
9298        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9299        let (_callee, args) = call(expr_of_single_stmt(&ast));
9300        assert_eq!(args.len(), 2);
9301    }
9302
9303    #[test]
9304    fn unclosed_call_diagnoses() {
9305        let (ast, diags) = parse_str("f(1, 2");
9306        assert!(!diags.is_empty());
9307        assert!(diags[0].message == "expected `,` or `)`" || diags[0].message == "expected `)`");
9308        assert!(ast.top_level.is_empty());
9309    }
9310
9311    #[test]
9312    fn call_expression_statement() {
9313        let (ast, diags) = parse_str(r#"greet("world");"#);
9314        assert!(diags.is_empty());
9315        let outer = expr_of_single_stmt(&ast);
9316        let (callee, args) = call(outer);
9317        assert!(matches!(
9318            ast.try_expr(callee).unwrap().kind,
9319            crate::ExprKind::Identifier(_)
9320        ));
9321        assert_eq!(args.len(), 1);
9322        assert_eq!(
9323            ast.try_expr(args[0]).unwrap().kind,
9324            crate::ExprKind::String("world".to_string())
9325        );
9326    }
9327
9328    #[test]
9329    fn snapshot_multi_call_program() {
9330        let (ast, diags) = parse_str("f(); g(1); h(f(2), 3);");
9331        assert!(diags.is_empty());
9332        assert_eq!(ast.top_level.len(), 3);
9333        insta::assert_debug_snapshot!(ast);
9334    }
9335
9336    #[test]
9337    fn parse_if_simple() {
9338        let (ast, diags) = parse_str("if (a) { }");
9339        assert!(diags.is_empty());
9340        let stmt = single_stmt(&ast);
9341        match stmt.kind {
9342            crate::StmtKind::If {
9343                condition,
9344                then_block,
9345                else_block,
9346            } => {
9347                assert!(matches!(
9348                    ast.try_expr(condition).unwrap().kind,
9349                    crate::ExprKind::Identifier(_)
9350                ));
9351                assert!(matches!(
9352                    ast.try_stmt(then_block).unwrap().kind,
9353                    crate::StmtKind::Block(ref v) if v.is_empty()
9354                ));
9355                assert!(else_block.is_none());
9356            }
9357            _ => panic!("expected If"),
9358        }
9359    }
9360
9361    #[test]
9362    fn parse_if_else() {
9363        let (ast, diags) = parse_str("if (a) { } else { }");
9364        assert!(diags.is_empty());
9365        let stmt = single_stmt(&ast);
9366        match stmt.kind {
9367            crate::StmtKind::If { else_block, .. } => {
9368                let else_id = else_block.expect("expected else block");
9369                assert!(matches!(
9370                    ast.try_stmt(else_id).unwrap().kind,
9371                    crate::StmtKind::Block(ref v) if v.is_empty()
9372                ));
9373            }
9374            _ => panic!("expected If"),
9375        }
9376    }
9377
9378    #[test]
9379    fn parse_else_if_chain() {
9380        let (ast, diags) = parse_str("if (a) { } else if (b) { } else { }");
9381        assert!(diags.is_empty());
9382        let stmt = single_stmt(&ast);
9383        let crate::StmtKind::If {
9384            else_block: Some(inner_id),
9385            ..
9386        } = stmt.kind
9387        else {
9388            panic!("expected outer If with else_block");
9389        };
9390        let inner = ast.try_stmt(inner_id).unwrap();
9391        let crate::StmtKind::If {
9392            else_block: Some(tail_id),
9393            ..
9394        } = inner.kind
9395        else {
9396            panic!("expected inner If for `else if`");
9397        };
9398        assert!(matches!(
9399            ast.try_stmt(tail_id).unwrap().kind,
9400            crate::StmtKind::Block(_)
9401        ));
9402    }
9403
9404    #[test]
9405    fn parse_if_with_body_statements() {
9406        let (ast, diags) = parse_str("if (a) { let x = 1; }");
9407        assert!(diags.is_empty());
9408        let stmt = single_stmt(&ast);
9409        match stmt.kind {
9410            crate::StmtKind::If { then_block, .. } => {
9411                let crate::StmtKind::Block(ref body) = ast.try_stmt(then_block).unwrap().kind
9412                else {
9413                    panic!("expected Block")
9414                };
9415                assert_eq!(body.len(), 1);
9416            }
9417            _ => panic!("expected If"),
9418        }
9419    }
9420
9421    #[test]
9422    fn parse_if_braceless_body() {
9423        let (ast, diags) = parse_str("if (a) return b;");
9424        assert!(diags.is_empty(), "{diags:?}");
9425        let stmt = single_stmt(&ast);
9426        let crate::StmtKind::If {
9427            then_block,
9428            else_block,
9429            ..
9430        } = stmt.kind
9431        else {
9432            panic!("expected If");
9433        };
9434        let crate::StmtKind::Block(ref body) = ast.try_stmt(then_block).unwrap().kind else {
9435            panic!("braceless body should wrap in a Block");
9436        };
9437        assert_eq!(body.len(), 1);
9438        assert!(matches!(
9439            ast.try_stmt(body[0]).unwrap().kind,
9440            crate::StmtKind::Return(_)
9441        ));
9442        assert!(else_block.is_none());
9443    }
9444
9445    #[test]
9446    fn parse_braceless_dangling_else_binds_nearest_if() {
9447        // `else` attaches to the inner `if`, not the outer one.
9448        let (ast, diags) = parse_str("if (a) if (b) x(); else y();");
9449        assert!(diags.is_empty(), "{diags:?}");
9450        let crate::StmtKind::If {
9451            then_block,
9452            else_block: outer_else,
9453            ..
9454        } = single_stmt(&ast).kind
9455        else {
9456            panic!("expected outer If");
9457        };
9458        assert!(outer_else.is_none(), "else must bind to the inner if");
9459        let crate::StmtKind::Block(ref body) = ast.try_stmt(then_block).unwrap().kind else {
9460            panic!("expected wrapped Block");
9461        };
9462        let crate::StmtKind::If {
9463            else_block: Some(_),
9464            ..
9465        } = ast.try_stmt(body[0]).unwrap().kind
9466        else {
9467            panic!("inner If should own the else");
9468        };
9469    }
9470
9471    #[test]
9472    fn parse_while_braceless_body() {
9473        let (ast, diags) = parse_str("while (a) x();");
9474        assert!(diags.is_empty(), "{diags:?}");
9475        let crate::StmtKind::While { body, .. } = single_stmt(&ast).kind else {
9476            panic!("expected While");
9477        };
9478        assert!(matches!(
9479            ast.try_stmt(body).unwrap().kind,
9480            crate::StmtKind::Block(ref v) if v.len() == 1
9481        ));
9482    }
9483
9484    #[test]
9485    fn parse_loop_with_empty_body() {
9486        for src in [
9487            "while (a());",
9488            "for (const x of xs);",
9489            "for (let i = 0; i < 3; i++);",
9490            "do ; while (a());",
9491        ] {
9492            let (ast, diags) = parse_str(src);
9493            assert!(diags.is_empty(), "{src}: {diags:?}");
9494            let body = match single_stmt(&ast).kind {
9495                crate::StmtKind::While { body, .. }
9496                | crate::StmtKind::DoWhile { body, .. }
9497                | crate::StmtKind::For { body, .. }
9498                | crate::StmtKind::ForOf { body, .. } => body,
9499                ref other => panic!("{src}: expected a loop, got {other:?}"),
9500            };
9501            assert!(
9502                matches!(ast.try_stmt(body).unwrap().kind, crate::StmtKind::Block(ref v) if v.is_empty()),
9503                "{src}"
9504            );
9505        }
9506    }
9507
9508    #[test]
9509    fn parse_if_with_empty_body_is_an_error() {
9510        let (_ast, diags) = parse_str("if (a);");
9511        assert_eq!(diags[0].message, "`if` has an empty body");
9512    }
9513
9514    #[test]
9515    fn parse_empty_branch_body_reports_once() {
9516        let (_ast, diags) = parse_str("if (a) ; else b();");
9517        assert_eq!(diags.len(), 1, "{diags:?}");
9518        assert_eq!(diags[0].message, "`if` has an empty body");
9519        let (_ast, diags) = parse_str("if (a) b(); else ;");
9520        assert_eq!(diags.len(), 1, "{diags:?}");
9521        assert_eq!(diags[0].message, "`else` has an empty body");
9522        // One diagnostic per stray `;`, and the `else if` chain still parses.
9523        let (_ast, diags) = parse_str("if (a) ; else if (b) ; else ;");
9524        let messages: Vec<_> = diags.iter().map(|d| d.message.as_str()).collect();
9525        assert_eq!(
9526            messages,
9527            [
9528                "`if` has an empty body",
9529                "`if` has an empty body",
9530                "`else` has an empty body"
9531            ]
9532        );
9533    }
9534
9535    #[test]
9536    fn parse_for_of_braceless_body() {
9537        let (ast, diags) = parse_str("for (const x of xs) f(x);");
9538        assert!(diags.is_empty(), "{diags:?}");
9539        let crate::StmtKind::ForOf { body, .. } = single_stmt(&ast).kind else {
9540            panic!("expected ForOf");
9541        };
9542        assert!(matches!(
9543            ast.try_stmt(body).unwrap().kind,
9544            crate::StmtKind::Block(ref v) if v.len() == 1
9545        ));
9546    }
9547
9548    #[test]
9549    fn parse_nested_if() {
9550        let (ast, diags) = parse_str("if (a) { if (b) { } }");
9551        assert!(diags.is_empty());
9552        let outer = single_stmt(&ast);
9553        let crate::StmtKind::If { then_block, .. } = outer.kind else {
9554            panic!("expected outer If");
9555        };
9556        let crate::StmtKind::Block(ref body) = ast.try_stmt(then_block).unwrap().kind else {
9557            panic!("expected Block")
9558        };
9559        assert_eq!(body.len(), 1);
9560        assert!(matches!(
9561            ast.try_stmt(body[0]).unwrap().kind,
9562            crate::StmtKind::If { .. }
9563        ));
9564    }
9565
9566    #[test]
9567    fn if_missing_open_paren_diagnoses() {
9568        let (ast, diags) = parse_str("if a { }");
9569        assert!(!diags.is_empty());
9570        assert_eq!(diags[0].message, "expected `(`");
9571        assert!(ast.top_level.is_empty());
9572    }
9573
9574    #[test]
9575    fn if_missing_close_paren_diagnoses() {
9576        let (ast, diags) = parse_str("if (a { }");
9577        assert!(!diags.is_empty());
9578        assert_eq!(diags[0].message, "expected `)`");
9579        assert!(ast.top_level.is_empty());
9580    }
9581
9582    #[test]
9583    fn if_braceless_body_parses() {
9584        let (ast, diags) = parse_str("if (a) x;");
9585        assert!(diags.is_empty(), "{diags:?}");
9586        let crate::StmtKind::If { then_block, .. } = single_stmt(&ast).kind else {
9587            panic!("expected If");
9588        };
9589        assert!(matches!(
9590            ast.try_stmt(then_block).unwrap().kind,
9591            crate::StmtKind::Block(ref v) if v.len() == 1
9592        ));
9593    }
9594
9595    #[test]
9596    fn snapshot_else_if_chain() {
9597        let (ast, diags) = parse_str("if (a) { } else if (b) { } else { }");
9598        assert!(diags.is_empty());
9599        insta::assert_debug_snapshot!(ast);
9600    }
9601
9602    #[test]
9603    fn parse_while_simple() {
9604        let (ast, diags) = parse_str("while (a) { }");
9605        assert!(diags.is_empty());
9606        let stmt = single_stmt(&ast);
9607        match stmt.kind {
9608            crate::StmtKind::While { condition, body } => {
9609                assert!(matches!(
9610                    ast.try_expr(condition).unwrap().kind,
9611                    crate::ExprKind::Identifier(_)
9612                ));
9613                assert!(matches!(
9614                    ast.try_stmt(body).unwrap().kind,
9615                    crate::StmtKind::Block(ref v) if v.is_empty()
9616                ));
9617            }
9618            _ => panic!("expected While"),
9619        }
9620    }
9621
9622    #[test]
9623    fn parse_while_with_body() {
9624        let (ast, diags) = parse_str("while (a) { x; y; }");
9625        assert!(diags.is_empty());
9626        let stmt = single_stmt(&ast);
9627        let crate::StmtKind::While { body, .. } = stmt.kind else {
9628            panic!("expected While");
9629        };
9630        let crate::StmtKind::Block(ref stmts) = ast.try_stmt(body).unwrap().kind else {
9631            panic!("expected Block")
9632        };
9633        assert_eq!(stmts.len(), 2);
9634    }
9635
9636    #[test]
9637    fn while_missing_paren_diagnoses() {
9638        let (ast, diags) = parse_str("while a { }");
9639        assert!(!diags.is_empty());
9640        assert_eq!(diags[0].message, "expected `(`");
9641        assert!(ast.top_level.is_empty());
9642    }
9643
9644    #[test]
9645    fn parse_for_all_slots() {
9646        let (ast, diags) = parse_str("for (let i = 0; i < 10; i = i + 1) { x; }");
9647        assert!(diags.is_empty(), "{diags:?}");
9648        let stmt = single_stmt(&ast);
9649        let crate::StmtKind::For {
9650            init,
9651            condition,
9652            update,
9653            body,
9654        } = stmt.kind
9655        else {
9656            panic!("expected For");
9657        };
9658        assert!(init.is_some(), "init missing");
9659        assert!(condition.is_some(), "condition missing");
9660        assert!(update.is_some(), "update missing");
9661        assert!(matches!(
9662            ast.try_stmt(body).unwrap().kind,
9663            crate::StmtKind::Block(_)
9664        ));
9665    }
9666
9667    #[test]
9668    fn parse_for_empty_slots() {
9669        let (ast, diags) = parse_str("for (;;) { break; }");
9670        assert!(diags.is_empty(), "{diags:?}");
9671        let stmt = single_stmt(&ast);
9672        let crate::StmtKind::For {
9673            init,
9674            condition,
9675            update,
9676            ..
9677        } = stmt.kind
9678        else {
9679            panic!("expected For");
9680        };
9681        assert!(init.is_none() && condition.is_none() && update.is_none());
9682    }
9683
9684    #[test]
9685    fn parse_for_of_const_binding() {
9686        let (ast, diags) = parse_str("for (const x of arr) { y; }");
9687        assert!(diags.is_empty(), "{diags:?}");
9688        let stmt = single_stmt(&ast);
9689        let crate::StmtKind::ForOf {
9690            binding_kind,
9691            ref name,
9692            ref ty,
9693            ..
9694        } = stmt.kind
9695        else {
9696            panic!("expected ForOf");
9697        };
9698        assert!(matches!(binding_kind, crate::BindingKind::Const));
9699        assert_eq!(name.name, "x");
9700        assert!(ty.is_none());
9701    }
9702
9703    #[test]
9704    fn parse_for_of_let_with_annotation() {
9705        let (ast, diags) = parse_str("for (let x: number of arr) { y; }");
9706        assert!(diags.is_empty(), "{diags:?}");
9707        let stmt = single_stmt(&ast);
9708        let crate::StmtKind::ForOf {
9709            binding_kind,
9710            ref ty,
9711            ..
9712        } = stmt.kind
9713        else {
9714            panic!("expected ForOf");
9715        };
9716        assert!(matches!(binding_kind, crate::BindingKind::Let));
9717        assert!(ty.is_some());
9718    }
9719
9720    #[test]
9721    fn parse_do_while_simple() {
9722        let (ast, diags) = parse_str("do { x; } while (a);");
9723        assert!(diags.is_empty(), "{diags:?}");
9724        let stmt = single_stmt(&ast);
9725        assert!(matches!(stmt.kind, crate::StmtKind::DoWhile { .. }));
9726    }
9727
9728    #[test]
9729    fn parse_break_and_continue() {
9730        let (ast, diags) = parse_str("while (a) { break; continue; }");
9731        assert!(diags.is_empty(), "{diags:?}");
9732        let stmt = single_stmt(&ast);
9733        let crate::StmtKind::While { body, .. } = stmt.kind else {
9734            panic!("expected While");
9735        };
9736        let crate::StmtKind::Block(ref stmts) = ast.try_stmt(body).unwrap().kind else {
9737            panic!("expected Block");
9738        };
9739        assert!(matches!(
9740            ast.try_stmt(stmts[0]).unwrap().kind,
9741            crate::StmtKind::Break
9742        ));
9743        assert!(matches!(
9744            ast.try_stmt(stmts[1]).unwrap().kind,
9745            crate::StmtKind::Continue
9746        ));
9747    }
9748
9749    #[test]
9750    fn parse_bare_return_in_function() {
9751        let (ast, diags) = parse_str("function f(): void { return; }");
9752        assert!(diags.is_empty());
9753        let f = single_stmt(&ast);
9754        let crate::StmtKind::Function { body, .. } = f.kind else {
9755            panic!("expected Function");
9756        };
9757        let crate::StmtKind::Block(ref stmts) = ast.try_stmt(body).unwrap().kind else {
9758            panic!("expected Block")
9759        };
9760        assert_eq!(stmts.len(), 1);
9761        match ast.try_stmt(stmts[0]).unwrap().kind {
9762            crate::StmtKind::Return(None) => {}
9763            _ => panic!("expected Return(None)"),
9764        }
9765    }
9766
9767    #[test]
9768    fn parse_return_with_value() {
9769        let (ast, diags) = parse_str("function f(): number { return 1; }");
9770        assert!(diags.is_empty());
9771        let f = single_stmt(&ast);
9772        let crate::StmtKind::Function { body, .. } = f.kind else {
9773            panic!("expected Function");
9774        };
9775        let crate::StmtKind::Block(ref stmts) = ast.try_stmt(body).unwrap().kind else {
9776            panic!("expected Block")
9777        };
9778        match ast.try_stmt(stmts[0]).unwrap().kind {
9779            crate::StmtKind::Return(Some(value)) => {
9780                assert_eq!(
9781                    ast.try_expr(value).unwrap().kind,
9782                    crate::ExprKind::Number(1.0)
9783                );
9784            }
9785            _ => panic!("expected Return(Some)"),
9786        }
9787    }
9788
9789    #[test]
9790    fn parse_return_with_expression() {
9791        let (ast, diags) = parse_str("function f(): number { return 1 + 2; }");
9792        assert!(diags.is_empty());
9793        let f = single_stmt(&ast);
9794        let crate::StmtKind::Function { body, .. } = f.kind else {
9795            panic!("expected Function");
9796        };
9797        let crate::StmtKind::Block(ref stmts) = ast.try_stmt(body).unwrap().kind else {
9798            panic!("expected Block")
9799        };
9800        match ast.try_stmt(stmts[0]).unwrap().kind {
9801            crate::StmtKind::Return(Some(value)) => {
9802                let (op, ..) = binary(ast.try_expr(value).unwrap());
9803                assert_eq!(op, crate::BinOp::Add);
9804            }
9805            _ => panic!("expected Return(Some(Binary))"),
9806        }
9807    }
9808
9809    #[test]
9810    fn return_missing_semicolon_diagnoses() {
9811        let (_, diags) = parse_str("function f(): number { return x x; }");
9812        assert!(!diags.is_empty());
9813        assert_eq!(diags[0].message, "expected `;` after return");
9814    }
9815
9816    #[test]
9817    fn parse_bare_block() {
9818        let (ast, diags) = parse_str("{ x; y; }");
9819        assert!(diags.is_empty());
9820        let stmt = single_stmt(&ast);
9821        let crate::StmtKind::Block(ref stmts) = stmt.kind else {
9822            panic!("expected Block");
9823        };
9824        assert_eq!(stmts.len(), 2);
9825    }
9826
9827    #[test]
9828    fn parse_empty_bare_block() {
9829        let (ast, diags) = parse_str("{ }");
9830        assert!(diags.is_empty());
9831        let stmt = single_stmt(&ast);
9832        let crate::StmtKind::Block(ref stmts) = stmt.kind else {
9833            panic!("expected Block");
9834        };
9835        assert!(stmts.is_empty());
9836    }
9837
9838    #[test]
9839    fn parse_nested_bare_blocks() {
9840        let (ast, diags) = parse_str("{ { } }");
9841        assert!(diags.is_empty());
9842        let outer = single_stmt(&ast);
9843        let crate::StmtKind::Block(ref stmts) = outer.kind else {
9844            panic!("expected outer Block");
9845        };
9846        assert_eq!(stmts.len(), 1);
9847        assert!(matches!(
9848            ast.try_stmt(stmts[0]).unwrap().kind,
9849            crate::StmtKind::Block(ref v) if v.is_empty()
9850        ));
9851    }
9852
9853    #[test]
9854    fn snapshot_function_with_if_and_return() {
9855        let source = "function isPositive(n: number): boolean {\n  if (n > 0) {\n    return true;\n  } else {\n    return false;\n  }\n}";
9856        let (ast, diags) = parse_str(source);
9857        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9858        insta::assert_debug_snapshot!(ast);
9859    }
9860
9861    fn object_literal(e: &crate::Expr) -> &[crate::ObjectLiteralMember] {
9862        match e.kind {
9863            crate::ExprKind::ObjectLiteral { ref members } => members.as_slice(),
9864            _ => panic!("expected ObjectLiteral, got {:?}", e.kind),
9865        }
9866    }
9867
9868    fn object_literal_field(m: &crate::ObjectLiteralMember) -> &crate::ObjectLiteralField {
9869        match m {
9870            crate::ObjectLiteralMember::Field(f) => f,
9871            crate::ObjectLiteralMember::Spread { .. }
9872            | crate::ObjectLiteralMember::Computed { .. } => {
9873                panic!("expected Field member, got Spread")
9874            }
9875        }
9876    }
9877
9878    fn array_literal(e: &crate::Expr) -> &[crate::ArrayLiteralElement] {
9879        match e.kind {
9880            crate::ExprKind::ArrayLiteral { ref elements } => elements.as_slice(),
9881            _ => panic!("expected ArrayLiteral, got {:?}", e.kind),
9882        }
9883    }
9884
9885    fn array_literal_value(el: &crate::ArrayLiteralElement) -> crate::ExprId {
9886        match el {
9887            crate::ArrayLiteralElement::Value(id) => *id,
9888            crate::ArrayLiteralElement::Spread { .. } => {
9889                panic!("expected Value element, got Spread")
9890            }
9891        }
9892    }
9893
9894    #[test]
9895    fn parse_empty_object_literal() {
9896        let (ast, diags) = parse_str("let x = {};");
9897        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9898        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9899            panic!("expected Let");
9900        };
9901        assert!(object_literal(ast.try_expr(value).unwrap()).is_empty());
9902    }
9903
9904    #[test]
9905    fn parse_object_literal_simple() {
9906        let (ast, diags) = parse_str("let p = { x: 1, y: 2 };");
9907        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9908        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9909            panic!("expected Let");
9910        };
9911        let members = object_literal(ast.try_expr(value).unwrap());
9912        assert_eq!(members.len(), 2);
9913        let f0 = object_literal_field(&members[0]);
9914        let f1 = object_literal_field(&members[1]);
9915        assert_eq!(f0.name.name, "x");
9916        assert_eq!(f1.name.name, "y");
9917        assert_eq!(
9918            ast.try_expr(f0.value).unwrap().kind,
9919            crate::ExprKind::Number(1.0)
9920        );
9921        assert_eq!(
9922            ast.try_expr(f1.value).unwrap().kind,
9923            crate::ExprKind::Number(2.0)
9924        );
9925    }
9926
9927    #[test]
9928    fn parse_object_literal_keyword_keys() {
9929        let (ast, diags) = parse_str("let p = { type: 1, default: 2, null: 3, true: 4 };");
9930        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9931        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9932            panic!("expected Let");
9933        };
9934        let names: Vec<&str> = object_literal(ast.try_expr(value).unwrap())
9935            .iter()
9936            .map(object_literal_field)
9937            .map(|f| f.name.name.as_str())
9938            .collect();
9939        assert_eq!(names, vec!["type", "default", "null", "true"]);
9940    }
9941
9942    #[test]
9943    fn parse_object_literal_string_key() {
9944        let (ast, diags) = parse_str(r#"let p = { "hello": 1 };"#);
9945        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9946        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9947            panic!("expected Let");
9948        };
9949        let members = object_literal(ast.try_expr(value).unwrap());
9950        assert_eq!(members.len(), 1);
9951        let f0 = object_literal_field(&members[0]);
9952        assert_eq!(f0.name.name, "hello");
9953    }
9954
9955    #[test]
9956    fn parse_object_literal_string_key_decodes_escape() {
9957        let (ast, diags) = parse_str(r#"let p = { "content\u002dtype": 1 };"#);
9958        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9959        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9960            panic!("expected Let");
9961        };
9962        let members = object_literal(ast.try_expr(value).unwrap());
9963        assert_eq!(members.len(), 1);
9964        let f0 = object_literal_field(&members[0]);
9965        assert_eq!(f0.name.name, "content-type");
9966    }
9967
9968    #[test]
9969    fn parse_object_literal_shorthand() {
9970        let (ast, diags) = parse_str("let p = { x, y: 2 };");
9971        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9972        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9973            panic!("expected Let");
9974        };
9975        let members = object_literal(ast.try_expr(value).unwrap());
9976        assert_eq!(members.len(), 2);
9977        let f0 = object_literal_field(&members[0]);
9978        assert_eq!(f0.name.name, "x");
9979        // `{ x }` desugars to `{ x: x }` — value is an identifier reference.
9980        assert_eq!(
9981            ast.try_expr(f0.value).unwrap().kind,
9982            crate::ExprKind::Identifier(crate::Ident {
9983                name: "x".into(),
9984                span: f0.name.span,
9985            })
9986        );
9987        let f1 = object_literal_field(&members[1]);
9988        assert_eq!(f1.name.name, "y");
9989        assert_eq!(
9990            ast.try_expr(f1.value).unwrap().kind,
9991            crate::ExprKind::Number(2.0)
9992        );
9993    }
9994
9995    #[test]
9996    fn parse_object_literal_string_key_shorthand_rejected() {
9997        let (_ast, diags) = parse_str(r#"let p = { "x" };"#);
9998        assert!(
9999            diags
10000                .iter()
10001                .any(|d| d.message.contains("expected `:` after field name")),
10002            "string-key shorthand should be rejected: {diags:?}"
10003        );
10004    }
10005
10006    #[test]
10007    fn parse_object_literal_trailing_comma() {
10008        let (ast, diags) = parse_str("let p = { x: 1, y: 2, };");
10009        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10010        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
10011            panic!("expected Let");
10012        };
10013        assert_eq!(object_literal(ast.try_expr(value).unwrap()).len(), 2);
10014    }
10015
10016    #[test]
10017    fn parse_object_literal_duplicate_key_diagnoses() {
10018        let (_, diags) = parse_str("let p = { x: 1, x: 2 };");
10019        assert_eq!(diags.len(), 1);
10020        assert_eq!(diags[0].message, "duplicate field `x` in object literal");
10021    }
10022
10023    #[test]
10024    fn parse_empty_array_literal() {
10025        let (ast, diags) = parse_str("let xs = [];");
10026        assert!(diags.is_empty());
10027        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
10028            panic!("expected Let");
10029        };
10030        assert!(array_literal(ast.try_expr(value).unwrap()).is_empty());
10031    }
10032
10033    #[test]
10034    fn parse_array_literal_simple() {
10035        let (ast, diags) = parse_str("let xs = [1, 2, 3];");
10036        assert!(diags.is_empty());
10037        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
10038            panic!("expected Let");
10039        };
10040        let elements = array_literal(ast.try_expr(value).unwrap());
10041        assert_eq!(elements.len(), 3);
10042        assert_eq!(
10043            ast.try_expr(array_literal_value(&elements[0]))
10044                .unwrap()
10045                .kind,
10046            crate::ExprKind::Number(1.0)
10047        );
10048        assert_eq!(
10049            ast.try_expr(array_literal_value(&elements[2]))
10050                .unwrap()
10051                .kind,
10052            crate::ExprKind::Number(3.0)
10053        );
10054    }
10055
10056    #[test]
10057    fn parse_array_literal_trailing_comma() {
10058        let (ast, diags) = parse_str("let xs = [1, 2,];");
10059        assert!(diags.is_empty());
10060        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
10061            panic!("expected Let");
10062        };
10063        assert_eq!(array_literal(ast.try_expr(value).unwrap()).len(), 2);
10064    }
10065
10066    #[test]
10067    fn parse_nested_array_in_object() {
10068        let (ast, diags) = parse_str("let p = { items: [1, 2] };");
10069        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10070        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
10071            panic!("expected Let");
10072        };
10073        let members = object_literal(ast.try_expr(value).unwrap());
10074        let f0 = object_literal_field(&members[0]);
10075        assert_eq!(f0.name.name, "items");
10076        let inner = array_literal(ast.try_expr(f0.value).unwrap());
10077        assert_eq!(inner.len(), 2);
10078    }
10079
10080    #[test]
10081    fn parse_nested_object_in_array() {
10082        let (ast, diags) = parse_str("let xs = [{ x: 1 }, { x: 2 }];");
10083        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10084        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
10085            panic!("expected Let");
10086        };
10087        let elements = array_literal(ast.try_expr(value).unwrap());
10088        assert_eq!(elements.len(), 2);
10089        assert_eq!(
10090            object_literal(ast.try_expr(array_literal_value(&elements[0])).unwrap()).len(),
10091            1
10092        );
10093        assert_eq!(
10094            object_literal(ast.try_expr(array_literal_value(&elements[1])).unwrap()).len(),
10095            1
10096        );
10097    }
10098
10099    #[test]
10100    fn brace_at_statement_start_is_block_not_object() {
10101        let (_, diags) = parse_str("{ x: 1 }");
10102        assert!(
10103            !diags.is_empty(),
10104            "expected a diagnostic for `x:` inside block"
10105        );
10106    }
10107
10108    fn field_access(e: &crate::Expr) -> (crate::ExprId, &str) {
10109        match e.kind {
10110            crate::ExprKind::FieldAccess { receiver, ref name } => (receiver, name.name.as_str()),
10111            _ => panic!("expected FieldAccess, got {:?}", e.kind),
10112        }
10113    }
10114
10115    fn index_access(e: &crate::Expr) -> (crate::ExprId, crate::ExprId) {
10116        match e.kind {
10117            crate::ExprKind::IndexAccess { receiver, index } => (receiver, index),
10118            _ => panic!("expected IndexAccess, got {:?}", e.kind),
10119        }
10120    }
10121
10122    #[test]
10123    fn parse_field_access_simple() {
10124        let (ast, diags) = parse_str("a.b;");
10125        assert!(diags.is_empty());
10126        let outer = expr_of_single_stmt(&ast);
10127        let (receiver, name) = field_access(outer);
10128        assert!(matches!(
10129            ast.try_expr(receiver).unwrap().kind,
10130            crate::ExprKind::Identifier(_)
10131        ));
10132        assert_eq!(name, "b");
10133    }
10134
10135    #[test]
10136    fn parse_field_access_keyword_name() {
10137        let (ast, diags) = parse_str("status.type.default.null;");
10138        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10139        let outer = expr_of_single_stmt(&ast);
10140        let (receiver, name) = field_access(outer);
10141        assert_eq!(name, "null");
10142        let (receiver, name) = field_access(ast.try_expr(receiver).unwrap());
10143        assert_eq!(name, "default");
10144        let (_receiver, name) = field_access(ast.try_expr(receiver).unwrap());
10145        assert_eq!(name, "type");
10146    }
10147
10148    #[test]
10149    fn parse_field_access_chain_left_assoc() {
10150        let (ast, diags) = parse_str("a.b.c;");
10151        assert!(diags.is_empty());
10152        let outer = expr_of_single_stmt(&ast);
10153        let (mid_id, c) = field_access(outer);
10154        assert_eq!(c, "c");
10155        let (a_id, b) = field_access(ast.try_expr(mid_id).unwrap());
10156        assert_eq!(b, "b");
10157        assert!(matches!(
10158            ast.try_expr(a_id).unwrap().kind,
10159            crate::ExprKind::Identifier(_)
10160        ));
10161    }
10162
10163    #[test]
10164    fn parse_index_access_simple() {
10165        let (ast, diags) = parse_str("a[0];");
10166        assert!(diags.is_empty());
10167        let outer = expr_of_single_stmt(&ast);
10168        let (receiver, idx) = index_access(outer);
10169        assert!(matches!(
10170            ast.try_expr(receiver).unwrap().kind,
10171            crate::ExprKind::Identifier(_)
10172        ));
10173        assert_eq!(
10174            ast.try_expr(idx).unwrap().kind,
10175            crate::ExprKind::Number(0.0)
10176        );
10177    }
10178
10179    #[test]
10180    fn parse_index_access_chain() {
10181        let (ast, diags) = parse_str("a[0][1];");
10182        assert!(diags.is_empty());
10183        let outer = expr_of_single_stmt(&ast);
10184        let (mid_id, one) = index_access(outer);
10185        assert_eq!(
10186            ast.try_expr(one).unwrap().kind,
10187            crate::ExprKind::Number(1.0)
10188        );
10189        let (a_id, zero) = index_access(ast.try_expr(mid_id).unwrap());
10190        assert_eq!(
10191            ast.try_expr(zero).unwrap().kind,
10192            crate::ExprKind::Number(0.0)
10193        );
10194        assert!(matches!(
10195            ast.try_expr(a_id).unwrap().kind,
10196            crate::ExprKind::Identifier(_)
10197        ));
10198    }
10199
10200    #[test]
10201    fn parse_mixed_access_and_call_chain() {
10202        let (ast, diags) = parse_str("a.b[c].d();");
10203        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10204        let outer = expr_of_single_stmt(&ast);
10205        let (callee, args) = call(outer);
10206        assert!(args.is_empty());
10207        let (idx_id, d_name) = field_access(ast.try_expr(callee).unwrap());
10208        assert_eq!(d_name, "d");
10209        let (b_id, c_idx) = index_access(ast.try_expr(idx_id).unwrap());
10210        assert!(matches!(
10211            ast.try_expr(c_idx).unwrap().kind,
10212            crate::ExprKind::Identifier(_)
10213        ));
10214        let (a_id, b_name) = field_access(ast.try_expr(b_id).unwrap());
10215        assert_eq!(b_name, "b");
10216        assert!(matches!(
10217            ast.try_expr(a_id).unwrap().kind,
10218            crate::ExprKind::Identifier(_)
10219        ));
10220    }
10221
10222    #[test]
10223    fn parse_call_then_field() {
10224        let (ast, diags) = parse_str("f().x;");
10225        assert!(diags.is_empty());
10226        let outer = expr_of_single_stmt(&ast);
10227        let (call_id, x_name) = field_access(outer);
10228        assert_eq!(x_name, "x");
10229        let (_callee, args) = call(ast.try_expr(call_id).unwrap());
10230        assert!(args.is_empty());
10231    }
10232
10233    #[test]
10234    fn parse_index_with_complex_expression() {
10235        let (ast, diags) = parse_str("a[i + 1];");
10236        assert!(diags.is_empty());
10237        let outer = expr_of_single_stmt(&ast);
10238        let (_, idx) = index_access(outer);
10239        let (op, ..) = binary(ast.try_expr(idx).unwrap());
10240        assert_eq!(op, crate::BinOp::Add);
10241    }
10242
10243    #[test]
10244    fn field_access_missing_name_diagnoses() {
10245        let (_, diags) = parse_str("a.;");
10246        assert!(!diags.is_empty());
10247        assert_eq!(diags[0].message, "expected field name after `.`");
10248    }
10249
10250    #[test]
10251    fn parse_postfix_increment() {
10252        let (ast, diags) = parse_str("x++;");
10253        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10254        let expr = expr_of_single_stmt(&ast);
10255        let crate::ExprKind::PostfixUnary { op, operand } = expr.kind else {
10256            panic!("expected PostfixUnary, got {:?}", expr.kind);
10257        };
10258        assert_eq!(op, crate::PostfixOp::Inc);
10259        assert!(matches!(
10260            ast.try_expr(operand).unwrap().kind,
10261            crate::ExprKind::Identifier(_)
10262        ));
10263        assert_eq!(expr.span, crate::Span::new(F, 0, 3).unwrap());
10264    }
10265
10266    #[test]
10267    fn parse_postfix_decrement() {
10268        let (ast, diags) = parse_str("x--;");
10269        assert!(diags.is_empty());
10270        let expr = expr_of_single_stmt(&ast);
10271        let crate::ExprKind::PostfixUnary { op, .. } = expr.kind else {
10272            panic!("expected PostfixUnary");
10273        };
10274        assert_eq!(op, crate::PostfixOp::Dec);
10275    }
10276
10277    #[test]
10278    fn parse_postfix_non_null_assertion() {
10279        let (ast, diags) = parse_str("x!;");
10280        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10281        let expr = expr_of_single_stmt(&ast);
10282        let crate::ExprKind::PostfixUnary { op, operand } = expr.kind else {
10283            panic!("expected PostfixUnary, got {:?}", expr.kind);
10284        };
10285        assert_eq!(op, crate::PostfixOp::NonNullAssert);
10286        assert!(matches!(
10287            ast.try_expr(operand).unwrap().kind,
10288            crate::ExprKind::Identifier(_)
10289        ));
10290        assert_eq!(expr.span, crate::Span::new(F, 0, 2).unwrap());
10291    }
10292
10293    #[test]
10294    fn parse_non_null_assertion_continues_chain() {
10295        let (ast, diags) = parse_str("x!.y;");
10296        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10297        let expr = expr_of_single_stmt(&ast);
10298        let crate::ExprKind::FieldAccess { receiver, name } = &expr.kind else {
10299            panic!("expected FieldAccess, got {:?}", expr.kind);
10300        };
10301        assert_eq!(name.name, "y");
10302        assert!(matches!(
10303            ast.try_expr(*receiver).unwrap().kind,
10304            crate::ExprKind::PostfixUnary {
10305                op: crate::PostfixOp::NonNullAssert,
10306                ..
10307            }
10308        ));
10309    }
10310
10311    #[test]
10312    fn parse_postfix_on_field_access() {
10313        let (ast, diags) = parse_str("o.f++;");
10314        assert!(diags.is_empty());
10315        let expr = expr_of_single_stmt(&ast);
10316        let crate::ExprKind::PostfixUnary { operand, .. } = expr.kind else {
10317            panic!("expected PostfixUnary");
10318        };
10319        assert!(matches!(
10320            ast.try_expr(operand).unwrap().kind,
10321            crate::ExprKind::FieldAccess { .. }
10322        ));
10323    }
10324
10325    #[test]
10326    fn parse_postfix_on_index_access() {
10327        let (ast, diags) = parse_str("xs[0]++;");
10328        assert!(diags.is_empty());
10329        let expr = expr_of_single_stmt(&ast);
10330        let crate::ExprKind::PostfixUnary { operand, .. } = expr.kind else {
10331            panic!("expected PostfixUnary");
10332        };
10333        assert!(matches!(
10334            ast.try_expr(operand).unwrap().kind,
10335            crate::ExprKind::IndexAccess { .. }
10336        ));
10337    }
10338
10339    #[test]
10340    fn parse_postfix_terminates_chain() {
10341        let (_, diags) = parse_str("x++.y;");
10342        assert!(!diags.is_empty());
10343        assert_eq!(diags[0].message, "expected `;` after expression");
10344    }
10345
10346    #[test]
10347    fn parse_postfix_in_binary_continues() {
10348        let (ast, diags) = parse_str("let y = x++ + 1;");
10349        assert!(diags.is_empty(), "unexpected: {diags:?}");
10350        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
10351            panic!("expected Let");
10352        };
10353        let (op, lhs, _) = binary(ast.try_expr(value).unwrap());
10354        assert_eq!(op, crate::BinOp::Add);
10355        assert!(matches!(
10356            ast.try_expr(lhs).unwrap().kind,
10357            crate::ExprKind::PostfixUnary { .. }
10358        ));
10359    }
10360
10361    #[test]
10362    fn parse_index_assignment() {
10363        let (ast, diags) = parse_str("xs[0] = 1;");
10364        assert!(diags.is_empty(), "unexpected: {diags:?}");
10365        let stmt = single_stmt(&ast);
10366        let crate::StmtKind::AssignIndex {
10367            receiver,
10368            index,
10369            value,
10370        } = stmt.kind
10371        else {
10372            panic!("expected AssignIndex, got {:?}", stmt.kind);
10373        };
10374        assert!(matches!(
10375            ast.try_expr(receiver).unwrap().kind,
10376            crate::ExprKind::Identifier(_)
10377        ));
10378        assert_eq!(
10379            ast.try_expr(index).unwrap().kind,
10380            crate::ExprKind::Number(0.0)
10381        );
10382        assert_eq!(
10383            ast.try_expr(value).unwrap().kind,
10384            crate::ExprKind::Number(1.0)
10385        );
10386    }
10387
10388    #[test]
10389    fn parse_for_update_postfix() {
10390        let (ast, diags) = parse_str("function main(): void { for (let i = 0; i < 3; i++) {} }");
10391        assert!(diags.is_empty(), "unexpected: {diags:?}");
10392        assert_eq!(ast.top_level.len(), 1);
10393    }
10394
10395    #[test]
10396    fn parse_for_update_index_assignment() {
10397        let (ast, diags) =
10398            parse_str("function main(): void { for (let i = 0; i < 3; xs[i] = i) {} }");
10399        assert!(diags.is_empty(), "unexpected: {diags:?}");
10400        assert_eq!(ast.top_level.len(), 1);
10401    }
10402
10403    #[test]
10404    fn index_access_missing_close_bracket_diagnoses() {
10405        let (_, diags) = parse_str("a[1;");
10406        assert!(!diags.is_empty());
10407        assert_eq!(diags[0].message, "expected `]`");
10408    }
10409
10410    #[test]
10411    fn field_access_inside_object_literal_value_does_not_steal_dot() {
10412        let (ast, diags) = parse_str("let x = { a: 1 }.foo;");
10413        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10414        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
10415            panic!("expected Let");
10416        };
10417        let (recv_id, name) = field_access(ast.try_expr(value).unwrap());
10418        assert_eq!(name, "foo");
10419        assert_eq!(object_literal(ast.try_expr(recv_id).unwrap()).len(), 1);
10420    }
10421
10422    fn type_of_let(stmt: &crate::Stmt) -> &crate::TypeAnnotation {
10423        match stmt.kind {
10424            crate::StmtKind::Let { ref ty, .. } => ty.as_ref().expect("expected type annotation"),
10425            _ => panic!("expected Let"),
10426        }
10427    }
10428
10429    #[test]
10430    fn parse_array_type_annotation() {
10431        let (ast, diags) = parse_str("let xs: number[] = null;");
10432        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10433        let ty = type_of_let(single_stmt(&ast));
10434        match ty.kind {
10435            crate::TypeAnnotationKind::Array(ref inner) => {
10436                assert!(matches!(inner.kind, crate::TypeAnnotationKind::Name { .. }));
10437            }
10438            _ => panic!("expected Array, got {:?}", ty.kind),
10439        }
10440    }
10441
10442    #[test]
10443    fn parse_nested_array_type_annotation() {
10444        let (ast, diags) = parse_str("let xs: number[][] = null;");
10445        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10446        let ty = type_of_let(single_stmt(&ast));
10447        let crate::TypeAnnotationKind::Array(ref inner) = ty.kind else {
10448            panic!("expected outer Array");
10449        };
10450        let crate::TypeAnnotationKind::Array(ref inner2) = inner.kind else {
10451            panic!("expected inner Array");
10452        };
10453        assert!(matches!(
10454            inner2.kind,
10455            crate::TypeAnnotationKind::Name { .. }
10456        ));
10457    }
10458
10459    #[test]
10460    fn parse_object_type_annotation_simple() {
10461        let (ast, diags) = parse_str("let p: { x: number; y: number } = null;");
10462        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10463        let ty = type_of_let(single_stmt(&ast));
10464        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10465            panic!("expected Object, got {:?}", ty.kind);
10466        };
10467        assert_eq!(fields.len(), 2);
10468        assert_eq!(fields[0].name.name, "x");
10469        assert_eq!(fields[1].name.name, "y");
10470    }
10471
10472    #[test]
10473    fn readonly_is_rejected_on_methods_accessors_and_method_signatures() {
10474        let source = "class K {\n\
10475             readonly m(): number { return 1; }\n\
10476             public readonly n<T>(): number { return 1; }\n\
10477             readonly get g(): number { return 1; }\n\
10478             readonly p: number = 1;\n\
10479             }\n\
10480             type T = { readonly r(a: number): boolean; readonly f: () => void };\n\
10481             interface I { readonly s(): void; readonly q: number; }\n";
10482        let (_, diags) = parse_str(source);
10483        let lines: Vec<usize> = diags
10484            .iter()
10485            .map(|d| {
10486                assert_eq!(
10487                    d.message,
10488                    "`readonly` can only modify a property or index signature"
10489                );
10490                source[..d.span.start as usize].matches('\n').count() + 1
10491            })
10492            .collect();
10493        assert_eq!(lines, vec![2, 3, 4, 7, 8]);
10494    }
10495
10496    #[test]
10497    fn parse_object_type_method_members() {
10498        // `m(): T` is the same member as `m: () => T`, so it parses to a function-typed
10499        // field — including through the `?` modifier.
10500        let (ast, diags) =
10501            parse_str("let p: { m(): number; opt?(): string; r(a: number): boolean } = null;");
10502        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10503        let ty = type_of_let(single_stmt(&ast));
10504        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10505            panic!("expected Object, got {:?}", ty.kind);
10506        };
10507        assert_eq!(fields.len(), 3);
10508        for field in fields {
10509            assert!(
10510                matches!(field.ty.kind, crate::TypeAnnotationKind::Function { .. }),
10511                "member `{}` should be function-typed, got {:?}",
10512                field.name.name,
10513                field.ty.kind,
10514            );
10515        }
10516        assert!(fields[1].optional, "`opt?()` is an optional member");
10517        let crate::TypeAnnotationKind::Function { ref params, .. } = fields[2].ty.kind else {
10518            unreachable!("checked above");
10519        };
10520        assert_eq!(params.len(), 1);
10521        assert_eq!(params[0].name.name, "a");
10522    }
10523
10524    #[test]
10525    fn parse_object_type_annotation_with_commas() {
10526        let (ast, diags) = parse_str("let p: { x: number, y: number } = null;");
10527        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10528        let ty = type_of_let(single_stmt(&ast));
10529        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10530            panic!("expected Object");
10531        };
10532        assert_eq!(fields.len(), 2);
10533    }
10534
10535    #[test]
10536    fn parse_object_type_annotation_empty() {
10537        let (ast, diags) = parse_str("let p: {} = null;");
10538        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10539        let ty = type_of_let(single_stmt(&ast));
10540        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10541            panic!("expected Object");
10542        };
10543        assert!(fields.is_empty());
10544    }
10545
10546    #[test]
10547    fn parse_object_type_with_optional_field() {
10548        let (ast, diags) = parse_str("let p: { id: number; nick?: string } = null;");
10549        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10550        let ty = type_of_let(single_stmt(&ast));
10551        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10552            panic!("expected Object");
10553        };
10554        assert_eq!(fields.len(), 2);
10555        assert_eq!(fields[0].name.name, "id");
10556        assert!(!fields[0].optional);
10557        assert_eq!(fields[1].name.name, "nick");
10558        assert!(fields[1].optional);
10559    }
10560
10561    #[test]
10562    fn parse_object_type_with_readonly_fields() {
10563        let (ast, diags) = parse_str(
10564            "let issue: { readonly id: string; readonly title?: string; body: string } = null;",
10565        );
10566        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10567        let ty = type_of_let(single_stmt(&ast));
10568        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10569            panic!("expected Object");
10570        };
10571        let fields: Vec<(&str, bool, bool)> = fields
10572            .iter()
10573            .map(|field| (field.name.name.as_str(), field.optional, field.readonly))
10574            .collect();
10575        assert_eq!(
10576            fields,
10577            vec![
10578                ("id", false, true),
10579                ("title", true, true),
10580                ("body", false, false),
10581            ]
10582        );
10583    }
10584
10585    #[test]
10586    fn parse_object_type_mixed_optional_and_required() {
10587        let (ast, diags) = parse_str("let p: { a?: number; b: string; c?: boolean } = null;");
10588        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10589        let ty = type_of_let(single_stmt(&ast));
10590        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10591            panic!("expected Object");
10592        };
10593        let opt: Vec<(&str, bool)> = fields
10594            .iter()
10595            .map(|f| (f.name.name.as_str(), f.optional))
10596            .collect();
10597        assert_eq!(opt, vec![("a", true), ("b", false), ("c", true)]);
10598    }
10599
10600    #[test]
10601    fn parse_object_type_keyword_fields() {
10602        let (ast, diags) =
10603            parse_str("let p: { type: string; default?: number; null: boolean } = null;");
10604        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10605        let ty = type_of_let(single_stmt(&ast));
10606        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10607            panic!("expected Object");
10608        };
10609        let opt: Vec<(&str, bool)> = fields
10610            .iter()
10611            .map(|f| (f.name.name.as_str(), f.optional))
10612            .collect();
10613        assert_eq!(
10614            opt,
10615            vec![("type", false), ("default", true), ("null", false)]
10616        );
10617    }
10618
10619    #[test]
10620    fn parse_object_type_quoted_fields() {
10621        let (ast, diags) =
10622            parse_str(r#"let p: { "content-type": string; "x\u002drequest-id"?: string } = null;"#);
10623        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10624        let ty = type_of_let(single_stmt(&ast));
10625        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10626            panic!("expected Object");
10627        };
10628        let opt: Vec<(&str, bool)> = fields
10629            .iter()
10630            .map(|f| (f.name.name.as_str(), f.optional))
10631            .collect();
10632        assert_eq!(opt, vec![("content-type", false), ("x-request-id", true)]);
10633    }
10634
10635    #[test]
10636    fn parse_interface_with_optional_property() {
10637        let (ast, diags) = parse_str("interface User { id: number; deletedAt?: string; }");
10638        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10639        let stmt = single_stmt(&ast);
10640        let crate::StmtKind::InterfaceDecl { ref members, .. } = stmt.kind else {
10641            panic!("expected interface decl");
10642        };
10643        let opt: Vec<(&str, bool)> = members
10644            .iter()
10645            .map(|m| match m {
10646                crate::InterfaceMember::Property { name, optional, .. } => {
10647                    (name.name.as_str(), *optional)
10648                }
10649                _ => panic!("expected property"),
10650            })
10651            .collect();
10652        assert_eq!(opt, vec![("id", false), ("deletedAt", true)]);
10653    }
10654
10655    #[test]
10656    fn parse_interface_with_readonly_properties() {
10657        let (ast, diags) = parse_str(
10658            "interface Issue { readonly id: string; readonly title?: string; body: string; }",
10659        );
10660        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10661        let stmt = single_stmt(&ast);
10662        let crate::StmtKind::InterfaceDecl { ref members, .. } = stmt.kind else {
10663            panic!("expected interface decl");
10664        };
10665        let opt: Vec<(&str, bool, bool)> = members
10666            .iter()
10667            .map(|member| match member {
10668                crate::InterfaceMember::Property {
10669                    name,
10670                    optional,
10671                    readonly,
10672                    ..
10673                } => (name.name.as_str(), *optional, *readonly),
10674                _ => panic!("expected property"),
10675            })
10676            .collect();
10677        assert_eq!(
10678            opt,
10679            vec![
10680                ("id", false, true),
10681                ("title", true, true),
10682                ("body", false, false),
10683            ]
10684        );
10685    }
10686
10687    #[test]
10688    fn parse_readonly_property_name_without_modifier() {
10689        let (ast, diags) = parse_str(
10690            "interface Meta { readonly: boolean; }\nlet meta: { readonly: boolean } = null;",
10691        );
10692        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10693        let crate::StmtKind::InterfaceDecl { ref members, .. } =
10694            ast.try_stmt(ast.top_level[0]).unwrap().kind
10695        else {
10696            panic!("expected interface decl");
10697        };
10698        let crate::InterfaceMember::Property {
10699            ref name, readonly, ..
10700        } = members[0]
10701        else {
10702            panic!("expected property");
10703        };
10704        assert_eq!(name.name, "readonly");
10705        assert!(
10706            !readonly,
10707            "`readonly` here is the property name, not a modifier"
10708        );
10709
10710        let ty = type_of_let(ast.try_stmt(ast.top_level[1]).unwrap());
10711        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10712            panic!("expected Object");
10713        };
10714        assert_eq!(fields[0].name.name, "readonly");
10715        assert!(!fields[0].readonly);
10716    }
10717
10718    #[test]
10719    fn parse_interface_keyword_members() {
10720        let (ast, diags) =
10721            parse_str("interface IssueStatus { type: string; default(): string; null: boolean; }");
10722        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10723        let stmt = single_stmt(&ast);
10724        let crate::StmtKind::InterfaceDecl { ref members, .. } = stmt.kind else {
10725            panic!("expected interface decl");
10726        };
10727        let names: Vec<&str> = members
10728            .iter()
10729            .map(|m| match m {
10730                crate::InterfaceMember::Method { name, .. }
10731                | crate::InterfaceMember::Property { name, .. } => name.name.as_str(),
10732                crate::InterfaceMember::IndexSignature(_) => panic!("unexpected index signature"),
10733            })
10734            .collect();
10735        assert_eq!(names, vec!["type", "default", "null"]);
10736    }
10737
10738    #[test]
10739    fn parse_interface_quoted_property_members() {
10740        let (ast, diags) = parse_str(
10741            r#"interface Headers { "content-type": string; "x\u002drequest-id"?: string; }"#,
10742        );
10743        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10744        let stmt = single_stmt(&ast);
10745        let crate::StmtKind::InterfaceDecl { ref members, .. } = stmt.kind else {
10746            panic!("expected interface decl");
10747        };
10748        let names: Vec<(&str, bool)> = members
10749            .iter()
10750            .map(|m| match m {
10751                crate::InterfaceMember::Property { name, optional, .. } => {
10752                    (name.name.as_str(), *optional)
10753                }
10754                _ => panic!("expected property"),
10755            })
10756            .collect();
10757        assert_eq!(names, vec![("content-type", false), ("x-request-id", true)]);
10758    }
10759
10760    #[test]
10761    fn parse_param_optional_marker() {
10762        let (ast, diags) = parse_str("function f(x?: number): void {}");
10763        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10764        let crate::StmtKind::Function { ref params, .. } = single_stmt(&ast).kind else {
10765            panic!("expected function")
10766        };
10767        assert!(params[0].optional);
10768        assert!(params[0].default.is_none());
10769    }
10770
10771    #[test]
10772    fn parse_interface_optional_method() {
10773        let (ast, diags) = parse_str("interface F { foo?(x?: number): void; }");
10774        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10775        let crate::StmtKind::InterfaceDecl { ref members, .. } = single_stmt(&ast).kind else {
10776            panic!("expected interface")
10777        };
10778        let crate::InterfaceMember::Property { optional, ty, .. } = &members[0] else {
10779            panic!("expected callable property")
10780        };
10781        assert!(*optional);
10782        let crate::TypeAnnotationKind::Function { params, .. } = &ty.kind else {
10783            panic!("expected function type")
10784        };
10785        assert!(params[0].optional);
10786    }
10787
10788    #[test]
10789    fn parse_interface_with_call_signature() {
10790        let (ast, diags) = parse_str("interface F { (s: string): number; }");
10791        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10792        let stmt = single_stmt(&ast);
10793        let crate::StmtKind::InterfaceDecl { ref members, .. } = stmt.kind else {
10794            panic!("expected interface decl");
10795        };
10796        assert_eq!(members.len(), 1);
10797        match &members[0] {
10798            crate::InterfaceMember::Method { name, params, .. } => {
10799                assert_eq!(name.name, "@call");
10800                assert_eq!(params.len(), 1);
10801                assert_eq!(params[0].name.name, "s");
10802            }
10803            other => panic!("expected method (call signature), got {other:?}"),
10804        }
10805    }
10806
10807    #[test]
10808    fn parse_interface_mixed_call_signature_and_method() {
10809        let (ast, diags) = parse_str("interface F { greet(): string; (x: number): number; }");
10810        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10811        let stmt = single_stmt(&ast);
10812        let crate::StmtKind::InterfaceDecl { ref members, .. } = stmt.kind else {
10813            panic!("expected interface decl");
10814        };
10815        let names: Vec<&str> = members
10816            .iter()
10817            .map(|m| match m {
10818                crate::InterfaceMember::Method { name, .. } => name.name.as_str(),
10819                _ => "<property>",
10820            })
10821            .collect();
10822        assert_eq!(names, vec!["greet", "@call"]);
10823    }
10824
10825    #[test]
10826    fn parse_interface_rejects_generic_call_signature() {
10827        let (_ast, diags) = parse_str("interface F { <T>(x: T): T; }");
10828        assert!(
10829            diags
10830                .iter()
10831                .any(|d| d.message.contains("generic call signatures")),
10832            "expected generic-call-signature diagnostic, got {diags:?}"
10833        );
10834    }
10835
10836    #[test]
10837    fn parse_object_type_with_array_field() {
10838        let (ast, diags) = parse_str("let p: { xs: number[] } = null;");
10839        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10840        let ty = type_of_let(single_stmt(&ast));
10841        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10842            panic!("expected Object");
10843        };
10844        assert_eq!(fields[0].name.name, "xs");
10845        assert!(matches!(
10846            fields[0].ty.kind,
10847            crate::TypeAnnotationKind::Array(_)
10848        ));
10849    }
10850
10851    #[test]
10852    fn parse_array_of_objects() {
10853        let (ast, diags) = parse_str("let xs: { x: number }[] = null;");
10854        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10855        let ty = type_of_let(single_stmt(&ast));
10856        let crate::TypeAnnotationKind::Array(ref inner) = ty.kind else {
10857            panic!("expected Array");
10858        };
10859        assert!(matches!(
10860            inner.kind,
10861            crate::TypeAnnotationKind::Object { .. }
10862        ));
10863    }
10864
10865    #[test]
10866    fn parse_tuple_type_annotation() {
10867        let (ast, diags) = parse_str("let p: [string, number] = null;");
10868        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10869        let ty = type_of_let(single_stmt(&ast));
10870        let crate::TypeAnnotationKind::Tuple(ref elems) = ty.kind else {
10871            panic!("expected Tuple, got {:?}", ty.kind);
10872        };
10873        assert_eq!(elems.len(), 2);
10874        assert!(matches!(
10875            elems[0].kind,
10876            crate::TypeAnnotationKind::Name { .. }
10877        ));
10878        assert!(matches!(
10879            elems[1].kind,
10880            crate::TypeAnnotationKind::Name { .. }
10881        ));
10882    }
10883
10884    #[test]
10885    fn parse_singleton_tuple_type() {
10886        let (ast, diags) = parse_str("let p: [number] = null;");
10887        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10888        let ty = type_of_let(single_stmt(&ast));
10889        let crate::TypeAnnotationKind::Tuple(ref elems) = ty.kind else {
10890            panic!("expected Tuple");
10891        };
10892        assert_eq!(elems.len(), 1);
10893    }
10894
10895    #[test]
10896    fn parse_nested_tuple_type() {
10897        let (ast, diags) = parse_str("let p: [[number, string], boolean] = null;");
10898        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10899        let ty = type_of_let(single_stmt(&ast));
10900        let crate::TypeAnnotationKind::Tuple(ref outer) = ty.kind else {
10901            panic!("expected outer Tuple");
10902        };
10903        assert_eq!(outer.len(), 2);
10904        let crate::TypeAnnotationKind::Tuple(ref inner) = outer[0].kind else {
10905            panic!("expected inner Tuple");
10906        };
10907        assert_eq!(inner.len(), 2);
10908    }
10909
10910    #[test]
10911    fn parse_array_of_tuple_type() {
10912        let (ast, diags) = parse_str("let xs: [string, number][] = null;");
10913        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10914        let ty = type_of_let(single_stmt(&ast));
10915        let crate::TypeAnnotationKind::Array(ref inner) = ty.kind else {
10916            panic!("expected outer Array, got {:?}", ty.kind);
10917        };
10918        assert!(matches!(inner.kind, crate::TypeAnnotationKind::Tuple(_)));
10919    }
10920
10921    #[test]
10922    fn parse_trailing_comma_tuple_type() {
10923        let (ast, diags) = parse_str("let p: [number, string,] = null;");
10924        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10925        let ty = type_of_let(single_stmt(&ast));
10926        let crate::TypeAnnotationKind::Tuple(ref elems) = ty.kind else {
10927            panic!("expected Tuple");
10928        };
10929        assert_eq!(elems.len(), 2);
10930    }
10931
10932    #[test]
10933    fn parse_empty_tuple_type_rejected() {
10934        let (_ast, diags) = parse_str("let p: [] = null;");
10935        assert!(
10936            diags.iter().any(|d| d
10937                .message
10938                .contains("tuple types must have at least one element")),
10939            "expected empty-tuple diagnostic, got: {diags:?}"
10940        );
10941    }
10942
10943    #[test]
10944    fn parse_labeled_tuple_drops_labels() {
10945        let (ast, diags) = parse_str("let p: [first: number, string] = null;");
10946        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10947        let ty = type_of_let(single_stmt(&ast));
10948        let crate::TypeAnnotationKind::Tuple(ref elems) = ty.kind else {
10949            panic!("expected Tuple, got {:?}", ty.kind);
10950        };
10951        assert_eq!(elems.len(), 2);
10952        assert!(
10953            elems
10954                .iter()
10955                .all(|e| matches!(e.kind, crate::TypeAnnotationKind::Name { .. }))
10956        );
10957    }
10958
10959    #[test]
10960    fn parse_labeled_optional_tuple_element() {
10961        let (ast, diags) = parse_str("let p: [first: number, second?: string] = null;");
10962        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10963        let ty = type_of_let(single_stmt(&ast));
10964        let crate::TypeAnnotationKind::Tuple(elements) = &ty.kind else {
10965            panic!("expected tuple")
10966        };
10967        assert!(matches!(
10968            elements[1].kind,
10969            crate::TypeAnnotationKind::Optional(_)
10970        ));
10971    }
10972
10973    #[test]
10974    fn parse_readonly_binds_to_the_postfix_type() {
10975        let (ast, diags) = parse_str("let p: readonly number[][] | null = null;");
10976        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10977        let ty = type_of_let(single_stmt(&ast));
10978        let crate::TypeAnnotationKind::Union(ref members) = ty.kind else {
10979            panic!("expected Union, got {:?}", ty.kind);
10980        };
10981        let crate::TypeAnnotationKind::Readonly(ref operand) = members[0].kind else {
10982            panic!("expected Readonly, got {:?}", members[0].kind);
10983        };
10984        let crate::TypeAnnotationKind::Array(ref inner) = operand.kind else {
10985            panic!("expected Array, got {:?}", operand.kind);
10986        };
10987        assert!(matches!(inner.kind, crate::TypeAnnotationKind::Array(_)));
10988    }
10989
10990    #[test]
10991    fn parse_readonly_as_a_type_name_is_not_the_operator() {
10992        let (ast, diags) = parse_str("let p: readonly = null;");
10993        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10994        let ty = type_of_let(single_stmt(&ast));
10995        assert!(matches!(ty.kind, crate::TypeAnnotationKind::Name { .. }));
10996    }
10997
10998    #[test]
10999    fn parse_generic_one_arg() {
11000        let (ast, diags) = parse_str("let x: Foo<number> = null;");
11001        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11002        let ty = type_of_let(single_stmt(&ast));
11003        let crate::TypeAnnotationKind::Name { ref name, ref args } = ty.kind else {
11004            panic!("expected Name, got {:?}", ty.kind);
11005        };
11006        assert_eq!(name.span, crate::Span::new(F, 7, 10).unwrap());
11007        assert_eq!(ty.span, crate::Span::new(F, 7, 18).unwrap());
11008        assert_eq!(args.len(), 1);
11009        assert!(matches!(
11010            args[0].kind,
11011            crate::TypeAnnotationKind::Name { .. }
11012        ));
11013    }
11014
11015    #[test]
11016    fn parse_generic_two_args() {
11017        let (ast, diags) = parse_str("let m: Map<string, number> = null;");
11018        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11019        let ty = type_of_let(single_stmt(&ast));
11020        let crate::TypeAnnotationKind::Name { ref args, .. } = ty.kind else {
11021            panic!("expected Name");
11022        };
11023        assert_eq!(args.len(), 2);
11024    }
11025
11026    #[test]
11027    fn parse_generic_nested() {
11028        let (ast, diags) = parse_str("let b: Box<Box<T>> = null;");
11029        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11030        let ty = type_of_let(single_stmt(&ast));
11031        let crate::TypeAnnotationKind::Name { ref args, .. } = ty.kind else {
11032            panic!("expected outer Name");
11033        };
11034        assert_eq!(args.len(), 1);
11035        let crate::TypeAnnotationKind::Name {
11036            args: ref inner, ..
11037        } = args[0].kind
11038        else {
11039            panic!("expected inner Name");
11040        };
11041        assert_eq!(inner.len(), 1);
11042    }
11043
11044    #[test]
11045    fn parse_generic_array_postfix() {
11046        let (ast, diags) = parse_str("let xs: Foo<number>[] = null;");
11047        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11048        let ty = type_of_let(single_stmt(&ast));
11049        let crate::TypeAnnotationKind::Array(ref inner) = ty.kind else {
11050            panic!("expected Array");
11051        };
11052        assert!(
11053            matches!(inner.kind, crate::TypeAnnotationKind::Name { ref args, .. } if args.len() == 1)
11054        );
11055    }
11056
11057    #[test]
11058    fn parse_generic_empty_diagnoses() {
11059        let (_ast, diags) = parse_str("let x: Foo<> = null;");
11060        assert!(
11061            diags
11062                .iter()
11063                .any(|d| d.message.contains("empty generic argument list")),
11064            "expected empty-generic-args diagnostic, got: {diags:?}"
11065        );
11066    }
11067
11068    #[test]
11069    fn parse_generic_trailing_comma_ok() {
11070        let (_ast, diags) = parse_str("let x: Foo<number,> = null;");
11071        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11072    }
11073
11074    #[test]
11075    fn parse_generic_missing_close_diagnoses() {
11076        let (_ast, diags) = parse_str("let x: Foo<number = null;");
11077        assert!(
11078            diags
11079                .iter()
11080                .any(|d| d.message.contains("expected `,` or `>`")),
11081            "expected unterminated-generic diagnostic, got: {diags:?}"
11082        );
11083    }
11084
11085    #[test]
11086    fn parse_qualified_two_segments() {
11087        let (ast, diags) = parse_str("let x: Foo.Bar = null;");
11088        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11089        let ty = type_of_let(single_stmt(&ast));
11090        let crate::TypeAnnotationKind::Qualified { ref path, ref args } = ty.kind else {
11091            panic!("expected Qualified, got {:?}", ty.kind);
11092        };
11093        assert_eq!(path.len(), 2);
11094        // `Foo` at 7..10, `Bar` at 11..14.
11095        assert_eq!(path[0].span, crate::Span::new(F, 7, 10).unwrap());
11096        assert_eq!(path[1].span, crate::Span::new(F, 11, 14).unwrap());
11097        assert!(args.is_empty());
11098        assert_eq!(ty.span, crate::Span::new(F, 7, 14).unwrap());
11099    }
11100
11101    #[test]
11102    fn parse_qualified_three_segments() {
11103        let (ast, diags) = parse_str("let x: Foo.Bar.Baz = null;");
11104        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11105        let ty = type_of_let(single_stmt(&ast));
11106        let crate::TypeAnnotationKind::Qualified { ref path, .. } = ty.kind else {
11107            panic!("expected Qualified, got {:?}", ty.kind);
11108        };
11109        assert_eq!(path.len(), 3);
11110    }
11111
11112    #[test]
11113    fn parse_qualified_with_generic_args() {
11114        let (ast, diags) = parse_str("let x: Foo.Box<number> = null;");
11115        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11116        let ty = type_of_let(single_stmt(&ast));
11117        let crate::TypeAnnotationKind::Qualified { ref path, ref args } = ty.kind else {
11118            panic!("expected Qualified, got {:?}", ty.kind);
11119        };
11120        assert_eq!(path.len(), 2);
11121        assert_eq!(args.len(), 1);
11122        assert!(matches!(
11123            args[0].kind,
11124            crate::TypeAnnotationKind::Name { .. }
11125        ));
11126    }
11127
11128    #[test]
11129    fn parse_qualified_with_array_postfix() {
11130        let (ast, diags) = parse_str("let xs: Foo.Bar[] = null;");
11131        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11132        let ty = type_of_let(single_stmt(&ast));
11133        let crate::TypeAnnotationKind::Array(ref inner) = ty.kind else {
11134            panic!("expected Array, got {:?}", ty.kind);
11135        };
11136        assert!(matches!(
11137            inner.kind,
11138            crate::TypeAnnotationKind::Qualified { .. }
11139        ));
11140    }
11141
11142    #[test]
11143    fn parse_qualified_trailing_dot_diagnoses() {
11144        let (_ast, diags) = parse_str("let x: Foo. = null;");
11145        assert!(
11146            diags.iter().any(|d| d
11147                .message
11148                .contains("expected identifier after `.` in type name")),
11149            "expected dotted-name diagnostic, got: {diags:?}"
11150        );
11151    }
11152
11153    #[test]
11154    fn parse_qualified_void_root_rejected_dot() {
11155        let (ast, diags) = parse_str("function f(): void.Bar { }");
11156        assert!(!diags.is_empty(), "expected a diagnostic for `void.Bar`");
11157        let _ = ast;
11158    }
11159
11160    fn union_members(ty: &crate::TypeAnnotation) -> &[crate::TypeAnnotation] {
11161        match &ty.kind {
11162            crate::TypeAnnotationKind::Union(members) => members,
11163            other => panic!("expected Union, got {other:?}"),
11164        }
11165    }
11166
11167    #[test]
11168    fn parse_union_two_members() {
11169        let (ast, diags) = parse_str("let x: number | string = null;");
11170        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11171        let ty = type_of_let(single_stmt(&ast));
11172        let members = union_members(ty);
11173        assert_eq!(members.len(), 2);
11174        assert!(matches!(
11175            members[0].kind,
11176            crate::TypeAnnotationKind::Name { .. }
11177        ));
11178        assert_eq!(members[0].span, crate::Span::new(F, 7, 13).unwrap()); // `number`
11179        assert!(matches!(
11180            members[1].kind,
11181            crate::TypeAnnotationKind::Name { .. }
11182        ));
11183        assert_eq!(members[1].span, crate::Span::new(F, 16, 22).unwrap()); // `string`
11184        assert_eq!(ty.span, crate::Span::new(F, 7, 22).unwrap());
11185    }
11186
11187    #[test]
11188    fn parse_union_three_members() {
11189        let (ast, diags) = parse_str("let x: A | B | C = null;");
11190        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11191        let ty = type_of_let(single_stmt(&ast));
11192        let members = union_members(ty);
11193        assert_eq!(members.len(), 3);
11194        for m in members {
11195            assert!(matches!(m.kind, crate::TypeAnnotationKind::Name { .. }));
11196        }
11197    }
11198
11199    #[test]
11200    fn parse_union_lower_than_array() {
11201        let (ast, diags) = parse_str("let x: number[] | string = null;");
11202        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11203        let ty = type_of_let(single_stmt(&ast));
11204        let members = union_members(ty);
11205        assert_eq!(members.len(), 2);
11206        assert!(
11207            matches!(members[0].kind, crate::TypeAnnotationKind::Array(_)),
11208            "expected first member to be Array, got {:?}",
11209            members[0].kind
11210        );
11211        assert!(matches!(
11212            members[1].kind,
11213            crate::TypeAnnotationKind::Name { .. }
11214        ));
11215    }
11216
11217    #[test]
11218    fn parse_union_with_null_member() {
11219        let (ast, diags) = parse_str("let x: number | null = null;");
11220        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11221        let ty = type_of_let(single_stmt(&ast));
11222        let members = union_members(ty);
11223        assert_eq!(members.len(), 2);
11224        assert_eq!(members[0].span, crate::Span::new(F, 7, 13).unwrap()); // `number`
11225        assert!(matches!(
11226            members[1].kind,
11227            crate::TypeAnnotationKind::Name { .. }
11228        ));
11229        assert_eq!(members[1].span, crate::Span::new(F, 16, 20).unwrap()); // `null`
11230    }
11231
11232    #[test]
11233    fn parse_union_in_function_return_type() {
11234        let (ast, diags) = parse_str("function f(): number | string { }");
11235        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11236        let stmt = single_stmt(&ast);
11237        match stmt.kind {
11238            crate::StmtKind::Function {
11239                ref return_type, ..
11240            } => {
11241                let return_type = return_type.as_ref().expect("plain return type");
11242                let members = union_members(return_type);
11243                assert_eq!(members.len(), 2);
11244            }
11245            _ => panic!("expected Function"),
11246        }
11247    }
11248
11249    #[test]
11250    fn parse_union_in_function_param_type() {
11251        let (ast, diags) = parse_str("function f(x: number | string): void { }");
11252        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11253        let stmt = single_stmt(&ast);
11254        match stmt.kind {
11255            crate::StmtKind::Function { ref params, .. } => {
11256                let p_ty = params[0].ty.as_ref().expect("param type");
11257                let members = union_members(p_ty);
11258                assert_eq!(members.len(), 2);
11259            }
11260            _ => panic!("expected Function"),
11261        }
11262    }
11263
11264    #[test]
11265    fn parse_union_in_object_field_type() {
11266        let (ast, diags) = parse_str("let p: { x: number | string } = null;");
11267        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11268        let ty = type_of_let(single_stmt(&ast));
11269        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
11270            panic!("expected Object, got {:?}", ty.kind);
11271        };
11272        assert_eq!(fields.len(), 1);
11273        let members = union_members(&fields[0].ty);
11274        assert_eq!(members.len(), 2);
11275    }
11276
11277    #[test]
11278    fn parse_union_leading_pipe_is_forgiven() {
11279        let (ast, diags) = parse_str("let x: | number | string = null;");
11280        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11281        let ty = type_of_let(single_stmt(&ast));
11282        let members = union_members(ty);
11283        assert_eq!(members.len(), 2);
11284    }
11285
11286    #[test]
11287    fn parse_union_leading_pipe_single_member_unwraps() {
11288        let (ast, diags) = parse_str("let x: | number = null;");
11289        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11290        let ty = type_of_let(single_stmt(&ast));
11291        assert!(matches!(ty.kind, crate::TypeAnnotationKind::Name { .. }));
11292    }
11293
11294    #[test]
11295    fn parse_union_trailing_pipe_diagnoses() {
11296        let (_, diags) = parse_str("let x: number | = null;");
11297        assert!(!diags.is_empty());
11298        assert_eq!(diags[0].message, "expected type");
11299    }
11300
11301    #[test]
11302    fn snapshot_union_type_mix() {
11303        let (ast, diags) = parse_str("let xs: number[] | string | null = null;");
11304        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11305        insta::assert_debug_snapshot!(ast);
11306    }
11307
11308    #[test]
11309    fn parse_object_type_duplicate_field_diagnoses() {
11310        let (_, diags) = parse_str("let p: { readonly x: number; x: string } = null;");
11311        assert!(!diags.is_empty());
11312        assert!(
11313            diags
11314                .iter()
11315                .any(|d| d.message == "duplicate field `x` in object type"),
11316            "expected duplicate-field diagnostic, got: {diags:?}"
11317        );
11318    }
11319
11320    #[test]
11321    fn snapshot_object_and_array_literals() {
11322        let (ast, diags) = parse_str(r#"let mix = [{ a: 1, b: "hi" }, {}];"#);
11323        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11324        insta::assert_debug_snapshot!(ast);
11325    }
11326
11327    #[test]
11328    fn snapshot_function_with_while() {
11329        let source = "function loop(n: number): void {\n  while (n > 0) {\n    log(n);\n  }\n}";
11330        let (ast, diags) = parse_str(source);
11331        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11332        insta::assert_debug_snapshot!(ast);
11333    }
11334
11335    fn parse_arrow_const(
11336        source: &str,
11337    ) -> (
11338        Ast,
11339        Vec<crate::ParamDecl>,
11340        Option<crate::TypeAnnotation>,
11341        crate::ArrowBody,
11342    ) {
11343        let (ast, diags) = parse_str(source);
11344        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11345        assert_eq!(ast.top_level.len(), 1);
11346        let stmt = ast.try_stmt(ast.top_level[0]).unwrap();
11347        let value = match &stmt.kind {
11348            StmtKind::Const { value, .. } => *value,
11349            _ => panic!("expected const declaration"),
11350        };
11351        let value = match ast.try_expr(value).unwrap().kind {
11352            ExprKind::FunctionExpression { function, .. } => function,
11353            _ => value,
11354        };
11355        let expr = ast.try_expr(value).unwrap().clone();
11356        let (params, return_type, body) = match expr.kind {
11357            ExprKind::Arrow {
11358                params,
11359                return_type,
11360                body,
11361                ..
11362            } => (params, return_type, body),
11363            other => panic!("expected Arrow, got {other:?}"),
11364        };
11365        (ast, params, return_type, body)
11366    }
11367
11368    #[test]
11369    fn parses_anonymous_function_expression_as_arrow() {
11370        let (ast, params, return_type, body) =
11371            parse_arrow_const("const f = function (x: number): number { return x + 1; };");
11372        assert_eq!(params.len(), 1);
11373        assert_eq!(params[0].name.name, "x");
11374        assert!(return_type.is_some());
11375        assert!(matches!(body, crate::ArrowBody::Block(_)));
11376        let _ = ast;
11377    }
11378
11379    #[test]
11380    fn parses_function_expression_with_no_params() {
11381        let (_ast, params, _return_type, body) =
11382            parse_arrow_const("const f = function (): void {};");
11383        assert!(params.is_empty());
11384        assert!(matches!(body, crate::ArrowBody::Block(_)));
11385    }
11386
11387    /// The named wrapper preserves its body signature.
11388    #[test]
11389    fn parses_named_function_expression_signature() {
11390        let (_ast, params, _return_type, _body) =
11391            parse_arrow_const("const f = function named(x: number): number { return x + 1; };");
11392        assert_eq!(params.len(), 1);
11393    }
11394
11395    /// Self references are resolved in the function expression scope.
11396    #[test]
11397    fn accepts_a_named_function_expression_that_calls_itself() {
11398        let (_ast, diags) =
11399            parse_str("const f = function bar(x: number): number { return bar(x); };");
11400        assert!(diags.is_empty(), "{diags:?}");
11401    }
11402
11403    /// A member name after `.` is never a reference to the function itself.
11404    #[test]
11405    fn accepts_a_named_function_expression_using_the_name_as_a_property() {
11406        let (_ast, diags) = parse_str("const f = function bar(o: O): number { return o.bar; };");
11407        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11408    }
11409
11410    /// Nor is a key before `:`.
11411    #[test]
11412    fn accepts_a_named_function_expression_using_the_name_as_an_object_key() {
11413        let (_ast, diags) = parse_str(
11414            "const f = function bar(x: number): number { const o = { bar: 1 }; return o.bar + x; };",
11415        );
11416        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11417    }
11418
11419    /// A string that happens to spell the name is not a reference.
11420    #[test]
11421    fn accepts_a_named_function_expression_with_its_name_in_a_string() {
11422        let (_ast, diags) =
11423            parse_str("const f = function bar(x: number): string { return \"bar\"; };");
11424        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11425    }
11426
11427    /// A self reference in a template substitution remains a normal expression.
11428    #[test]
11429    fn accepts_a_self_call_inside_a_template_substitution() {
11430        let (_ast, diags) =
11431            parse_str("const f = function bar(x: number): string { return `${bar(0)}`; };");
11432        assert!(diags.is_empty(), "{diags:?}");
11433    }
11434
11435    #[test]
11436    fn rejects_a_generic_function_expression() {
11437        let (_ast, diags) = parse_str("const f = function <T>(x: T): T { return x; };");
11438        assert!(
11439            diags
11440                .iter()
11441                .any(|d| d.message.contains("generic function expressions")),
11442            "generic function expression should be rejected: {diags:?}"
11443        );
11444    }
11445
11446    #[test]
11447    fn accepts_this_inside_a_function_expression_in_a_class_method() {
11448        let (_ast, diags) = parse_str(
11449            "class C { x: number = 1; m(): number { \
11450             const f = function (this: { x: number }): number { return this.x; }; return f(); } }",
11451        );
11452        assert!(diags.is_empty(), "{diags:?}");
11453    }
11454
11455    #[test]
11456    fn retains_this_boundary_inside_method_shorthand() {
11457        let (ast, diags) = parse_str(
11458            "class C { x: number = 1; m(): number { \
11459             const o = { x: 2, g(): number { return this.x; } }; return o.g(); } }",
11460        );
11461        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11462        assert!((0..ast.exprs_len()).any(|index| matches!(
11463            ast.try_expr(crate::ExprId(index as u32)).unwrap().kind,
11464            ExprKind::ThisOutsideReceiver
11465        )));
11466    }
11467
11468    #[test]
11469    fn this_still_parses_in_an_ordinary_class_method() {
11470        let (_ast, diags) = parse_str("class C { x: number = 1; m(): number { return this.x; } }");
11471        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11472    }
11473
11474    /// `function` is a valid property name; the expression form must not shadow that.
11475    #[test]
11476    fn function_keyword_still_parses_as_an_object_key() {
11477        let (_ast, diags) = parse_str("let o = { function: 1 };");
11478        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11479    }
11480
11481    fn object_literal_member_values(source: &str) -> (Ast, Vec<crate::ExprId>) {
11482        let (ast, diags) = parse_str(source);
11483        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11484        let stmt = ast.try_stmt(ast.top_level[0]).unwrap();
11485        let value = match &stmt.kind {
11486            StmtKind::Const { value, .. } => *value,
11487            other => panic!("expected a const declaration, got {other:?}"),
11488        };
11489        let members = match &ast.try_expr(value).unwrap().kind {
11490            ExprKind::ObjectLiteral { members } => members
11491                .iter()
11492                .map(crate::ObjectLiteralMember::value)
11493                .collect::<Vec<_>>(),
11494            other => panic!("expected ObjectLiteral, got {other:?}"),
11495        };
11496        (ast, members)
11497    }
11498
11499    #[test]
11500    fn parses_object_literal_method_shorthand_as_arrow() {
11501        let (ast, members) =
11502            object_literal_member_values("const o = { m(x: number): number { return x; } };");
11503        assert_eq!(members.len(), 1);
11504        match &ast.try_expr(members[0]).unwrap().kind {
11505            ExprKind::Arrow { params, body, .. } => {
11506                assert_eq!(params.len(), 1);
11507                assert_eq!(params[0].name.name, "x");
11508                assert!(matches!(body, crate::ArrowBody::Block(_)));
11509            }
11510            other => panic!("expected Arrow, got {other:?}"),
11511        }
11512    }
11513
11514    /// A keyword is a valid method name, just as it is a valid property name.
11515    #[test]
11516    fn parses_method_shorthand_with_a_keyword_name() {
11517        let (ast, members) =
11518            object_literal_member_values("const o = { if(x: number): number { return x; } };");
11519        assert!(matches!(
11520            ast.try_expr(members[0]).unwrap().kind,
11521            ExprKind::Arrow { .. }
11522        ));
11523    }
11524
11525    #[test]
11526    fn parses_method_shorthand_alongside_plain_and_shorthand_properties() {
11527        let (ast, members) =
11528            object_literal_member_values("const o = { a: 1, m(): void {}, b: 2 };");
11529        assert_eq!(members.len(), 3);
11530        assert!(matches!(
11531            ast.try_expr(members[1]).unwrap().kind,
11532            ExprKind::Arrow { .. }
11533        ));
11534    }
11535
11536    #[test]
11537    fn parses_bare_ident_arrow_expression_body() {
11538        let (_ast, params, return_type, body) = parse_arrow_const("const f = x => x + 1;");
11539        assert_eq!(params.len(), 1);
11540        assert_eq!(params[0].name.name, "x");
11541        assert!(params[0].ty.is_none());
11542        assert!(return_type.is_none());
11543        assert!(matches!(body, crate::ArrowBody::Expr(_)));
11544    }
11545
11546    #[test]
11547    fn parses_zero_param_arrow() {
11548        let (_ast, params, return_type, body) = parse_arrow_const("const f = () => 0;");
11549        assert!(params.is_empty());
11550        assert!(return_type.is_none());
11551        assert!(matches!(body, crate::ArrowBody::Expr(_)));
11552    }
11553
11554    #[test]
11555    fn parses_paren_unannotated_arrow() {
11556        let (_ast, params, _return_type, _body) = parse_arrow_const("const f = (x) => x;");
11557        assert_eq!(params.len(), 1);
11558        assert_eq!(params[0].name.name, "x");
11559        assert!(params[0].ty.is_none());
11560    }
11561
11562    #[test]
11563    fn parses_typed_param_arrow() {
11564        let (_ast, params, return_type, body) =
11565            parse_arrow_const("const f = (x: number) => x * 2;");
11566        assert_eq!(params.len(), 1);
11567        assert!(params[0].ty.is_some());
11568        assert!(return_type.is_none());
11569        assert!(matches!(body, crate::ArrowBody::Expr(_)));
11570    }
11571
11572    #[test]
11573    fn parses_typed_param_and_return_arrow() {
11574        let (_ast, params, return_type, body) =
11575            parse_arrow_const("const f = (x: number): number => x * 2;");
11576        assert_eq!(params.len(), 1);
11577        assert!(params[0].ty.is_some());
11578        assert!(return_type.is_some());
11579        assert!(matches!(body, crate::ArrowBody::Expr(_)));
11580    }
11581
11582    #[test]
11583    fn parses_multi_param_unannotated_arrow() {
11584        let (_ast, params, _return_type, _body) = parse_arrow_const("const f = (x, y) => x + y;");
11585        assert_eq!(params.len(), 2);
11586        assert_eq!(params[0].name.name, "x");
11587        assert_eq!(params[1].name.name, "y");
11588        assert!(params[0].ty.is_none());
11589        assert!(params[1].ty.is_none());
11590    }
11591
11592    #[test]
11593    fn parses_multi_param_typed_arrow() {
11594        let (_ast, params, _return_type, _body) =
11595            parse_arrow_const("const f = (x: number, y: number) => x + y;");
11596        assert_eq!(params.len(), 2);
11597        assert!(params[0].ty.is_some());
11598        assert!(params[1].ty.is_some());
11599    }
11600
11601    #[test]
11602    fn parses_block_body_arrow() {
11603        let (_ast, params, _return_type, body) =
11604            parse_arrow_const("const f = (x: number) => { return x; };");
11605        assert_eq!(params.len(), 1);
11606        assert!(matches!(body, crate::ArrowBody::Block(_)));
11607    }
11608
11609    #[test]
11610    fn rejects_typed_bare_ident_arrow() {
11611        let (_ast, diags) = parse_str("const f = x: number => x;");
11612        assert!(
11613            !diags.is_empty(),
11614            "expected diagnostics for `x: number => …`",
11615        );
11616    }
11617
11618    #[test]
11619    fn arrow_inside_call_argument_parses() {
11620        let (ast, diags) = parse_str("f((x: number) => x + 1, 2);");
11621        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11622        let stmt = ast.try_stmt(ast.top_level[0]).unwrap();
11623        let call = match &stmt.kind {
11624            StmtKind::Expr(eid) => ast.try_expr(*eid).unwrap(),
11625            _ => panic!("expected expression statement"),
11626        };
11627        let args = match &call.kind {
11628            ExprKind::Call { args, .. } => args.clone(),
11629            other => panic!("expected Call, got {other:?}"),
11630        };
11631        assert_eq!(args.len(), 2);
11632        assert!(matches!(
11633            ast.try_expr(args[0]).unwrap().kind,
11634            ExprKind::Arrow { .. }
11635        ));
11636    }
11637
11638    #[test]
11639    fn function_decl_param_still_requires_annotation() {
11640        let (_ast, diags) = parse_str("function f(x): number { return x; }");
11641        assert!(
11642            diags.iter().any(|d| d.message.contains("type annotation")),
11643            "expected `parameter requires a type annotation` diagnostic, got {diags:?}",
11644        );
11645    }
11646
11647    #[test]
11648    fn paren_expression_still_parses_when_no_arrow() {
11649        let (ast, diags) = parse_str("const a = (1 + 2);");
11650        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11651        let stmt = ast.try_stmt(ast.top_level[0]).unwrap();
11652        let value = match &stmt.kind {
11653            StmtKind::Const { value, .. } => *value,
11654            _ => panic!("expected const declaration"),
11655        };
11656        assert!(matches!(
11657            ast.try_expr(value).unwrap().kind,
11658            ExprKind::Paren(_)
11659        ));
11660    }
11661
11662    #[test]
11663    fn parse_named_import_single() {
11664        let (ast, diags) = parse_str("import { v4 } from \"submilli:uuid\";");
11665        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11666        assert_eq!(ast.top_level.len(), 1);
11667        let stmt = ast.try_stmt(ast.top_level[0]).unwrap();
11668        let (module, kind) = match &stmt.kind {
11669            StmtKind::Import { module, kind, .. } => (module, kind),
11670            other => panic!("expected Import, got {other:?}"),
11671        };
11672        assert_eq!(module, "submilli:uuid");
11673        let specs = match kind {
11674            ImportKind::Named(specs) => specs,
11675            other => panic!("expected Named, got {other:?}"),
11676        };
11677        assert_eq!(specs.len(), 1);
11678        assert_eq!(specs[0].imported_name.name, "v4");
11679        assert_eq!(specs[0].local_name.name, "v4");
11680    }
11681
11682    #[test]
11683    fn parse_named_import_multi_and_alias() {
11684        let (ast, diags) =
11685            parse_str("import { v4, v7 as makeId, validate } from \"submilli:uuid\";");
11686        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11687        let specs = match &ast.try_stmt(ast.top_level[0]).unwrap().kind {
11688            StmtKind::Import {
11689                kind: ImportKind::Named(s),
11690                ..
11691            } => s.clone(),
11692            other => panic!("expected named import, got {other:?}"),
11693        };
11694        assert_eq!(specs.len(), 3);
11695        assert_eq!(specs[0].imported_name.name, "v4");
11696        assert_eq!(specs[0].local_name.name, "v4");
11697        assert_eq!(specs[1].imported_name.name, "v7");
11698        assert_eq!(specs[1].local_name.name, "makeId");
11699        assert_eq!(specs[2].imported_name.name, "validate");
11700        assert_eq!(specs[2].local_name.name, "validate");
11701    }
11702
11703    #[test]
11704    fn parse_namespace_import() {
11705        let (ast, diags) = parse_str("import uuid from \"submilli:uuid\";");
11706        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11707        let kind = match &ast.try_stmt(ast.top_level[0]).unwrap().kind {
11708            StmtKind::Import { kind, .. } => kind.clone(),
11709            other => panic!("expected import, got {other:?}"),
11710        };
11711        let local = match kind {
11712            ImportKind::Namespace { local_name } => local_name,
11713            other => panic!("expected Namespace, got {other:?}"),
11714        };
11715        assert_eq!(local.name, "uuid");
11716    }
11717
11718    #[test]
11719    fn parse_wildcard_namespace_import_synonym() {
11720        let (ast, diags) = parse_str("import * as uuid from \"submilli:uuid\";");
11721        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11722        let kind = match &ast.try_stmt(ast.top_level[0]).unwrap().kind {
11723            StmtKind::Import { kind, .. } => kind.clone(),
11724            other => panic!("expected import, got {other:?}"),
11725        };
11726        let local = match kind {
11727            ImportKind::Namespace { local_name } => local_name,
11728            other => panic!("expected Namespace, got {other:?}"),
11729        };
11730        assert_eq!(local.name, "uuid");
11731    }
11732
11733    #[test]
11734    fn parse_wildcard_namespace_import_without_as_rejected() {
11735        let (_ast, diags) = parse_str("import * from \"submilli:uuid\";");
11736        assert!(
11737            diags
11738                .iter()
11739                .any(|d| d.message.contains("expected `as <name>`")),
11740            "expected missing-as diagnostic, got: {diags:?}",
11741        );
11742    }
11743
11744    #[test]
11745    fn parse_combined_default_and_named_rejected() {
11746        let (_ast, diags) = parse_str("import uuid, { v4 } from \"submilli:uuid\";");
11747        assert!(
11748            diags
11749                .iter()
11750                .any(|d| d.message.contains("combining default and named")),
11751            "expected combined-import diagnostic, got: {diags:?}",
11752        );
11753    }
11754
11755    #[test]
11756    fn parse_side_effect_import_rejected() {
11757        let (_ast, diags) = parse_str("import \"submilli:uuid\";");
11758        assert!(
11759            diags
11760                .iter()
11761                .any(|d| d.message.contains("side-effect-only imports")),
11762            "expected side-effect-import diagnostic, got: {diags:?}",
11763        );
11764    }
11765
11766    #[test]
11767    fn parse_empty_specifier_list_rejected() {
11768        let (_ast, diags) = parse_str("import {} from \"submilli:uuid\";");
11769        assert!(
11770            diags
11771                .iter()
11772                .any(|d| d.message.contains("empty import specifier list")),
11773            "expected empty-specifier diagnostic, got: {diags:?}",
11774        );
11775    }
11776
11777    #[test]
11778    fn parse_import_inside_function_body_rejected() {
11779        let (_ast, diags) =
11780            parse_str("function main(): void { import { v4 } from \"submilli:uuid\"; }");
11781        assert!(
11782            diags
11783                .iter()
11784                .any(|d| d.message.contains("must appear at the top of the file")),
11785            "expected top-level diagnostic, got: {diags:?}",
11786        );
11787    }
11788
11789    #[test]
11790    fn parse_missing_from_rejected() {
11791        let (_ast, diags) = parse_str("import { v4 } \"submilli:uuid\";");
11792        assert!(
11793            diags.iter().any(|d| d.message.contains("expected `from`")),
11794            "expected missing-from diagnostic, got: {diags:?}",
11795        );
11796    }
11797
11798    #[test]
11799    fn parse_missing_module_specifier_rejected() {
11800        let (_ast, diags) = parse_str("import { v4 } from;");
11801        assert!(
11802            diags
11803                .iter()
11804                .any(|d| d.message.contains("expected module specifier")),
11805            "expected missing-module diagnostic, got: {diags:?}",
11806        );
11807    }
11808
11809    #[test]
11810    fn parse_export_function_marks_exported() {
11811        // `export function main() {}` parses to a single function declaration in
11812        // `top_level`, plus an `exported_decls` entry pointing at it.
11813        let (ast, diags) = parse_str("export function main(): void {}");
11814        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11815        let stmt = single_stmt(&ast);
11816        assert!(
11817            matches!(stmt.kind, crate::StmtKind::Function { .. }),
11818            "expected Function decl, got {:?}",
11819            stmt.kind,
11820        );
11821        assert_eq!(ast.exported_decls.len(), 1, "one exported decl");
11822        assert_eq!(ast.exported_decls[0].stmt, ast.top_level[0]);
11823    }
11824
11825    #[test]
11826    fn parse_export_const_type_interface_enum_are_marked() {
11827        for src in [
11828            "export const X: number = 1;",
11829            "export type Id = string;",
11830            "export interface P { x: number; }",
11831            "export enum E { A, B }",
11832        ] {
11833            let (ast, diags) = parse_str(src);
11834            assert!(diags.is_empty(), "unexpected diags for {src:?}: {diags:?}");
11835            assert_eq!(ast.top_level.len(), 1, "one decl for {src:?}");
11836            assert_eq!(ast.exported_decls.len(), 1, "one exported decl for {src:?}");
11837        }
11838    }
11839
11840    #[test]
11841    fn parse_export_default_is_rejected_with_focused_message() {
11842        let (_ast, diags) = parse_str("export default function main(): void {}");
11843        assert!(
11844            diags
11845                .iter()
11846                .any(|d| d.message.contains("`export default` is not supported")),
11847            "expected focused export-default diagnostic, got: {diags:?}",
11848        );
11849        assert!(
11850            !diags.iter().any(|d| d.message.contains("expected `;`")),
11851            "the spurious missing-semicolon error must be gone: {diags:?}",
11852        );
11853    }
11854
11855    #[test]
11856    fn parse_export_list_parses_as_export_from() {
11857        // Form 2 (`export { x };`, no `from`) now parses cleanly — the
11858        // single-file gating happens in the typechecker, not the parser.
11859        let (ast, diags) = parse_str("function main(): void {}\nexport { main };");
11860        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11861        assert_eq!(ast.top_level.len(), 2, "function + export-from");
11862        let export = ast.try_stmt(ast.top_level[1]).unwrap();
11863        let crate::StmtKind::ExportFrom { specs, source, .. } = &export.kind else {
11864            panic!("expected ExportFrom, got {:?}", export.kind);
11865        };
11866        assert!(source.is_none(), "bare `export {{ x }}` has no source");
11867        assert_eq!(specs.len(), 1);
11868        assert_eq!(specs[0].imported_name.name, "main");
11869        assert_eq!(specs[0].local_name.name, "main");
11870    }
11871
11872    #[test]
11873    fn parse_export_from_with_source_and_alias_parses() {
11874        let (ast, diags) = parse_str("export { foo, bar as baz } from \"./util\";");
11875        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11876        let export = ast.try_stmt(ast.top_level[0]).unwrap();
11877        let crate::StmtKind::ExportFrom { specs, source, .. } = &export.kind else {
11878            panic!("expected ExportFrom, got {:?}", export.kind);
11879        };
11880        let Some((module, _)) = source else {
11881            panic!("expected a `from` source");
11882        };
11883        assert_eq!(module, "./util");
11884        assert_eq!(specs.len(), 2);
11885        assert_eq!(specs[1].imported_name.name, "bar");
11886        assert_eq!(specs[1].local_name.name, "baz");
11887    }
11888
11889    #[test]
11890    fn parse_export_inside_function_body_rejected() {
11891        let (_ast, diags) = parse_str("function main(): void { export const X: number = 1; }");
11892        assert!(
11893            diags
11894                .iter()
11895                .any(|d| d.message.contains("must appear at the top of the file")),
11896            "expected top-level diagnostic, got: {diags:?}",
11897        );
11898    }
11899
11900    #[test]
11901    fn parse_throw_with_expression() {
11902        let (ast, diags) = parse_str(r#"throw new Error("oops");"#);
11903        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11904        let stmt = single_stmt(&ast);
11905        let crate::StmtKind::Throw { value } = stmt.kind else {
11906            panic!("expected Throw, got {:?}", stmt.kind);
11907        };
11908        assert!(matches!(
11909            ast.try_expr(value).unwrap().kind,
11910            crate::ExprKind::New { .. }
11911        ));
11912    }
11913
11914    #[test]
11915    fn parse_new_with_dotted_callee() {
11916        let (ast, diags) = parse_str("new Temporal.Duration({});");
11917        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11918        let crate::StmtKind::Expr(expr_id) = single_stmt(&ast).kind else {
11919            panic!("expected expression statement");
11920        };
11921        let crate::ExprKind::New { callee, .. } = &ast.try_expr(expr_id).unwrap().kind else {
11922            panic!("expected New");
11923        };
11924        let crate::ExprKind::FieldAccess { name, .. } = &ast.try_expr(*callee).unwrap().kind else {
11925            panic!("expected FieldAccess callee");
11926        };
11927        assert_eq!(name.name, "Duration");
11928    }
11929
11930    #[test]
11931    fn parse_new_with_three_level_dotted_callee() {
11932        let (ast, diags) = parse_str("new Foo.Bar.Baz();");
11933        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11934        let crate::StmtKind::Expr(expr_id) = single_stmt(&ast).kind else {
11935            panic!("expected expression statement");
11936        };
11937        let crate::ExprKind::New { callee, .. } = &ast.try_expr(expr_id).unwrap().kind else {
11938            panic!("expected New");
11939        };
11940        let crate::ExprKind::FieldAccess { receiver, name } = &ast.try_expr(*callee).unwrap().kind
11941        else {
11942            panic!("expected outer FieldAccess");
11943        };
11944        assert_eq!(name.name, "Baz");
11945        let crate::ExprKind::FieldAccess { name: mid_name, .. } =
11946            &ast.try_expr(*receiver).unwrap().kind
11947        else {
11948            panic!("expected inner FieldAccess");
11949        };
11950        assert_eq!(mid_name.name, "Bar");
11951    }
11952
11953    #[test]
11954    fn parse_new_with_trailing_dot_diagnoses() {
11955        let (_ast, diags) = parse_str("new Foo.;");
11956        assert!(
11957            diags.iter().any(|d| d
11958                .message
11959                .contains("expected identifier after `.` in constructor name")),
11960            "expected trailing-dot diagnostic, got: {diags:?}",
11961        );
11962    }
11963
11964    #[test]
11965    fn throw_without_expression_diagnoses() {
11966        let (_ast, diags) = parse_str("throw;");
11967        assert!(
11968            diags
11969                .iter()
11970                .any(|d| d.message == "expected expression after `throw`"),
11971            "expected bare-throw diagnostic, got: {diags:?}",
11972        );
11973    }
11974
11975    #[test]
11976    fn parse_try_catch() {
11977        let (ast, diags) = parse_str("try { } catch (e: Error) { }");
11978        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11979        let stmt = single_stmt(&ast);
11980        let crate::StmtKind::Try {
11981            body,
11982            ref catches,
11983            ref finally,
11984        } = stmt.kind
11985        else {
11986            panic!("expected Try, got {:?}", stmt.kind);
11987        };
11988        assert!(matches!(
11989            ast.try_stmt(body).unwrap().kind,
11990            crate::StmtKind::Block(ref v) if v.is_empty()
11991        ));
11992        let [clause] = catches.as_slice() else {
11993            panic!("expected one catch clause, got {catches:?}");
11994        };
11995        assert_eq!(clause.binding.name, "e");
11996        assert!(clause.ty.is_some());
11997        assert!(finally.is_none());
11998    }
11999
12000    #[test]
12001    fn parse_try_finally() {
12002        let (ast, diags) = parse_str("try { } finally { }");
12003        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12004        let stmt = single_stmt(&ast);
12005        let crate::StmtKind::Try {
12006            ref catches,
12007            ref finally,
12008            ..
12009        } = stmt.kind
12010        else {
12011            panic!("expected Try");
12012        };
12013        assert!(catches.is_empty());
12014        let finally_id = finally.expect("finally block");
12015        assert!(matches!(
12016            ast.try_stmt(finally_id).unwrap().kind,
12017            crate::StmtKind::Block(_)
12018        ));
12019    }
12020
12021    #[test]
12022    fn parse_try_catch_finally() {
12023        let (ast, diags) = parse_str("try { } catch (e: Error) { } finally { }");
12024        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12025        let stmt = single_stmt(&ast);
12026        let crate::StmtKind::Try {
12027            ref catches,
12028            ref finally,
12029            ..
12030        } = stmt.kind
12031        else {
12032            panic!("expected Try");
12033        };
12034        assert_eq!(catches.len(), 1);
12035        assert!(finally.is_some());
12036    }
12037
12038    #[test]
12039    fn bare_try_diagnoses() {
12040        let (_ast, diags) = parse_str("try { }");
12041        assert!(
12042            diags
12043                .iter()
12044                .any(|d| d.message == "try requires at least one of `catch` or `finally`"),
12045            "expected bare-try diagnostic, got: {diags:?}",
12046        );
12047    }
12048
12049    #[test]
12050    fn parse_untyped_catch() {
12051        let (ast, diags) = parse_str("try { } catch (e) { }");
12052        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12053        let stmt = single_stmt(&ast);
12054        let crate::StmtKind::Try {
12055            ref catches,
12056            ref finally,
12057            ..
12058        } = stmt.kind
12059        else {
12060            panic!("expected Try");
12061        };
12062        let [clause] = catches.as_slice() else {
12063            panic!("expected one catch clause, got {catches:?}");
12064        };
12065        assert_eq!(clause.binding.name, "e");
12066        assert!(clause.ty.is_none());
12067        assert!(finally.is_none());
12068    }
12069
12070    #[test]
12071    fn parse_multiple_catch_clauses() {
12072        let (ast, diags) = parse_str("try { } catch (e: HttpError) { } catch (f) { }");
12073        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12074        let stmt = single_stmt(&ast);
12075        let crate::StmtKind::Try {
12076            ref catches,
12077            ref finally,
12078            ..
12079        } = stmt.kind
12080        else {
12081            panic!("expected Try");
12082        };
12083        let [first, second] = catches.as_slice() else {
12084            panic!("expected two catch clauses, got {catches:?}");
12085        };
12086        assert_eq!(first.binding.name, "e");
12087        assert!(first.ty.is_some());
12088        assert_eq!(second.binding.name, "f");
12089        assert!(second.ty.is_none());
12090        assert!(finally.is_none());
12091    }
12092
12093    #[test]
12094    fn catch_after_finally_diagnoses() {
12095        let (_ast, diags) = parse_str("try { } finally { } catch (e: Error) { }");
12096        assert!(
12097            !diags.is_empty(),
12098            "expected diagnostic for catch-after-finally"
12099        );
12100    }
12101
12102    #[test]
12103    fn catch_missing_open_paren_diagnoses() {
12104        let (_ast, diags) = parse_str("try { } catch e: Error) { }");
12105        assert!(
12106            diags
12107                .iter()
12108                .any(|d| d.message == "expected `(` after `catch`"),
12109            "expected open-paren diagnostic, got: {diags:?}",
12110        );
12111    }
12112
12113    fn class_members(ast: &Ast) -> &[crate::ClassMember] {
12114        match &single_stmt(ast).kind {
12115            StmtKind::ClassDecl { members, .. } => members,
12116            other => panic!("expected ClassDecl, got {other:?}"),
12117        }
12118    }
12119
12120    #[test]
12121    fn parse_animal_class() {
12122        let src = r#"
12123class Animal {
12124  name: string;
12125  private sound: string;
12126  readonly species: string;
12127
12128  constructor(name: string, sound: string, species: string) {
12129    this.name = name;
12130    this.sound = sound;
12131    this.species = species;
12132  }
12133
12134  speak(): string {
12135    return this.name + " says " + this.sound;
12136  }
12137}
12138"#;
12139        let (ast, diags) = parse_str(src);
12140        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12141        let StmtKind::ClassDecl {
12142            name,
12143            generics,
12144            extends,
12145            implements,
12146            members,
12147            ..
12148        } = &single_stmt(&ast).kind
12149        else {
12150            panic!("expected ClassDecl");
12151        };
12152        assert_eq!(name.name, "Animal");
12153        assert!(generics.is_empty());
12154        assert!(extends.is_none());
12155        assert!(implements.is_empty());
12156        assert_eq!(members.len(), 5);
12157
12158        let crate::ClassMember::Field {
12159            name, modifiers, ..
12160        } = &members[0]
12161        else {
12162            panic!("member 0 should be a field");
12163        };
12164        assert_eq!(name.name, "name");
12165        assert_eq!(modifiers.visibility, crate::Visibility::Public);
12166        assert!(modifiers.readonly.is_none());
12167
12168        let crate::ClassMember::Field { modifiers, .. } = &members[1] else {
12169            panic!("member 1 should be a field");
12170        };
12171        assert_eq!(modifiers.visibility, crate::Visibility::Private);
12172
12173        let crate::ClassMember::Field { modifiers, .. } = &members[2] else {
12174            panic!("member 2 should be a field");
12175        };
12176        assert!(modifiers.readonly.is_some());
12177        assert_eq!(modifiers.visibility, crate::Visibility::Public);
12178
12179        assert!(matches!(
12180            &members[3],
12181            crate::ClassMember::Constructor { .. }
12182        ));
12183
12184        let crate::ClassMember::Method { name, .. } = &members[4] else {
12185            panic!("member 4 should be a method");
12186        };
12187        assert_eq!(name.name, "speak");
12188    }
12189
12190    #[test]
12191    fn parse_dog_class_extends_super_and_void_method() {
12192        let src = r#"
12193class Dog extends Animal {
12194  private tricks: string[];
12195
12196  constructor(name: string) {
12197    super(name, "woof", "canis familiaris");
12198    this.tricks = [];
12199  }
12200
12201  learn(trick: string): void {
12202    this.tricks.push(trick);
12203  }
12204}
12205"#;
12206        let (ast, diags) = parse_str(src);
12207        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12208        let StmtKind::ClassDecl {
12209            extends, members, ..
12210        } = &single_stmt(&ast).kind
12211        else {
12212            panic!("expected ClassDecl");
12213        };
12214        assert!(extends.is_some(), "Dog should extend Animal");
12215        assert_eq!(members.len(), 3);
12216        assert!(matches!(&members[0], crate::ClassMember::Field { .. }));
12217        assert!(matches!(
12218            &members[1],
12219            crate::ClassMember::Constructor { .. }
12220        ));
12221        assert!(matches!(&members[2], crate::ClassMember::Method { .. }));
12222    }
12223
12224    #[test]
12225    fn parse_class_implements_list() {
12226        let (ast, diags) = parse_str("class C implements I, J { run(): void {} }");
12227        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12228        let StmtKind::ClassDecl { implements, .. } = &single_stmt(&ast).kind else {
12229            panic!("expected ClassDecl");
12230        };
12231        assert_eq!(implements.len(), 2);
12232    }
12233
12234    #[test]
12235    fn parse_class_extends_and_implements() {
12236        let (ast, diags) = parse_str("class C extends B implements I { run(): void {} }");
12237        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12238        let StmtKind::ClassDecl {
12239            extends,
12240            implements,
12241            ..
12242        } = &single_stmt(&ast).kind
12243        else {
12244            panic!("expected ClassDecl");
12245        };
12246        assert!(extends.is_some());
12247        assert_eq!(implements.len(), 1);
12248    }
12249
12250    #[test]
12251    fn parse_class_generics_optional_field_and_initializer() {
12252        let (ast, diags) = parse_str("class Box<T> { value?: T; items: T[] = []; }");
12253        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12254        let StmtKind::ClassDecl {
12255            generics, members, ..
12256        } = &single_stmt(&ast).kind
12257        else {
12258            panic!("expected ClassDecl");
12259        };
12260        assert_eq!(generics.len(), 1);
12261        assert_eq!(generics[0].name, "T");
12262
12263        let crate::ClassMember::Field {
12264            optional,
12265            initializer,
12266            ..
12267        } = &members[0]
12268        else {
12269            panic!("member 0 should be a field");
12270        };
12271        assert!(*optional);
12272        assert!(initializer.is_none());
12273
12274        let crate::ClassMember::Field { initializer, .. } = &members[1] else {
12275            panic!("member 1 should be a field");
12276        };
12277        assert!(initializer.is_some());
12278    }
12279
12280    #[test]
12281    fn export_class_is_marked() {
12282        let (ast, diags) = parse_str("export class Foo {}");
12283        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12284        assert_eq!(ast.top_level.len(), 1);
12285        assert_eq!(ast.exported_decls.len(), 1);
12286        assert!(matches!(
12287            &single_stmt(&ast).kind,
12288            StmtKind::ClassDecl { .. }
12289        ));
12290    }
12291
12292    #[test]
12293    fn parameterless_index_signature_is_reported_from_its_member_start() {
12294        for (source, expected) in [
12295            ("interface P { readonly []: number }", "readonly []"),
12296            ("let p: { readonly []: number } = {};", "readonly []"),
12297            ("let q: { []: number } = {};", "[]"),
12298        ] {
12299            let (_ast, diags) = parse_str(source);
12300            let diag = diags
12301                .iter()
12302                .find(|d| d.message.contains("must declare exactly one parameter"))
12303                .unwrap_or_else(|| panic!("no diagnostic for {source}"));
12304            let reported = &source[diag.span.start as usize..diag.span.end as usize];
12305            assert_eq!(reported, expected, "{source}");
12306        }
12307    }
12308
12309    #[test]
12310    fn unterminated_object_type_expects_closing_brace() {
12311        let (_ast, diags) = parse_str("let p: { a: number\n");
12312        assert!(
12313            diags.iter().any(|d| d.message == "expected `}`"),
12314            "{diags:?}"
12315        );
12316    }
12317
12318    #[test]
12319    fn conflicting_visibility_modifiers_diagnose() {
12320        let (_ast, diags) = parse_str("class C { public private x: number; }");
12321        assert!(
12322            diags
12323                .iter()
12324                .any(|d| d.message.contains("at most one visibility modifier")),
12325            "got: {diags:?}",
12326        );
12327    }
12328
12329    #[test]
12330    fn duplicate_readonly_modifier_diagnoses() {
12331        let (_ast, diags) = parse_str("class C { readonly readonly x: number; }");
12332        assert!(
12333            diags
12334                .iter()
12335                .any(|d| d.message.contains("duplicate `readonly`")),
12336            "got: {diags:?}",
12337        );
12338    }
12339
12340    #[test]
12341    fn field_named_like_a_modifier_is_allowed() {
12342        let (ast, diags) = parse_str("class C { private: number; }");
12343        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12344        let crate::ClassMember::Field {
12345            name, modifiers, ..
12346        } = &class_members(&ast)[0]
12347        else {
12348            panic!("expected a field");
12349        };
12350        assert_eq!(name.name, "private");
12351        assert_eq!(modifiers.visibility, crate::Visibility::Public);
12352    }
12353
12354    #[test]
12355    fn method_named_get_is_allowed() {
12356        let (ast, diags) = parse_str("class C { get(): number { return 1; } }");
12357        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12358        let crate::ClassMember::Method { name, .. } = &class_members(&ast)[0] else {
12359            panic!("expected a method");
12360        };
12361        assert_eq!(name.name, "get");
12362    }
12363
12364    #[test]
12365    fn static_method_parses() {
12366        let (ast, diags) = parse_str("class C { static f(): number { return 1; } }");
12367        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12368        let crate::ClassMember::Method {
12369            name, modifiers, ..
12370        } = &class_members(&ast)[0]
12371        else {
12372            panic!("expected a method");
12373        };
12374        assert_eq!(name.name, "f");
12375        assert!(modifiers.static_span.is_some());
12376    }
12377
12378    #[test]
12379    fn static_readonly_field_parses() {
12380        let (ast, diags) = parse_str("class C { static readonly x: number = 1; }");
12381        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12382        let crate::ClassMember::Field {
12383            name,
12384            modifiers,
12385            initializer,
12386            ..
12387        } = &class_members(&ast)[0]
12388        else {
12389            panic!("expected a field");
12390        };
12391        assert_eq!(name.name, "x");
12392        assert!(modifiers.static_span.is_some());
12393        assert!(modifiers.readonly.is_some());
12394        assert!(initializer.is_some());
12395    }
12396
12397    #[test]
12398    fn private_static_method_parses() {
12399        let (ast, diags) = parse_str("class C { private static f(): void {} }");
12400        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12401        let crate::ClassMember::Method { modifiers, .. } = &class_members(&ast)[0] else {
12402            panic!("expected a method");
12403        };
12404        assert_eq!(modifiers.visibility, crate::Visibility::Private);
12405        assert!(modifiers.static_span.is_some());
12406    }
12407
12408    #[test]
12409    fn method_named_static_is_allowed() {
12410        let (ast, diags) = parse_str("class C { static(): number { return 1; } }");
12411        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12412        let crate::ClassMember::Method {
12413            name, modifiers, ..
12414        } = &class_members(&ast)[0]
12415        else {
12416            panic!("expected a method");
12417        };
12418        assert_eq!(name.name, "static");
12419        assert!(modifiers.static_span.is_none());
12420    }
12421
12422    #[test]
12423    fn visibility_after_static_diagnoses() {
12424        let (_ast, diags) = parse_str("class C { static private f(): void {} }");
12425        assert!(
12426            diags
12427                .iter()
12428                .any(|d| d.message.contains("`private` must come before `static`")),
12429            "got: {diags:?}",
12430        );
12431    }
12432
12433    #[test]
12434    fn duplicate_static_modifier_diagnoses() {
12435        let (_ast, diags) = parse_str("class C { static static f(): void {} }");
12436        assert!(
12437            diags
12438                .iter()
12439                .any(|d| d.message.contains("duplicate `static` modifier")),
12440            "got: {diags:?}",
12441        );
12442    }
12443
12444    #[test]
12445    fn static_accessor_is_rejected() {
12446        let (_ast, diags) = parse_str("class C { static get x(): number { return 1; } }");
12447        assert!(
12448            diags
12449                .iter()
12450                .any(|d| d.message.contains("static accessors are not supported")),
12451            "got: {diags:?}",
12452        );
12453    }
12454
12455    #[test]
12456    fn static_constructor_is_rejected() {
12457        let (_ast, diags) = parse_str("class C { static constructor() {} }");
12458        assert!(
12459            diags
12460                .iter()
12461                .any(|d| d.message.contains("a constructor cannot be `static`")),
12462            "got: {diags:?}",
12463        );
12464    }
12465
12466    #[test]
12467    fn optional_static_field_is_rejected() {
12468        let (_ast, diags) = parse_str("class C { static x?: number; }");
12469        assert!(
12470            diags
12471                .iter()
12472                .any(|d| d.message.contains("a static field cannot be optional")),
12473            "got: {diags:?}",
12474        );
12475    }
12476
12477    #[test]
12478    fn protected_member_is_rejected() {
12479        let (_ast, diags) = parse_str("class C { protected x: number; }");
12480        assert!(
12481            diags
12482                .iter()
12483                .any(|d| d.message.contains("`protected` is not supported")),
12484            "got: {diags:?}",
12485        );
12486    }
12487
12488    #[test]
12489    fn abstract_member_is_rejected() {
12490        let (_ast, diags) = parse_str("class C { abstract run(): void; }");
12491        assert!(
12492            diags
12493                .iter()
12494                .any(|d| d.message.contains("abstract classes are not supported")),
12495            "got: {diags:?}",
12496        );
12497    }
12498
12499    #[test]
12500    fn getter_parses_as_accessor() {
12501        let (ast, diags) = parse_str("class C { get value(): number { return 1; } }");
12502        assert!(diags.is_empty(), "got: {diags:?}");
12503        let crate::ClassMember::Accessor { kind, name, .. } = &class_members(&ast)[0] else {
12504            panic!("expected an accessor member");
12505        };
12506        assert_eq!(*kind, crate::AccessorKind::Get);
12507        assert_eq!(name.name, "value");
12508    }
12509
12510    #[test]
12511    fn getter_without_return_type_is_rejected() {
12512        let (ast, diags) = parse_str("class C { get value() { return 1; } }");
12513        assert_eq!(diags.len(), 1, "got: {diags:?}");
12514        assert_eq!(diags[0].message, "expected `:` and return type");
12515        assert_eq!(diags[0].help, vec!["get name(): T { … }".to_string()]);
12516        assert!(
12517            matches!(
12518                &class_members(&ast)[0],
12519                crate::ClassMember::Accessor {
12520                    return_type: None,
12521                    ..
12522                }
12523            ),
12524            "the getter is still recovered as a member"
12525        );
12526    }
12527
12528    #[test]
12529    fn setter_parses_as_accessor() {
12530        let (ast, diags) = parse_str("class C { set value(v: number) {} }");
12531        assert!(diags.is_empty(), "got: {diags:?}");
12532        let crate::ClassMember::Accessor { kind, param, .. } = &class_members(&ast)[0] else {
12533            panic!("expected an accessor member");
12534        };
12535        assert_eq!(*kind, crate::AccessorKind::Set);
12536        assert!(param.is_some(), "setter should have a parameter");
12537    }
12538
12539    #[test]
12540    fn param_property_modifier_outside_constructor_is_rejected() {
12541        let (_ast, diags) = parse_str("function f(public x: number): void {}");
12542        assert!(
12543            diags.iter().any(|d| d
12544                .message
12545                .contains("parameter properties are only allowed in a constructor")),
12546            "got: {diags:?}",
12547        );
12548    }
12549
12550    #[test]
12551    fn constructor_return_type_is_rejected() {
12552        let (_ast, diags) = parse_str("class C { constructor(): void {} }");
12553        assert!(
12554            diags.iter().any(|d| d
12555                .message
12556                .contains("constructor cannot declare a return type")),
12557            "got: {diags:?}",
12558        );
12559    }
12560
12561    #[test]
12562    fn field_initializer_without_type_is_rejected() {
12563        let (_ast, diags) = parse_str("class C { x = 1; }");
12564        assert!(
12565            diags
12566                .iter()
12567                .any(|d| d.message.contains("class fields require a type annotation")),
12568            "got: {diags:?}",
12569        );
12570    }
12571
12572    #[test]
12573    fn this_outside_method_is_left_to_typechecker() {
12574        let (_ast, diags) = parse_str("function f(): string { return this.name; }");
12575        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12576    }
12577
12578    #[test]
12579    fn super_outside_method_is_rejected() {
12580        let (_ast, diags) = parse_str("function f(): void { super(1); }");
12581        assert!(
12582            diags.iter().any(|d| d
12583                .message
12584                .contains("`super` is only valid inside a class method or constructor body")),
12585            "got: {diags:?}",
12586        );
12587    }
12588
12589    #[test]
12590    fn this_inside_method_is_accepted_by_parser() {
12591        let (_ast, diags) = parse_str("class C { x: number; read(): number { return this.x; } }");
12592        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12593    }
12594    #[test]
12595    fn parse_void_expression_preserves_operand_and_precedence() {
12596        let (ast, diags) = parse_str("void effect() === undefined;");
12597        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
12598        let expression = expr_of_single_stmt(&ast);
12599        let crate::ExprKind::Binary { lhs, rhs, .. } = expression.kind else {
12600            panic!("expected equality")
12601        };
12602        let crate::ExprKind::Void { operand } = ast.try_expr(lhs).unwrap().kind else {
12603            panic!("expected void")
12604        };
12605        assert!(matches!(
12606            ast.try_expr(operand).unwrap().kind,
12607            crate::ExprKind::Call { .. }
12608        ));
12609        assert!(matches!(
12610            ast.try_expr(rhs).unwrap().kind,
12611            crate::ExprKind::Identifier(_)
12612        ));
12613    }
12614
12615    #[test]
12616    fn parse_void_newline_and_object_operands() {
12617        for source in [
12618            "void\n0;",
12619            "void { value: 1 };",
12620            "void\n{ value: 1 };",
12621            "void /x/;",
12622            "void void 0;",
12623        ] {
12624            let (_, diags) = parse_str(source);
12625            assert!(diags.is_empty(), "{source}: {diags:?}");
12626        }
12627        let (_, diags) = parse_str("void; 0;");
12628        assert!(
12629            !diags.is_empty(),
12630            "an explicit semicolon cannot split void from its operand"
12631        );
12632    }
12633
12634    #[test]
12635    fn parse_optional_parameters_and_defaults_in_callable_forms() {
12636        for source in [
12637            "function f(x = 1): number { return x; }",
12638            "function f(x: number = 1, y: number): number { return y; }",
12639            "function f({ x } = { x: 1 }): number { return x; }",
12640            "const f = (x?: number) => x;",
12641            "const f = (x: number = effect()) => x;",
12642            "const f = function(x: number = effect()) { return x; };",
12643            "const f = { m(x: number = effect()): number { return x; } };",
12644            "const f = ([x = 1]: number[] = values) => x;",
12645            "let f: (x?: number, ...rest: string[]) => void = value;",
12646            "let f: { method?(x?: number): void } = value;",
12647            "function f(x: number = 1, y?: number): void {}",
12648        ] {
12649            let (_, diags) = parse_str(source);
12650            assert!(diags.is_empty(), "{source}: {diags:?}");
12651        }
12652    }
12653
12654    #[test]
12655    fn parse_invalid_optional_parameters() {
12656        for source in [
12657            "function f(x?: number, y: number): void {}",
12658            "const f = (x?: number, y: number) => y;",
12659            "let f: (x?: number, y: number) => void = value;",
12660            "function f(...x?: number[]): void {}",
12661            "function f(x?: number = 1): void {}",
12662            "const f = (x?: number = 1) => x;",
12663            "function f({x}?: T): void {}",
12664        ] {
12665            let (_, diags) = parse_str(source);
12666            assert!(!diags.is_empty(), "expected diagnostic for {source}");
12667        }
12668    }
12669
12670    #[test]
12671    fn parse_optional_tuple_forms_and_order() {
12672        for source in [
12673            "let x: [number, string?] = value;",
12674            "let x: [x?: number, y?: string] = value;",
12675            "let x: readonly [(number | null)?, string?] = value;",
12676        ] {
12677            let (_, diags) = parse_str(source);
12678            assert!(diags.is_empty(), "{source}: {diags:?}");
12679        }
12680        for source in [
12681            "let x: [number?, string] = value;",
12682            "let x: [x: number?] = value;",
12683        ] {
12684            let (_, diags) = parse_str(source);
12685            assert!(!diags.is_empty(), "expected diagnostic for {source}");
12686        }
12687    }
12688
12689    #[test]
12690    fn parse_array_pattern_defaults_preserve_holes() {
12691        let (ast, diags) = parse_str("const [, a = effect(), b] = values;");
12692        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
12693        let crate::StmtKind::ConstPattern { ref binding, .. } = single_stmt(&ast).kind else {
12694            panic!("expected pattern")
12695        };
12696        let crate::Binding::Array {
12697            elems, defaults, ..
12698        } = binding
12699        else {
12700            panic!("expected array")
12701        };
12702        assert_eq!(elems.len(), 3);
12703        assert_eq!(defaults.len(), elems.len());
12704        assert!(elems[0].is_none());
12705        assert!(defaults[0].is_none());
12706        assert!(defaults[1].is_some());
12707        assert!(defaults[2].is_none());
12708    }
12709
12710    #[test]
12711    fn parse_optional_class_method_forms() {
12712        let (ast, diags) =
12713            parse_str("class C { declared?(x?: number): void; implemented?(): void\n{} }");
12714        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
12715        let members = class_members(&ast);
12716        assert!(matches!(
12717            &members[0],
12718            crate::ClassMember::Field {
12719                optional: true,
12720                initializer: None,
12721                ty: crate::TypeAnnotation {
12722                    kind: crate::TypeAnnotationKind::Function { .. },
12723                    ..
12724                },
12725                ..
12726            }
12727        ));
12728        assert!(matches!(
12729            &members[1],
12730            crate::ClassMember::Method { optional: true, .. }
12731        ));
12732    }
12733    #[test]
12734    fn defaults_retain_their_callable_receiver_scope() {
12735        for source in [
12736            "class C { value: number = 1; method(value: number = this.value): number { return value; } }",
12737            "class C { constructor(value: number = this.value) {} value: number = 1; }",
12738            "const f = function(this: { value: number }, value: number = this.value): number { return value; };",
12739        ] {
12740            let (ast, diagnostics) = parse_str(source);
12741            assert!(diagnostics.is_empty(), "{source}: {diagnostics:?}");
12742            assert!(
12743                ast.source_expressions()
12744                    .iter()
12745                    .any(|expr| matches!(expr.kind, ExprKind::This))
12746            );
12747            assert!(
12748                !ast.source_expressions()
12749                    .iter()
12750                    .any(|expr| matches!(expr.kind, ExprKind::ThisOutsideReceiver))
12751            );
12752        }
12753    }
12754}