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 = self.expect_identifier("expected identifier after `let`/`const`")?;
339            Some(self.ident_from_token(&name_tok))
340        } else {
341            None
342        };
343
344        let ty = if matches!(self.peek().kind, TokenKind::Colon) {
345            self.advance();
346            Some(self.parse_type_annotation()?)
347        } else {
348            None
349        };
350
351        let implicit = !is_const
352            && ty.is_some()
353            && !matches!(self.peek().kind, TokenKind::Equals)
354            && matches!(self.peek().kind, TokenKind::Semicolon);
355        let value = if let (true, Some(name)) = (implicit, &name) {
356            // `let x: T;` starts as `undefined`; the typechecker requires `T` to allow it.
357            let value = parse_arena_result(
358                self.ast.try_push_expr(Expr {
359                    kind: ExprKind::Identifier(Ident {
360                        name: "undefined".to_string(),
361                        span: name.span,
362                    }),
363                    span: name.span,
364                }),
365                &mut self.fatal,
366            )?;
367            self.ast.implicit_initializers.insert(value);
368            self.ast.synthetic_undefined.insert(value);
369            value
370        } else {
371            if !matches!(self.peek().kind, TokenKind::Equals) {
372                let (msg, help) = if is_const {
373                    (
374                        "`const` declaration requires an initializer",
375                        vec!["const x: T = expr;".to_string()],
376                    )
377                } else {
378                    (
379                        "`let` declaration requires an initializer",
380                        vec![
381                            "let x: T = expr;".to_string(),
382                            "or declare one that starts empty: `let x: T | undefined;`".to_string(),
383                        ],
384                    )
385                };
386                self.error_at_peek_with_help(msg, help);
387                return None;
388            }
389            self.advance();
390            self.parse_expression()?
391        };
392
393        if !self.at_statement_end() {
394            self.error_at_peek("expected `;` after declaration");
395            return None;
396        }
397        let end = self.finish_statement();
398
399        let span = self.span(kw.span.start, end);
400        let kind = match (binding, name) {
401            (Some(binding), _) => {
402                if is_const {
403                    StmtKind::ConstPattern {
404                        binding,
405                        ty,
406                        value,
407                        doc,
408                    }
409                } else {
410                    StmtKind::LetPattern {
411                        binding,
412                        ty,
413                        value,
414                        doc,
415                    }
416                }
417            }
418            (None, Some(name)) => {
419                if is_const {
420                    StmtKind::Const {
421                        name,
422                        ty,
423                        value,
424                        doc,
425                    }
426                } else {
427                    StmtKind::Let {
428                        name,
429                        ty,
430                        value,
431                        doc,
432                    }
433                }
434            }
435            (None, None) => return self.invariant_failure("either binding or name must be Some"),
436        };
437        parse_arena_result(self.ast.try_push_stmt(Stmt { kind, span }), &mut self.fatal)
438    }
439
440    // One level only; nested patterns are rejected.
441    fn parse_binding(&mut self) -> Option<Binding> {
442        match self.peek().kind {
443            TokenKind::LeftBrace => self.parse_object_binding(),
444            TokenKind::LeftBracket => self.parse_array_binding(),
445            _ => {
446                self.error_at_peek("expected `{` or `[` to start a destructuring pattern");
447                None
448            }
449        }
450    }
451
452    fn parse_object_binding(&mut self) -> Option<Binding> {
453        let open = self.advance();
454        let mut fields: Vec<ObjectPatternField> = Vec::new();
455        let mut rest: Option<Ident> = None;
456
457        if matches!(self.peek().kind, TokenKind::RightBrace) {
458            self.error_at_peek("empty object destructuring pattern");
459            return None;
460        }
461
462        loop {
463            if matches!(self.peek().kind, TokenKind::DotDotDot) {
464                let dots = self.advance();
465                let name_tok = self.expect_identifier("expected identifier after `...`")?;
466                let name = self.ident_from_token(&name_tok);
467                if !matches!(self.peek().kind, TokenKind::RightBrace) {
468                    self.error_at_peek_with_help(
469                        "rest element must be the last element in a destructuring pattern",
470                        vec!["move `...` after every other binding".to_string()],
471                    );
472                    return None;
473                }
474                rest = Some(Ident {
475                    name: name.name,
476                    span: self.span(dots.span.start, name.span.end),
477                });
478                break;
479            }
480
481            let source_tok = self.expect_property_name("expected field name in object pattern")?;
482            let source = self.ident_from_token(&source_tok);
483            let field_span_start = source.span.start;
484
485            let (local, field_end) = if matches!(self.peek().kind, TokenKind::Colon) {
486                self.advance();
487                match self.peek().kind {
488                    TokenKind::LeftBrace | TokenKind::LeftBracket => {
489                        self.error_at_peek_with_help(
490                                "nested destructuring is not supported",
491                                vec![
492                                    "destructure once and access nested fields explicitly: const { a } = obj; const inner = a.b;"
493                                        .to_string(),
494                                ],
495                            );
496                        return None;
497                    }
498                    _ => {
499                        let local_tok =
500                            self.expect_identifier("expected identifier after `:` in pattern")?;
501                        let local = self.ident_from_token(&local_tok);
502                        let end = local.span.end;
503                        (local, end)
504                    }
505                }
506            } else {
507                if !matches!(source_tok.kind, TokenKind::Identifier) {
508                    self.error_at_peek("expected `:` after keyword field name in object pattern");
509                    return None;
510                }
511                self.reject_strict_mode_reserved_word(&source_tok);
512                let end = source.span.end;
513                (source.clone(), end)
514            };
515
516            let default = if matches!(self.peek().kind, TokenKind::Equals) {
517                self.advance();
518                Some(self.parse_expression()?)
519            } else {
520                None
521            };
522            let field_end = match default {
523                Some(value) => {
524                    parse_arena_result(self.ast.try_expr(value), &mut self.fatal)?
525                        .span
526                        .end
527                }
528                None => field_end,
529            };
530
531            fields.push(ObjectPatternField {
532                source,
533                local,
534                default,
535                span: self.span(field_span_start, field_end),
536            });
537
538            match self.peek().kind {
539                TokenKind::Comma => {
540                    self.advance();
541                    if matches!(self.peek().kind, TokenKind::RightBrace) {
542                        break;
543                    }
544                }
545                TokenKind::RightBrace => break,
546                _ => {
547                    self.error_at_peek("expected `,` or `}` in object pattern");
548                    return None;
549                }
550            }
551        }
552
553        if !matches!(self.peek().kind, TokenKind::RightBrace) {
554            self.error_at_peek("expected `}` to close object pattern");
555            return None;
556        }
557        let close = self.advance();
558
559        Some(Binding::Object {
560            fields,
561            rest,
562            span: self.span(open.span.start, close.span.end),
563        })
564    }
565
566    fn parse_array_binding(&mut self) -> Option<Binding> {
567        let open = self.advance();
568        let mut elems: Vec<Option<Ident>> = Vec::new();
569        let mut defaults = Vec::new();
570        let mut rest: Option<Ident> = None;
571
572        if matches!(self.peek().kind, TokenKind::RightBracket) {
573            self.error_at_peek("empty array destructuring pattern");
574            return None;
575        }
576
577        loop {
578            match self.peek().kind {
579                TokenKind::Comma => {
580                    elems.push(None);
581                    defaults.push(None);
582                    self.advance();
583                    if matches!(self.peek().kind, TokenKind::RightBracket) {
584                        break;
585                    }
586                    continue;
587                }
588                TokenKind::RightBracket => break,
589                TokenKind::DotDotDot => {
590                    let dots = self.advance();
591                    let name_tok = self.expect_identifier("expected identifier after `...`")?;
592                    let name = self.ident_from_token(&name_tok);
593                    if !matches!(self.peek().kind, TokenKind::RightBracket) {
594                        self.error_at_peek_with_help(
595                            "rest element must be the last element in a destructuring pattern",
596                            vec!["move `...` after every other binding".to_string()],
597                        );
598                        return None;
599                    }
600                    rest = Some(Ident {
601                        name: name.name,
602                        span: self.span(dots.span.start, name.span.end),
603                    });
604                    break;
605                }
606                TokenKind::LeftBrace | TokenKind::LeftBracket => {
607                    self.error_at_peek_with_help(
608                        "nested destructuring is not supported",
609                        vec![
610                            "destructure once and access nested elements explicitly: const [a] = arr; const inner = a[0];"
611                                .to_string(),
612                        ],
613                    );
614                    return None;
615                }
616                _ => {
617                    let name_tok =
618                        self.expect_identifier("expected identifier in array pattern")?;
619                    let name = self.ident_from_token(&name_tok);
620
621                    let default = if matches!(self.peek().kind, TokenKind::Equals) {
622                        self.advance();
623                        Some(self.parse_expression()?)
624                    } else {
625                        None
626                    };
627                    defaults.push(default);
628                    elems.push(Some(name));
629
630                    match self.peek().kind {
631                        TokenKind::Comma => {
632                            self.advance();
633                            if matches!(self.peek().kind, TokenKind::RightBracket) {
634                                break;
635                            }
636                        }
637                        TokenKind::RightBracket => break,
638                        _ => {
639                            self.error_at_peek("expected `,` or `]` in array pattern");
640                            return None;
641                        }
642                    }
643                }
644            }
645        }
646
647        if !matches!(self.peek().kind, TokenKind::RightBracket) {
648            self.error_at_peek("expected `]` to close array pattern");
649            return None;
650        }
651        let close = self.advance();
652
653        Some(Binding::Array {
654            elems,
655            defaults,
656            rest,
657            span: self.span(open.span.start, close.span.end),
658        })
659    }
660
661    fn parse_function_decl(&mut self) -> Option<StmtId> {
662        let doc = self.take_leading_doc();
663        let kw = self.advance();
664
665        let name_tok = self.expect_identifier("expected function name")?;
666        let name = self.ident_from_token(&name_tok);
667
668        let generics = if matches!(self.peek().kind, TokenKind::LessThan) {
669            self.parse_generic_param_list()?
670        } else {
671            Vec::new()
672        };
673
674        if !matches!(self.peek().kind, TokenKind::LeftParen) {
675            self.error_at_peek("expected `(` after function name");
676            return None;
677        }
678        self.advance();
679
680        let params = self.parse_param_list(false)?;
681        self.advance(); // `)` — parse_param_list left us on it
682
683        if !matches!(self.peek().kind, TokenKind::Colon) {
684            self.error_at_peek_with_help(
685                "expected `:` and return type",
686                vec!["function name(): T { … }".to_string()],
687            );
688            return None;
689        }
690        self.advance();
691        let (return_type, type_predicate) =
692            self.parse_predicate_or_return_type(TypePos::Anywhere)?;
693
694        // A declaration nested in a method has no receiver of its own, and
695        // must not see the method's.
696        let body = self.parse_outer_this_boundary(Self::parse_block)?;
697        let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
698            .span
699            .end;
700
701        parse_arena_result(
702            self.ast.try_push_stmt(Stmt {
703                kind: StmtKind::Function {
704                    name,
705                    generics,
706                    params,
707                    return_type,
708                    type_predicate,
709                    body,
710                    doc,
711                },
712                span: self.span(kw.span.start, body_end),
713            }),
714            &mut self.fatal,
715        )
716    }
717
718    fn parse_class_decl(&mut self) -> Option<StmtId> {
719        let doc = self.take_leading_doc();
720        let kw = self.advance();
721        // Classes are collected from the module's top level only; a nested one
722        // would otherwise be dropped unchecked.
723        if self.block_depth > 0 {
724            self.error_at_with_help(
725                kw.span,
726                "a `class` must be declared at the top level of the module",
727                vec!["move this `class` outside any block or function body".to_string()],
728            );
729        }
730        let name = self.parse_type_decl_name("a", "class")?;
731
732        let generics = if matches!(self.peek().kind, TokenKind::LessThan) {
733            self.parse_generic_param_list()?
734        } else {
735            Vec::new()
736        };
737
738        let extends = if matches!(self.peek().kind, TokenKind::Extends) {
739            self.advance();
740            Some(self.parse_type_annotation()?)
741        } else {
742            None
743        };
744
745        let mut implements: Vec<crate::TypeAnnotation> = Vec::new();
746        if matches!(self.peek().kind, TokenKind::Implements) {
747            self.advance();
748            loop {
749                implements.push(self.parse_type_annotation()?);
750                if matches!(self.peek().kind, TokenKind::Comma) {
751                    self.advance();
752                    continue;
753                }
754                break;
755            }
756        }
757
758        if !matches!(self.peek().kind, TokenKind::LeftBrace) {
759            self.error_at_peek("expected `{` after class header");
760            return None;
761        }
762        self.advance();
763
764        let mut members: Vec<crate::ClassMember> = Vec::new();
765        while !matches!(self.peek().kind, TokenKind::RightBrace | TokenKind::Eof) {
766            // Stray `;` between members is accepted (TS allows it).
767            if matches!(self.peek().kind, TokenKind::Semicolon) {
768                self.advance();
769                continue;
770            }
771            members.push(self.parse_class_member()?);
772        }
773
774        if !matches!(self.peek().kind, TokenKind::RightBrace) {
775            self.error_at_peek("expected `}` to close class");
776            return None;
777        }
778        let close = self.advance();
779
780        parse_arena_result(
781            self.ast.try_push_stmt(Stmt {
782                kind: StmtKind::ClassDecl {
783                    name,
784                    generics,
785                    extends,
786                    implements,
787                    members,
788                    doc,
789                },
790                span: self.span(kw.span.start, close.span.end),
791            }),
792            &mut self.fatal,
793        )
794    }
795
796    fn parse_class_member(&mut self) -> Option<crate::ClassMember> {
797        let doc = self.take_leading_doc();
798        let member_start = self.peek().span.start;
799
800        // Excluded modifiers — reject before anything else, naming the fix.
801        if self.peek_identifier_text_is("protected") {
802            let tok = self.advance();
803            self.error_at_with_help(
804                tok.span,
805                "`protected` is not supported",
806                vec!["use `private` or `public`".to_string()],
807            );
808            return None;
809        }
810        if self.peek_identifier_text_is("abstract") {
811            let tok = self.advance();
812            self.error_at_with_help(
813                tok.span,
814                "abstract classes are not supported",
815                vec!["provide a concrete implementation instead of `abstract`".to_string()],
816            );
817            return None;
818        }
819
820        let modifiers = self.parse_class_modifiers();
821
822        // Getter/setter: `get`/`set` <name> ( … ). A member literally named `get`/`set`
823        // (followed directly by `(`) is an ordinary method and stays accepted.
824        for (kw, kind) in [
825            ("get", crate::AccessorKind::Get),
826            ("set", crate::AccessorKind::Set),
827        ] {
828            if self.peek_identifier_text_is(kw)
829                && is_property_name(&self.peek_at(1).kind)
830                && matches!(self.peek_at(2).kind, TokenKind::LeftParen)
831            {
832                if let Some(span) = modifiers.readonly {
833                    self.reject_readonly_modifier(span);
834                }
835                if let Some(span) = modifiers.static_span {
836                    self.error_at_with_help(
837                        span,
838                        "static accessors are not supported",
839                        vec!["use a static method: `static name(): T { … }`".to_string()],
840                    );
841                    return None;
842                }
843                return self.parse_accessor(member_start, modifiers, kind, doc);
844            }
845        }
846
847        // Constructor.
848        if self.peek_identifier_text_is("constructor")
849            && matches!(self.peek_at(1).kind, TokenKind::LeftParen)
850        {
851            if let Some(span) = modifiers.static_span {
852                self.error_at_with_help(
853                    span,
854                    "a constructor cannot be `static`",
855                    vec![
856                        "remove `static` — the constructor already belongs to the class, not \
857                         instances"
858                            .to_string(),
859                    ],
860                );
861                return None;
862            }
863            if let Some(span) = modifiers.readonly {
864                self.error_at_with_help(
865                    span,
866                    "a constructor cannot be `readonly`",
867                    vec!["remove `readonly`; it applies to fields".to_string()],
868                );
869                return None;
870            }
871            return self.parse_constructor(member_start, modifiers.visibility, doc);
872        }
873
874        let name = self.expect_property_ident("expected class member name")?;
875        let optional_span = if matches!(self.peek().kind, TokenKind::Question) {
876            Some(self.advance().span)
877        } else {
878            None
879        };
880        let optional = optional_span.is_some();
881
882        // Method.
883        if matches!(self.peek().kind, TokenKind::LessThan | TokenKind::LeftParen) {
884            if let Some(span) = modifiers.readonly {
885                self.reject_readonly_modifier(span);
886            }
887            let generics = if matches!(self.peek().kind, TokenKind::LessThan) {
888                self.parse_generic_param_list()?
889            } else {
890                Vec::new()
891            };
892            if !matches!(self.peek().kind, TokenKind::LeftParen) {
893                self.error_at_peek("expected `(` to start the method parameter list");
894                return None;
895            }
896            self.advance();
897            let params = self.parse_class_member_params(false)?;
898            self.advance(); // `)` — parse_param_list left us on it
899            if !matches!(self.peek().kind, TokenKind::Colon) {
900                self.error_at_peek_with_help(
901                    "expected `:` and return type",
902                    vec!["method name(): T { … }".to_string()],
903                );
904                return None;
905            }
906            self.advance();
907            let return_type = self.parse_type_annotation()?;
908            if self.at_inserted_semicolon() && matches!(self.peek_at(1).kind, TokenKind::LeftBrace)
909            {
910                self.advance();
911            }
912            if optional
913                && matches!(
914                    self.peek().kind,
915                    TokenKind::Semicolon | TokenKind::RightBrace
916                )
917            {
918                if !generics.is_empty() {
919                    self.error_at(
920                        name.span,
921                        "generic optional method declarations are not supported",
922                    );
923                    return None;
924                }
925                let end = if matches!(self.peek().kind, TokenKind::Semicolon) {
926                    self.advance().span.end
927                } else {
928                    return_type.span.end
929                };
930                let ty = self.method_declaration_type(
931                    params,
932                    return_type,
933                    self.span(name.span.start, end),
934                )?;
935                return Some(crate::ClassMember::Field {
936                    name,
937                    modifiers,
938                    optional: true,
939                    ty,
940                    initializer: None,
941                    span: self.span(member_start, end),
942                    doc,
943                });
944            }
945            let body = self.parse_class_member_body()?;
946            let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
947                .span
948                .end;
949            return Some(crate::ClassMember::Method {
950                name,
951                optional,
952                modifiers,
953                generics,
954                params,
955                return_type,
956                body,
957                span: self.span(member_start, body_end),
958                doc,
959            });
960        }
961
962        // Field.
963        if let Some(span) = optional_span
964            && modifiers.static_span.is_some()
965        {
966            self.error_at_with_help(
967                span,
968                "a static field cannot be optional",
969                vec!["a static field must be initialized; give it a value with `= …`".to_string()],
970            );
971            return None;
972        }
973        if !matches!(self.peek().kind, TokenKind::Colon) {
974            if matches!(self.peek().kind, TokenKind::Equals) {
975                self.error_at_peek_with_help(
976                    "class fields require a type annotation",
977                    vec!["add `: T` before the initializer, e.g. `name: string = …`".to_string()],
978                );
979            } else {
980                self.error_at_peek("expected `:` and a type for the class field");
981            }
982            return None;
983        }
984        self.advance();
985        let ty = self.parse_type_annotation()?;
986        let initializer = if matches!(self.peek().kind, TokenKind::Equals) {
987            self.advance();
988            // A field initializer may read `this` (the typechecker binds it); gate
989            // it like a member body so `this.x` parses.
990            self.class_member_body_depth += 1;
991            let init = self.parse_expression();
992            self.class_member_body_depth -= 1;
993            Some(init?)
994        } else {
995            None
996        };
997        if !matches!(self.peek().kind, TokenKind::Semicolon) {
998            self.error_at_peek("expected `;` after class field");
999            return None;
1000        }
1001        let semi = self.advance();
1002        Some(crate::ClassMember::Field {
1003            name,
1004            modifiers,
1005            optional,
1006            ty,
1007            initializer,
1008            span: self.span(member_start, semi.span.end),
1009            doc,
1010        })
1011    }
1012
1013    fn parse_constructor(
1014        &mut self,
1015        member_start: u32,
1016        visibility: crate::Visibility,
1017        doc: Option<crate::DocComment>,
1018    ) -> Option<crate::ClassMember> {
1019        self.advance(); // `constructor`
1020        self.advance(); // `(` — guaranteed by the caller
1021        let params = self.parse_class_member_params(true)?;
1022        self.advance(); // `)` — parse_param_list left us on it
1023        if matches!(self.peek().kind, TokenKind::Colon) {
1024            let colon = self.advance();
1025            let _ = self.parse_type_annotation();
1026            self.error_at_with_help(
1027                colon.span,
1028                "a constructor cannot declare a return type",
1029                vec![
1030                    "drop the `: T` — a constructor always returns the class instance".to_string(),
1031                ],
1032            );
1033        }
1034        let body = self.parse_class_member_body()?;
1035        let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
1036            .span
1037            .end;
1038        Some(crate::ClassMember::Constructor {
1039            visibility,
1040            params,
1041            body,
1042            span: self.span(member_start, body_end),
1043            doc,
1044        })
1045    }
1046
1047    /// `get name(): T { … }` / `set name(v: T) { … }`. The caller has confirmed the
1048    /// `get`/`set` keyword, a property name, and a `(` follow.
1049    fn parse_accessor(
1050        &mut self,
1051        member_start: u32,
1052        modifiers: crate::ClassModifiers,
1053        kind: crate::AccessorKind,
1054        doc: Option<crate::DocComment>,
1055    ) -> Option<crate::ClassMember> {
1056        self.advance(); // `get` / `set`
1057        let name = self.expect_property_ident("expected accessor name")?;
1058        self.advance(); // `(` — guaranteed by the caller
1059        let mut params = self.parse_class_member_params(false)?;
1060        self.advance(); // `)`
1061        let param = match kind {
1062            crate::AccessorKind::Get => {
1063                if !params.is_empty() {
1064                    self.error_at(name.span, "a getter cannot declare parameters");
1065                }
1066                None
1067            }
1068            crate::AccessorKind::Set => {
1069                if params.len() != 1 {
1070                    self.error_at(name.span, "a setter must declare exactly one parameter");
1071                    return None;
1072                }
1073                Some(Box::new(params.remove(0)))
1074            }
1075        };
1076        let return_type = if matches!(self.peek().kind, TokenKind::Colon) {
1077            self.advance();
1078            Some(self.parse_type_annotation()?)
1079        } else {
1080            // A getter's type is its return type, which, as for a method, is
1081            // written rather than inferred. The body still parses, so the
1082            // rest of the class is checked.
1083            if kind == crate::AccessorKind::Get {
1084                self.error_at_peek_with_help(
1085                    "expected `:` and return type",
1086                    vec!["get name(): T { … }".to_string()],
1087                );
1088            }
1089            None
1090        };
1091        let body = self.parse_class_member_body()?;
1092        let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
1093            .span
1094            .end;
1095        Some(crate::ClassMember::Accessor {
1096            name,
1097            modifiers,
1098            kind,
1099            param,
1100            return_type,
1101            body,
1102            span: self.span(member_start, body_end),
1103            doc,
1104        })
1105    }
1106
1107    /// `public`/`private`/`static`/`readonly`. Duplicate or conflicting modifiers are
1108    /// diagnosed but parsing continues so the member shape is still recovered.
1109    /// Visibility must precede `static` and `readonly` (the TypeScript order);
1110    /// `readonly` is accepted on either side of `static` — no semantic difference
1111    /// under our subset.
1112    fn parse_class_modifiers(&mut self) -> crate::ClassModifiers {
1113        let mut visibility = crate::Visibility::Public;
1114        let mut visibility_span: Option<Span> = None;
1115        let mut readonly: Option<Span> = None;
1116        let mut static_span: Option<Span> = None;
1117        loop {
1118            if self.peek_word_is_class_modifier("public")
1119                || self.peek_word_is_class_modifier("private")
1120            {
1121                let (kind, word) = if self.peek_identifier_text_is("private") {
1122                    (crate::Visibility::Private, "private")
1123                } else {
1124                    (crate::Visibility::Public, "public")
1125                };
1126                let tok = self.advance();
1127                if visibility_span.is_some() {
1128                    self.error_at_with_help(
1129                        tok.span,
1130                        "a class member may have at most one visibility modifier",
1131                        vec!["keep a single `public` or `private`".to_string()],
1132                    );
1133                    continue;
1134                }
1135                // TypeScript reports one misplaced modifier per member; `static` is
1136                // named first when both precede.
1137                let preceding = static_span
1138                    .map(|_| "static")
1139                    .or(readonly.map(|_| "readonly"));
1140                if let Some(preceding) = preceding {
1141                    self.error_at_with_help(
1142                        tok.span,
1143                        format!("`{word}` must come before `{preceding}`"),
1144                        vec![format!("write `{word} {preceding} <name>`")],
1145                    );
1146                }
1147                visibility = kind;
1148                visibility_span = Some(tok.span);
1149                continue;
1150            }
1151            if self.peek_word_is_class_modifier("static") {
1152                let tok = self.advance();
1153                if static_span.is_some() {
1154                    self.error_at(tok.span, "duplicate `static` modifier");
1155                } else {
1156                    static_span = Some(tok.span);
1157                }
1158                continue;
1159            }
1160            if self.peek_word_is_class_modifier("readonly") {
1161                let tok = self.advance();
1162                if readonly.is_some() {
1163                    self.error_at(tok.span, "duplicate `readonly` modifier");
1164                } else {
1165                    readonly = Some(tok.span);
1166                }
1167                continue;
1168            }
1169            break;
1170        }
1171        crate::ClassModifiers {
1172            visibility,
1173            visibility_span,
1174            readonly,
1175            static_span,
1176        }
1177    }
1178
1179    /// A contextual modifier keyword counts as a modifier only when another member token
1180    /// (the real name, or a further modifier) follows — otherwise the word is the member
1181    /// name itself (e.g. a field named `private`). Mirrors `eat_readonly_property_modifier`.
1182    /// As in TypeScript, only `static` may be followed by a line break; in a class body
1183    /// ASI already ends the member there, but a constructor parameter list has no ASI.
1184    fn peek_word_is_class_modifier(&self, word: &str) -> bool {
1185        if !self.peek_identifier_text_is(word) {
1186            return false;
1187        }
1188        if word != "static" && self.line_break_after_peek() {
1189            return false;
1190        }
1191        let next = &self.peek_at(1).kind;
1192        is_property_name(next) || matches!(next, TokenKind::StringLiteral(_))
1193    }
1194
1195    fn parse_class_member_params(
1196        &mut self,
1197        allow_param_properties: bool,
1198    ) -> Option<Vec<ParamDecl>> {
1199        let saved = self.class_member_body_depth;
1200        self.class_member_body_depth = 1;
1201        let params = self.parse_param_list(allow_param_properties);
1202        self.class_member_body_depth = saved;
1203        params
1204    }
1205
1206    fn parse_class_member_body(&mut self) -> Option<StmtId> {
1207        self.class_member_body_depth += 1;
1208        let body = self.parse_block();
1209        self.class_member_body_depth -= 1;
1210        body
1211    }
1212
1213    fn parse_interface_decl(&mut self) -> Option<StmtId> {
1214        let doc = self.take_leading_doc();
1215        let kw = self.advance();
1216        let name = self.parse_type_decl_name("an", "interface")?;
1217
1218        let generics = if matches!(self.peek().kind, TokenKind::LessThan) {
1219            self.parse_generic_param_list()?
1220        } else {
1221            Vec::new()
1222        };
1223
1224        let mut extends = Vec::new();
1225        if matches!(self.peek().kind, TokenKind::Extends) {
1226            self.advance();
1227            loop {
1228                extends.push(self.parse_type_annotation()?);
1229                if !matches!(self.peek().kind, TokenKind::Comma) {
1230                    break;
1231                }
1232                self.advance();
1233            }
1234        }
1235        if !matches!(self.peek().kind, TokenKind::LeftBrace) {
1236            self.error_at_peek("expected `{` after interface name");
1237            return None;
1238        }
1239        self.advance();
1240
1241        let mut members: Vec<crate::InterfaceMember> = Vec::new();
1242        while !matches!(self.peek().kind, TokenKind::RightBrace | TokenKind::Eof)
1243            && !self.peek_starts_declaration()
1244        {
1245            // Call signatures `(params): ret;` are stored under the sentinel name `@call`.
1246            // The `@` prefix is not a valid identifier start, so collisions with user methods
1247            // are impossible.
1248            let member_doc = self.take_leading_doc();
1249            if matches!(self.peek().kind, TokenKind::LeftParen) {
1250                let open = self.advance();
1251                let params = self.parse_param_list(false)?;
1252                self.advance();
1253                if !matches!(self.peek().kind, TokenKind::Colon) {
1254                    self.error_at_peek("expected `:` and return type");
1255                    return None;
1256                }
1257                self.advance();
1258                let return_type = self.parse_type_annotation()?;
1259                let end = self.finish_interface_member(return_type.span.end)?;
1260                let span = self.span(open.span.start, end);
1261                let sentinel = Ident {
1262                    name: "@call".to_string(),
1263                    span: open.span,
1264                };
1265                members.push(crate::InterfaceMember::Method {
1266                    name: sentinel,
1267                    optional: false,
1268                    generics: Vec::new(),
1269                    params,
1270                    return_type,
1271                    span,
1272                    doc: member_doc,
1273                });
1274                continue;
1275            }
1276            if matches!(self.peek().kind, TokenKind::LessThan) {
1277                self.error_at_peek("generic call signatures are not yet supported");
1278                return None;
1279            }
1280            let member_start = self.peek().span.start;
1281            let readonly = self.eat_readonly_property_modifier();
1282            if self.peek_is_parameterless_index_signature() {
1283                self.skip_parameterless_index_signature(member_start)?;
1284                self.finish_interface_member(self.prev_token_end())?;
1285                continue;
1286            }
1287            if matches!(self.peek().kind, TokenKind::LeftBracket) {
1288                let signature = self.parse_index_signature(readonly)?;
1289                self.finish_interface_member(signature.span.end)?;
1290                members.push(crate::InterfaceMember::IndexSignature(signature));
1291                continue;
1292            }
1293            let member_name = self.expect_property_ident("expected interface member name")?;
1294            let optional = matches!(self.peek().kind, TokenKind::Question);
1295            if optional {
1296                self.advance();
1297            }
1298            if matches!(self.peek().kind, TokenKind::Colon) {
1299                self.advance();
1300                let ty = self.parse_type_annotation()?;
1301                let end = self.finish_interface_member(ty.span.end)?;
1302                let span = self.span(member_name.span.start, end);
1303                members.push(crate::InterfaceMember::Property {
1304                    name: member_name,
1305                    ty,
1306                    optional,
1307                    readonly,
1308                    span,
1309                    doc: member_doc,
1310                });
1311                continue;
1312            }
1313            if readonly {
1314                self.reject_readonly_modifier(self.readonly_modifier_span(member_start));
1315            }
1316            let m_generics = if matches!(self.peek().kind, TokenKind::LessThan) {
1317                self.parse_generic_param_list()?
1318            } else {
1319                Vec::new()
1320            };
1321            if !matches!(self.peek().kind, TokenKind::LeftParen) {
1322                self.error_at_peek(
1323                    "expected `(` to start a method signature or `:` to start a property",
1324                );
1325                return None;
1326            }
1327            self.advance();
1328            let params = self.parse_param_list(false)?;
1329            self.advance();
1330            if !matches!(self.peek().kind, TokenKind::Colon) {
1331                self.error_at_peek("expected `:` and return type");
1332                return None;
1333            }
1334            self.advance();
1335            let return_type = self.parse_type_annotation()?;
1336            let end = self.finish_interface_member(return_type.span.end)?;
1337            let span = self.span(member_name.span.start, end);
1338            if optional {
1339                if !m_generics.is_empty() {
1340                    self.error_at(
1341                        member_name.span,
1342                        "generic optional method declarations are not supported",
1343                    );
1344                    return None;
1345                }
1346                let ty = self.method_declaration_type(params, return_type, span)?;
1347                members.push(crate::InterfaceMember::Property {
1348                    name: member_name,
1349                    ty,
1350                    optional: true,
1351                    readonly,
1352                    span,
1353                    doc: member_doc,
1354                });
1355            } else {
1356                members.push(crate::InterfaceMember::Method {
1357                    name: member_name,
1358                    optional,
1359                    generics: m_generics,
1360                    params,
1361                    return_type,
1362                    span,
1363                    doc: member_doc,
1364                });
1365            }
1366        }
1367
1368        if !matches!(self.peek().kind, TokenKind::RightBrace) {
1369            self.error_at_peek("expected `}` to close interface");
1370            return None;
1371        }
1372        let close = self.advance();
1373
1374        parse_arena_result(
1375            self.ast.try_push_stmt(Stmt {
1376                kind: StmtKind::InterfaceDecl {
1377                    name,
1378                    generics,
1379                    extends,
1380                    members,
1381                    doc,
1382                },
1383                span: self.span(kw.span.start, close.span.end),
1384            }),
1385            &mut self.fatal,
1386        )
1387    }
1388
1389    fn method_declaration_type(
1390        &mut self,
1391        params: Vec<ParamDecl>,
1392        return_type: TypeAnnotation,
1393        span: Span,
1394    ) -> Option<TypeAnnotation> {
1395        let mut fields = Vec::new();
1396        for param in params {
1397            if param.pattern.is_some() || param.default.is_some() {
1398                self.error_at(
1399                    param.name.span,
1400                    "a method declaration cannot contain a parameter pattern or default value",
1401                );
1402                return None;
1403            }
1404            let Some(ty) = param.ty else {
1405                return self.invariant_failure("method parameter annotation was not retained");
1406            };
1407            fields.push(TypeAnnotationField {
1408                name: param.name,
1409                ty,
1410                optional: param.optional,
1411                readonly: false,
1412                rest: param.rest,
1413                method: false,
1414            });
1415        }
1416        Some(TypeAnnotation {
1417            kind: TypeAnnotationKind::Function {
1418                params: fields,
1419                return_type: Box::new(return_type),
1420            },
1421            span,
1422        })
1423    }
1424
1425    fn finish_interface_member(&mut self, body_end: u32) -> Option<u32> {
1426        self.finish_type_member(body_end, "expected `;`, `,`, or `}` after interface member")
1427    }
1428
1429    /// Consume the separator after an interface or type-literal member and return
1430    /// where the member ends. `;` and `,` are interchangeable and the last member's
1431    /// is optional. A line break also ends a member, as in TypeScript: ASI inserts
1432    /// `;` there, but keeps some tokens, such as `[` and `(`, attached. `body_end` is
1433    /// where the member ends when it carries no separator of its own.
1434    fn finish_type_member(&mut self, body_end: u32, message: &str) -> Option<u32> {
1435        match self.peek().kind {
1436            TokenKind::Semicolon | TokenKind::Comma => Some(self.advance().span.end),
1437            TokenKind::RightBrace => Some(body_end),
1438            _ if self.line_break_before_peek() => Some(body_end),
1439            _ => {
1440                self.error_at_peek(message);
1441                None
1442            }
1443        }
1444    }
1445
1446    fn parse_type_alias_decl(&mut self) -> Option<StmtId> {
1447        let doc = self.take_leading_doc();
1448        let kw = self.advance();
1449        let name = self.parse_type_decl_name("a", "type alias")?;
1450
1451        let generics: Vec<Ident> = if matches!(self.peek().kind, TokenKind::LessThan) {
1452            self.parse_generic_param_list()?
1453        } else {
1454            Vec::new()
1455        };
1456
1457        if !matches!(self.peek().kind, TokenKind::Equals) {
1458            self.error_at_peek("expected `=` after type alias name");
1459            return None;
1460        }
1461        self.advance();
1462
1463        let ty = self.parse_type_annotation()?;
1464
1465        // ASI keeps a `[` on the next line attached, but the type already ended at
1466        // the line break (see `parse_type_array_inner`), so the break ends the alias.
1467        let end = if matches!(self.peek().kind, TokenKind::Semicolon) {
1468            self.advance().span.end
1469        } else if self.line_break_before_peek() {
1470            self.prev_token_end()
1471        } else {
1472            self.error_at_peek("expected `;` after type alias body");
1473            return None;
1474        };
1475
1476        parse_arena_result(
1477            self.ast.try_push_stmt(Stmt {
1478                kind: StmtKind::TypeAliasDecl {
1479                    name,
1480                    generics,
1481                    ty,
1482                    doc,
1483                },
1484                span: self.span(kw.span.start, end),
1485            }),
1486            &mut self.fatal,
1487        )
1488    }
1489
1490    // Parser enforces kind-coherence: an enum cannot mix numeric and string initializers.
1491    fn parse_enum_decl(&mut self) -> Option<StmtId> {
1492        #[derive(Copy, Clone)]
1493        enum Kind {
1494            Number,
1495            String,
1496        }
1497        let doc = self.take_leading_doc();
1498        let kw = self.advance();
1499        let name = self.parse_type_decl_name("an", "enum")?;
1500
1501        if !matches!(self.peek().kind, TokenKind::LeftBrace) {
1502            self.error_at_peek("expected `{` after enum name");
1503            return None;
1504        }
1505        self.advance();
1506
1507        let mut members: Vec<EnumMember> = Vec::new();
1508        let mut first_explicit: Option<(Kind, Span)> = None;
1509        while !matches!(self.peek().kind, TokenKind::RightBrace | TokenKind::Eof) {
1510            // ASI inserts `;` after `,` on a newline — tolerate it so multi-line enums parse.
1511            if matches!(self.peek().kind, TokenKind::Semicolon) {
1512                self.advance();
1513                continue;
1514            }
1515            let member_doc = self.take_leading_doc();
1516            let member_name_tok = self.expect_identifier_name("expected enum member name")?;
1517            let member_name = self.ident_from_token(&member_name_tok);
1518
1519            let value = if matches!(self.peek().kind, TokenKind::Equals) {
1520                self.advance();
1521                let init = self.parse_enum_initializer()?;
1522                let (this_kind, this_span) = match &init {
1523                    EnumInitializer::Number { span, .. } => (Kind::Number, *span),
1524                    EnumInitializer::String { span, .. } => (Kind::String, *span),
1525                };
1526                match first_explicit {
1527                    None => first_explicit = Some((this_kind, this_span)),
1528                    Some((prev, _)) => {
1529                        let mismatched = matches!(
1530                            (prev, this_kind),
1531                            (Kind::Number, Kind::String) | (Kind::String, Kind::Number)
1532                        );
1533                        if mismatched {
1534                            self.error_at(
1535                                this_span,
1536                                "mixed numeric and string enum members are not allowed",
1537                            );
1538                        }
1539                    }
1540                }
1541                Some(init)
1542            } else {
1543                None
1544            };
1545
1546            let end_span = value
1547                .as_ref()
1548                .map_or(member_name.span, super::ast::EnumInitializer::span);
1549            let member_span = self.span(member_name.span.start, end_span.end);
1550            members.push(EnumMember {
1551                name: member_name,
1552                value,
1553                span: member_span,
1554                doc: member_doc,
1555            });
1556
1557            match self.peek().kind {
1558                // ASI supplies the `;` before a closing `}` and after a newline, so an
1559                // enum body sees one wherever a comma was omitted.
1560                TokenKind::Comma | TokenKind::Semicolon => {
1561                    self.advance();
1562                }
1563                TokenKind::RightBrace | TokenKind::Eof => {}
1564                _ => {
1565                    self.error_at_peek("expected `,` or `}` after enum member");
1566                    return None;
1567                }
1568            }
1569        }
1570
1571        if !matches!(self.peek().kind, TokenKind::RightBrace) {
1572            self.error_at_peek("expected `}` to close enum");
1573            return None;
1574        }
1575        let close = self.advance();
1576
1577        parse_arena_result(
1578            self.ast.try_push_stmt(Stmt {
1579                kind: StmtKind::EnumDecl { name, members, doc },
1580                span: self.span(kw.span.start, close.span.end),
1581            }),
1582            &mut self.fatal,
1583        )
1584    }
1585
1586    /// `export` on a top-level declaration. Two forms (multi-file.md §3):
1587    /// Form 1 marks a declaration (`export function …`) — the declaration is
1588    /// parsed normally and recorded in `exported_decls`; Form 2 is a re-export
1589    /// (`export { a, b as c } from "./util";` / `export { x };`) parsed into an
1590    /// `ExportFrom` node. `export` is top-level only; inside a function body it
1591    /// is a parse error. `export default` stays rejected.
1592    fn parse_export(&mut self) -> Option<StmtId> {
1593        let export_tok = self.advance();
1594        let export_span = export_tok.span;
1595
1596        let nested = self.block_depth > 0;
1597        if nested {
1598            self.error_at_with_help(
1599                export_span,
1600                "`export` statements must appear at the top of the file",
1601                vec!["move this `export` to the top level of the module".to_string()],
1602            );
1603            // Fall through to parse the declaration / list anyway so recovery
1604            // stays in sync, but return None below so it isn't recorded.
1605        }
1606
1607        // `export type { … }` — TS's type-only re-export, accepted as a no-op
1608        // synonym: exports carry both value and type spaces here.
1609        if self.peek_identifier_text_is("type")
1610            && matches!(self.peek_at(1).kind, TokenKind::LeftBrace)
1611        {
1612            self.advance();
1613        }
1614
1615        let declares = matches!(
1616            self.peek().kind,
1617            TokenKind::Function
1618                | TokenKind::Class
1619                | TokenKind::Const
1620                | TokenKind::Let
1621                | TokenKind::Interface
1622                | TokenKind::Enum
1623        ) || self.at_type_alias_head();
1624        if declares {
1625            // Carry any doc comment that attached to `export` onto the
1626            // declaration token so the declaration parser still sees it.
1627            if export_tok.leading_doc.is_some() && self.peek().leading_doc.is_none() {
1628                if let Some(token) = self.tokens.get_mut(self.pos) {
1629                    token.leading_doc = export_tok.leading_doc;
1630                } else {
1631                    return self.invariant_failure("export cursor is outside token stream");
1632                }
1633            }
1634            let id = self.parse_statement()?;
1635            if nested {
1636                return None;
1637            }
1638            self.ast.exported_decls.push(ExportedDecl {
1639                stmt: id,
1640                export_span,
1641            });
1642            return Some(id);
1643        }
1644
1645        match self.peek().kind {
1646            TokenKind::LeftBrace => {
1647                // A doc comment attaches to the `export` keyword, which we've
1648                // already consumed — carry it onto the re-export node.
1649                let doc = export_tok
1650                    .leading_doc
1651                    .as_ref()
1652                    .and_then(|raw| self.parse_doc(raw))
1653                    .or_else(|| self.take_leading_doc());
1654                self.parse_export_from(export_span, nested, doc)
1655            }
1656            TokenKind::Default => {
1657                self.error_at_peek_with_help(
1658                    "`export default` is not supported",
1659                    vec![
1660                        "default exports are out of scope; drop `default` — a named \
1661                         `export` marks the declaration visible to sibling modules"
1662                            .to_string(),
1663                    ],
1664                );
1665                None
1666            }
1667            _ => {
1668                self.error_at_peek_with_help(
1669                    "expected a declaration after `export`",
1670                    vec![
1671                        "`export` may precede `function`, `class`, `const`, `let`, `type`, \
1672                         `interface`, or `enum`, or take the form `export { … }`"
1673                            .to_string(),
1674                    ],
1675                );
1676                None
1677            }
1678        }
1679    }
1680
1681    /// Parse Form 2 re-export: `export { a, b as c } from "./util";` or the
1682    /// bare `export { x };`. The leading `export` has already been consumed.
1683    fn parse_export_from(
1684        &mut self,
1685        export_span: Span,
1686        nested: bool,
1687        doc: Option<crate::DocComment>,
1688    ) -> Option<StmtId> {
1689        let (specs, open_span) = self.parse_specifier_list(SpecifierList::Export)?;
1690        if specs.is_empty() {
1691            self.error_at_with_help(
1692                open_span,
1693                "empty export specifier list",
1694                vec!["name what you re-export: `export { name } from \"./util\";`".to_string()],
1695            );
1696            return None;
1697        }
1698
1699        let source = if self.peek_identifier_text_is("from") {
1700            self.advance();
1701            let module_tok = self.peek().clone();
1702            let module = if let TokenKind::StringLiteral(s) = &module_tok.kind {
1703                s.clone()
1704            } else {
1705                self.error_at_peek("expected module specifier string after `from`");
1706                return None;
1707            };
1708            self.advance();
1709            Some((module, module_tok.span))
1710        } else {
1711            None
1712        };
1713
1714        if !matches!(self.peek().kind, TokenKind::Semicolon) {
1715            self.error_at_peek("expected `;` after export statement");
1716            return None;
1717        }
1718        let semi = self.advance();
1719
1720        if nested {
1721            return None;
1722        }
1723
1724        parse_arena_result(
1725            self.ast.try_push_stmt(Stmt {
1726                kind: StmtKind::ExportFrom { specs, source, doc },
1727                span: self.span(export_span.start, semi.span.end),
1728            }),
1729            &mut self.fatal,
1730        )
1731    }
1732
1733    fn parse_import_decl(&mut self) -> Option<StmtId> {
1734        let doc = self.take_leading_doc();
1735        let kw = self.advance();
1736        let kw_span = kw.span;
1737
1738        let nested = self.block_depth > 0;
1739        if nested {
1740            self.error_at_with_help(
1741                kw_span,
1742                "`import` statements must appear at the top of the file",
1743                vec!["move this `import` outside any block / function body".to_string()],
1744            );
1745            // Continue parsing to consume the whole import so recovery isn't confused.
1746        }
1747
1748        let kind = self.parse_import_kind()?;
1749
1750        if !self.peek_identifier_text_is("from") {
1751            self.error_at_peek("expected `from` after import specifier list");
1752            return None;
1753        }
1754        self.advance();
1755
1756        let module_tok = self.peek().clone();
1757        let module = if let TokenKind::StringLiteral(s) = &module_tok.kind {
1758            s.clone()
1759        } else {
1760            self.error_at_peek("expected module specifier string after `from`");
1761            return None;
1762        };
1763        let module_span = module_tok.span;
1764        self.advance();
1765
1766        if !matches!(self.peek().kind, TokenKind::Semicolon) {
1767            self.error_at_peek("expected `;` after import statement");
1768            return None;
1769        }
1770        let semi = self.advance();
1771
1772        if nested {
1773            return None;
1774        }
1775
1776        parse_arena_result(
1777            self.ast.try_push_stmt(Stmt {
1778                kind: StmtKind::Import {
1779                    module,
1780                    module_span,
1781                    kind,
1782                    doc,
1783                },
1784                span: self.span(kw_span.start, semi.span.end),
1785            }),
1786            &mut self.fatal,
1787        )
1788    }
1789
1790    fn parse_import_kind(&mut self) -> Option<ImportKind> {
1791        // `import type …` — TS's type-only import, accepted as a no-op synonym:
1792        // imports carry both value and type spaces here. `import type from "m"`
1793        // stays a namespace import named `type`, so `type` is a modifier only
1794        // when what follows can't be a complete import clause without it.
1795        if self.peek_identifier_text_is("type")
1796            && match self.peek_at(1).kind {
1797                TokenKind::LeftBrace | TokenKind::Star => true,
1798                TokenKind::Identifier => {
1799                    !(self.peek_at_is_word(1, "from")
1800                        && matches!(self.peek_at(2).kind, TokenKind::StringLiteral(_)))
1801                }
1802                _ => false,
1803            }
1804        {
1805            self.advance();
1806        }
1807
1808        match self.peek().kind {
1809            TokenKind::LeftBrace => self.parse_named_imports(),
1810            TokenKind::Identifier => {
1811                let local_tok = self.advance();
1812                let local_name = self.ident_from_token(&local_tok);
1813                if matches!(self.peek().kind, TokenKind::Comma) {
1814                    self.error_at_peek_with_help(
1815                        "combining default and named imports is not supported",
1816                        vec!["use two separate `import` statements".to_string()],
1817                    );
1818                    return None;
1819                }
1820                Some(ImportKind::Namespace { local_name })
1821            }
1822            // `import * as ns from "pkg"` — forgiveness synonym for `import ns from "pkg"`.
1823            // Both are identical since we have no default exports.
1824            TokenKind::Star => {
1825                self.advance();
1826                if !self.peek_identifier_text_is("as") {
1827                    self.error_at_peek_with_help(
1828                        "expected `as <name>` after `import *`",
1829                        vec![
1830                            "name the namespace binding: \
1831                             `import * as ns from \"pkg\";` (or use the \
1832                             short form: `import ns from \"pkg\";`)"
1833                                .to_string(),
1834                        ],
1835                    );
1836                    return None;
1837                }
1838                self.advance();
1839                let local_tok = self.expect_identifier("expected namespace name after `as`")?;
1840                let local_name = self.ident_from_token(&local_tok);
1841                Some(ImportKind::Namespace { local_name })
1842            }
1843            TokenKind::StringLiteral(_) => {
1844                self.error_at_peek_with_help(
1845                    "side-effect-only imports are not supported",
1846                    vec![
1847                        "name what you import: `import { name } from \"pkg\";` \
1848                         or `import ns from \"pkg\";`"
1849                            .to_string(),
1850                    ],
1851                );
1852                None
1853            }
1854            _ => {
1855                self.error_at_peek("expected `{` or namespace name after `import`");
1856                None
1857            }
1858        }
1859    }
1860
1861    fn parse_named_imports(&mut self) -> Option<ImportKind> {
1862        let (specs, open_span) = self.parse_specifier_list(SpecifierList::Import)?;
1863        if specs.is_empty() {
1864            self.error_at_with_help(
1865                open_span,
1866                "empty import specifier list",
1867                vec!["name what you import: `import { name } from \"pkg\";`".to_string()],
1868            );
1869            return None;
1870        }
1871        Some(ImportKind::Named(specs))
1872    }
1873
1874    /// Parse a `{ a, b as c }` specifier list shared by `import` and re-`export`.
1875    /// `list` says whether the specifiers bind local names (an import) and names the
1876    /// construct in diagnostics. Returns the specifiers (possibly
1877    /// empty — callers reject empty with construct-specific help) and the span of
1878    /// the opening brace.
1879    fn parse_specifier_list(
1880        &mut self,
1881        list: SpecifierList,
1882    ) -> Option<(Vec<ImportSpecifier>, Span)> {
1883        let what = list.noun();
1884        let open = self.advance();
1885        let mut specs: Vec<ImportSpecifier> = Vec::new();
1886        while !matches!(self.peek().kind, TokenKind::RightBrace | TokenKind::Eof) {
1887            // Inline type-only modifier (`{ type X }`), accepted as a no-op
1888            // synonym. `{ type as t }` instead names the binding `type`.
1889            if self.peek_identifier_text_is("type")
1890                && matches!(self.peek_at(1).kind, TokenKind::Identifier)
1891                && !self.peek_at_is_word(1, "as")
1892            {
1893                self.advance();
1894            }
1895            let imported_tok =
1896                self.expect_identifier_name(&format!("expected {what} specifier name"))?;
1897            let imported_name = self.ident_from_token(&imported_tok);
1898
1899            let binds_local = list == SpecifierList::Import;
1900            let local_name = if self.peek_identifier_text_is("as") {
1901                self.advance();
1902                let local_tok = self.expect_identifier_name("expected local name after `as`")?;
1903                if binds_local {
1904                    self.reject_strict_mode_reserved_word(&local_tok);
1905                }
1906                self.ident_from_token(&local_tok)
1907            } else {
1908                if binds_local {
1909                    self.reject_strict_mode_reserved_word(&imported_tok);
1910                }
1911                imported_name.clone()
1912            };
1913
1914            specs.push(ImportSpecifier {
1915                imported_name,
1916                local_name,
1917            });
1918
1919            match self.peek().kind {
1920                TokenKind::Comma => {
1921                    self.advance();
1922                }
1923                TokenKind::RightBrace | TokenKind::Eof => {}
1924                _ => {
1925                    self.error_at_peek(format!("expected `,` or `}}` after {what} specifier"));
1926                    return None;
1927                }
1928            }
1929        }
1930
1931        if !matches!(self.peek().kind, TokenKind::RightBrace) {
1932            self.error_at_peek(format!("expected `}}` to close {what} specifier list"));
1933            return None;
1934        }
1935        self.advance();
1936
1937        Some((specs, open.span))
1938    }
1939
1940    fn parse_enum_initializer(&mut self) -> Option<EnumInitializer> {
1941        let neg = if matches!(self.peek().kind, TokenKind::Minus) {
1942            Some(self.advance())
1943        } else {
1944            None
1945        };
1946        let tok = self.advance();
1947        match &tok.kind {
1948            TokenKind::NumberLiteral(n) => {
1949                let value = if neg.is_some() { -n } else { *n };
1950                // tsc constant-folds enum initializers, which turns `-0` into `0`.
1951                let value = if value == 0.0 { 0.0 } else { value };
1952                let start = neg.as_ref().map_or(tok.span.start, |t| t.span.start);
1953                Some(EnumInitializer::Number {
1954                    value,
1955                    span: self.span(start, tok.span.end),
1956                })
1957            }
1958            TokenKind::StringLiteral(s) => {
1959                if let Some(neg_tok) = &neg {
1960                    self.error_at(
1961                        self.span(neg_tok.span.start, tok.span.end),
1962                        "cannot negate a string enum initializer",
1963                    );
1964                    return None;
1965                }
1966                Some(EnumInitializer::String {
1967                    value: s.clone(),
1968                    span: tok.span,
1969                })
1970            }
1971            _ => {
1972                let span = neg.map_or(tok.span, |t| self.span(t.span.start, tok.span.end));
1973                self.error_at(
1974                    span,
1975                    "expected a number or string literal for enum initializer",
1976                );
1977                None
1978            }
1979        }
1980    }
1981
1982    /// Whether the token at `index` is `operator` (`keyof`, `readonly`) used as a
1983    /// prefix type operator.
1984    ///
1985    /// Both are contextual keywords — valid as ordinary identifiers in TypeScript and
1986    /// Submilli — so they are only operators when a type follows. A bare type name is
1987    /// never followed by another type name, so requiring one separates `keyof T` from
1988    /// a type literally named `keyof`.
1989    fn is_contextual_type_operator(&self, index: usize, operator: &str) -> bool {
1990        let Some(tok) = self.tokens.get(index) else {
1991            return false;
1992        };
1993        if !self.token_is_word(tok, operator) {
1994            return false;
1995        }
1996        matches!(
1997            self.tokens.get(index + 1).map(|t| &t.kind),
1998            Some(
1999                TokenKind::Identifier
2000                    | TokenKind::Void
2001                    | TokenKind::LeftBrace
2002                    | TokenKind::LeftParen
2003                    | TokenKind::LeftBracket
2004                    | TokenKind::StringLiteral(_)
2005                    | TokenKind::NumberLiteral(_)
2006                    | TokenKind::BooleanLiteral(_)
2007                    | TokenKind::NullLiteral
2008            )
2009        )
2010    }
2011
2012    fn parse_type_argument_list(&mut self) -> Option<(Vec<TypeAnnotation>, u32)> {
2013        let lt = self.advance();
2014        if matches!(self.peek().kind, TokenKind::GreaterThan) {
2015            self.error_at(lt.span, "empty generic argument list `<>` is not allowed");
2016            return None;
2017        }
2018        let mut args = Vec::new();
2019        loop {
2020            args.push(self.parse_type_annotation()?);
2021            match self.peek().kind {
2022                TokenKind::Comma => {
2023                    self.advance();
2024                    if matches!(self.peek().kind, TokenKind::GreaterThan) {
2025                        let gt = self.advance();
2026                        return Some((args, gt.span.end));
2027                    }
2028                }
2029                TokenKind::GreaterThan => {
2030                    let gt = self.advance();
2031                    return Some((args, gt.span.end));
2032                }
2033                _ => {
2034                    self.error_at(
2035                        lt.span,
2036                        "expected `,` or `>` to close generic argument list",
2037                    );
2038                    return None;
2039                }
2040            }
2041        }
2042    }
2043
2044    /// The name a `class`, `interface`, `type` or `enum` declares, `noun` naming
2045    /// which. A built-in type's name is reported, as in TypeScript: `class number {}`
2046    /// would read as the built-in in some positions and the class in others.
2047    fn parse_type_decl_name(&mut self, article: &str, noun: &str) -> Option<Ident> {
2048        let name_tok = self.expect_identifier(&format!("expected {noun} name"))?;
2049        let name = self.ident_from_token(&name_tok);
2050        if BUILT_IN_TYPE_NAMES.contains(&name.name.as_str()) {
2051            self.error_at_with_help(
2052                name.span,
2053                format!(
2054                    "`{}` is a built-in type and can't be used as {article} {noun} name",
2055                    name.name
2056                ),
2057                vec![format!("rename the {noun}")],
2058            );
2059        }
2060        Some(name)
2061    }
2062
2063    fn parse_generic_param_list(&mut self) -> Option<Vec<Ident>> {
2064        let lt = self.advance();
2065        if matches!(self.peek().kind, TokenKind::GreaterThan) {
2066            self.error_at(lt.span, "empty generic parameter list `<>` is not allowed");
2067            return None;
2068        }
2069        let mut generics = Vec::new();
2070        loop {
2071            let name_tok = self.expect_identifier("expected generic parameter name")?;
2072            let name = self.ident_from_token(&name_tok);
2073            // A duplicate is reported and dropped, as `tsc` drops it: the first
2074            // parameter keeps the name, and the type arguments count against the
2075            // parameters that remain.
2076            if generics.iter().any(|g: &Ident| g.name == name.name) {
2077                self.error_at_with_help(
2078                    name.span,
2079                    format!("duplicate type parameter `{}`", name.name),
2080                    vec!["give each type parameter its own name".to_string()],
2081                );
2082            } else {
2083                generics.push(name);
2084            }
2085            match self.peek().kind {
2086                TokenKind::Comma => {
2087                    self.advance();
2088                    if matches!(self.peek().kind, TokenKind::GreaterThan) {
2089                        self.advance();
2090                        return Some(generics);
2091                    }
2092                }
2093                TokenKind::GreaterThan => {
2094                    self.advance();
2095                    return Some(generics);
2096                }
2097                _ => {
2098                    self.error_at_peek("expected `,` or `>`");
2099                    return None;
2100                }
2101            }
2102        }
2103    }
2104
2105    fn parse_param_list(&mut self, allow_param_properties: bool) -> Option<Vec<ParamDecl>> {
2106        if matches!(self.peek().kind, TokenKind::RightParen) {
2107            return Some(Vec::new());
2108        }
2109        let mut params = Vec::new();
2110        loop {
2111            let param = self.parse_param(allow_param_properties)?;
2112            let is_rest = param.rest;
2113            params.push(param);
2114            match self.peek().kind {
2115                TokenKind::Comma => {
2116                    if is_rest {
2117                        let comma = self.advance();
2118                        self.error_at(comma.span, "rest parameter must be the last parameter");
2119                        return None;
2120                    }
2121                    self.advance();
2122                    if matches!(self.peek().kind, TokenKind::RightParen) {
2123                        break;
2124                    }
2125                }
2126                TokenKind::RightParen => break,
2127                _ => {
2128                    self.error_at_peek("expected `,` or `)`");
2129                    return None;
2130                }
2131            }
2132        }
2133        self.check_parameter_order(params.iter().map(ParamOrder::of_param));
2134        Some(params)
2135    }
2136
2137    fn parse_param(&mut self, allow_param_properties: bool) -> Option<ParamDecl> {
2138        // Parameter properties: `constructor(public x: T)` declares + assigns a
2139        // field. A modifier keyword counts only when a parameter name follows it
2140        // (so a param literally named `public`/`readonly` still parses).
2141        let modifiers = if self.peek_word_is_class_modifier("public")
2142            || self.peek_word_is_class_modifier("private")
2143            || self.peek_word_is_class_modifier("readonly")
2144        {
2145            let span = self.peek().span;
2146            let parsed = self.parse_class_modifiers();
2147            if allow_param_properties {
2148                Some(parsed)
2149            } else {
2150                self.error_at_with_help(
2151                    span,
2152                    "parameter properties are only allowed in a constructor",
2153                    vec![
2154                        "move the `public`/`private`/`readonly` modifier to a constructor parameter"
2155                            .to_string(),
2156                    ],
2157                );
2158                None
2159            }
2160        } else {
2161            None
2162        };
2163
2164        let rest = if matches!(self.peek().kind, TokenKind::DotDotDot) {
2165            self.advance();
2166            true
2167        } else {
2168            false
2169        };
2170        if modifiers.is_some() && rest {
2171            self.error_at_peek("a parameter property cannot be a rest parameter");
2172            return None;
2173        }
2174
2175        // Placeholder `name` is the empty identifier at the pattern's span;
2176        // the pre-infer lowering pass replaces it with a fresh `#pattern_p_N`.
2177        let (name, pattern) = match self.peek().kind {
2178            TokenKind::LeftBrace | TokenKind::LeftBracket => {
2179                if rest {
2180                    self.error_at_peek("rest parameter cannot be destructured");
2181                    return None;
2182                }
2183                if modifiers.is_some() {
2184                    self.error_at_peek("a parameter property cannot be destructured");
2185                    return None;
2186                }
2187                let binding = self.parse_binding()?;
2188                let placeholder = Ident {
2189                    name: String::new(),
2190                    span: binding.span(),
2191                };
2192                (placeholder, Some(binding))
2193            }
2194            _ => {
2195                let name_tok = self.expect_identifier(if rest {
2196                    "expected identifier after `...`"
2197                } else {
2198                    "expected parameter name"
2199                })?;
2200                let name = self.ident_from_token(&name_tok);
2201                (name, None)
2202            }
2203        };
2204
2205        let optional = self.parse_parameter_optional(rest, pattern.is_some())?;
2206        let ty = if matches!(self.peek().kind, TokenKind::Colon) {
2207            self.advance();
2208            Some(self.parse_type_annotation()?)
2209        } else if !rest && matches!(self.peek().kind, TokenKind::Equals) {
2210            None
2211        } else {
2212            if rest {
2213                self.error_at_peek_with_help(
2214                    "rest parameter requires a type annotation",
2215                    vec!["function name(...args: T[]): R { … }".to_string()],
2216                );
2217            } else {
2218                self.error_at_peek_with_help(
2219                    "parameter requires a type annotation",
2220                    vec!["add a type (`x: T`) or a default initializer (`x = value`)".to_string()],
2221                );
2222            }
2223            return None;
2224        };
2225        let default = self.parse_parameter_default(rest, optional)?;
2226        Some(ParamDecl {
2227            name,
2228            ty,
2229            default,
2230            optional,
2231            pattern,
2232            rest,
2233            modifiers,
2234        })
2235    }
2236
2237    fn parse_parameter_optional(&mut self, rest: bool, pattern: bool) -> Option<bool> {
2238        if !matches!(self.peek().kind, TokenKind::Question) {
2239            return Some(false);
2240        }
2241        let question = self.advance();
2242        if rest || pattern {
2243            self.error_at(
2244                question.span,
2245                if rest {
2246                    "a rest parameter cannot be optional"
2247                } else {
2248                    "a destructured parameter cannot be optional; use a default value"
2249                },
2250            );
2251            return None;
2252        }
2253        Some(true)
2254    }
2255
2256    /// The `= value` after a parameter, if any. An optional parameter's default is
2257    /// reported but still parsed, so the rest of the list is checked too.
2258    fn parse_parameter_default(&mut self, rest: bool, optional: bool) -> Option<Option<ExprId>> {
2259        if !matches!(self.peek().kind, TokenKind::Equals) {
2260            return Some(None);
2261        }
2262        if rest {
2263            self.error_rest_parameter_default();
2264            return None;
2265        }
2266        if optional {
2267            self.error_at_peek_with_help(
2268                "an optional parameter cannot have a default value",
2269                vec!["drop the `?`: a parameter with a default is already optional".to_string()],
2270            );
2271        }
2272        self.advance();
2273        Some(Some(self.parse_expression()?))
2274    }
2275
2276    /// Rest's default is the empty array, so an explicit one is rejected.
2277    fn error_rest_parameter_default(&mut self) {
2278        self.error_at_peek_with_help(
2279            "rest parameter cannot have a default value",
2280            vec!["omit the `= …`; an unspecified rest defaults to `[]`".to_string()],
2281        );
2282    }
2283
2284    /// A caller cannot skip an optional argument and still pass a later one, so a
2285    /// required parameter after an optional one is reported. Only the first is, and
2286    /// parsing continues.
2287    fn check_parameter_order<'p>(&mut self, params: impl IntoIterator<Item = ParamOrder<'p>>) {
2288        let mut follows_optional = false;
2289        for param in params {
2290            if param.optional {
2291                follows_optional = true;
2292                continue;
2293            }
2294            if !follows_optional || !param.required {
2295                continue;
2296            }
2297            let label = match param.name {
2298                Some(name) => format!("parameter `{name}`"),
2299                None => "destructured parameter".to_string(),
2300            };
2301            self.error_at_with_help(
2302                param.span,
2303                format!("required {label} cannot follow an optional parameter"),
2304                vec!["make it optional too (`x?: T`), give it a default (`x: T = …`), or move it before the optional parameters".to_string()],
2305            );
2306            return;
2307        }
2308    }
2309
2310    /// Drop a `;` that ASI inserted directly before `next`, where the grammar requires
2311    /// `next` and so no statement can have ended. ASI is a token-stream pre-pass with no
2312    /// parser feedback, so it cannot see that a `}` ends a `do` body rather than a
2313    /// statement, or that the `{` after a signature's return annotation opens its body.
2314    fn eat_asi_semicolon_before(&mut self, next: TokenKind) {
2315        if matches!(self.peek().kind, TokenKind::Semicolon) && self.peek_at(1).kind == next {
2316            self.advance();
2317        }
2318    }
2319
2320    fn parse_block(&mut self) -> Option<StmtId> {
2321        self.eat_asi_semicolon_before(TokenKind::LeftBrace);
2322        if !matches!(self.peek().kind, TokenKind::LeftBrace) {
2323            self.error_at_peek("expected `{`");
2324            return None;
2325        }
2326        let open = self.advance();
2327        self.block_depth += 1;
2328
2329        let mut stmts = Vec::new();
2330        while !matches!(self.peek().kind, TokenKind::RightBrace | TokenKind::Eof) {
2331            if matches!(self.peek().kind, TokenKind::Semicolon) {
2332                self.advance();
2333                continue;
2334            }
2335            let pos_before = self.pos;
2336            if let Some(id) = self.parse_statement() {
2337                stmts.push(id);
2338            } else {
2339                self.recover();
2340                if self.pos == pos_before && !self.is_at_eof() {
2341                    self.advance();
2342                }
2343            }
2344        }
2345
2346        self.block_depth -= 1;
2347        if !matches!(self.peek().kind, TokenKind::RightBrace) {
2348            self.error_at_peek("expected `}`");
2349            return None;
2350        }
2351        let close = self.advance();
2352
2353        let span = self.span(open.span.start, close.span.end);
2354        parse_arena_result(
2355            self.ast.try_push_stmt(Stmt {
2356                kind: StmtKind::Block(stmts),
2357                span,
2358            }),
2359            &mut self.fatal,
2360        )
2361    }
2362
2363    // Control-flow bodies accept either a braced block or a single statement;
2364    // `if (c) return x;` is identical to `if (c) { return x; }` under our type
2365    // system (forgiveness principle). Downstream passes assume a `Block`, so a
2366    // braceless statement is wrapped in one.
2367    fn parse_control_body(&mut self) -> Option<StmtId> {
2368        if matches!(self.peek().kind, TokenKind::LeftBrace) {
2369            return self.parse_block();
2370        }
2371        // A binding here would go out of scope as soon as it was made. TypeScript
2372        // or JavaScript rejects each of these, so it is reported and then parsed
2373        // as usual. Only a declaration is reported: a `let` followed by
2374        // anything else already fails to parse.
2375        let keyword = match self.peek().kind {
2376            TokenKind::Let => Some(("a", "let")),
2377            TokenKind::Const => Some(("a", "const")),
2378            TokenKind::Function => Some(("a", "function")),
2379            // A nested `class` is reported wherever it is: see `parse_class_decl`.
2380            TokenKind::Interface => Some(("an", "interface")),
2381            TokenKind::Enum => Some(("an", "enum")),
2382            _ if self.peek_identifier_text_is("type") => Some(("a", "type")),
2383            _ => None,
2384        };
2385        // After `let`/`const`, a bracket or brace starts a destructuring
2386        // pattern; after `type`, which is also an ordinary identifier, it is an
2387        // index or nothing a declaration can be.
2388        let declares = match self.peek_at(1).kind {
2389            TokenKind::Identifier => true,
2390            TokenKind::LeftBracket | TokenKind::LeftBrace => !matches!(keyword, Some((_, "type"))),
2391            _ => false,
2392        };
2393        if let Some((article, keyword)) = keyword
2394            && declares
2395        {
2396            self.error_at_peek_with_help(
2397                format!(
2398                    "{article} `{keyword}` declaration can't be the body of a statement without braces"
2399                ),
2400                vec![format!("wrap it in braces: `{{ {keyword} … }}`")],
2401            );
2402        }
2403        self.block_depth += 1;
2404        let stmt = self.parse_statement();
2405        self.block_depth -= 1;
2406        let stmt = stmt?;
2407        let span = parse_arena_result(self.ast.try_stmt(stmt), &mut self.fatal)?.span;
2408        parse_arena_result(
2409            self.ast.try_push_stmt(Stmt {
2410                kind: StmtKind::Block(vec![stmt]),
2411                span,
2412            }),
2413            &mut self.fatal,
2414        )
2415    }
2416
2417    /// A loop body may also be a lone `;`: `while (advance());` does all its work in
2418    /// the header. Branches do not accept it; see `parse_branch_body`.
2419    fn parse_loop_body(&mut self) -> Option<StmtId> {
2420        if !matches!(self.peek().kind, TokenKind::Semicolon) {
2421            return self.parse_control_body();
2422        }
2423        self.empty_body_at_semicolon()
2424    }
2425
2426    /// An `if` or `else` body. Unlike a loop, a branch cannot be a lone `;`: `if (c);`
2427    /// silently detaches the block meant to follow it. The `;` is reported, then
2428    /// consumed as an empty body so an `else` after it doesn't cascade into a second error.
2429    fn parse_branch_body(&mut self, keyword: &str) -> Option<StmtId> {
2430        if !matches!(self.peek().kind, TokenKind::Semicolon) {
2431            return self.parse_control_body();
2432        }
2433        self.error_at_peek_with_help(
2434            format!("`{keyword}` has an empty body"),
2435            vec![format!("remove the `;` after `{keyword}`")],
2436        );
2437        self.empty_body_at_semicolon()
2438    }
2439
2440    /// Consumes a lone `;` as an empty block spanning it.
2441    fn empty_body_at_semicolon(&mut self) -> Option<StmtId> {
2442        let semi = self.advance();
2443        parse_arena_result(
2444            self.ast.try_push_stmt(Stmt {
2445                kind: StmtKind::Block(vec![]),
2446                span: semi.span,
2447            }),
2448            &mut self.fatal,
2449        )
2450    }
2451
2452    fn parse_if(&mut self) -> Option<StmtId> {
2453        self.with_recursion_limit(Self::parse_if_inner)
2454    }
2455
2456    fn parse_if_inner(&mut self) -> Option<StmtId> {
2457        let kw = self.advance();
2458        let condition = self.parse_paren_condition()?;
2459        let then_block = self.parse_branch_body("if")?;
2460
2461        let else_block = if matches!(self.peek().kind, TokenKind::Else) {
2462            self.advance();
2463            if matches!(self.peek().kind, TokenKind::If) {
2464                Some(self.parse_if()?)
2465            } else {
2466                Some(self.parse_branch_body("else")?)
2467            }
2468        } else {
2469            None
2470        };
2471
2472        let end = match else_block {
2473            Some(id) => {
2474                parse_arena_result(self.ast.try_stmt(id), &mut self.fatal)?
2475                    .span
2476                    .end
2477            }
2478            None => {
2479                parse_arena_result(self.ast.try_stmt(then_block), &mut self.fatal)?
2480                    .span
2481                    .end
2482            }
2483        };
2484
2485        parse_arena_result(
2486            self.ast.try_push_stmt(Stmt {
2487                kind: StmtKind::If {
2488                    condition,
2489                    then_block,
2490                    else_block,
2491                },
2492                span: self.span(kw.span.start, end),
2493            }),
2494            &mut self.fatal,
2495        )
2496    }
2497
2498    fn parse_while(&mut self) -> Option<StmtId> {
2499        let kw = self.advance();
2500        let condition = self.parse_paren_condition()?;
2501        let body = self.parse_loop_body()?;
2502        let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
2503            .span
2504            .end;
2505
2506        parse_arena_result(
2507            self.ast.try_push_stmt(Stmt {
2508                kind: StmtKind::While { condition, body },
2509                span: self.span(kw.span.start, body_end),
2510            }),
2511            &mut self.fatal,
2512        )
2513    }
2514
2515    // Disambiguates C-style for and for-of via non-consuming lookahead for `of`.
2516    fn parse_for(&mut self) -> Option<StmtId> {
2517        let kw = self.advance();
2518        if !matches!(self.peek().kind, TokenKind::LeftParen) {
2519            self.error_at_peek("expected `(` after `for`");
2520            return None;
2521        }
2522        self.advance();
2523
2524        if self.scan_for_of_shape() {
2525            self.parse_for_of_tail(kw)
2526        } else {
2527            self.parse_c_for_tail(kw)
2528        }
2529    }
2530
2531    // Counts bracket/angle depth to skip past optional type annotations.
2532    fn scan_for_of_shape(&self) -> bool {
2533        if !matches!(self.peek().kind, TokenKind::Let | TokenKind::Const) {
2534            return false;
2535        }
2536        // Object patterns are tentatively accepted here; `parse_for_of_tail` rejects them.
2537        let (start, mut depth) = match self.peek_at(1).kind {
2538            TokenKind::Identifier => match self.peek_at(2).kind {
2539                TokenKind::Identifier if self.peek_at_is_word(2, "of") => return true,
2540                TokenKind::Colon => (3, 0i32),
2541                _ => return false,
2542            },
2543            TokenKind::LeftBracket | TokenKind::LeftBrace => (2, 1i32),
2544            _ => return false,
2545        };
2546        let mut i = start;
2547        loop {
2548            match self.peek_at(i).kind {
2549                TokenKind::Eof => return false,
2550                TokenKind::LessThan
2551                | TokenKind::LeftParen
2552                | TokenKind::LeftBracket
2553                | TokenKind::LeftBrace => {
2554                    depth += 1;
2555                }
2556                TokenKind::GreaterThan
2557                | TokenKind::RightParen
2558                | TokenKind::RightBracket
2559                | TokenKind::RightBrace => {
2560                    if depth == 0 {
2561                        return false;
2562                    }
2563                    depth -= 1;
2564                }
2565                // A user type literally named `of` in the annotation would
2566                // misclassify here and error loudly downstream — acceptable.
2567                TokenKind::Identifier if depth == 0 && self.peek_at_is_word(i, "of") => {
2568                    return true;
2569                }
2570                TokenKind::Equals | TokenKind::Semicolon if depth == 0 => return false,
2571                _ => {}
2572            }
2573            i += 1;
2574        }
2575    }
2576
2577    // The trailing `;` of init-statement parsers doubles as the first `;` of the for header.
2578    fn parse_c_for_tail(&mut self, kw: Token) -> Option<StmtId> {
2579        let init = if matches!(self.peek().kind, TokenKind::Semicolon) {
2580            self.advance();
2581            None
2582        } else {
2583            let init_id = match self.peek().kind {
2584                TokenKind::Let => self.parse_let_or_const(false)?,
2585                TokenKind::Const => self.parse_let_or_const(true)?,
2586                _ => self.parse_expression_statement()?,
2587            };
2588            Some(init_id)
2589        };
2590        let for_stmt = self.finish_c_for(kw, init);
2591        if for_stmt.is_none()
2592            && let Some(init) = init
2593        {
2594            self.discard_detached_pattern(init);
2595        }
2596        for_stmt
2597    }
2598
2599    fn finish_c_for(&mut self, kw: Token, init: Option<StmtId>) -> Option<StmtId> {
2600        let condition = if matches!(self.peek().kind, TokenKind::Semicolon) {
2601            None
2602        } else {
2603            Some(self.parse_expression()?)
2604        };
2605        if !matches!(self.peek().kind, TokenKind::Semicolon) {
2606            self.error_at_peek("expected `;` after `for` condition");
2607            return None;
2608        }
2609        self.advance();
2610
2611        let update = if matches!(self.peek().kind, TokenKind::RightParen) {
2612            None
2613        } else {
2614            Some(self.parse_for_update_stmt()?)
2615        };
2616        if !matches!(self.peek().kind, TokenKind::RightParen) {
2617            self.error_at_peek("expected `)` after `for` update");
2618            return None;
2619        }
2620        self.advance();
2621
2622        let body = self.parse_loop_body()?;
2623        let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
2624            .span
2625            .end;
2626        parse_arena_result(
2627            self.ast.try_push_stmt(Stmt {
2628                kind: StmtKind::For {
2629                    init,
2630                    condition,
2631                    update,
2632                    body,
2633                },
2634                span: self.span(kw.span.start, body_end),
2635            }),
2636            &mut self.fatal,
2637        )
2638    }
2639
2640    /// A destructuring `for` initializer is lowered with its loop, so one whose
2641    /// loop failed to parse would otherwise reach inference unlowered.
2642    fn discard_detached_pattern(&mut self, init: StmtId) {
2643        let Some(stmt) = parse_arena_result(self.ast.try_stmt_mut(init), &mut self.fatal) else {
2644            return;
2645        };
2646        if matches!(
2647            stmt.kind,
2648            StmtKind::LetPattern { .. } | StmtKind::ConstPattern { .. }
2649        ) {
2650            stmt.kind = StmtKind::Block(Vec::new());
2651        }
2652    }
2653
2654    fn parse_for_update_stmt(&mut self) -> Option<StmtId> {
2655        let expr_id = self.parse_expression()?;
2656        let span = parse_arena_result(self.ast.try_expr(expr_id), &mut self.fatal)?.span;
2657        let kind = self.statement_kind_for(expr_id)?;
2658        parse_arena_result(self.ast.try_push_stmt(Stmt { kind, span }), &mut self.fatal)
2659    }
2660
2661    fn parse_for_of_tail(&mut self, kw: Token) -> Option<StmtId> {
2662        let binding_tok = self.advance();
2663        let binding_kind = match binding_tok.kind {
2664            TokenKind::Let => crate::BindingKind::Let,
2665            TokenKind::Const => crate::BindingKind::Const,
2666            _ => return self.invariant_failure("scan_for_of_shape gated this"),
2667        };
2668        let (name, binding) = match self.peek().kind {
2669            TokenKind::LeftBracket => {
2670                let b = self.parse_binding()?;
2671                (None, Some(b))
2672            }
2673            TokenKind::LeftBrace => {
2674                let b = self.parse_binding()?;
2675                self.error_at(
2676                    b.span(),
2677                    "object destructuring is not supported in `for-of`; use array destructuring or unpack inside the loop body",
2678                );
2679                return None;
2680            }
2681            _ => {
2682                let name_tok =
2683                    self.expect_identifier("expected identifier after `let`/`const` in `for-of`")?;
2684                (Some(self.ident_from_token(&name_tok)), None)
2685            }
2686        };
2687        let ty = if matches!(self.peek().kind, TokenKind::Colon) {
2688            self.advance();
2689            Some(self.parse_type_annotation()?)
2690        } else {
2691            None
2692        };
2693        if !self.peek_identifier_text_is("of") {
2694            self.error_at_peek("expected `of` in `for-of`");
2695            return None;
2696        }
2697        self.advance();
2698
2699        let iter = self.parse_expression()?;
2700        if !matches!(self.peek().kind, TokenKind::RightParen) {
2701            self.error_at_peek("expected `)` after `for-of` iterable");
2702            return None;
2703        }
2704        self.advance();
2705
2706        let body = self.parse_loop_body()?;
2707        let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
2708            .span
2709            .end;
2710        let kind = match (binding, name) {
2711            (Some(binding), _) => StmtKind::ForOfPattern {
2712                binding_kind,
2713                binding,
2714                ty,
2715                iter,
2716                body,
2717            },
2718            (None, Some(name)) => StmtKind::ForOf {
2719                binding_kind,
2720                name,
2721                ty,
2722                iter,
2723                body,
2724            },
2725            (None, None) => return self.invariant_failure("either binding or name must be Some"),
2726        };
2727        parse_arena_result(
2728            self.ast.try_push_stmt(Stmt {
2729                kind,
2730                span: self.span(kw.span.start, body_end),
2731            }),
2732            &mut self.fatal,
2733        )
2734    }
2735
2736    fn parse_do_while(&mut self) -> Option<StmtId> {
2737        let kw = self.advance();
2738        let body = self.parse_loop_body()?;
2739        self.eat_asi_semicolon_before(TokenKind::While);
2740        if !matches!(self.peek().kind, TokenKind::While) {
2741            self.error_at_peek("expected `while` after `do` body");
2742            return None;
2743        }
2744        self.advance();
2745        let condition = self.parse_paren_condition()?;
2746        // ECMAScript inserts the terminator after a do-while's `)` unconditionally, so the
2747        // `;` is optional here — ASI can't supply it, since that `)` closes a `while`
2748        // header everywhere else.
2749        let end = if matches!(self.peek().kind, TokenKind::Semicolon) {
2750            self.advance().span.end
2751        } else {
2752            parse_arena_result(self.ast.try_expr(condition), &mut self.fatal)?
2753                .span
2754                .end
2755        };
2756        parse_arena_result(
2757            self.ast.try_push_stmt(Stmt {
2758                kind: StmtKind::DoWhile { body, condition },
2759                span: self.span(kw.span.start, end),
2760            }),
2761            &mut self.fatal,
2762        )
2763    }
2764
2765    // Multiple consecutive `case`/`default` labels share the next body.
2766    fn parse_switch(&mut self) -> Option<StmtId> {
2767        let kw = self.advance();
2768        if !matches!(self.peek().kind, TokenKind::LeftParen) {
2769            self.error_at_peek("expected `(` after `switch`");
2770            return None;
2771        }
2772        self.advance();
2773        let discriminant = self.parse_expression()?;
2774        if !matches!(self.peek().kind, TokenKind::RightParen) {
2775            self.error_at_peek("expected `)` after `switch` discriminant");
2776            return None;
2777        }
2778        self.advance();
2779
2780        if !matches!(self.peek().kind, TokenKind::LeftBrace) {
2781            self.error_at_peek("expected `{` to start `switch` body");
2782            return None;
2783        }
2784        let open = self.advance();
2785        self.block_depth += 1;
2786
2787        let mut cases: Vec<SwitchCase> = Vec::new();
2788        let mut default: Option<SwitchDefault> = None;
2789        let mut pending_values: Vec<ExprId> = Vec::new();
2790        let mut pending_label_start: Option<u32> = None;
2791        let mut pending_default: Option<Span> = None;
2792
2793        loop {
2794            match self.peek().kind {
2795                TokenKind::Eof => break,
2796                // ASI inserts `;` after `:` on a newline — treat it as a no-op here.
2797                TokenKind::Semicolon => {
2798                    self.advance();
2799                }
2800                TokenKind::RightBrace => {
2801                    // Case bodies are required (typechecker enforces break/return termination).
2802                    if pending_label_start.is_some() {
2803                        self.error_at_peek(
2804                            "expected case body before `}` — case labels must be followed by statements",
2805                        );
2806                        return None;
2807                    }
2808                    break;
2809                }
2810                TokenKind::Case => {
2811                    let case_kw = self.advance();
2812                    if pending_label_start.is_none() {
2813                        pending_label_start = Some(case_kw.span.start);
2814                    }
2815                    let value = self.parse_expression()?;
2816                    if !matches!(self.peek().kind, TokenKind::Colon) {
2817                        self.error_at_peek("expected `:` after `case` label");
2818                        return None;
2819                    }
2820                    self.advance();
2821                    pending_values.push(value);
2822                }
2823                TokenKind::Default => {
2824                    let kw_tok = self.advance();
2825                    if default.is_some() {
2826                        self.error_at(kw_tok.span, "duplicate `default` clause in `switch`");
2827                        return None;
2828                    }
2829                    if !matches!(self.peek().kind, TokenKind::Colon) {
2830                        self.error_at_peek("expected `:` after `default`");
2831                        return None;
2832                    }
2833                    self.advance();
2834                    if pending_label_start.is_none() {
2835                        pending_label_start = Some(kw_tok.span.start);
2836                    }
2837                    pending_default = Some(kw_tok.span);
2838                }
2839                _ => {
2840                    if pending_label_start.is_none() {
2841                        self.error_at_peek(
2842                            "expected `case` or `default` at the start of a `switch` body",
2843                        );
2844                        return None;
2845                    }
2846                    let Some(label_start) = pending_label_start else {
2847                        return self.invariant_failure("switch arm has no label");
2848                    };
2849                    let body = self.parse_switch_arm_body(label_start)?;
2850                    let arm_span =
2851                        parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?.span;
2852                    if !pending_values.is_empty() {
2853                        let first = parse_arena_result(
2854                            self.ast.try_expr(pending_values[0]),
2855                            &mut self.fatal,
2856                        )?
2857                        .span
2858                        .start;
2859                        let case_span = self.span(first, arm_span.end);
2860                        cases.push(SwitchCase {
2861                            values: std::mem::take(&mut pending_values),
2862                            body,
2863                            span: case_span,
2864                        });
2865                    }
2866                    if let Some(def_kw_span) = pending_default.take() {
2867                        default = Some(SwitchDefault {
2868                            body,
2869                            span: self.span(def_kw_span.start, arm_span.end),
2870                        });
2871                    }
2872                    pending_label_start = None;
2873                }
2874            }
2875        }
2876
2877        self.block_depth -= 1;
2878        if !matches!(self.peek().kind, TokenKind::RightBrace) {
2879            self.error_at_peek("expected `}` to close `switch` body");
2880            return None;
2881        }
2882        let close = self.advance();
2883
2884        let _ = open;
2885        parse_arena_result(
2886            self.ast.try_push_stmt(Stmt {
2887                kind: StmtKind::Switch {
2888                    discriminant,
2889                    cases,
2890                    default,
2891                },
2892                span: self.span(kw.span.start, close.span.end),
2893            }),
2894            &mut self.fatal,
2895        )
2896    }
2897
2898    // `body_start` is the label keyword's offset so diagnostics point at the label.
2899    fn parse_switch_arm_body(&mut self, body_start: u32) -> Option<StmtId> {
2900        let mut stmts: Vec<StmtId> = Vec::new();
2901        let mut end_offset = body_start;
2902        loop {
2903            if matches!(
2904                self.peek().kind,
2905                TokenKind::Case | TokenKind::Default | TokenKind::RightBrace | TokenKind::Eof
2906            ) {
2907                break;
2908            }
2909            if matches!(self.peek().kind, TokenKind::Semicolon) {
2910                end_offset = self.peek().span.end;
2911                self.advance();
2912                continue;
2913            }
2914            let pos_before = self.pos;
2915            if let Some(id) = self.parse_statement() {
2916                end_offset = parse_arena_result(self.ast.try_stmt(id), &mut self.fatal)?
2917                    .span
2918                    .end;
2919                stmts.push(id);
2920            } else {
2921                self.recover();
2922                if self.pos == pos_before && !self.is_at_eof() {
2923                    self.advance();
2924                }
2925                // Recovery consumed tokens but produced no statement; advance the body
2926                // end past them so the arm span can't end before its own case value.
2927                end_offset = end_offset.max(self.prev_token_end());
2928            }
2929        }
2930        let span = self.span(body_start, end_offset);
2931        parse_arena_result(
2932            self.ast.try_push_stmt(Stmt {
2933                kind: StmtKind::Block(stmts),
2934                span,
2935            }),
2936            &mut self.fatal,
2937        )
2938    }
2939
2940    fn parse_break(&mut self) -> Option<StmtId> {
2941        let kw = self.advance();
2942        if !matches!(self.peek().kind, TokenKind::Semicolon) {
2943            self.error_at_peek("expected `;` after `break`");
2944            return None;
2945        }
2946        let semi = self.advance();
2947        parse_arena_result(
2948            self.ast.try_push_stmt(Stmt {
2949                kind: StmtKind::Break,
2950                span: self.span(kw.span.start, semi.span.end),
2951            }),
2952            &mut self.fatal,
2953        )
2954    }
2955
2956    fn parse_continue(&mut self) -> Option<StmtId> {
2957        let kw = self.advance();
2958        if !matches!(self.peek().kind, TokenKind::Semicolon) {
2959            self.error_at_peek("expected `;` after `continue`");
2960            return None;
2961        }
2962        let semi = self.advance();
2963        parse_arena_result(
2964            self.ast.try_push_stmt(Stmt {
2965                kind: StmtKind::Continue,
2966                span: self.span(kw.span.start, semi.span.end),
2967            }),
2968            &mut self.fatal,
2969        )
2970    }
2971
2972    fn parse_return(&mut self) -> Option<StmtId> {
2973        let kw = self.advance();
2974
2975        let value = if matches!(self.peek().kind, TokenKind::Semicolon) {
2976            None
2977        } else {
2978            Some(self.parse_expression()?)
2979        };
2980
2981        if !self.at_statement_end() {
2982            self.error_at_peek("expected `;` after return");
2983            return None;
2984        }
2985        let end = self.finish_statement();
2986
2987        parse_arena_result(
2988            self.ast.try_push_stmt(Stmt {
2989                kind: StmtKind::Return(value),
2990                span: self.span(kw.span.start, end),
2991            }),
2992            &mut self.fatal,
2993        )
2994    }
2995
2996    fn parse_throw(&mut self) -> Option<StmtId> {
2997        let kw = self.advance();
2998
2999        if matches!(self.peek().kind, TokenKind::Semicolon) {
3000            self.error_at_peek_with_help(
3001                "expected expression after `throw`",
3002                vec!["throw new Error(\"message\");".to_string()],
3003            );
3004            return None;
3005        }
3006
3007        let value = self.parse_expression()?;
3008
3009        if !self.at_statement_end() {
3010            self.error_at_peek("expected `;` after `throw`");
3011            return None;
3012        }
3013        let end = self.finish_statement();
3014
3015        parse_arena_result(
3016            self.ast.try_push_stmt(Stmt {
3017                kind: StmtKind::Throw { value },
3018                span: self.span(kw.span.start, end),
3019            }),
3020            &mut self.fatal,
3021        )
3022    }
3023
3024    fn parse_try(&mut self) -> Option<StmtId> {
3025        let kw = self.advance();
3026
3027        let body = self.parse_block()?;
3028
3029        let mut catches: Vec<CatchClause> = Vec::new();
3030        let mut finally: Option<StmtId> = None;
3031        let mut tail_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
3032            .span
3033            .end;
3034
3035        loop {
3036            match self.peek().kind {
3037                TokenKind::Catch => {
3038                    if finally.is_some() {
3039                        self.error_at_peek("`catch` clause must appear before `finally`");
3040                        return None;
3041                    }
3042                    let clause = self.parse_catch_clause()?;
3043                    tail_end = clause.span.end;
3044                    catches.push(clause);
3045                }
3046                TokenKind::Finally => {
3047                    if finally.is_some() {
3048                        self.error_at_peek("duplicate `finally` clause");
3049                        return None;
3050                    }
3051                    self.advance();
3052                    let block = self.parse_block()?;
3053                    tail_end = parse_arena_result(self.ast.try_stmt(block), &mut self.fatal)?
3054                        .span
3055                        .end;
3056                    finally = Some(block);
3057                }
3058                _ => break,
3059            }
3060        }
3061
3062        if catches.is_empty() && finally.is_none() {
3063            self.error_at(
3064                self.span(kw.span.start, tail_end),
3065                "try requires at least one of `catch` or `finally`",
3066            );
3067            return None;
3068        }
3069
3070        parse_arena_result(
3071            self.ast.try_push_stmt(Stmt {
3072                kind: StmtKind::Try {
3073                    body,
3074                    catches,
3075                    finally,
3076                },
3077                span: self.span(kw.span.start, tail_end),
3078            }),
3079            &mut self.fatal,
3080        )
3081    }
3082
3083    fn parse_catch_clause(&mut self) -> Option<CatchClause> {
3084        let kw = self.advance();
3085
3086        let (binding, ty) = if matches!(self.peek().kind, TokenKind::LeftBrace) {
3087            // This binding is inaccessible to source code, including nested catches.
3088            (
3089                Ident {
3090                    name: "#catch".into(),
3091                    span: kw.span,
3092                },
3093                None,
3094            )
3095        } else {
3096            self.parse_catch_binding()?
3097        };
3098
3099        let body = self.parse_block()?;
3100        let body_end = parse_arena_result(self.ast.try_stmt(body), &mut self.fatal)?
3101            .span
3102            .end;
3103
3104        Some(CatchClause {
3105            binding,
3106            ty,
3107            body,
3108            span: self.span(kw.span.start, body_end),
3109        })
3110    }
3111
3112    fn parse_catch_binding(&mut self) -> Option<(Ident, Option<TypeAnnotation>)> {
3113        if !matches!(self.peek().kind, TokenKind::LeftParen) {
3114            self.error_at_peek("expected `(` after `catch`");
3115            return None;
3116        }
3117        self.advance();
3118        let name_tok = self.expect_identifier("expected catch binding name")?;
3119        let binding = self.ident_from_token(&name_tok);
3120        let ty = if matches!(self.peek().kind, TokenKind::Colon) {
3121            self.advance();
3122            if self.reject_any_catch_annotation() {
3123                // The error is reported; parsing continues as an unannotated catch.
3124                None
3125            } else {
3126                Some(self.parse_type_annotation()?)
3127            }
3128        } else {
3129            None
3130        };
3131        if !matches!(self.peek().kind, TokenKind::RightParen) {
3132            self.error_at_peek("expected `)` after catch binding");
3133            return None;
3134        }
3135        self.advance();
3136        Some((binding, ty))
3137    }
3138
3139    /// Reports `catch (e: any)` and consumes the `any`, returning whether it did.
3140    /// Only exactly `any)` is handled; compound types keep the general diagnostic.
3141    fn reject_any_catch_annotation(&mut self) -> bool {
3142        let is_bare_any = self.peek_identifier_text_is("any")
3143            && matches!(self.peek_at(1).kind, TokenKind::RightParen);
3144        if !is_bare_any {
3145            return false;
3146        }
3147        let any_tok = self.advance();
3148        // The general `any` help suggests `unknown`, which a catch binding refuses.
3149        self.error_at_with_help(
3150            any_tok.span,
3151            "`any` is not supported",
3152            vec![
3153                "write `catch (e)` to catch every thrown error, or name an error class, \
3154                 as in `catch (e: RangeError)`, to catch only that type"
3155                    .to_string(),
3156            ],
3157        );
3158        true
3159    }
3160
3161    fn parse_paren_condition(&mut self) -> Option<ExprId> {
3162        if !matches!(self.peek().kind, TokenKind::LeftParen) {
3163            self.error_at_peek("expected `(`");
3164            return None;
3165        }
3166        self.advance();
3167        let expr = self.parse_expression()?;
3168        if !matches!(self.peek().kind, TokenKind::RightParen) {
3169            self.error_at_peek("expected `)`");
3170            return None;
3171        }
3172        self.advance();
3173        Some(expr)
3174    }
3175
3176    fn parse_predicate_or_return_type(
3177        &mut self,
3178        type_pos: TypePos,
3179    ) -> Option<(
3180        Option<TypeAnnotation>,
3181        Option<crate::TypePredicateAnnotation>,
3182    )> {
3183        if matches!(self.peek().kind, TokenKind::Identifier) && self.peek_at_is_word(1, "is") {
3184            let param_tok = self.advance();
3185            let param = self.ident_from_token(&param_tok);
3186            self.advance();
3187            let asserted = self.parse_type_annotation_in(type_pos)?;
3188            let span = self.span(param.span.start, asserted.span.end);
3189            return Some((
3190                None,
3191                Some(crate::TypePredicateAnnotation {
3192                    param,
3193                    asserted,
3194                    span,
3195                }),
3196            ));
3197        }
3198        Some((Some(self.parse_type_annotation_in(type_pos)?), None))
3199    }
3200
3201    fn parse_type_annotation(&mut self) -> Option<TypeAnnotation> {
3202        self.parse_type_annotation_in(TypePos::Anywhere)
3203    }
3204
3205    fn parse_type_annotation_in(&mut self, type_pos: TypePos) -> Option<TypeAnnotation> {
3206        // A leading `|` (prettier's multiline format) is accepted and discarded.
3207        let leading_pipe = matches!(self.peek().kind, TokenKind::Pipe);
3208        if leading_pipe {
3209            self.advance();
3210        }
3211        let first = self.parse_type_array(type_pos)?;
3212        if !matches!(self.peek().kind, TokenKind::Pipe) {
3213            return Some(first);
3214        }
3215        let start = first.span.start;
3216        let mut end = first.span.end;
3217        let mut members = vec![first];
3218        while matches!(self.peek().kind, TokenKind::Pipe) {
3219            self.advance();
3220            let member = self.parse_type_array(type_pos)?;
3221            end = member.span.end;
3222            members.push(member);
3223        }
3224        Some(TypeAnnotation {
3225            kind: TypeAnnotationKind::Union(members),
3226            span: self.span(start, end),
3227        })
3228    }
3229
3230    fn parse_type_array(&mut self, type_pos: TypePos) -> Option<TypeAnnotation> {
3231        self.with_recursion_limit(|parser| parser.parse_type_array_inner(type_pos))
3232    }
3233
3234    fn parse_type_array_inner(&mut self, type_pos: TypePos) -> Option<TypeAnnotation> {
3235        // `keyof` is contextual, not reserved: TypeScript allows it as an ordinary
3236        // identifier, and so does Submilli. It is an operator only when another type
3237        // follows it, which an identifier in type position never does.
3238        if self.is_contextual_type_operator(self.pos, "keyof") {
3239            let kw = self.advance();
3240            let operand = self.parse_type_array(type_pos)?;
3241            let span = self.span(kw.span.start, operand.span.end);
3242            return Some(TypeAnnotation {
3243                kind: TypeAnnotationKind::KeyOf(Box::new(operand)),
3244                span,
3245            });
3246        }
3247        if self.is_contextual_type_operator(self.pos, "readonly") {
3248            return self.parse_readonly_type(type_pos);
3249        }
3250
3251        let mut ty = match self.peek().kind {
3252            // `typeof x` names the type of a *value*. Unlike `keyof` this is already a
3253            // reserved word (the `typeof x === "string"` guard uses it), so it needs no
3254            // contextual check — in type position it is only ever the operator. Built
3255            // here rather than returned early so the `[]` suffix loop below still runs,
3256            // making `typeof a[]` an array of it, as TypeScript parses it too.
3257            TokenKind::Typeof => {
3258                let kw = self.advance();
3259                let first = self.expect_identifier("expected a value name after `typeof`")?;
3260                let mut path = vec![self.type_name(first.span)?];
3261                let mut end = first.span.end;
3262                while matches!(self.peek().kind, TokenKind::Dot) {
3263                    self.advance();
3264                    let seg = self.expect_identifier_name("expected a property name after `.`")?;
3265                    end = seg.span.end;
3266                    path.push(self.type_name(seg.span)?);
3267                }
3268                TypeAnnotation {
3269                    kind: TypeAnnotationKind::TypeOf { path },
3270                    span: self.span(kw.span.start, end),
3271                }
3272            }
3273            TokenKind::Identifier | TokenKind::Void => {
3274                // `void` never starts a qualified path.
3275                let leading_is_identifier = matches!(self.peek().kind, TokenKind::Identifier);
3276                let tok = self.advance();
3277                let first_span = tok.span;
3278                let mut path_end = first_span.end;
3279                let first_name = self.type_name(first_span)?;
3280                let mut path = vec![first_name.clone()];
3281                if leading_is_identifier {
3282                    while matches!(self.peek().kind, TokenKind::Dot) {
3283                        self.advance();
3284                        if !matches!(self.peek().kind, TokenKind::Identifier) {
3285                            self.error_at_peek("expected identifier after `.` in type name");
3286                            return None;
3287                        }
3288                        let seg = self.advance();
3289                        path_end = seg.span.end;
3290                        path.push(self.type_name(seg.span)?);
3291                    }
3292                }
3293                // Type arguments, like the `[]` suffix, must start on the type's line.
3294                let (args, end) = if matches!(self.peek().kind, TokenKind::LessThan)
3295                    && !self.line_break_before_peek()
3296                {
3297                    self.parse_type_argument_list()?
3298                } else {
3299                    (Vec::new(), path_end)
3300                };
3301                let outer_span = self.span(first_span.start, end);
3302                let kind = if path.len() == 1 {
3303                    if first_name.name == "any" {
3304                        self.error_at_with_help(
3305                            first_span,
3306                            "`any` is not supported",
3307                            vec![
3308                                "annotate a concrete type instead — an object shape \
3309                                 like `{ id: string }`, a named `interface`, or \
3310                                 `unknown` (a safe dynamic type you narrow before use)"
3311                                    .to_string(),
3312                            ],
3313                        );
3314                    }
3315                    TypeAnnotationKind::Name {
3316                        name: first_name,
3317                        args,
3318                    }
3319                } else {
3320                    TypeAnnotationKind::Qualified { path, args }
3321                };
3322                TypeAnnotation {
3323                    kind,
3324                    span: outer_span,
3325                }
3326            }
3327            // `null<T>` is meaningless — no type args after `null`.
3328            TokenKind::NullLiteral => {
3329                let tok = self.advance();
3330                TypeAnnotation {
3331                    kind: TypeAnnotationKind::Name {
3332                        name: self.type_name(tok.span)?,
3333                        args: Vec::new(),
3334                    },
3335                    span: tok.span,
3336                }
3337            }
3338            TokenKind::StringLiteral(_) => {
3339                let tok = self.advance();
3340                let span = tok.span;
3341                let TokenKind::StringLiteral(s) = tok.kind else {
3342                    // advance returns the token just matched, before moving the cursor.
3343                    unreachable!("advance preserves the matched StringLiteral variant");
3344                };
3345                TypeAnnotation {
3346                    kind: TypeAnnotationKind::StringLiteral(s),
3347                    span,
3348                }
3349            }
3350            TokenKind::NumberLiteral(_) => {
3351                let tok = self.advance();
3352                let span = tok.span;
3353                let TokenKind::NumberLiteral(v) = tok.kind else {
3354                    // advance returns the token just matched, before moving the cursor.
3355                    unreachable!("advance preserves the matched NumberLiteral variant");
3356                };
3357                // Canonicalize `-0.0` → `0.0` so literal type `0` matches both signs.
3358                let canonical = if v == 0.0 { 0.0 } else { v };
3359                TypeAnnotation {
3360                    kind: TypeAnnotationKind::NumberLiteral(crate::types::LiteralF64(canonical)),
3361                    span,
3362                }
3363            }
3364            TokenKind::BigIntLiteral(_) => {
3365                let tok = self.advance();
3366                let TokenKind::BigIntLiteral(digits) = tok.kind else {
3367                    // advance returns the token just matched, before moving the cursor.
3368                    unreachable!("advance preserves the matched BigIntLiteral variant");
3369                };
3370                TypeAnnotation {
3371                    kind: TypeAnnotationKind::BigIntLiteral(digits),
3372                    span: tok.span,
3373                }
3374            }
3375            TokenKind::Minus
3376                if matches!(
3377                    self.peek_at(1).kind,
3378                    TokenKind::NumberLiteral(_) | TokenKind::BigIntLiteral(_)
3379                ) =>
3380            {
3381                let minus = self.advance().span;
3382                let tok = self.advance();
3383                let span = self.span(minus.start, tok.span.end);
3384                let kind =
3385                    match tok.kind {
3386                        TokenKind::NumberLiteral(v) => TypeAnnotationKind::NumberLiteral(
3387                            crate::types::LiteralF64(if v == 0.0 { 0.0 } else { -v }),
3388                        ),
3389                        TokenKind::BigIntLiteral(digits) => TypeAnnotationKind::BigIntLiteral(
3390                            crate::types::negate_bigint_digits(&digits),
3391                        ),
3392                        // The guard admits only the two numeric literal tokens.
3393                        _ => unreachable!("guarded to a numeric literal after `-`"),
3394                    };
3395                TypeAnnotation { kind, span }
3396            }
3397            TokenKind::BooleanLiteral(value) => {
3398                let span = self.advance().span;
3399                TypeAnnotation {
3400                    kind: TypeAnnotationKind::BooleanLiteral(value),
3401                    span,
3402                }
3403            }
3404            TokenKind::LeftBrace => self.parse_object_type_annotation()?,
3405            TokenKind::LeftParen if self.paren_at_opens_function_type(self.pos, type_pos) => {
3406                self.parse_function_type_annotation()?
3407            }
3408            TokenKind::LeftParen => self.parse_grouped_or_function_type()?,
3409            // Leading `[` is unambiguously a tuple (postfix `[]` can only follow a base type).
3410            TokenKind::LeftBracket => self.parse_tuple_type_annotation()?,
3411            _ => {
3412                self.error_at_peek("expected type");
3413                return None;
3414            }
3415        };
3416        // Array suffixes nest boxed annotations without recursing, so each one
3417        // spends the recursive grammar budget to keep the chain's drop bounded.
3418        let mut array_suffixes = 0usize;
3419        // As in TypeScript, a line break ends the type before `[`: in an object type,
3420        // `a: B` ⏎ `[k: string]: V` is a field followed by an index signature.
3421        while matches!(self.peek().kind, TokenKind::LeftBracket) && !self.line_break_before_peek() {
3422            array_suffixes += 1;
3423            if self.recursion_depth.saturating_add(array_suffixes) >= MAX_PARSE_DEPTH {
3424                self.stop_at_recursion_limit();
3425                return None;
3426            }
3427            let open = self.advance();
3428            if !matches!(self.peek().kind, TokenKind::RightBracket) {
3429                self.error_at(open.span, "expected `]` to close array type");
3430                return None;
3431            }
3432            let close = self.advance();
3433            let span = self.span(ty.span.start, close.span.end);
3434            ty = TypeAnnotation {
3435                kind: TypeAnnotationKind::Array(Box::new(ty)),
3436                span,
3437            };
3438        }
3439        Some(ty)
3440    }
3441
3442    /// `readonly T[]` or `readonly [A, B]`. The operand binds like `keyof`'s, so
3443    /// `readonly T[][]` is a readonly array of mutable arrays and
3444    /// `readonly T[] | null` a union with a readonly member.
3445    fn parse_readonly_type(&mut self, type_pos: TypePos) -> Option<TypeAnnotation> {
3446        let kw = self.advance();
3447        let opens_group = matches!(self.peek().kind, TokenKind::LeftParen);
3448        let operand = self.parse_type_array(type_pos)?;
3449        // `readonly (T[])` is rejected as TypeScript rejects it: the operand must be
3450        // written as an array or tuple type, not grouped into one.
3451        let grouped = opens_group
3452            && matches!(
3453                self.pos
3454                    .checked_sub(1)
3455                    .and_then(|index| self.tokens.get(index))
3456                    .map(|token| &token.kind),
3457                Some(TokenKind::RightParen)
3458            );
3459        if grouped
3460            || !matches!(
3461                operand.kind,
3462                TypeAnnotationKind::Array(_) | TypeAnnotationKind::Tuple(_)
3463            )
3464        {
3465            self.error_at_with_help(
3466                kw.span,
3467                "`readonly` only applies to array and tuple types",
3468                vec![
3469                    "write `readonly T[]` or `readonly [A, B]`; to protect an object's \
3470                     properties, mark each one `readonly` instead"
3471                        .to_string(),
3472                ],
3473            );
3474            return Some(operand);
3475        }
3476        let span = self.span(kw.span.start, operand.span.end);
3477        Some(TypeAnnotation {
3478            kind: TypeAnnotationKind::Readonly(Box::new(operand)),
3479            span,
3480        })
3481    }
3482
3483    /// In type position `(` is ambiguous: a function type's parameter list, or a
3484    /// grouping paren the postfix `[]` / `|` then composes with. Decided by what the
3485    /// parens *contain*, not by what follows them — a trailing `=>` can belong to an
3486    /// enclosing arrow function's body (`(): (string | null) => null`), so looking
3487    /// past the `)` would read that return type as a parameter list. The exception is
3488    /// `(T)` / `(T, U)`, spelled identically either way, which only a trailing `=>`
3489    /// can break.
3490    ///
3491    /// Empty parens count as a parameter list: `()` groups nothing, so the
3492    /// function-type parse's "expected `=>`" is the message that names the fix.
3493    fn paren_at_opens_function_type(&self, index: usize, type_pos: TypePos) -> bool {
3494        let after = |offset: usize| self.tokens.get(index + offset).map(|t| &t.kind);
3495        match (after(1), after(2)) {
3496            // Only a parameter list is empty or starts with a rest element.
3497            (Some(TokenKind::RightParen | TokenKind::DotDotDot), _) => true,
3498            // A named, annotated parameter — no type starts this way.
3499            (Some(TokenKind::Identifier), Some(TokenKind::Colon | TokenKind::Question)) => true,
3500            // `(T)` and `(T, U)` are spelled exactly like a grouped type. v1
3501            // rejects bare parameter names, and a trailing `=>` is the only sign
3502            // the writer meant one — routing those to the function-type parse
3503            // keeps its "params require named annotations" message reachable in
3504            // declaration position (`type F = (T) => R`). In an arrow's *return*
3505            // annotation the trailing `=>` is the body's, so `(): (T) => x` loses
3506            // that message and falls back to the expression parse; dropping the
3507            // redundant parens is the fix there.
3508            (Some(TokenKind::Identifier), Some(TokenKind::Comma | TokenKind::RightParen)) => {
3509                type_pos == TypePos::Anywhere
3510                    && self.index_after_matching_paren(index).is_some_and(|i| {
3511                        matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Arrow))
3512                    })
3513            }
3514            // Anything else can only begin a type, so the parens group.
3515            _ => false,
3516        }
3517    }
3518
3519    /// Parse a `(` the contents rule called a group. If that fails and a `=>` follows
3520    /// the matching `)`, the author was writing a function type after all — a typo in
3521    /// its parameter list is what made it look like a group — so re-parse that way to
3522    /// recover the diagnostic naming the parameter rule instead of a stray-`)` error.
3523    fn parse_grouped_or_function_type(&mut self) -> Option<TypeAnnotation> {
3524        let open = self.pos;
3525        let diagnostics_before = self.diagnostics.len();
3526        if let Some(ty) = self.parse_grouped_type_annotation() {
3527            return Some(ty);
3528        }
3529        let arrow_follows = self
3530            .index_after_matching_paren(open)
3531            .is_some_and(|i| matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Arrow)));
3532        if !arrow_follows {
3533            return None;
3534        }
3535        self.pos = open;
3536        self.diagnostics.truncate(diagnostics_before);
3537        self.parse_function_type_annotation()
3538    }
3539
3540    /// A parenthesized type is its inner type — the parens only group, so no node
3541    /// records them. The span covers them so a diagnostic points at what was written.
3542    fn parse_grouped_type_annotation(&mut self) -> Option<TypeAnnotation> {
3543        let open = self.advance();
3544        let inner = self.parse_type_annotation()?;
3545        if !matches!(self.peek().kind, TokenKind::RightParen) {
3546            self.error_at_peek("expected `)` to close a parenthesized type");
3547            return None;
3548        }
3549        let close = self.advance();
3550        Some(TypeAnnotation {
3551            span: self.span(open.span.start, close.span.end),
3552            ..inner
3553        })
3554    }
3555
3556    // Param names are required — bare `(T, U) => R` is rejected.
3557    fn parse_function_type_annotation(&mut self) -> Option<TypeAnnotation> {
3558        let open = self.advance();
3559        let params = self.parse_function_type_params()?;
3560        if !matches!(self.peek().kind, TokenKind::Arrow) {
3561            self.error_at_peek("expected `=>` in function-type annotation");
3562            return None;
3563        }
3564        self.advance();
3565        let return_type = self.parse_type_annotation()?;
3566        let end = return_type.span.end;
3567        Some(TypeAnnotation {
3568            kind: TypeAnnotationKind::Function {
3569                params,
3570                return_type: Box::new(return_type),
3571            },
3572            span: self.span(open.span.start, end),
3573        })
3574    }
3575
3576    /// The `(…)` of a function type, its `(` already consumed and its `)` consumed here.
3577    /// Shared with a type literal's method members, which spell the same parameter list
3578    /// before a `:` rather than a `=>`.
3579    fn parse_function_type_params(&mut self) -> Option<Vec<TypeAnnotationField>> {
3580        let mut params: Vec<TypeAnnotationField> = Vec::new();
3581        if !matches!(self.peek().kind, TokenKind::RightParen) {
3582            loop {
3583                let rest = if matches!(self.peek().kind, TokenKind::DotDotDot) {
3584                    self.advance();
3585                    true
3586                } else {
3587                    false
3588                };
3589                let name_tok = self.expect_identifier(if rest {
3590                    "expected identifier after `...` in function-type annotation"
3591                } else {
3592                    "expected parameter name in function-type annotation"
3593                })?;
3594                let name = self.ident_from_token(&name_tok);
3595                let optional = self.parse_parameter_optional(rest, false)?;
3596                if !matches!(self.peek().kind, TokenKind::Colon) {
3597                    if rest {
3598                        self.error_at_peek_with_help(
3599                            "rest parameter requires a type annotation",
3600                            vec!["(...args: T[]) => R".to_string()],
3601                        );
3602                    } else {
3603                        self.error_at_peek(
3604                            "expected `:` after parameter name (function-type params \
3605                             require named annotations in v1)",
3606                        );
3607                    }
3608                    return None;
3609                }
3610                self.advance();
3611                let ty = self.parse_type_annotation()?;
3612                if rest && !is_rest_array_annotation(&ty) {
3613                    self.error_at(ty.span, "rest parameter type must be an array");
3614                    return None;
3615                }
3616                if matches!(self.peek().kind, TokenKind::Equals) {
3617                    if rest {
3618                        self.error_rest_parameter_default();
3619                    } else {
3620                        self.error_at_peek(
3621                            "default values in function-type annotations are not yet supported",
3622                        );
3623                    }
3624                    return None;
3625                }
3626                params.push(TypeAnnotationField {
3627                    name,
3628                    ty,
3629                    optional,
3630                    readonly: false,
3631                    rest,
3632                    method: false,
3633                });
3634                match self.peek().kind {
3635                    TokenKind::Comma => {
3636                        if rest {
3637                            let comma = self.advance();
3638                            self.error_at(comma.span, "rest parameter must be the last parameter");
3639                            return None;
3640                        }
3641                        self.advance();
3642                        if matches!(self.peek().kind, TokenKind::RightParen) {
3643                            break;
3644                        }
3645                    }
3646                    TokenKind::RightParen => break,
3647                    _ => {
3648                        self.error_at_peek("expected `,` or `)`");
3649                        return None;
3650                    }
3651                }
3652            }
3653            self.check_parameter_order(params.iter().map(ParamOrder::of_field));
3654        }
3655        if !matches!(self.peek().kind, TokenKind::RightParen) {
3656            self.error_at_peek("expected `)`");
3657            return None;
3658        }
3659        self.advance();
3660        Some(params)
3661    }
3662
3663    fn parse_tuple_type_annotation(&mut self) -> Option<TypeAnnotation> {
3664        let open = self.advance();
3665        if matches!(self.peek().kind, TokenKind::RightBracket) {
3666            let close = self.peek().span;
3667            self.error_at(
3668                self.span(open.span.start, close.end),
3669                "tuple types must have at least one element",
3670            );
3671            return None;
3672        }
3673        let mut elements: Vec<TypeAnnotation> = Vec::new();
3674        loop {
3675            if matches!(self.peek().kind, TokenKind::DotDotDot) {
3676                self.error_at_with_help(
3677                    self.peek().span,
3678                    "rest elements in tuple types are not supported",
3679                    vec!["use an array type `T[]` for a list of varying length".to_string()],
3680                );
3681                return None;
3682            }
3683            // Labels may be mixed with unlabeled elements, as TypeScript allows
3684            // since 5.2.
3685            let label = self.skip_tuple_element_label();
3686            let mut ty = self.parse_type_annotation()?;
3687            let suffix_optional = matches!(self.peek().kind, TokenKind::Question);
3688            if suffix_optional && label != TupleElementLabel::None {
3689                self.error_at_peek_with_help(
3690                    "put the optional marker after the tuple element label",
3691                    vec!["write `name?: T` instead of `name: T?`".to_string()],
3692                );
3693                return None;
3694            }
3695            let optional = label == TupleElementLabel::Optional || suffix_optional;
3696            if optional {
3697                let end = if suffix_optional {
3698                    self.advance().span.end
3699                } else {
3700                    ty.span.end
3701                };
3702                let span = self.span(ty.span.start, end);
3703                ty = TypeAnnotation {
3704                    kind: TypeAnnotationKind::Optional(Box::new(ty)),
3705                    span,
3706                };
3707            } else if elements
3708                .iter()
3709                .any(|element| matches!(element.kind, TypeAnnotationKind::Optional(_)))
3710            {
3711                self.error_at(
3712                    ty.span,
3713                    "a required tuple element cannot follow an optional element",
3714                );
3715                return None;
3716            }
3717            elements.push(ty);
3718            match self.peek().kind {
3719                TokenKind::Comma => {
3720                    self.advance();
3721                    if matches!(self.peek().kind, TokenKind::RightBracket) {
3722                        break;
3723                    }
3724                }
3725                TokenKind::RightBracket => break,
3726                _ => {
3727                    self.error_at_peek("expected `,` or `]`");
3728                    return None;
3729                }
3730            }
3731        }
3732        if !matches!(self.peek().kind, TokenKind::RightBracket) {
3733            self.error_at_peek("expected `]`");
3734            return None;
3735        }
3736        let close = self.advance();
3737        Some(TypeAnnotation {
3738            kind: TypeAnnotationKind::Tuple(elements),
3739            span: self.span(open.span.start, close.span.end),
3740        })
3741    }
3742
3743    /// Labels document positions; retain only whether the label was optional.
3744    fn skip_tuple_element_label(&mut self) -> TupleElementLabel {
3745        if !is_property_name(&self.peek().kind) {
3746            return TupleElementLabel::None;
3747        }
3748        match self.peek_at(1).kind {
3749            TokenKind::Colon => {
3750                self.advance();
3751                self.advance();
3752                TupleElementLabel::Required
3753            }
3754            TokenKind::Question if matches!(self.peek_at(2).kind, TokenKind::Colon) => {
3755                self.advance();
3756                self.advance();
3757                self.advance();
3758                TupleElementLabel::Optional
3759            }
3760            _ => TupleElementLabel::None,
3761        }
3762    }
3763
3764    /// `[]` or `[]: V` — an index signature with no parameter, which TypeScript
3765    /// reads and rejects.
3766    fn peek_is_parameterless_index_signature(&self) -> bool {
3767        matches!(self.peek().kind, TokenKind::LeftBracket)
3768            && matches!(self.peek_at(1).kind, TokenKind::RightBracket)
3769    }
3770
3771    /// Diagnoses and skips a parameterless index signature (see
3772    /// `peek_is_parameterless_index_signature`) so the members after it still parse.
3773    /// `member_start` is where the member begins, at a `readonly` modifier if any.
3774    fn skip_parameterless_index_signature(&mut self, member_start: u32) -> Option<()> {
3775        self.advance();
3776        let close = self.advance();
3777        self.error_at_with_help(
3778            self.span(member_start, close.span.end),
3779            "an index signature must declare exactly one parameter",
3780            vec!["write `[key: string]: V`".to_string()],
3781        );
3782        if matches!(self.peek().kind, TokenKind::Colon) {
3783            self.advance();
3784            self.parse_type_annotation()?;
3785        }
3786        Some(())
3787    }
3788
3789    fn parse_index_signature(&mut self, readonly: bool) -> Option<crate::IndexSignatureAnnotation> {
3790        let open = self.advance();
3791        self.expect_property_ident("expected index parameter name")?;
3792        if !matches!(self.peek().kind, TokenKind::Colon) {
3793            self.error_at_peek("expected `:` after index parameter name");
3794            return None;
3795        }
3796        self.advance();
3797        if !self.peek_identifier_text_is("string") {
3798            self.error_at_peek("index signatures require string keys; use `[key: string]: V`");
3799            return None;
3800        }
3801        self.advance();
3802        if !matches!(self.peek().kind, TokenKind::RightBracket) {
3803            self.error_at_peek("expected `]` after string index key type");
3804            return None;
3805        }
3806        self.advance();
3807        if !matches!(self.peek().kind, TokenKind::Colon) {
3808            self.error_at_peek("expected `:` before index value type");
3809            return None;
3810        }
3811        self.advance();
3812        let value = self.parse_type_annotation()?;
3813        Some(crate::IndexSignatureAnnotation {
3814            span: self.span(open.span.start, value.span.end),
3815            value,
3816            readonly,
3817        })
3818    }
3819
3820    fn parse_object_type_annotation(&mut self) -> Option<TypeAnnotation> {
3821        let open = self.advance();
3822        let mut fields: Vec<TypeAnnotationField> = Vec::new();
3823        let mut index = None;
3824        while !matches!(self.peek().kind, TokenKind::RightBrace | TokenKind::Eof)
3825            && !self.peek_starts_declaration()
3826        {
3827            let member_start = self.peek().span.start;
3828            let readonly = self.eat_readonly_property_modifier();
3829            if self.peek_is_parameterless_index_signature() {
3830                self.skip_parameterless_index_signature(member_start)?;
3831            } else if self.peek_is_construct_signature() {
3832                self.reject_construct_signature()?;
3833            } else if matches!(self.peek().kind, TokenKind::LeftBracket) {
3834                let signature = self.parse_index_signature(readonly)?;
3835                if index.is_some() {
3836                    self.error_at_with_help(
3837                        signature.span,
3838                        "duplicate string index signature",
3839                        vec![],
3840                    );
3841                }
3842                index = Some(Box::new(signature));
3843            } else {
3844                let field = self.parse_object_type_field(readonly, member_start)?;
3845                if let Some(existing) = fields.iter().find(|f| f.name.name == field.name.name) {
3846                    self.diagnostics.push(Diagnostic {
3847                        severity: Severity::Error,
3848                        span: field.name.span,
3849                        message: format!("duplicate field `{}` in object type", field.name.name),
3850                        help: vec![],
3851                        notes: vec![(existing.name.span, "first defined here".into())],
3852                    });
3853                }
3854                fields.push(field);
3855            }
3856            self.finish_type_member(self.prev_token_end(), "expected `;`, `,`, or `}`")?;
3857        }
3858        if !matches!(self.peek().kind, TokenKind::RightBrace) {
3859            self.error_at_peek("expected `}`");
3860            return None;
3861        }
3862        let close = self.advance();
3863        Some(TypeAnnotation {
3864            kind: TypeAnnotationKind::Object { index, fields },
3865            span: self.span(open.span.start, close.span.end),
3866        })
3867    }
3868
3869    /// Whether the next tokens start a declaration (`function f`, `class C`, `type T`,
3870    /// …, optionally after `export`, `declare`, `async` or `abstract`) or an export
3871    /// statement (`export {`, `export *`, `export default`), which no type member can:
3872    /// a keyword is a member name only before `:`, `?` or `(`. A member list that reaches one was left unclosed, so it stops there
3873    /// and leaves the declaration to be parsed.
3874    fn peek_starts_declaration(&self) -> bool {
3875        if matches!(self.peek().kind, TokenKind::Export)
3876            && matches!(
3877                self.peek_at(1).kind,
3878                TokenKind::Default | TokenKind::LeftBrace | TokenKind::Star
3879            )
3880        {
3881            return true;
3882        }
3883        let mut offset = 0;
3884        while self.peek_at_is_declaration_modifier(offset) {
3885            offset += 1;
3886        }
3887        let declaration_keyword = matches!(
3888            self.peek_at(offset).kind,
3889            TokenKind::Function
3890                | TokenKind::Class
3891                | TokenKind::Let
3892                | TokenKind::Const
3893                | TokenKind::Enum
3894                | TokenKind::Interface
3895        ) || self.peek_at_is_word(offset, "type");
3896        declaration_keyword && matches!(self.peek_at(offset + 1).kind, TokenKind::Identifier)
3897    }
3898
3899    /// `export`, `declare`, `async` or `abstract` before a declaration keyword.
3900    fn peek_at_is_declaration_modifier(&self, offset: usize) -> bool {
3901        matches!(self.peek_at(offset).kind, TokenKind::Export)
3902            || ["declare", "async", "abstract"]
3903                .iter()
3904                .any(|word| self.peek_at_is_word(offset, word))
3905    }
3906
3907    /// `new (params): T` or `new <T>(params): T` inside a type literal: a construct
3908    /// signature, which TypeScript reads where it would otherwise see a method named
3909    /// `new`. A quoted `"new"()`, an optional `new?()` and a field `new: T` are
3910    /// ordinary members, as in TypeScript.
3911    fn peek_is_construct_signature(&self) -> bool {
3912        matches!(self.peek().kind, TokenKind::New)
3913            && matches!(
3914                self.peek_at(1).kind,
3915                TokenKind::LeftParen | TokenKind::LessThan
3916            )
3917    }
3918
3919    /// Only interfaces declare construct signatures, so one in a type literal (see
3920    /// `peek_is_construct_signature`) is diagnosed, then skipped rather than misread
3921    /// as a method named `new`.
3922    fn reject_construct_signature(&mut self) -> Option<()> {
3923        let keyword = self.advance();
3924        self.error_at_with_help(
3925            keyword.span,
3926            "construct signatures are not supported in object types",
3927            vec![
3928                "declare an `interface` with the `new (…)` signature, or pass a factory \
3929                 function such as `() => T`"
3930                    .to_string(),
3931            ],
3932        );
3933        if matches!(self.peek().kind, TokenKind::LessThan) {
3934            self.parse_generic_param_list()?;
3935        }
3936        if !matches!(self.peek().kind, TokenKind::LeftParen) {
3937            self.error_at_peek("expected `(` after construct signature type parameters");
3938            return None;
3939        }
3940        self.parse_object_type_method_signature(keyword.span.start)?;
3941        Some(())
3942    }
3943
3944    /// `name: T`, `name?: T` or the method signature `name(params): T`.
3945    fn parse_object_type_field(
3946        &mut self,
3947        readonly: bool,
3948        member_start: u32,
3949    ) -> Option<TypeAnnotationField> {
3950        let name = self.expect_property_ident("expected field name in object type")?;
3951        // `name?: T` — omittable at construction; reads widen to `T | undefined`.
3952        let optional = matches!(self.peek().kind, TokenKind::Question);
3953        if optional {
3954            self.advance();
3955        }
3956        let method = matches!(self.peek().kind, TokenKind::LeftParen);
3957        let ty = if method {
3958            if readonly {
3959                self.reject_readonly_modifier(self.readonly_modifier_span(member_start));
3960            }
3961            self.parse_object_type_method_signature(name.span.start)?
3962        } else {
3963            if !matches!(self.peek().kind, TokenKind::Colon) {
3964                self.error_at_peek("expected `:` after field name");
3965                return None;
3966            }
3967            self.advance();
3968            self.parse_type_annotation()?
3969        };
3970        Some(TypeAnnotationField {
3971            name,
3972            ty,
3973            optional,
3974            readonly,
3975            rest: false,
3976            method,
3977        })
3978    }
3979
3980    /// `m(params): T` inside a type literal, spelled as the function-typed field
3981    /// `m: (params) => T` — the two are interchangeable in a structural shape, and
3982    /// interfaces already accept both spellings for the same member. `start` is the
3983    /// member name's offset, so the annotation's span covers the name too.
3984    fn parse_object_type_method_signature(&mut self, start: u32) -> Option<TypeAnnotation> {
3985        self.advance();
3986        let params = self.parse_function_type_params()?;
3987        if !matches!(self.peek().kind, TokenKind::Colon) {
3988            self.error_at_peek("expected `:` and return type after method parameters");
3989            return None;
3990        }
3991        self.advance();
3992        let return_type = self.parse_type_annotation()?;
3993        let end = return_type.span.end;
3994        Some(TypeAnnotation {
3995            kind: TypeAnnotationKind::Function {
3996                params,
3997                return_type: Box::new(return_type),
3998            },
3999            span: self.span(start, end),
4000        })
4001    }
4002
4003    /// An identifier that names a binding or a type, which rejects the words strict
4004    /// mode reserves (see [`STRICT_MODE_RESERVED_WORDS`]).
4005    fn expect_identifier(&mut self, message: &str) -> Option<Token> {
4006        let tok = self.expect_identifier_name(message)?;
4007        self.reject_strict_mode_reserved_word(&tok);
4008        Some(tok)
4009    }
4010
4011    /// An identifier in a position where strict mode's reserved words are allowed: an
4012    /// enum member, a property, or the name an import specifier refers to.
4013    fn expect_identifier_name(&mut self, message: &str) -> Option<Token> {
4014        if matches!(self.peek().kind, TokenKind::Identifier) {
4015            return Some(self.advance());
4016        }
4017        if is_reserved_identifier_word(&self.peek().kind) {
4018            let tok = self.advance();
4019            let keyword = &self.source[tok.span.start as usize..tok.span.end as usize];
4020            self.error_at_with_help(
4021                tok.span,
4022                format!("`{keyword}` is a reserved keyword and can't be used as a name"),
4023                vec![format!(
4024                    "rename it, for example: `{}`",
4025                    reserved_keyword_rename_example(keyword)
4026                )],
4027            );
4028            return None;
4029        }
4030        self.error_at_peek(message.to_string());
4031        None
4032    }
4033
4034    /// Reports a binding named by a word strict mode reserves. Parsing continues
4035    /// with the name: nothing else about it is wrong.
4036    fn reject_strict_mode_reserved_word(&mut self, tok: &Token) {
4037        let word = &self.source[tok.span.start as usize..tok.span.end as usize];
4038        if !STRICT_MODE_RESERVED_WORDS.contains(&word) {
4039            return;
4040        }
4041        self.error_at_with_help(
4042            tok.span,
4043            format!("`{word}` is a reserved word in strict mode and can't be used as a name"),
4044            vec![format!(
4045                "rename it, for example: `{}`",
4046                reserved_keyword_rename_example(word)
4047            )],
4048        );
4049    }
4050
4051    fn ident_from_token(&self, tok: &Token) -> Ident {
4052        Ident {
4053            name: self.source[tok.span.start as usize..tok.span.end as usize].to_string(),
4054            span: tok.span,
4055        }
4056    }
4057
4058    fn property_ident_from_token(&self, tok: &Token) -> Ident {
4059        if let TokenKind::StringLiteral(s) = &tok.kind {
4060            Ident {
4061                name: s.clone(),
4062                span: tok.span,
4063            }
4064        } else {
4065            self.ident_from_token(tok)
4066        }
4067    }
4068
4069    fn expect_property_name(&mut self, message: &str) -> Option<Token> {
4070        if !is_property_name(&self.peek().kind) {
4071            self.error_at_peek(message.to_string());
4072            return None;
4073        }
4074        Some(self.advance())
4075    }
4076
4077    fn expect_property_ident(&mut self, message: &str) -> Option<Ident> {
4078        if !is_property_name(&self.peek().kind)
4079            && !matches!(self.peek().kind, TokenKind::StringLiteral(_))
4080        {
4081            self.error_at_peek(message.to_string());
4082            return None;
4083        }
4084        let tok = self.advance();
4085        Some(self.property_ident_from_token(&tok))
4086    }
4087
4088    /// The span of a `readonly` modifier that [`Parser::eat_readonly_property_modifier`]
4089    /// consumed at `member_start`.
4090    fn readonly_modifier_span(&self, member_start: u32) -> Span {
4091        self.span(member_start, member_start + "readonly".len() as u32)
4092    }
4093
4094    /// Reports `readonly` on a method, accessor or method signature, as TypeScript
4095    /// does (TS1024). Parsing continues: the modifier changes nothing else.
4096    fn reject_readonly_modifier(&mut self, span: Span) {
4097        self.error_at_with_help(
4098            span,
4099            "`readonly` can only modify a property or index signature",
4100            vec!["remove `readonly`".to_string()],
4101        );
4102    }
4103
4104    fn eat_readonly_property_modifier(&mut self) -> bool {
4105        if self.peek_identifier_text_is("readonly")
4106            && !self.line_break_after_peek()
4107            && (self.peek_starts_property_after_readonly()
4108                || matches!(self.peek_at(1).kind, TokenKind::LeftBracket))
4109        {
4110            self.advance();
4111            return true;
4112        }
4113        false
4114    }
4115
4116    fn peek_starts_property_after_readonly(&self) -> bool {
4117        let name = &self.peek_at(1).kind;
4118        let after_name = &self.peek_at(2).kind;
4119        let after_optional = &self.peek_at(3).kind;
4120        // A member starts its type with `:`, or its parameter list with `(` — otherwise
4121        // `readonly` is the member's own name.
4122        let starts_member_type =
4123            |kind: &TokenKind| matches!(kind, TokenKind::Colon | TokenKind::LeftParen);
4124        (is_property_name(name) || matches!(name, TokenKind::StringLiteral(_)))
4125            && (starts_member_type(after_name)
4126                || matches!(after_name, TokenKind::Question) && starts_member_type(after_optional))
4127    }
4128
4129    fn peek_identifier_text_is(&self, expected: &str) -> bool {
4130        self.token_is_word(self.peek(), expected)
4131    }
4132
4133    fn peek_at_is_word(&self, offset: usize, expected: &str) -> bool {
4134        self.token_is_word(self.peek_at(offset), expected)
4135    }
4136
4137    fn token_is_word(&self, tok: &Token, expected: &str) -> bool {
4138        matches!(tok.kind, TokenKind::Identifier)
4139            && &self.source[tok.span.start as usize..tok.span.end as usize] == expected
4140    }
4141
4142    /// `delete` is lexed as an identifier: `map.delete(k)`, a `delete()` method and
4143    /// `submilli:http`'s `delete(url)` function all use the name. So it is the
4144    /// operator only when an operand that can't be an argument list follows it, and
4145    /// `delete (o.x)` stays a call. `as` and `satisfies` are lexed as identifiers too:
4146    /// `delete as unknown` casts the function value, while `delete as.x` deletes from a
4147    /// binding named `as`.
4148    fn at_delete_operator(&self) -> bool {
4149        if !self.peek_identifier_text_is("delete") {
4150            return false;
4151        }
4152        if !matches!(
4153            self.peek_at(1).kind,
4154            TokenKind::Identifier | TokenKind::This
4155        ) {
4156            return false;
4157        }
4158        let next_is_cast_keyword =
4159            self.peek_at_is_word(1, "as") || self.peek_at_is_word(1, "satisfies");
4160        !next_is_cast_keyword || cannot_start_type(&self.peek_at(2).kind)
4161    }
4162
4163    /// `type X …` commits to a type-alias declaration only when followed by an
4164    /// identifier, mirroring TypeScript; `type = 5`, `type;`, `type(x)` stay
4165    /// expression statements over a binding named `type`.
4166    fn at_type_alias_head(&self) -> bool {
4167        self.peek_identifier_text_is("type")
4168            && matches!(self.peek_at(1).kind, TokenKind::Identifier)
4169    }
4170
4171    /// Assignment is the lowest-precedence expression and right-associative:
4172    /// `a = b = c` assigns `c` to both, and yields it.
4173    fn parse_expression(&mut self) -> Option<ExprId> {
4174        self.with_recursion_limit(Self::parse_expression_inner)
4175    }
4176
4177    fn parse_expression_inner(&mut self) -> Option<ExprId> {
4178        let written = self.parse_conditional()?;
4179        let shift_assignment = self.peek_shift().filter(|shift| shift.assignment);
4180        if !is_assign_lookahead(&self.peek().kind) && shift_assignment.is_none() {
4181            return Some(written);
4182        }
4183        let target_span = parse_arena_result(self.ast.try_expr(written), &mut self.fatal)?.span;
4184        let target = self.assignment_target(written)?;
4185        let op_tok = self.advance();
4186        let mut op_span = op_tok.span;
4187        let op = if let Some(ShiftToken { op, tokens, .. }) = shift_assignment {
4188            for _ in 1..tokens {
4189                op_span.end = self.advance().span.end;
4190            }
4191            Some(op)
4192        } else {
4193            compound_op_for_token(&op_tok.kind)
4194        };
4195        let value = self.parse_expression()?;
4196        let value_span = parse_arena_result(self.ast.try_expr(value), &mut self.fatal)?.span;
4197        parse_arena_result(
4198            self.ast.try_push_expr(Expr {
4199                kind: ExprKind::Assign {
4200                    target,
4201                    op,
4202                    op_span,
4203                    value,
4204                },
4205                span: self.span(target_span.start, value_span.end),
4206            }),
4207            &mut self.fatal,
4208        )
4209    }
4210
4211    /// The binding, field, or element `written` names, through any parentheses:
4212    /// `(a) = 1` assigns `a`, as in JavaScript.
4213    fn assignment_target(&mut self, written: ExprId) -> Option<ExprId> {
4214        let mut target = written;
4215        while let ExprKind::Paren(inner) =
4216            parse_arena_result(self.ast.try_expr(target), &mut self.fatal)?.kind
4217        {
4218            target = inner;
4219        }
4220        if matches!(
4221            parse_arena_result(self.ast.try_expr(target), &mut self.fatal)?.kind,
4222            ExprKind::Identifier(_) | ExprKind::FieldAccess { .. } | ExprKind::IndexAccess { .. }
4223        ) {
4224            return Some(target);
4225        }
4226        let error_span = parse_arena_result(self.ast.try_expr(written), &mut self.fatal)?.span;
4227        self.error_at_with_help(
4228            error_span,
4229            "invalid assignment target",
4230            vec![
4231                "assign to a variable, a field (`o.f = …`), or an element (`a[i] = …`)".to_string(),
4232            ],
4233        );
4234        None
4235    }
4236
4237    fn parse_conditional(&mut self) -> Option<ExprId> {
4238        if self.is_arrow_start() {
4239            return self.parse_arrow();
4240        }
4241        let cond = self.parse_binary(0)?;
4242        // Ternary sits below every binary op; right-associative.
4243        if !matches!(self.peek().kind, TokenKind::Question) {
4244            return Some(cond);
4245        }
4246        self.advance();
4247        let then_ = self.parse_expression()?;
4248        if !matches!(self.peek().kind, TokenKind::Colon) {
4249            self.error_at_peek_with_help(
4250                "expected `:` to complete ternary expression",
4251                vec!["const x = cond ? then : else;".to_string()],
4252            );
4253            return None;
4254        }
4255        self.advance();
4256        let else_ = self.parse_expression()?;
4257        let cond_span = parse_arena_result(self.ast.try_expr(cond), &mut self.fatal)?.span;
4258        let else_span = parse_arena_result(self.ast.try_expr(else_), &mut self.fatal)?.span;
4259        parse_arena_result(
4260            self.ast.try_push_expr(Expr {
4261                kind: ExprKind::Ternary { cond, then_, else_ },
4262                span: self.span(cond_span.start, else_span.end),
4263            }),
4264            &mut self.fatal,
4265        )
4266    }
4267
4268    fn is_arrow_start(&self) -> bool {
4269        match self.peek().kind {
4270            TokenKind::Identifier => matches!(self.peek_at(1).kind, TokenKind::Arrow),
4271            TokenKind::LeftParen => self.find_arrow_after_matching_paren(),
4272            _ => false,
4273        }
4274    }
4275
4276    /// Index just past the `)` matching the `(` at `index`; `None` when the parens
4277    /// never balance. Pure lookahead — emits no diagnostics.
4278    fn index_after_matching_paren(&self, index: usize) -> Option<usize> {
4279        if !matches!(
4280            self.tokens.get(index).map(|t| &t.kind),
4281            Some(TokenKind::LeftParen)
4282        ) {
4283            return None;
4284        }
4285        self.scan_past_balanced(
4286            index,
4287            |k| matches!(k, TokenKind::LeftParen),
4288            |k| matches!(k, TokenKind::RightParen),
4289        )
4290    }
4291
4292    fn find_arrow_after_matching_paren(&self) -> bool {
4293        let Some(i) = self.index_after_matching_paren(self.pos) else {
4294            return false;
4295        };
4296        let after_type = if matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Colon)) {
4297            match self.scan_past_type_annotation(i + 1, TypePos::ArrowReturn) {
4298                Some(p) => p,
4299                None => return false,
4300            }
4301        } else {
4302            i
4303        };
4304        matches!(
4305            self.tokens.get(after_type).map(|t| &t.kind),
4306            Some(TokenKind::Arrow),
4307        )
4308    }
4309
4310    // Pure lookahead used only by the arrow-disambiguator. No diagnostics.
4311    // Must accept every form `parse_type_annotation` accepts: a form missing
4312    // here makes the disambiguator reject a valid arrow, so the expression
4313    // parser reports a misleading `expected expression` at the return type.
4314    // `arrow_return_type_scanner_matches_type_grammar` pins the two together.
4315    fn scan_past_type_annotation(&self, start: usize, type_pos: TypePos) -> Option<usize> {
4316        self.scan_past_type_annotation_at_depth(start, type_pos, 0)
4317    }
4318
4319    fn scan_past_type_annotation_at_depth(
4320        &self,
4321        start: usize,
4322        type_pos: TypePos,
4323        depth: usize,
4324    ) -> Option<usize> {
4325        if depth >= MAX_PARSE_DEPTH {
4326            return None;
4327        }
4328        if matches!(
4329            self.tokens.get(start).map(|t| &t.kind),
4330            Some(TokenKind::Identifier)
4331        ) && self
4332            .tokens
4333            .get(start + 1)
4334            .is_some_and(|t| self.token_is_word(t, "is"))
4335        {
4336            return self.scan_past_type_annotation_at_depth(start + 2, type_pos, depth + 1);
4337        }
4338        // Leading `|` (prettier's multiline union format).
4339        let start = if matches!(
4340            self.tokens.get(start).map(|t| &t.kind),
4341            Some(TokenKind::Pipe)
4342        ) {
4343            start + 1
4344        } else {
4345            start
4346        };
4347        let mut i = self.scan_past_single_type_member(start, type_pos, depth + 1)?;
4348        while matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Pipe)) {
4349            i = self.scan_past_single_type_member(i + 1, type_pos, depth + 1)?;
4350        }
4351        Some(i)
4352    }
4353
4354    fn scan_past_single_type_member(
4355        &self,
4356        start: usize,
4357        type_pos: TypePos,
4358        depth: usize,
4359    ) -> Option<usize> {
4360        if depth >= MAX_PARSE_DEPTH {
4361            return None;
4362        }
4363        if self.is_contextual_type_operator(start, "keyof")
4364            || self.is_contextual_type_operator(start, "readonly")
4365        {
4366            return self.scan_past_single_type_member(start + 1, type_pos, depth + 1);
4367        }
4368        let mut i = start;
4369        match self.tokens.get(i)?.kind {
4370            TokenKind::Identifier | TokenKind::Void => {
4371                i += 1;
4372                while matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Dot)) {
4373                    if !matches!(
4374                        self.tokens.get(i + 1).map(|t| &t.kind),
4375                        Some(TokenKind::Identifier)
4376                    ) {
4377                        return None;
4378                    }
4379                    i += 2;
4380                }
4381                if matches!(
4382                    self.tokens.get(i).map(|t| &t.kind),
4383                    Some(TokenKind::LessThan)
4384                ) {
4385                    i = self.scan_past_balanced(
4386                        i,
4387                        |k| matches!(k, TokenKind::LessThan),
4388                        |k| matches!(k, TokenKind::GreaterThan),
4389                    )?;
4390                }
4391            }
4392            TokenKind::NullLiteral
4393            | TokenKind::StringLiteral(_)
4394            | TokenKind::NumberLiteral(_)
4395            | TokenKind::BooleanLiteral(_) => {
4396                i += 1;
4397            }
4398            TokenKind::LeftBrace => {
4399                i = self.scan_past_balanced(
4400                    i,
4401                    |k| matches!(k, TokenKind::LeftBrace),
4402                    |k| matches!(k, TokenKind::RightBrace),
4403                )?;
4404            }
4405            TokenKind::LeftBracket => {
4406                i = self.scan_past_balanced(
4407                    i,
4408                    |k| matches!(k, TokenKind::LeftBracket),
4409                    |k| matches!(k, TokenKind::RightBracket),
4410                )?;
4411            }
4412            // Same `(`-disambiguation `parse_type_array` uses, and it has to agree:
4413            // a function type carries its own `=>`, so everything after it is the
4414            // return type and the scan ends with that. Grouping parens instead fall
4415            // through to the postfix `[]` loop below.
4416            TokenKind::LeftParen => {
4417                let is_function_type = self.paren_at_opens_function_type(i, type_pos);
4418                i = self.scan_past_balanced(
4419                    i,
4420                    |k| matches!(k, TokenKind::LeftParen),
4421                    |k| matches!(k, TokenKind::RightParen),
4422                )?;
4423                if is_function_type {
4424                    if !matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Arrow)) {
4425                        return None;
4426                    }
4427                    return self.scan_past_type_annotation_at_depth(
4428                        i + 1,
4429                        TypePos::Anywhere,
4430                        depth + 1,
4431                    );
4432                }
4433            }
4434            _ => return None,
4435        }
4436        while matches!(
4437            self.tokens.get(i).map(|t| &t.kind),
4438            Some(TokenKind::LeftBracket)
4439        ) {
4440            i += 1;
4441            if !matches!(
4442                self.tokens.get(i).map(|t| &t.kind),
4443                Some(TokenKind::RightBracket)
4444            ) {
4445                return None;
4446            }
4447            i += 1;
4448        }
4449        Some(i)
4450    }
4451
4452    // `start` must index the opening token; returns the index just past the
4453    // matching close, or None if the tokens run out first.
4454    fn scan_past_balanced(
4455        &self,
4456        start: usize,
4457        is_open: fn(&TokenKind) -> bool,
4458        is_close: fn(&TokenKind) -> bool,
4459    ) -> Option<usize> {
4460        let mut depth: i32 = 1;
4461        let mut i = start + 1;
4462        while i < self.tokens.len() && depth > 0 {
4463            let token = self.tokens.get(i)?;
4464            let kind = &token.kind;
4465            if is_open(kind) {
4466                depth += 1;
4467            } else if is_close(kind) {
4468                depth -= 1;
4469            } else if matches!(kind, TokenKind::Eof) {
4470                return None;
4471            }
4472            i += 1;
4473        }
4474        if depth == 0 { Some(i) } else { None }
4475    }
4476
4477    fn parse_arrow(&mut self) -> Option<ExprId> {
4478        let start_span = self.peek().span;
4479
4480        let params = match self.peek().kind {
4481            TokenKind::Identifier => {
4482                let name_tok = self.advance();
4483                let name = self.ident_from_token(&name_tok);
4484                vec![ParamDecl {
4485                    name,
4486                    ty: None,
4487                    default: None,
4488                    optional: false,
4489                    pattern: None,
4490                    rest: false,
4491                    modifiers: None,
4492                }]
4493            }
4494            TokenKind::LeftParen => {
4495                self.advance();
4496                let params = if matches!(self.peek().kind, TokenKind::RightParen) {
4497                    Vec::new()
4498                } else {
4499                    self.parse_arrow_param_list()?
4500                };
4501                if !matches!(self.peek().kind, TokenKind::RightParen) {
4502                    self.error_at_peek("expected `)`");
4503                    return None;
4504                }
4505                self.advance();
4506                params
4507            }
4508            _ => return self.invariant_failure("is_arrow_start guarded the entry"),
4509        };
4510
4511        let (return_type, type_predicate) = if matches!(self.peek().kind, TokenKind::Colon) {
4512            self.advance();
4513            self.parse_predicate_or_return_type(TypePos::ArrowReturn)?
4514        } else {
4515            (None, None)
4516        };
4517
4518        if !matches!(self.peek().kind, TokenKind::Arrow) {
4519            self.error_at_peek("expected `=>` in arrow function");
4520            return None;
4521        }
4522        self.advance();
4523
4524        let (body, end_pos) = if matches!(self.peek().kind, TokenKind::LeftBrace) {
4525            let block = self.parse_block()?;
4526            let span = parse_arena_result(self.ast.try_stmt(block), &mut self.fatal)?.span;
4527            (ArrowBody::Block(block), span.end)
4528        } else {
4529            let expr = self.parse_expression()?;
4530            let span = parse_arena_result(self.ast.try_expr(expr), &mut self.fatal)?.span;
4531            (ArrowBody::Expr(expr), span.end)
4532        };
4533
4534        parse_arena_result(
4535            self.ast.try_push_expr(Expr {
4536                kind: ExprKind::Arrow {
4537                    params,
4538                    return_type,
4539                    type_predicate,
4540                    body,
4541                },
4542                span: self.span(start_span.start, end_pos),
4543            }),
4544            &mut self.fatal,
4545        )
4546    }
4547
4548    /// Function expressions share parameter/body parsing with arrows, but retain
4549    /// their own receiver and optional recursive-name binding.
4550    fn parse_function_expression(&mut self) -> Option<ExprId> {
4551        let kw = self.advance();
4552
4553        if matches!(self.peek().kind, TokenKind::LessThan) {
4554            self.error_at_peek("generic function expressions are not supported");
4555            return None;
4556        }
4557
4558        let self_name = if matches!(self.peek().kind, TokenKind::Identifier) {
4559            let name_tok = self.advance();
4560            Some(self.ident_from_token(&name_tok))
4561        } else {
4562            None
4563        };
4564
4565        if !matches!(self.peek().kind, TokenKind::LeftParen) {
4566            self.error_at_peek("expected `(` after `function`");
4567            return None;
4568        }
4569        self.advance();
4570
4571        let this_type = if matches!(self.peek().kind, TokenKind::This) {
4572            self.advance();
4573            if !matches!(self.peek().kind, TokenKind::Colon) {
4574                self.error_at_peek("expected a type annotation after `this`");
4575                return None;
4576            }
4577            self.advance();
4578            let ty = self.parse_type_annotation()?;
4579            if matches!(self.peek().kind, TokenKind::Comma) {
4580                self.advance();
4581            } else if !matches!(self.peek().kind, TokenKind::RightParen) {
4582                self.error_at_peek("expected `,` or `)` after the `this` parameter");
4583                return None;
4584            }
4585            Some(ty)
4586        } else {
4587            None
4588        };
4589        let params = if matches!(self.peek().kind, TokenKind::RightParen) {
4590            Vec::new()
4591        } else {
4592            let saved_class = self.class_member_body_depth;
4593            let saved_function = self.function_expression_body_depth;
4594            self.class_member_body_depth = 0;
4595            self.function_expression_body_depth = 1;
4596            let params = self.parse_arrow_param_list();
4597            self.class_member_body_depth = saved_class;
4598            self.function_expression_body_depth = saved_function;
4599            params?
4600        };
4601        if !matches!(self.peek().kind, TokenKind::RightParen) {
4602            self.error_at_peek("expected `)`");
4603            return None;
4604        }
4605        self.advance();
4606
4607        let (return_type, type_predicate) = if matches!(self.peek().kind, TokenKind::Colon) {
4608            self.advance();
4609            self.parse_predicate_or_return_type(TypePos::ArrowReturn)?
4610        } else {
4611            (None, None)
4612        };
4613
4614        if !matches!(self.peek().kind, TokenKind::LeftBrace) {
4615            self.error_at_peek("expected `{` to open the function body");
4616            return None;
4617        }
4618        let saved = self.class_member_body_depth;
4619        self.class_member_body_depth = 0;
4620        self.function_expression_body_depth += 1;
4621        let block = self.parse_block();
4622        self.function_expression_body_depth -= 1;
4623        self.class_member_body_depth = saved;
4624        let block = block?;
4625        let end = parse_arena_result(self.ast.try_stmt(block), &mut self.fatal)?
4626            .span
4627            .end;
4628
4629        let function = parse_arena_result(
4630            self.ast.try_push_expr(Expr {
4631                kind: ExprKind::Arrow {
4632                    params,
4633                    return_type,
4634                    type_predicate,
4635                    body: ArrowBody::Block(block),
4636                },
4637                span: self.span(kw.span.start, end),
4638            }),
4639            &mut self.fatal,
4640        )?;
4641        parse_arena_result(
4642            self.ast.try_push_expr(Expr {
4643                kind: ExprKind::FunctionExpression {
4644                    name: self_name,
4645                    this_type,
4646                    function,
4647                },
4648                span: self.span(kw.span.start, end),
4649            }),
4650            &mut self.fatal,
4651        )
4652    }
4653
4654    /// Shorthand methods still use arrow lowering. Reject their receiver syntax
4655    /// so an enclosing class or function receiver cannot leak into the body.
4656    fn parse_outer_this_boundary<T>(
4657        &mut self,
4658        parse: impl FnOnce(&mut Self) -> Option<T>,
4659    ) -> Option<T> {
4660        let saved = self.class_member_body_depth;
4661        self.class_member_body_depth = 0;
4662        let saved_function = self.function_expression_body_depth;
4663        self.function_expression_body_depth = 0;
4664        let parsed = parse(self);
4665        self.class_member_body_depth = saved;
4666        self.function_expression_body_depth = saved_function;
4667        parsed
4668    }
4669
4670    fn parse_arrow_param_list(&mut self) -> Option<Vec<ParamDecl>> {
4671        let mut params = Vec::new();
4672        loop {
4673            let rest = if matches!(self.peek().kind, TokenKind::DotDotDot) {
4674                self.advance();
4675                true
4676            } else {
4677                false
4678            };
4679            // The lowering pass rewrites expression-body arrows to block form when any param
4680            // has a destructuring pattern.
4681            let (name, pattern) = match self.peek().kind {
4682                TokenKind::LeftBrace | TokenKind::LeftBracket => {
4683                    if rest {
4684                        self.error_at_peek("rest parameter cannot be destructured");
4685                        return None;
4686                    }
4687                    let binding = self.parse_binding()?;
4688                    let placeholder = Ident {
4689                        name: String::new(),
4690                        span: binding.span(),
4691                    };
4692                    (placeholder, Some(binding))
4693                }
4694                _ => {
4695                    let name_tok = self.expect_identifier(if rest {
4696                        "expected identifier after `...`"
4697                    } else {
4698                        "expected parameter name"
4699                    })?;
4700                    let name = self.ident_from_token(&name_tok);
4701                    (name, None)
4702                }
4703            };
4704            let optional = self.parse_parameter_optional(rest, pattern.is_some())?;
4705            let ty = if matches!(self.peek().kind, TokenKind::Colon) {
4706                self.advance();
4707                Some(self.parse_type_annotation()?)
4708            } else {
4709                // Rest without annotation is OK at parse time; the typechecker fills it
4710                // from a contextual function-type hint if available.
4711                None
4712            };
4713            let default = self.parse_parameter_default(rest, optional)?;
4714            params.push(ParamDecl {
4715                name,
4716                ty,
4717                default,
4718                optional,
4719                pattern,
4720                rest,
4721                modifiers: None,
4722            });
4723            match self.peek().kind {
4724                TokenKind::Comma => {
4725                    if rest {
4726                        let comma = self.advance();
4727                        self.error_at(comma.span, "rest parameter must be the last parameter");
4728                        return None;
4729                    }
4730                    self.advance();
4731                    if matches!(self.peek().kind, TokenKind::RightParen) {
4732                        break;
4733                    }
4734                }
4735                TokenKind::RightParen => break,
4736                _ => {
4737                    self.error_at_peek("expected `,` or `)`");
4738                    return None;
4739                }
4740            }
4741        }
4742        self.check_parameter_order(params.iter().map(ParamOrder::of_param));
4743        Some(params)
4744    }
4745
4746    fn parse_binary(&mut self, min_prec: u8) -> Option<ExprId> {
4747        self.with_recursion_limit(|parser| parser.parse_binary_inner(min_prec))
4748    }
4749
4750    /// Angle tokens stay separate for nested type arguments. Only adjacent
4751    /// source tokens form a shift; whitespace and comments cannot join them.
4752    fn peek_shift(&self) -> Option<ShiftToken> {
4753        let first = self.peek();
4754        let second = self.peek_at(1);
4755        if first.span.end != second.span.start {
4756            return None;
4757        }
4758        match (&first.kind, &second.kind) {
4759            (TokenKind::LessThan, TokenKind::LessThan) => {
4760                Some(ShiftToken::new(BinOp::Shl, 2, false))
4761            }
4762            (TokenKind::LessThan, TokenKind::LessEquals) => {
4763                Some(ShiftToken::new(BinOp::Shl, 2, true))
4764            }
4765            (TokenKind::GreaterThan, TokenKind::GreaterEquals) => {
4766                Some(ShiftToken::new(BinOp::Shr, 2, true))
4767            }
4768            (TokenKind::GreaterThan, TokenKind::GreaterThan) => {
4769                let third = self.peek_at(2);
4770                if second.span.end == third.span.start {
4771                    match third.kind {
4772                        TokenKind::GreaterThan => {
4773                            return Some(ShiftToken::new(BinOp::UnsignedShr, 3, false));
4774                        }
4775                        TokenKind::GreaterEquals => {
4776                            return Some(ShiftToken::new(BinOp::UnsignedShr, 3, true));
4777                        }
4778                        _ => {}
4779                    }
4780                }
4781                Some(ShiftToken::new(BinOp::Shr, 2, false))
4782            }
4783            _ => None,
4784        }
4785    }
4786
4787    fn peek_binary(&self) -> Option<BinaryToken> {
4788        if let Some(shift) = self.peek_shift() {
4789            return (!shift.assignment).then_some(BinaryToken {
4790                op: shift.op,
4791                precedence: SHIFT_PRECEDENCE,
4792                tokens: shift.tokens,
4793            });
4794        }
4795        peek_binop(&self.peek().kind).map(|(op, precedence)| BinaryToken {
4796            op,
4797            precedence,
4798            tokens: 1,
4799        })
4800    }
4801
4802    fn parse_binary_inner(&mut self, min_prec: u8) -> Option<ExprId> {
4803        let mut lhs = self.parse_cast()?;
4804        // Track the previous op to reject `a || b ?? c` / `a ?? b || c` mixes. Both
4805        // reach this loop: `??` parses its right operand above `&&` (see
4806        // `right_operand_min_prec`), so `||` / `&&` after it is left for this check.
4807        let mut last_op: Option<BinOp> = None;
4808        let mut reported_mixing = false;
4809        while let Some(BinaryToken {
4810            op,
4811            precedence: prec,
4812            tokens: count,
4813        }) = self.peek_binary()
4814        {
4815            if prec < min_prec {
4816                break;
4817            }
4818            if !reported_mixing
4819                && let Some(last) = last_op
4820                && mixed_logical(last, op)
4821            {
4822                reported_mixing = true;
4823                let op_span = self.peek().span;
4824                // Parsing continues as if the left side were grouped, so one error
4825                // inside an `if (...)` header doesn't cascade through the statement.
4826                // Later mixes in the same chain add nothing, so only the first is reported.
4827                self.error_at_with_help(
4828                    op_span,
4829                    "mixing `??` with `||` / `&&` requires parentheses",
4830                    vec![mixed_logical_help(last, op)],
4831                );
4832            }
4833            if op == BinOp::Pow
4834                && matches!(
4835                    parse_arena_result(self.ast.try_expr(lhs), &mut self.fatal)?.kind,
4836                    ExprKind::Unary { .. }
4837                        | ExprKind::Typeof { .. }
4838                        | ExprKind::Delete { .. }
4839                        | ExprKind::Void { .. }
4840                )
4841            {
4842                let error_span = parse_arena_result(self.ast.try_expr(lhs), &mut self.fatal)?.span;
4843                self.error_at_with_help(
4844                    error_span,
4845                    "an unparenthesized unary expression cannot be the left operand of `**`",
4846                    vec!["choose the grouping explicitly: `(-x) ** 2` or `-(x ** 2)`".into()],
4847                );
4848                return None;
4849            }
4850            let loose = matches!(self.peek().kind, TokenKind::EqEq | TokenKind::BangEq);
4851            for _ in 0..count {
4852                self.advance();
4853            }
4854            let rhs = self.parse_binary(right_operand_min_prec(op, prec))?;
4855            let lhs_span = parse_arena_result(self.ast.try_expr(lhs), &mut self.fatal)?.span;
4856            let rhs_span = parse_arena_result(self.ast.try_expr(rhs), &mut self.fatal)?.span;
4857            if loose {
4858                self.reject_loose_nullish_comparison(op, lhs, rhs)?;
4859            }
4860            lhs = parse_arena_result(
4861                self.ast.try_push_expr(Expr {
4862                    kind: ExprKind::Binary { op, lhs, rhs },
4863                    span: self.span(lhs_span.start, rhs_span.end),
4864                }),
4865                &mut self.fatal,
4866            )?;
4867            last_op = Some(op);
4868        }
4869        Some(lhs)
4870    }
4871
4872    // Higher than every binary op, lower than every unary prefix. Left-associative.
4873    fn parse_cast(&mut self) -> Option<ExprId> {
4874        let mut expr = self.parse_unary()?;
4875        while matches!(self.peek().kind, TokenKind::Instanceof)
4876            || self.peek_identifier_text_is("as")
4877        {
4878            let is_instanceof = matches!(self.peek().kind, TokenKind::Instanceof);
4879            self.advance();
4880            let ty = self.parse_type_annotation()?;
4881            let start = parse_arena_result(self.ast.try_expr(expr), &mut self.fatal)?
4882                .span
4883                .start;
4884            let end = ty.span.end;
4885            let kind = if is_instanceof {
4886                ExprKind::InstanceOf { value: expr, ty }
4887            } else {
4888                self.last_as_type_end = Some(end);
4889                ExprKind::As { expr, ty }
4890            };
4891            expr = parse_arena_result(
4892                self.ast.try_push_expr(Expr {
4893                    kind,
4894                    span: self.span(start, end),
4895                }),
4896                &mut self.fatal,
4897            )?;
4898        }
4899        Some(expr)
4900    }
4901
4902    fn parse_unary(&mut self) -> Option<ExprId> {
4903        self.with_recursion_limit(Self::parse_unary_inner)
4904    }
4905
4906    fn parse_unary_inner(&mut self) -> Option<ExprId> {
4907        // `typeof` is parsed as a unary prefix so `typeof x === "T"` works without a new layer;
4908        // the typechecker rejects `Typeof` outside the recognized equality fold position.
4909        if matches!(self.peek().kind, TokenKind::Typeof) {
4910            return self.parse_prefix_expr(|operand| ExprKind::Typeof { operand });
4911        }
4912        if matches!(self.peek().kind, TokenKind::Void) {
4913            return self.parse_prefix_expr(|operand| ExprKind::Void { operand });
4914        }
4915        if self.at_delete_operator() {
4916            return self.parse_prefix_expr(|operand| ExprKind::Delete { operand });
4917        }
4918        if matches!(self.peek().kind, TokenKind::New) {
4919            let op_tok = self.advance();
4920            let mut callee = self.parse_atom()?;
4921            while matches!(self.peek().kind, TokenKind::Dot) {
4922                self.advance();
4923                if !matches!(self.peek().kind, TokenKind::Identifier) {
4924                    self.error_at_peek("expected identifier after `.` in constructor name");
4925                    return None;
4926                }
4927                let ident_tok = self.advance();
4928                let name = self.ident_from_token(&ident_tok);
4929                let receiver_span =
4930                    parse_arena_result(self.ast.try_expr(callee), &mut self.fatal)?.span;
4931                let combined_span = self.span(receiver_span.start, ident_tok.span.end);
4932                callee = parse_arena_result(
4933                    self.ast.try_push_expr(Expr {
4934                        kind: ExprKind::FieldAccess {
4935                            receiver: callee,
4936                            name,
4937                        },
4938                        span: combined_span,
4939                    }),
4940                    &mut self.fatal,
4941                )?;
4942            }
4943            let type_args = if matches!(self.peek().kind, TokenKind::LessThan) {
4944                let saved_pos = self.pos;
4945                let saved_diag_len = self.diagnostics.len();
4946                if let Some(args) = self.try_parse_type_args_only() {
4947                    Some(args)
4948                } else {
4949                    self.pos = saved_pos;
4950                    self.diagnostics.truncate(saved_diag_len);
4951                    None
4952                }
4953            } else {
4954                None
4955            };
4956            if !matches!(self.peek().kind, TokenKind::LeftParen) {
4957                self.error_at_peek("expected `(` after constructor name in `new` expression");
4958                return None;
4959            }
4960            self.advance();
4961            let args = if matches!(self.peek().kind, TokenKind::RightParen) {
4962                Vec::new()
4963            } else {
4964                self.parse_call_args()?
4965            };
4966            if !matches!(self.peek().kind, TokenKind::RightParen) {
4967                self.error_at_peek("expected `)`");
4968                return None;
4969            }
4970            let close = self.advance();
4971            let new_expr = parse_arena_result(
4972                self.ast.try_push_expr(Expr {
4973                    kind: ExprKind::New {
4974                        callee,
4975                        type_args,
4976                        args,
4977                    },
4978                    span: self.span(op_tok.span.start, close.span.end),
4979                }),
4980                &mut self.fatal,
4981            )?;
4982            // `new Foo()` is a postfix primary: `new Map().set(...)`, `new Map().size`,
4983            // `new Foo()[i]` all continue the chain off the constructor result.
4984            return self.parse_postfix_from(new_expr);
4985        }
4986        let op = match self.peek().kind {
4987            TokenKind::Bang => UnOp::Not,
4988            TokenKind::Tilde => UnOp::BitNot,
4989            TokenKind::Minus => UnOp::Neg,
4990            TokenKind::Plus => UnOp::Pos,
4991            _ => return self.parse_postfix(),
4992        };
4993        self.parse_prefix_expr(|operand| ExprKind::Unary { op, operand })
4994    }
4995
4996    /// Consume a prefix operator and its operand, spanning both.
4997    fn parse_prefix_expr(&mut self, kind: impl FnOnce(ExprId) -> ExprKind) -> Option<ExprId> {
4998        let op_tok = self.advance();
4999        // ASI cannot distinguish the `void` type from the expression operator.
5000        // In expression position a newline after the keyword still awaits an operand.
5001        if matches!(op_tok.kind, TokenKind::Void) && self.at_inserted_semicolon() {
5002            self.advance();
5003        }
5004        let operand = self.parse_unary()?;
5005        let operand_span = parse_arena_result(self.ast.try_expr(operand), &mut self.fatal)?.span;
5006        parse_arena_result(
5007            self.ast.try_push_expr(Expr {
5008                kind: kind(operand),
5009                span: self.span(op_tok.span.start, operand_span.end),
5010            }),
5011            &mut self.fatal,
5012        )
5013    }
5014
5015    fn parse_postfix(&mut self) -> Option<ExprId> {
5016        let expr = self.parse_atom()?;
5017        self.parse_postfix_from(expr)
5018    }
5019
5020    fn parse_postfix_from(&mut self, mut expr: ExprId) -> Option<ExprId> {
5021        // OptionalChain is emitted once at end-of-loop so a speculative-`<` rewind
5022        // never strands a half-built chain. Everything before the first `?.` accumulates
5023        // in `expr` normally; from the first `?.` onward ops go into `parts`.
5024        let mut chain_parts: Option<(ExprId, Vec<crate::ChainPart>)> = None;
5025        loop {
5026            match self.peek().kind {
5027                TokenKind::LeftParen => {
5028                    if let Some((_, parts)) = &mut chain_parts {
5029                        let part = self.parse_chain_call_tail(/*optional=*/ false)?;
5030                        parts.push(part);
5031                    } else {
5032                        expr = self.parse_call_tail(expr, None)?;
5033                    }
5034                }
5035                TokenKind::Dot => {
5036                    if let Some((_, parts)) = &mut chain_parts {
5037                        let part = self.parse_chain_field_tail(/*optional=*/ false)?;
5038                        parts.push(part);
5039                    } else {
5040                        expr = self.parse_field_access_tail(expr)?;
5041                    }
5042                }
5043                TokenKind::LeftBracket => {
5044                    if let Some((_, parts)) = &mut chain_parts {
5045                        let part = self.parse_chain_index_tail(/*optional=*/ false)?;
5046                        parts.push(part);
5047                    } else {
5048                        expr = self.parse_index_access_tail(expr)?;
5049                    }
5050                }
5051                TokenKind::QuestionDot => {
5052                    if chain_parts.is_none() {
5053                        chain_parts = Some((expr, Vec::new()));
5054                    }
5055                    self.advance();
5056                    let part = match self.peek().kind {
5057                        TokenKind::LeftParen => self.parse_chain_call_tail(true)?,
5058                        TokenKind::LeftBracket => self.parse_chain_index_tail(true)?,
5059                        _ if is_property_name(&self.peek().kind) => {
5060                            self.parse_chain_field_tail(true)?
5061                        }
5062                        _ => {
5063                            self.error_at_peek(
5064                                "expected field name, `(` for call, or `[` for index after `?.`",
5065                            );
5066                            return None;
5067                        }
5068                    };
5069                    if let Some((_, parts)) = &mut chain_parts {
5070                        parts.push(part);
5071                    }
5072                }
5073                // `<` after a callable expression might be a generic call `f<T>(args)`.
5074                // Speculatively parse; on failure restore state and let `parse_binary`
5075                // treat `<` as a comparison.
5076                TokenKind::LessThan => {
5077                    if chain_parts.is_some() {
5078                        // `a?.<T>()` not yet supported — terminate the chain.
5079                        break;
5080                    }
5081                    let saved_pos = self.pos;
5082                    let saved_diag_len = self.diagnostics.len();
5083                    if let Some(call_id) = self.try_parse_generic_call_tail(expr) {
5084                        expr = call_id;
5085                    } else {
5086                        self.pos = saved_pos;
5087                        self.diagnostics.truncate(saved_diag_len);
5088                        break;
5089                    }
5090                }
5091                TokenKind::Bang => {
5092                    if let Some((_, parts)) = &mut chain_parts {
5093                        let tok = self.advance();
5094                        parts.push(crate::ChainPart::NonNull { span: tok.span });
5095                        continue;
5096                    }
5097                    let tok = self.advance();
5098                    let start = parse_arena_result(self.ast.try_expr(expr), &mut self.fatal)?
5099                        .span
5100                        .start;
5101                    expr = parse_arena_result(
5102                        self.ast.try_push_expr(Expr {
5103                            kind: ExprKind::PostfixUnary {
5104                                op: PostfixOp::NonNullAssert,
5105                                operand: expr,
5106                            },
5107                            span: self.span(start, tok.span.end),
5108                        }),
5109                        &mut self.fatal,
5110                    )?;
5111                }
5112                // Postfix `++`/`--` terminates the chain; `a?.b++` is rejected.
5113                TokenKind::PlusPlus | TokenKind::MinusMinus => {
5114                    if chain_parts.is_some() {
5115                        break;
5116                    }
5117                    let tok = self.advance();
5118                    let op = if matches!(tok.kind, TokenKind::PlusPlus) {
5119                        PostfixOp::Inc
5120                    } else {
5121                        PostfixOp::Dec
5122                    };
5123                    let start = parse_arena_result(self.ast.try_expr(expr), &mut self.fatal)?
5124                        .span
5125                        .start;
5126                    expr = parse_arena_result(
5127                        self.ast.try_push_expr(Expr {
5128                            kind: ExprKind::PostfixUnary { op, operand: expr },
5129                            span: self.span(start, tok.span.end),
5130                        }),
5131                        &mut self.fatal,
5132                    )?;
5133                    break;
5134                }
5135                _ => break,
5136            }
5137        }
5138        if let Some((base, parts)) = chain_parts {
5139            let base_span = parse_arena_result(self.ast.try_expr(base), &mut self.fatal)?.span;
5140            let end = parts.last().map_or(base_span.end, |p| p.span().end);
5141            Some(parse_arena_result(
5142                self.ast.try_push_expr(Expr {
5143                    kind: ExprKind::OptionalChain { base, parts },
5144                    span: self.span(base_span.start, end),
5145                }),
5146                &mut self.fatal,
5147            )?)
5148        } else {
5149            Some(expr)
5150        }
5151    }
5152
5153    // `.` is consumed here for the non-optional case; `?.` was consumed by the caller.
5154    fn parse_chain_field_tail(&mut self, optional: bool) -> Option<crate::ChainPart> {
5155        let start_pos = self.peek().span.start;
5156        if !optional {
5157            self.advance();
5158        }
5159        let name_tok = if is_property_name(&self.peek().kind) {
5160            self.advance()
5161        } else {
5162            let what = if optional { "?." } else { "." };
5163            self.error_at_peek(format!("expected field name after `{what}`"));
5164            return None;
5165        };
5166        let name = self.ident_from_token(&name_tok);
5167        let end = name.span.end;
5168        Some(crate::ChainPart::Field {
5169            name,
5170            optional,
5171            span: self.span(start_pos, end),
5172        })
5173    }
5174
5175    fn parse_chain_index_tail(&mut self, optional: bool) -> Option<crate::ChainPart> {
5176        let open = self.advance();
5177        let idx = self.parse_expression()?;
5178        if !matches!(self.peek().kind, TokenKind::RightBracket) {
5179            self.error_at_peek("expected `]`");
5180            return None;
5181        }
5182        let close = self.advance();
5183        Some(crate::ChainPart::Index {
5184            idx,
5185            optional,
5186            span: self.span(open.span.start, close.span.end),
5187        })
5188    }
5189
5190    fn parse_chain_call_tail(&mut self, optional: bool) -> Option<crate::ChainPart> {
5191        let open = self.advance();
5192        let args = if matches!(self.peek().kind, TokenKind::RightParen) {
5193            Vec::new()
5194        } else {
5195            self.parse_call_args()?
5196        };
5197        if !matches!(self.peek().kind, TokenKind::RightParen) {
5198            self.error_at_peek("expected `)`");
5199            return None;
5200        }
5201        let close = self.advance();
5202        Some(crate::ChainPart::Call {
5203            args,
5204            type_args: None,
5205            optional,
5206            span: self.span(open.span.start, close.span.end),
5207        })
5208    }
5209
5210    fn parse_field_access_tail(&mut self, receiver: ExprId) -> Option<ExprId> {
5211        self.advance();
5212        // Keywords are valid field names — `Foo.new(args)` is the canonical constructor call.
5213        let name_tok = if is_property_name(&self.peek().kind) {
5214            self.advance()
5215        } else {
5216            self.error_at_peek("expected field name after `.`");
5217            return None;
5218        };
5219        let name = self.ident_from_token(&name_tok);
5220        let receiver_span = parse_arena_result(self.ast.try_expr(receiver), &mut self.fatal)?.span;
5221        parse_arena_result(
5222            self.ast.try_push_expr(Expr {
5223                kind: ExprKind::FieldAccess {
5224                    receiver,
5225                    name: name.clone(),
5226                },
5227                span: self.span(receiver_span.start, name.span.end),
5228            }),
5229            &mut self.fatal,
5230        )
5231    }
5232
5233    fn parse_index_access_tail(&mut self, receiver: ExprId) -> Option<ExprId> {
5234        self.advance();
5235        let index = self.parse_expression()?;
5236        if !matches!(self.peek().kind, TokenKind::RightBracket) {
5237            self.error_at_peek("expected `]`");
5238            return None;
5239        }
5240        let close = self.advance();
5241        let receiver_span = parse_arena_result(self.ast.try_expr(receiver), &mut self.fatal)?.span;
5242        parse_arena_result(
5243            self.ast.try_push_expr(Expr {
5244                kind: ExprKind::IndexAccess { receiver, index },
5245                span: self.span(receiver_span.start, close.span.end),
5246            }),
5247            &mut self.fatal,
5248        )
5249    }
5250
5251    fn parse_call_tail(
5252        &mut self,
5253        callee: ExprId,
5254        type_args: Option<Vec<TypeAnnotation>>,
5255    ) -> Option<ExprId> {
5256        self.advance();
5257        let args = if matches!(self.peek().kind, TokenKind::RightParen) {
5258            Vec::new()
5259        } else {
5260            self.parse_call_args()?
5261        };
5262        if !matches!(self.peek().kind, TokenKind::RightParen) {
5263            self.error_at_peek("expected `)`");
5264            return None;
5265        }
5266        let close = self.advance();
5267
5268        let callee_span = parse_arena_result(self.ast.try_expr(callee), &mut self.fatal)?.span;
5269        parse_arena_result(
5270            self.ast.try_push_expr(Expr {
5271                kind: ExprKind::Call {
5272                    callee,
5273                    type_args,
5274                    args,
5275                },
5276                span: self.span(callee_span.start, close.span.end),
5277            }),
5278            &mut self.fatal,
5279        )
5280    }
5281
5282    // Caller restores position + diagnostics on `None` so `<` is re-interpreted as comparison.
5283    fn try_parse_generic_call_tail(&mut self, callee: ExprId) -> Option<ExprId> {
5284        let type_args = self.try_parse_type_args_only()?;
5285        if !matches!(self.peek().kind, TokenKind::LeftParen) {
5286            return None;
5287        }
5288        self.parse_call_tail(callee, Some(type_args))
5289    }
5290
5291    fn try_parse_type_args_only(&mut self) -> Option<Vec<TypeAnnotation>> {
5292        if !matches!(self.peek().kind, TokenKind::LessThan) {
5293            return self.invariant_failure("type argument parser expected `<`");
5294        }
5295        self.advance();
5296
5297        if matches!(self.peek().kind, TokenKind::GreaterThan) {
5298            return None;
5299        }
5300
5301        let mut type_args = Vec::new();
5302        loop {
5303            let ta = self.parse_type_annotation()?;
5304            type_args.push(ta);
5305            match self.peek().kind {
5306                TokenKind::Comma => {
5307                    self.advance();
5308                    if matches!(self.peek().kind, TokenKind::GreaterThan) {
5309                        self.advance();
5310                        break;
5311                    }
5312                }
5313                TokenKind::GreaterThan => {
5314                    self.advance();
5315                    break;
5316                }
5317                _ => return None,
5318            }
5319        }
5320        Some(type_args)
5321    }
5322
5323    fn parse_call_args(&mut self) -> Option<Vec<ExprId>> {
5324        let mut args = Vec::new();
5325        loop {
5326            let arg = self.parse_expression()?;
5327            args.push(arg);
5328            match self.peek().kind {
5329                TokenKind::Comma => {
5330                    self.advance();
5331                    if matches!(self.peek().kind, TokenKind::RightParen) {
5332                        return Some(args);
5333                    }
5334                }
5335                TokenKind::RightParen => return Some(args),
5336                _ => {
5337                    self.error_at_peek("expected `,` or `)`");
5338                    return None;
5339                }
5340            }
5341        }
5342    }
5343
5344    /// `<T>expr`, TypeScript's original cast syntax, lowered to the same
5345    /// [`ExprKind::As`] node `expr as T` produces — so it is checked and emitted
5346    /// identically, including the runtime `ref.test` our `as` performs.
5347    ///
5348    /// The operand is a unary expression, which is what binds `<T>-1` as a cast of
5349    /// `-1` rather than a cast of `1` that is then negated.
5350    fn parse_angle_cast(&mut self) -> Option<ExprId> {
5351        let open = self.advance();
5352        let ty = self.parse_type_annotation()?;
5353        if !matches!(self.peek().kind, TokenKind::GreaterThan) {
5354            self.error_at_peek("expected `>` to close a type assertion");
5355            return None;
5356        }
5357        self.advance();
5358        let expr = self.parse_unary()?;
5359        let end = parse_arena_result(self.ast.try_expr(expr), &mut self.fatal)?
5360            .span
5361            .end;
5362        parse_arena_result(
5363            self.ast.try_push_expr(Expr {
5364                kind: ExprKind::As { expr, ty },
5365                span: self.span(open.span.start, end),
5366            }),
5367            &mut self.fatal,
5368        )
5369    }
5370
5371    fn parse_atom(&mut self) -> Option<ExprId> {
5372        match self.peek().kind {
5373            // `<T>expr` — the older spelling of `expr as T`, lowered to the same node.
5374            // Unambiguous only because this is an atom's first token: a `<` that means
5375            // comparison always has a left operand, and one that opens a generic
5376            // argument list always follows a callee, so neither reaches here.
5377            TokenKind::LessThan => return self.parse_angle_cast(),
5378            TokenKind::LeftParen => return self.parse_paren(),
5379            // `{` here is an object literal; statement-start `{` is dispatched to
5380            // `parse_block` before `parse_atom` runs, so the two stay disjoint.
5381            TokenKind::LeftBrace => return self.parse_object_literal(),
5382            TokenKind::LeftBracket => return self.parse_array_literal(),
5383            TokenKind::TemplateHead(_) => return self.parse_template_literal(),
5384            TokenKind::This => return self.parse_this_or_super(true),
5385            TokenKind::Super => return self.parse_this_or_super(false),
5386            TokenKind::Function => return self.parse_function_expression(),
5387            _ => {}
5388        }
5389        if !matches!(
5390            self.peek().kind,
5391            TokenKind::NumberLiteral(_)
5392                | TokenKind::BigIntLiteral(_)
5393                | TokenKind::StringLiteral(_)
5394                | TokenKind::TemplateNoSubstitution(_)
5395                | TokenKind::BooleanLiteral(_)
5396                | TokenKind::NullLiteral
5397                | TokenKind::Identifier
5398                | TokenKind::RegexLiteral { .. }
5399        ) {
5400            self.error_at_peek("expected expression");
5401            return None;
5402        }
5403        let tok = self.advance();
5404        let span = tok.span;
5405        let kind = match tok.kind {
5406            TokenKind::NumberLiteral(v) => ExprKind::Number(v),
5407            TokenKind::BigIntLiteral(s) => ExprKind::BigInt(s),
5408            TokenKind::StringLiteral(s) => ExprKind::String(s),
5409            TokenKind::TemplateNoSubstitution(s) => ExprKind::String(s),
5410            TokenKind::BooleanLiteral(b) => ExprKind::Boolean(b),
5411            TokenKind::NullLiteral => ExprKind::Null,
5412            TokenKind::Identifier => ExprKind::Identifier(Ident {
5413                name: self.source[span.start as usize..span.end as usize].to_string(),
5414                span,
5415            }),
5416            TokenKind::RegexLiteral { source, flags } => ExprKind::Regex { source, flags },
5417            // No cursor movement occurs between the accepted-kind check and advance.
5418            _ => unreachable!("advance preserves the accepted expression token"),
5419        };
5420        parse_arena_result(self.ast.try_push_expr(Expr { kind, span }), &mut self.fatal)
5421    }
5422
5423    /// Ordinary functions establish a `this` boundary and exclude an enclosing
5424    /// class's `super`; arrows inherit the current parsing context.
5425    fn parse_this_or_super(&mut self, is_this: bool) -> Option<ExprId> {
5426        let tok = self.advance();
5427        let span = tok.span;
5428        if !is_this && self.class_member_body_depth == 0 {
5429            self.error_at_with_help(
5430                span,
5431                "`super` is only valid inside a class method or constructor body",
5432                vec![
5433                    "call `super(...)` in a subclass constructor or `super.method(...)` \
5434                     in a subclass method"
5435                        .to_string(),
5436                ],
5437            );
5438        }
5439        let kind = if is_this
5440            && self.class_member_body_depth == 0
5441            && self.function_expression_body_depth == 0
5442        {
5443            ExprKind::ThisOutsideReceiver
5444        } else if is_this {
5445            ExprKind::This
5446        } else {
5447            ExprKind::Super
5448        };
5449        parse_arena_result(self.ast.try_push_expr(Expr { kind, span }), &mut self.fatal)
5450    }
5451
5452    // parse_atom dispatches immediately after matching TemplateHead.
5453    // The remaining template tokens are still parsed and diagnosed normally.
5454    fn parse_template_literal(&mut self) -> Option<ExprId> {
5455        let head_tok = self.advance();
5456        let TokenKind::TemplateHead(head) = head_tok.kind else {
5457            unreachable!("template dispatch requires TemplateHead");
5458        };
5459        let start = head_tok.span.start;
5460        let mut parts: Vec<String> = vec![head];
5461        let mut exprs: Vec<ExprId> = Vec::new();
5462        let mut substitution_spans: Vec<Span> = Vec::new();
5463        // The token before a substitution ends just past its `${`; the one after
5464        // starts at its `}`.
5465        let mut substitution_start = head_tok.span.end.saturating_sub(2);
5466        loop {
5467            let expr = self.parse_expression()?;
5468            exprs.push(expr);
5469            let closing = self.peek().span.start;
5470            let substitution_span = self.span(substitution_start, closing.saturating_add(1));
5471            match self.peek().kind.clone() {
5472                TokenKind::TemplateMiddle(s) => {
5473                    let tok = self.advance();
5474                    substitution_spans.push(substitution_span);
5475                    substitution_start = tok.span.end.saturating_sub(2);
5476                    parts.push(s);
5477                }
5478                TokenKind::TemplateTail(s) => {
5479                    let tok = self.advance();
5480                    substitution_spans.push(substitution_span);
5481                    parts.push(s);
5482                    return parse_arena_result(
5483                        self.ast.try_push_expr(Expr {
5484                            kind: ExprKind::TemplateLiteral {
5485                                parts,
5486                                exprs,
5487                                substitution_spans,
5488                            },
5489                            span: self.span(start, tok.span.end),
5490                        }),
5491                        &mut self.fatal,
5492                    );
5493                }
5494                _ => {
5495                    self.error_at_peek("expected `}` to close template interpolation");
5496                    return None;
5497                }
5498            }
5499        }
5500    }
5501
5502    fn parse_object_literal(&mut self) -> Option<ExprId> {
5503        let open = self.advance();
5504
5505        let mut members: Vec<ObjectLiteralMember> = Vec::new();
5506        if !matches!(self.peek().kind, TokenKind::RightBrace) {
5507            loop {
5508                if matches!(self.peek().kind, TokenKind::DotDotDot) {
5509                    let dots = self.advance();
5510                    let value = self.parse_expression()?;
5511                    let value_span =
5512                        parse_arena_result(self.ast.try_expr(value), &mut self.fatal)?.span;
5513                    members.push(ObjectLiteralMember::Spread {
5514                        value,
5515                        span: self.span(dots.span.start, value_span.end),
5516                    });
5517                } else if matches!(self.peek().kind, TokenKind::LeftBracket) {
5518                    self.advance();
5519                    let key = self.parse_expression()?;
5520                    if !matches!(self.peek().kind, TokenKind::RightBracket) {
5521                        self.error_at_peek("expected `]` after computed property key");
5522                        return None;
5523                    }
5524                    self.advance();
5525                    if !matches!(self.peek().kind, TokenKind::Colon) {
5526                        self.error_at_peek("expected `:` after computed property key; computed methods are not supported");
5527                        return None;
5528                    }
5529                    self.advance();
5530                    let value = self.parse_expression()?;
5531                    members.push(ObjectLiteralMember::Computed { key, value });
5532                } else {
5533                    let field = self.parse_object_literal_field()?;
5534                    // Spread members are dynamic — only literal fields participate in
5535                    // the duplicate-key check.
5536                    if let Some(existing) = members.iter().find_map(|m| match m {
5537                        ObjectLiteralMember::Field(f) if f.name.name == field.name.name => Some(f),
5538                        _ => None,
5539                    }) {
5540                        self.diagnostics.push(Diagnostic {
5541                            severity: Severity::Error,
5542                            span: field.name.span,
5543                            message: format!(
5544                                "duplicate field `{}` in object literal",
5545                                field.name.name
5546                            ),
5547                            help: vec![],
5548                            notes: vec![(existing.name.span, "first defined here".into())],
5549                        });
5550                        // Keep parsing so the user sees all duplicates at once.
5551                    }
5552                    members.push(ObjectLiteralMember::Field(field));
5553                }
5554                if self.at_inserted_semicolon()
5555                    && matches!(self.peek_at(1).kind, TokenKind::RightBrace)
5556                {
5557                    self.advance();
5558                }
5559                match self.peek().kind {
5560                    TokenKind::Comma => {
5561                        self.advance();
5562                        if matches!(self.peek().kind, TokenKind::RightBrace) {
5563                            break;
5564                        }
5565                    }
5566                    TokenKind::RightBrace => break,
5567                    _ => {
5568                        self.error_at_peek("expected `,` or `}`");
5569                        return None;
5570                    }
5571                }
5572            }
5573        }
5574
5575        if !matches!(self.peek().kind, TokenKind::RightBrace) {
5576            self.error_at_peek("expected `}`");
5577            return None;
5578        }
5579        let close = self.advance();
5580
5581        parse_arena_result(
5582            self.ast.try_push_expr(Expr {
5583                kind: ExprKind::ObjectLiteral { members },
5584                span: self.span(open.span.start, close.span.end),
5585            }),
5586            &mut self.fatal,
5587        )
5588    }
5589
5590    fn parse_object_literal_field(&mut self) -> Option<ObjectLiteralField> {
5591        let (name, shorthandable) = match self.peek().kind.clone() {
5592            TokenKind::Identifier => {
5593                let tok = self.advance();
5594                (self.ident_from_token(&tok), true)
5595            }
5596            kind if is_property_name(&kind) => {
5597                let tok = self.advance();
5598                (self.ident_from_token(&tok), false)
5599            }
5600            TokenKind::StringLiteral(_) => {
5601                let tok = self.advance();
5602                (self.property_ident_from_token(&tok), false)
5603            }
5604            _ => {
5605                self.error_at_peek("expected field name");
5606                return None;
5607            }
5608        };
5609
5610        // `{ m(x: T): R { … } }` is shorthand for `{ m: (x: T): R => { … } }`. Method
5611        // shorthand carries no `this` of its own here, for the same reason a function
5612        // expression does not, so the arrow is an exact lowering. Checked before the
5613        // `:` branch and independently of `shorthandable`, because a keyword or string
5614        // key can name a method too.
5615        if matches!(self.peek().kind, TokenKind::LeftParen) {
5616            let value = self.parse_method_shorthand_body(name.span)?;
5617            return Some(ObjectLiteralField { name, value });
5618        }
5619
5620        if !matches!(self.peek().kind, TokenKind::Colon) {
5621            // `{ x }` is shorthand for `{ x: x }`. Only identifier keys can use
5622            // it — a string key like `{ "x" }` has no binding to reference.
5623            if shorthandable {
5624                let value = parse_arena_result(
5625                    self.ast.try_push_expr(Expr {
5626                        kind: ExprKind::Identifier(name.clone()),
5627                        span: name.span,
5628                    }),
5629                    &mut self.fatal,
5630                )?;
5631                return Some(ObjectLiteralField { name, value });
5632            }
5633            self.error_at_peek("expected `:` after field name");
5634            return None;
5635        }
5636        self.advance();
5637
5638        let value = self.parse_expression()?;
5639        Some(ObjectLiteralField { name, value })
5640    }
5641
5642    /// The `(params): R { … }` tail of an object-literal method, lowered to an arrow.
5643    /// `name_span` is the key's span, so the arrow spans the whole member.
5644    fn parse_method_shorthand_body(&mut self, name_span: Span) -> Option<ExprId> {
5645        self.advance();
5646
5647        let params = if matches!(self.peek().kind, TokenKind::RightParen) {
5648            Vec::new()
5649        } else {
5650            self.parse_arrow_param_list()?
5651        };
5652        if !matches!(self.peek().kind, TokenKind::RightParen) {
5653            self.error_at_peek("expected `)`");
5654            return None;
5655        }
5656        self.advance();
5657
5658        let (return_type, type_predicate) = if matches!(self.peek().kind, TokenKind::Colon) {
5659            self.advance();
5660            self.parse_predicate_or_return_type(TypePos::ArrowReturn)?
5661        } else {
5662            (None, None)
5663        };
5664
5665        if !matches!(self.peek().kind, TokenKind::LeftBrace) {
5666            self.error_at_peek("expected `{` to open the method body");
5667            return None;
5668        }
5669        let block = self.parse_outer_this_boundary(Self::parse_block)?;
5670        let end = parse_arena_result(self.ast.try_stmt(block), &mut self.fatal)?
5671            .span
5672            .end;
5673
5674        parse_arena_result(
5675            self.ast.try_push_expr(Expr {
5676                kind: ExprKind::Arrow {
5677                    params,
5678                    return_type,
5679                    type_predicate,
5680                    body: ArrowBody::Block(block),
5681                },
5682                span: self.span(name_span.start, end),
5683            }),
5684            &mut self.fatal,
5685        )
5686    }
5687
5688    fn parse_array_literal(&mut self) -> Option<ExprId> {
5689        let open = self.advance();
5690
5691        let mut elements: Vec<ArrayLiteralElement> = Vec::new();
5692        if !matches!(self.peek().kind, TokenKind::RightBracket) {
5693            loop {
5694                let element = if matches!(self.peek().kind, TokenKind::DotDotDot) {
5695                    let dots = self.advance();
5696                    let value = self.parse_expression()?;
5697                    let value_span =
5698                        parse_arena_result(self.ast.try_expr(value), &mut self.fatal)?.span;
5699                    ArrayLiteralElement::Spread {
5700                        value,
5701                        span: self.span(dots.span.start, value_span.end),
5702                    }
5703                } else {
5704                    ArrayLiteralElement::Value(self.parse_expression()?)
5705                };
5706                elements.push(element);
5707                match self.peek().kind {
5708                    TokenKind::Comma => {
5709                        self.advance();
5710                        if matches!(self.peek().kind, TokenKind::RightBracket) {
5711                            break;
5712                        }
5713                    }
5714                    TokenKind::RightBracket => break,
5715                    _ => {
5716                        self.error_at_peek("expected `,` or `]`");
5717                        return None;
5718                    }
5719                }
5720            }
5721        }
5722
5723        if !matches!(self.peek().kind, TokenKind::RightBracket) {
5724            self.error_at_peek("expected `]`");
5725            return None;
5726        }
5727        let close = self.advance();
5728
5729        parse_arena_result(
5730            self.ast.try_push_expr(Expr {
5731                kind: ExprKind::ArrayLiteral { elements },
5732                span: self.span(open.span.start, close.span.end),
5733            }),
5734            &mut self.fatal,
5735        )
5736    }
5737
5738    fn parse_paren(&mut self) -> Option<ExprId> {
5739        let open = self.advance();
5740        let inner = self.parse_expression()?;
5741        if !matches!(self.peek().kind, TokenKind::RightParen) {
5742            self.error_at_peek("expected `)`");
5743            return None;
5744        }
5745        let close = self.advance();
5746        let span = self.span(open.span.start, close.span.end);
5747        parse_arena_result(
5748            self.ast.try_push_expr(Expr {
5749                kind: ExprKind::Paren(inner),
5750                span,
5751            }),
5752            &mut self.fatal,
5753        )
5754    }
5755
5756    fn with_recursion_limit<T>(&mut self, parse: impl FnOnce(&mut Self) -> Option<T>) -> Option<T> {
5757        if self.fatal.is_some() {
5758            return None;
5759        }
5760        if self.recursion_limit_span.is_some() || self.recursion_depth >= MAX_PARSE_DEPTH {
5761            self.stop_at_recursion_limit();
5762            return None;
5763        }
5764        self.recursion_depth += 1;
5765        let result = parse(self);
5766        self.recursion_depth -= 1;
5767        result
5768    }
5769
5770    fn stop_at_recursion_limit(&mut self) {
5771        self.recursion_limit_span.get_or_insert(self.peek().span);
5772        // Stop recovery and enclosing block loops as well as recursive calls.
5773        self.pos = self.tokens.len().saturating_sub(1);
5774    }
5775
5776    fn recover(&mut self) {
5777        while !self.is_at_eof() {
5778            match self.peek().kind {
5779                TokenKind::Semicolon => {
5780                    self.advance();
5781                    return;
5782                }
5783                TokenKind::RightBrace
5784                | TokenKind::Let
5785                | TokenKind::Const
5786                | TokenKind::Function
5787                | TokenKind::Export
5788                | TokenKind::If
5789                | TokenKind::While
5790                | TokenKind::Return
5791                | TokenKind::Try
5792                | TokenKind::Throw => return,
5793                _ => {
5794                    self.advance();
5795                }
5796            }
5797        }
5798    }
5799
5800    fn peek(&self) -> &Token {
5801        self.tokens.get(self.pos).unwrap_or(&self.eof)
5802    }
5803
5804    fn peek_at(&self, offset: usize) -> &Token {
5805        self.pos
5806            .checked_add(offset)
5807            .and_then(|index| self.tokens.get(index))
5808            .unwrap_or(&self.eof)
5809    }
5810
5811    /// ASI inserts its `;` as a zero-width token; a written `;` always has width.
5812    fn at_inserted_semicolon(&self) -> bool {
5813        let token = self.peek();
5814        matches!(token.kind, TokenKind::Semicolon) && token.span.start == token.span.end
5815    }
5816
5817    fn advance(&mut self) -> Token {
5818        let tok = self.peek().clone();
5819        if self.pos < self.tokens.len().saturating_sub(1) {
5820            self.pos += 1;
5821        }
5822        tok
5823    }
5824
5825    fn is_at_eof(&self) -> bool {
5826        self.fatal.is_some() || matches!(self.peek().kind, TokenKind::Eof)
5827    }
5828
5829    /// Whether a line break separates the next token from the one before it.
5830    fn line_break_before_peek(&self) -> bool {
5831        self.source_has_line_break(self.prev_token_end(), self.peek().span.start)
5832    }
5833
5834    /// Whether a line break separates the next token from the one after it.
5835    fn line_break_after_peek(&self) -> bool {
5836        self.source_has_line_break(self.peek().span.end, self.peek_at(1).span.start)
5837    }
5838
5839    /// The ASI pass drops newline tokens, so line breaks between tokens are read
5840    /// from the source.
5841    fn source_has_line_break(&self, start: u32, end: u32) -> bool {
5842        self.source
5843            .get(start as usize..end as usize)
5844            .is_some_and(|gap| gap.contains(['\n', '\r']))
5845    }
5846
5847    /// Whether the statement being parsed ends here: at a `;`, or at a line break
5848    /// right after an `as T`. ASI keeps a `[` or `(` on the next line attached, but
5849    /// the type already stopped at the break (see `parse_type_array_inner`), and no
5850    /// expression continues past a type, so TypeScript ends the statement there.
5851    fn at_statement_end(&self) -> bool {
5852        matches!(self.peek().kind, TokenKind::Semicolon)
5853            || (self.last_as_type_end == Some(self.prev_token_end())
5854                && self.line_break_before_peek())
5855    }
5856
5857    /// Consumes the `;` that [`Parser::at_statement_end`] found, if there is one, and
5858    /// returns the statement's end offset.
5859    fn finish_statement(&mut self) -> u32 {
5860        if matches!(self.peek().kind, TokenKind::Semicolon) {
5861            return self.advance().span.end;
5862        }
5863        self.prev_token_end()
5864    }
5865
5866    /// End offset of the most recently consumed token (the body of the file's first
5867    /// token if nothing has been consumed yet).
5868    fn prev_token_end(&self) -> u32 {
5869        self.tokens
5870            .get(self.pos.saturating_sub(1))
5871            .unwrap_or(&self.eof)
5872            .span
5873            .end
5874    }
5875
5876    fn take_leading_doc(&mut self) -> Option<crate::DocComment> {
5877        let parsed = self
5878            .peek()
5879            .leading_doc
5880            .as_ref()
5881            .map(crate::parse_doc_comment)
5882            .transpose();
5883        match parsed {
5884            Ok(doc) => doc,
5885            Err(error) => {
5886                self.fatal = Some(error.into_compiler_failure(CompilerStage::Parse));
5887                None
5888            }
5889        }
5890    }
5891
5892    fn parse_doc(&mut self, raw: &crate::RawDoc) -> Option<crate::DocComment> {
5893        match crate::parse_doc_comment(raw) {
5894            Ok(doc) => Some(doc),
5895            Err(error) => {
5896                self.fatal = Some(error.into_compiler_failure(CompilerStage::Parse));
5897                None
5898            }
5899        }
5900    }
5901
5902    fn error_at(&mut self, span: Span, message: impl Into<String>) {
5903        self.diagnostics.push(Diagnostic {
5904            severity: Severity::Error,
5905            span,
5906            message: message.into(),
5907            help: vec![],
5908            notes: vec![],
5909        });
5910    }
5911
5912    /// `==` is `===` here, so `x == null` would silently miss `undefined` — the
5913    /// one case where TypeScript's loose equality differs in a way code relies on.
5914    fn reject_loose_nullish_comparison(
5915        &mut self,
5916        op: BinOp,
5917        lhs: ExprId,
5918        rhs: ExprId,
5919    ) -> Option<()> {
5920        let lhs_span = parse_arena_result(self.ast.try_expr(lhs), &mut self.fatal)?.span;
5921        let rhs_span = parse_arena_result(self.ast.try_expr(rhs), &mut self.fatal)?.span;
5922        let (nullish, operand, nullish_first) =
5923            match (self.nullish_operand(lhs)?, self.nullish_operand(rhs)?) {
5924                (Some(left), Some(right)) => {
5925                    self.reject_nullish_against_nullish(op, lhs_span, rhs_span, [left, right]);
5926                    return Some(());
5927                }
5928                (None, Some(nullish)) => (nullish, lhs, false),
5929                (Some(nullish), None) => (nullish, rhs, true),
5930                (None, None) => return Some(()),
5931            };
5932        let comparison = LooseComparison {
5933            nullish,
5934            nullish_first,
5935            negated: op == BinOp::NotEq,
5936        };
5937        let operand_span = parse_arena_result(self.ast.try_expr(operand), &mut self.fatal)?.span;
5938        let x = self.single_line_source(operand_span).unwrap_or("x");
5939        let strict = self.strict_suggestion(&comparison, x);
5940        let either = self.either_suggestion(&comparison, operand, x)?;
5941        let literal = comparison.nullish.literal.text();
5942        // Quote the comparison as written, so `null == z` stays in that order.
5943        let written = self
5944            .single_line_source(self.span(lhs_span.start, rhs_span.end))
5945            .map_or_else(
5946                || {
5947                    let written_op = if comparison.negated { "!=" } else { "==" };
5948                    comparison.ordered(x, written_op, literal)
5949                },
5950                str::to_string,
5951            );
5952        self.error_at_with_help(
5953            self.span(lhs_span.start, rhs_span.end),
5954            format!(
5955                "`{written}` does not also match `{}`: `==` compares like `===`",
5956                comparison.nullish.literal.other().text()
5957            ),
5958            vec![
5959                either,
5960                format!("to test for `{literal}` alone, write `{strict}`"),
5961            ],
5962        );
5963        Some(())
5964    }
5965
5966    /// Both sides are always nullish. TypeScript's `==` treats `null` and
5967    /// `undefined` as equal, so the comparison has one value, which the help
5968    /// names; a `void` operand with effects still runs, as a statement.
5969    fn reject_nullish_against_nullish(
5970        &mut self,
5971        op: BinOp,
5972        lhs: Span,
5973        rhs: Span,
5974        sides: [NullishOperand; 2],
5975    ) {
5976        let span = self.span(lhs.start, rhs.end);
5977        // A comparison written over several lines can't be quoted on one, so
5978        // the message names it without the source.
5979        let subject = self.single_line_source(span).map_or_else(
5980            || "this comparison".to_string(),
5981            |written| format!("`{written}`"),
5982        );
5983        let value = if op == BinOp::NotEq { "false" } else { "true" };
5984        let effects: Vec<String> = sides
5985            .iter()
5986            .filter_map(|side| side.effect)
5987            .map(|effect| {
5988                self.single_line_source(effect.operand).map_or_else(
5989                    || "the `void` operand's expression".to_string(),
5990                    |statement| format!("`{statement};`"),
5991                )
5992            })
5993            .collect();
5994        let help = if effects.is_empty() {
5995            format!("in TypeScript this is always `{value}`; write `{value}`")
5996        } else {
5997            format!(
5998                "in TypeScript this is always `{value}`; run {} {}, then use `{value}`",
5999                effects.join(" and "),
6000                if effects.len() > 1 {
6001                    "each as its own statement"
6002                } else {
6003                    "as its own statement"
6004                }
6005            )
6006        };
6007        self.error_at_with_help(
6008            span,
6009            format!("{subject} compares two nullish values: `==` compares like `===`"),
6010            vec![help],
6011        );
6012    }
6013
6014    /// The strict comparison against the written nullish value. A `void`
6015    /// operand with effects stays as written, so the operand runs once.
6016    fn strict_suggestion(&self, comparison: &LooseComparison, x: &str) -> String {
6017        let eq = comparison.strict_op();
6018        match comparison.nullish.effect {
6019            Some(effect) => {
6020                let void = self.single_line_source(effect.void).unwrap_or("void expr");
6021                comparison.ordered(x, eq, void)
6022            }
6023            None => format!("{x} {eq} {}", comparison.nullish.literal.text()),
6024        }
6025    }
6026
6027    /// "To test for either" names the operand twice, so one with effects is
6028    /// bound to a `const` first, and a `void` operand's expression runs as a
6029    /// statement of its own.
6030    fn either_suggestion(
6031        &mut self,
6032        comparison: &LooseComparison,
6033        operand: ExprId,
6034        x: &str,
6035    ) -> Option<String> {
6036        let mut steps = Vec::new();
6037        let tested = if self.repeats_without_effect(operand)? {
6038            x
6039        } else {
6040            steps.push(format!("bind `{x}` to a `const` (`const value = {x};`)"));
6041            "value"
6042        };
6043        if let Some(effect) = comparison.nullish.effect {
6044            let statement = self.single_line_source(effect.operand).unwrap_or("expr");
6045            let step = format!("run `{statement};` as its own statement");
6046            if comparison.nullish_first {
6047                steps.insert(0, step);
6048            } else {
6049                steps.push(step);
6050            }
6051        }
6052        let eq = comparison.strict_op();
6053        let joiner = if comparison.negated { "&&" } else { "||" };
6054        let both = format!("{tested} {eq} null {joiner} {tested} {eq} undefined");
6055        Some(if steps.is_empty() {
6056            format!("to test for either, write `{both}`")
6057        } else {
6058            format!(
6059                "to test for either, first {}, then write `{both}`",
6060                steps.join(", then ")
6061            )
6062        })
6063    }
6064
6065    /// Whether naming `id` twice evaluates it twice with no effect: a literal,
6066    /// or a reference path.
6067    fn repeats_without_effect(&mut self, id: ExprId) -> Option<bool> {
6068        let unwrapped = self.unparenthesized(id)?;
6069        let expr = parse_arena_result(self.ast.try_expr(unwrapped), &mut self.fatal)?;
6070        if is_literal(&expr.kind) {
6071            return Some(true);
6072        }
6073        if self
6074            .nullish_operand(unwrapped)?
6075            .is_some_and(|nullish| nullish.effect.is_none())
6076        {
6077            return Some(true);
6078        }
6079        self.is_reference_path(id)
6080    }
6081
6082    /// `null`, `undefined` or `void e`, looking through parentheses.
6083    fn nullish_operand(&mut self, id: ExprId) -> Option<Option<NullishOperand>> {
6084        let id = self.unparenthesized(id)?;
6085        let expr = parse_arena_result(self.ast.try_expr(id), &mut self.fatal)?;
6086        let span = expr.span;
6087        Some(match &expr.kind {
6088            ExprKind::Null => Some(NullishOperand {
6089                literal: Nullish::Null,
6090                effect: None,
6091            }),
6092            ExprKind::Identifier(ident) if ident.name == "undefined" => Some(NullishOperand {
6093                literal: Nullish::Undefined,
6094                effect: None,
6095            }),
6096            ExprKind::Void { operand } => {
6097                let operand_id = self.unparenthesized(*operand)?;
6098                let operand =
6099                    parse_arena_result(self.ast.try_expr(operand_id), &mut self.fatal)?.clone();
6100                // A literal or a reference path reads with no effect to keep.
6101                let effect_free =
6102                    is_literal(&operand.kind) || self.is_reference_path(operand_id)?;
6103                let effect = (!effect_free).then_some(VoidEffect {
6104                    void: span,
6105                    operand: operand.span,
6106                });
6107                Some(NullishOperand {
6108                    literal: Nullish::Undefined,
6109                    effect,
6110                })
6111            }
6112            _ => None,
6113        })
6114    }
6115
6116    /// An identifier, `this`, or a `.field` chain on one: naming it twice evaluates
6117    /// it twice with no effect.
6118    fn is_reference_path(&mut self, id: ExprId) -> Option<bool> {
6119        let mut id = id;
6120        loop {
6121            match &parse_arena_result(self.ast.try_expr(id), &mut self.fatal)?.kind {
6122                ExprKind::Identifier(_) | ExprKind::This => return Some(true),
6123                ExprKind::FieldAccess { receiver, .. } => id = *receiver,
6124                ExprKind::Paren(inner) => id = *inner,
6125                _ => return Some(false),
6126            }
6127        }
6128    }
6129
6130    fn unparenthesized(&mut self, id: ExprId) -> Option<ExprId> {
6131        let mut id = id;
6132        loop {
6133            match parse_arena_result(self.ast.try_expr(id), &mut self.fatal)?.kind {
6134                ExprKind::Paren(inner) => id = inner,
6135                _ => return Some(id),
6136            }
6137        }
6138    }
6139
6140    fn single_line_source(&self, span: Span) -> Option<&'a str> {
6141        self.source
6142            .get(span.start as usize..span.end as usize)
6143            .filter(|text| !text.contains('\n'))
6144    }
6145
6146    fn error_at_with_help(&mut self, span: Span, message: impl Into<String>, help: Vec<String>) {
6147        self.diagnostics.push(Diagnostic {
6148            severity: Severity::Error,
6149            span,
6150            message: message.into(),
6151            help,
6152            notes: vec![],
6153        });
6154    }
6155
6156    fn invariant_failure<T>(&mut self, message: &str) -> Option<T> {
6157        self.fatal.get_or_insert_with(|| CompilerFailure::Internal {
6158            stage: CompilerStage::Parse,
6159            span: None,
6160            message: message.into(),
6161        });
6162        self.pos = self.tokens.len();
6163        None
6164    }
6165
6166    fn error_at_peek(&mut self, message: impl Into<String>) {
6167        let span = self.peek().span;
6168        self.error_at(span, message);
6169    }
6170
6171    fn error_at_peek_with_help(&mut self, message: impl Into<String>, help: Vec<String>) {
6172        let span = self.peek().span;
6173        self.error_at_with_help(span, message, help);
6174    }
6175
6176    fn error_count(&self) -> usize {
6177        self.diagnostics
6178            .iter()
6179            .filter(|d| d.severity == Severity::Error)
6180            .count()
6181    }
6182}
6183
6184fn is_property_name(kind: &TokenKind) -> bool {
6185    matches!(
6186        kind,
6187        TokenKind::Identifier
6188            | TokenKind::BooleanLiteral(_)
6189            | TokenKind::NullLiteral
6190            | TokenKind::Let
6191            | TokenKind::Const
6192            | TokenKind::Function
6193            | TokenKind::If
6194            | TokenKind::Else
6195            | TokenKind::While
6196            | TokenKind::Do
6197            | TokenKind::For
6198            | TokenKind::Break
6199            | TokenKind::Continue
6200            | TokenKind::Return
6201            | TokenKind::Switch
6202            | TokenKind::Case
6203            | TokenKind::Default
6204            | TokenKind::Void
6205            | TokenKind::Interface
6206            | TokenKind::Enum
6207            | TokenKind::In
6208            | TokenKind::Typeof
6209            | TokenKind::Import
6210            | TokenKind::Export
6211            | TokenKind::New
6212            | TokenKind::Try
6213            | TokenKind::Catch
6214            | TokenKind::Finally
6215            | TokenKind::Throw
6216            | TokenKind::Class
6217            | TokenKind::Extends
6218            | TokenKind::Implements
6219            | TokenKind::Super
6220            | TokenKind::This
6221    )
6222}
6223
6224fn is_reserved_identifier_word(kind: &TokenKind) -> bool {
6225    matches!(
6226        kind,
6227        TokenKind::BooleanLiteral(_)
6228            | TokenKind::NullLiteral
6229            | TokenKind::Let
6230            | TokenKind::Const
6231            | TokenKind::Function
6232            | TokenKind::If
6233            | TokenKind::Else
6234            | TokenKind::While
6235            | TokenKind::Do
6236            | TokenKind::For
6237            | TokenKind::Break
6238            | TokenKind::Continue
6239            | TokenKind::Return
6240            | TokenKind::Switch
6241            | TokenKind::Case
6242            | TokenKind::Default
6243            | TokenKind::Export
6244            | TokenKind::Void
6245            | TokenKind::Interface
6246            | TokenKind::Enum
6247            | TokenKind::In
6248            | TokenKind::Typeof
6249            | TokenKind::Import
6250            | TokenKind::New
6251            | TokenKind::Try
6252            | TokenKind::Catch
6253            | TokenKind::Finally
6254            | TokenKind::Throw
6255            | TokenKind::Class
6256            | TokenKind::Extends
6257            | TokenKind::Implements
6258            | TokenKind::Super
6259            | TokenKind::This
6260    )
6261}
6262
6263/// Which statement a `{ … }` specifier list belongs to. An import binds local
6264/// names, which strict mode restricts; an export's are module export names,
6265/// which it doesn't.
6266#[derive(Clone, Copy, PartialEq, Eq)]
6267enum SpecifierList {
6268    Import,
6269    Export,
6270}
6271
6272impl SpecifierList {
6273    fn noun(self) -> &'static str {
6274        match self {
6275            SpecifierList::Import => "import",
6276            SpecifierList::Export => "export",
6277        }
6278    }
6279}
6280
6281/// Words strict mode reserves that Submilli otherwise lexes as identifiers: they
6282/// can't name a binding or a type, but stay valid as property names and modifiers.
6283/// (`implements`, `interface` and `let` are keywords already.)
6284const STRICT_MODE_RESERVED_WORDS: [&str; 6] = [
6285    "package",
6286    "private",
6287    "protected",
6288    "public",
6289    "static",
6290    "yield",
6291];
6292
6293fn reserved_keyword_rename_example(keyword: &str) -> &'static str {
6294    match keyword {
6295        "default" => "defaultValue",
6296        _ => "valueName",
6297    }
6298}
6299
6300fn mixed_logical(prev: BinOp, next: BinOp) -> bool {
6301    let prev_logical = matches!(prev, BinOp::Or | BinOp::And);
6302    let next_logical = matches!(next, BinOp::Or | BinOp::And);
6303    let prev_nullish = matches!(prev, BinOp::NullishCoalesce);
6304    let next_nullish = matches!(next, BinOp::NullishCoalesce);
6305    (prev_logical && next_nullish) || (prev_nullish && next_logical)
6306}
6307
6308/// Spells out both groupings in the order the operators were written. Called
6309/// only for pairs `mixed_logical` accepts, so the non-`??` operator is `||` or `&&`.
6310fn mixed_logical_help(prev: BinOp, next: BinOp) -> String {
6311    let symbol = |op: BinOp| if op == BinOp::And { "&&" } else { "||" };
6312    let (first, second) = if prev == BinOp::NullishCoalesce {
6313        ("??", symbol(next))
6314    } else {
6315        (symbol(prev), "??")
6316    };
6317    format!(
6318        "add parentheses to choose the grouping: `(a {first} b) {second} c` or `a {first} (b {second} c)`"
6319    )
6320}
6321
6322const LOGICAL_AND_PREC: u8 = 2;
6323
6324/// One side of a loose comparison that is always nullish.
6325#[derive(Clone, Copy)]
6326struct NullishOperand {
6327    literal: Nullish,
6328    /// Set for `void e` when `e` is not a literal and may have effects.
6329    effect: Option<VoidEffect>,
6330}
6331
6332#[derive(Clone, Copy, PartialEq, Eq)]
6333enum Nullish {
6334    Null,
6335    Undefined,
6336}
6337
6338impl Nullish {
6339    fn text(self) -> &'static str {
6340        match self {
6341            Nullish::Null => "null",
6342            Nullish::Undefined => "undefined",
6343        }
6344    }
6345
6346    /// The nullish value a loose comparison against `self` fails to match.
6347    fn other(self) -> Self {
6348        match self {
6349            Nullish::Null => Nullish::Undefined,
6350            Nullish::Undefined => Nullish::Null,
6351        }
6352    }
6353}
6354
6355/// A rejected `x == null`-style comparison, as written.
6356struct LooseComparison {
6357    nullish: NullishOperand,
6358    /// Whether the nullish side is written first, as in `null == x`.
6359    nullish_first: bool,
6360    /// `!=` rather than `==`.
6361    negated: bool,
6362}
6363
6364impl LooseComparison {
6365    fn strict_op(&self) -> &'static str {
6366        if self.negated { "!==" } else { "===" }
6367    }
6368
6369    /// `x op nullish`, in the order the comparison was written.
6370    fn ordered(&self, x: &str, op: &str, nullish: &str) -> String {
6371        if self.nullish_first {
6372            format!("{nullish} {op} {x}")
6373        } else {
6374            format!("{x} {op} {nullish}")
6375        }
6376    }
6377}
6378
6379#[derive(Clone, Copy)]
6380struct VoidEffect {
6381    /// The whole `void e`.
6382    void: Span,
6383    /// The `e`, without enclosing parentheses.
6384    operand: Span,
6385}
6386
6387fn is_literal(kind: &ExprKind) -> bool {
6388    matches!(
6389        kind,
6390        ExprKind::Number(_)
6391            | ExprKind::BigInt(_)
6392            | ExprKind::String(_)
6393            | ExprKind::Boolean(_)
6394            | ExprKind::Null
6395    )
6396}
6397
6398/// What `Parser::check_parameter_order` needs from either kind of parameter list.
6399struct ParamOrder<'p> {
6400    optional: bool,
6401    /// Neither optional, defaulted, nor rest.
6402    required: bool,
6403    /// `None` for a destructuring pattern.
6404    name: Option<&'p str>,
6405    span: Span,
6406}
6407
6408impl<'p> ParamOrder<'p> {
6409    fn of_param(param: &'p ParamDecl) -> Self {
6410        let (name, span) = match &param.pattern {
6411            Some(binding) => (None, binding.span()),
6412            None => (Some(param.name.name.as_str()), param.name.span),
6413        };
6414        Self {
6415            optional: param.optional,
6416            required: !param.is_omittable() && !param.rest,
6417            name,
6418            span,
6419        }
6420    }
6421
6422    fn of_field(field: &'p TypeAnnotationField) -> Self {
6423        Self {
6424            optional: field.optional,
6425            required: !field.optional && !field.rest,
6426            name: Some(field.name.name.as_str()),
6427            span: field.name.span,
6428        }
6429    }
6430}
6431
6432/// A binary operator at the cursor, which may span several tokens.
6433struct BinaryToken {
6434    op: BinOp,
6435    precedence: u8,
6436    tokens: usize,
6437}
6438
6439/// `<<`, `>>`, `>>>` or their compound assignments, read from adjacent angle tokens.
6440struct ShiftToken {
6441    op: BinOp,
6442    tokens: usize,
6443    /// The operator ends in `=`: `<<=`, `>>=`, `>>>=`.
6444    assignment: bool,
6445}
6446
6447impl ShiftToken {
6448    fn new(op: BinOp, tokens: usize, assignment: bool) -> Self {
6449        Self {
6450            op,
6451            tokens,
6452            assignment,
6453        }
6454    }
6455}
6456
6457/// Between relational (7) and additive (9) in the `peek_binop` table.
6458const SHIFT_PRECEDENCE: u8 = 8;
6459
6460#[derive(Clone, Copy, PartialEq)]
6461enum TupleElementLabel {
6462    None,
6463    /// `name: T`
6464    Required,
6465    /// `name?: T`
6466    Optional,
6467}
6468
6469fn peek_binop(kind: &TokenKind) -> Option<(BinOp, u8)> {
6470    Some(match kind {
6471        TokenKind::QuestionQuestion => (BinOp::NullishCoalesce, 0),
6472        TokenKind::PipePipe => (BinOp::Or, 1),
6473        TokenKind::AmpAmp => (BinOp::And, LOGICAL_AND_PREC),
6474        TokenKind::Pipe => (BinOp::BitOr, 3),
6475        TokenKind::Caret => (BinOp::BitXor, 4),
6476        TokenKind::Amp => (BinOp::BitAnd, 5),
6477        TokenKind::EqEqEq | TokenKind::EqEq => (BinOp::Eq, 6),
6478        TokenKind::BangEqEq | TokenKind::BangEq => (BinOp::NotEq, 6),
6479        TokenKind::LessThan => (BinOp::Lt, 7),
6480        TokenKind::GreaterThan => (BinOp::Gt, 7),
6481        TokenKind::LessEquals => (BinOp::Le, 7),
6482        TokenKind::GreaterEquals => (BinOp::Ge, 7),
6483        TokenKind::In => (BinOp::In, 7),
6484        // Shifts take `SHIFT_PRECEDENCE` (8); they span several angle tokens, so
6485        // `Parser::peek_shift` recognizes them rather than this table.
6486        TokenKind::Plus => (BinOp::Add, 9),
6487        TokenKind::Minus => (BinOp::Sub, 9),
6488        TokenKind::Star => (BinOp::Mul, 10),
6489        TokenKind::Slash => (BinOp::Div, 10),
6490        TokenKind::Percent => (BinOp::Rem, 10),
6491        // `**` recurses with `prec` not `prec + 1` — that's what makes it right-associative.
6492        TokenKind::StarStar => (BinOp::Pow, 11),
6493        _ => return None,
6494    })
6495}
6496
6497/// After `as` or `satisfies`, a token that can only extend or end an operand, never
6498/// begin a type. `[` counts, as TypeScript reads `delete as [0]` as an index, so a
6499/// tuple cast of a binding named `delete` needs parentheses.
6500fn cannot_start_type(kind: &TokenKind) -> bool {
6501    matches!(
6502        kind,
6503        TokenKind::Dot
6504            | TokenKind::QuestionDot
6505            | TokenKind::LeftBracket
6506            | TokenKind::Bang
6507            | TokenKind::Semicolon
6508            | TokenKind::RightParen
6509            | TokenKind::RightBrace
6510            | TokenKind::Comma
6511            | TokenKind::Eof
6512    )
6513}
6514
6515/// The lowest precedence the right operand of `op` may contain. `**` is
6516/// right-associative, so it recurses at its own level. An operand of `??` may
6517/// not be an unparenthesized `||` / `&&` expression, so it stops below them too.
6518fn right_operand_min_prec(op: BinOp, prec: u8) -> u8 {
6519    match op {
6520        BinOp::Pow => prec,
6521        BinOp::NullishCoalesce => LOGICAL_AND_PREC + 1,
6522        _ => prec + 1,
6523    }
6524}
6525
6526fn compound_op_for_token(kind: &TokenKind) -> Option<BinOp> {
6527    Some(match kind {
6528        TokenKind::PlusEquals => BinOp::Add,
6529        TokenKind::MinusEquals => BinOp::Sub,
6530        TokenKind::StarEquals => BinOp::Mul,
6531        TokenKind::SlashEquals => BinOp::Div,
6532        TokenKind::PercentEquals => BinOp::Rem,
6533        TokenKind::StarStarEquals => BinOp::Pow,
6534        TokenKind::AmpEquals => BinOp::BitAnd,
6535        TokenKind::PipeEquals => BinOp::BitOr,
6536        TokenKind::CaretEquals => BinOp::BitXor,
6537        _ => return None,
6538    })
6539}
6540
6541fn is_assign_lookahead(kind: &TokenKind) -> bool {
6542    matches!(kind, TokenKind::Equals) || compound_op_for_token(kind).is_some()
6543}
6544
6545/// Parser productions use `None` for recovery; arena errors also latch a fatal
6546/// cause so speculative parsing cannot turn them into ordinary syntax errors.
6547fn parse_arena_result<T>(
6548    result: Result<T, crate::arena::ArenaError>,
6549    fatal: &mut Option<CompilerFailure>,
6550) -> Option<T> {
6551    match result {
6552        Ok(value) => Some(value),
6553        Err(error) => {
6554            fatal.get_or_insert_with(|| error.into_compiler_failure(CompilerStage::Parse));
6555            None
6556        }
6557    }
6558}
6559
6560/// Whether a rest parameter's annotation names an array: `T[]`, or `readonly T[]`.
6561fn is_rest_array_annotation(ty: &crate::TypeAnnotation) -> bool {
6562    use crate::ast::TypeAnnotationKind;
6563    match &ty.kind {
6564        TypeAnnotationKind::Array(_) => true,
6565        TypeAnnotationKind::Readonly(inner) => matches!(inner.kind, TypeAnnotationKind::Array(_)),
6566        _ => false,
6567    }
6568}
6569
6570#[cfg(test)]
6571mod tests {
6572    use super::{MAX_ERRORS, MAX_PARSE_DEPTH, Parser, parse};
6573    use crate::source::Sources;
6574    use crate::{Asi, Ast, ExprKind, FileId, ImportKind, StmtKind, Token, TokenKind, diagnostics};
6575
6576    const F: FileId = FileId(0);
6577
6578    #[test]
6579    fn malformed_direct_token_streams_return_typed_failures() {
6580        use crate::compiler_error::CompilerFailure;
6581        for tokens in [
6582            Vec::new(),
6583            vec![Token::new(
6584                TokenKind::Identifier,
6585                crate::Span::new(F, 0, 2).unwrap(),
6586            )],
6587            vec![
6588                Token::new(
6589                    TokenKind::Identifier,
6590                    crate::Span {
6591                        file: F,
6592                        start: 1,
6593                        end: 2,
6594                    },
6595                ),
6596                Token::new(TokenKind::Eof, crate::Span::new(F, 2, 2).unwrap()),
6597            ],
6598            vec![
6599                Token::new(TokenKind::Eof, crate::Span::at(F)),
6600                Token::new(TokenKind::Eof, crate::Span::at(F)),
6601            ],
6602            vec![
6603                Token::new(
6604                    TokenKind::Identifier,
6605                    crate::Span {
6606                        file: F,
6607                        start: 2,
6608                        end: 0,
6609                    },
6610                ),
6611                Token::new(TokenKind::Eof, crate::Span::new(F, 2, 2).unwrap()),
6612            ],
6613            vec![Token::new(TokenKind::Eof, crate::Span::at(FileId(9)))],
6614        ] {
6615            let error = super::parse_checked("é", tokens, F).expect_err("invalid token metadata");
6616            assert!(matches!(
6617                error.fatal,
6618                Some(CompilerFailure::Internal { .. })
6619            ));
6620        }
6621    }
6622
6623    #[test]
6624    fn malformed_documentation_metadata_returns_a_fatal_error() {
6625        let source = "/** hello */ function main(): void {}";
6626        let valid = tokens_of(source);
6627        for doc in [
6628            crate::RawDoc {
6629                text: "/** hi */".into(),
6630                span: crate::Span {
6631                    file: F,
6632                    start: u32::MAX,
6633                    end: u32::MAX,
6634                },
6635            },
6636            crate::RawDoc {
6637                text: "/** different */".into(),
6638                span: crate::Span::new(F, 0, 12).unwrap(),
6639            },
6640            crate::RawDoc {
6641                text: "/** hello */".into(),
6642                span: crate::Span::new(FileId(9), 0, 12).unwrap(),
6643            },
6644        ] {
6645            let mut tokens = valid.clone();
6646            tokens[0].leading_doc = Some(doc);
6647            let error = super::parse_checked(source, tokens, F).expect_err("invalid doc metadata");
6648            assert!(error.to_string().contains("documentation metadata"));
6649        }
6650        assert!(super::parse_checked(source, valid, F).is_ok());
6651    }
6652
6653    #[test]
6654    fn invalid_parser_dispatch_survives_diagnostic_rollback() {
6655        let mut parser = parser_from_source("let x = 1;");
6656        assert!(parser.try_parse_type_args_only().is_none());
6657        parser.diagnostics.clear();
6658        assert!(parser.fatal.is_some());
6659        assert!(parser.parse_expression().is_none());
6660    }
6661
6662    #[test]
6663    fn invalid_arena_read_stops_parser_and_survives_diagnostic_rollback() {
6664        use crate::{
6665            ExprId,
6666            compiler_error::{CompilerFailure, CompilerStage},
6667        };
6668        let mut parser = parser_from_source("let x = 1;");
6669        assert!(parser.statement_kind_for(ExprId(u32::MAX)).is_none());
6670        assert!(matches!(
6671            parser.fatal,
6672            Some(CompilerFailure::Internal {
6673                stage: CompilerStage::Parse,
6674                span: None,
6675                ..
6676            })
6677        ));
6678        parser.diagnostics.clear();
6679        let position = parser.pos;
6680        assert!(parser.parse_statement().is_none());
6681        parser.recover();
6682        assert_eq!(parser.pos, position);
6683        assert!(parser.ast.expr_ids().unwrap().next().is_none());
6684    }
6685
6686    fn tokens_of(source: &str) -> Vec<Token> {
6687        let mut asi = Asi::new(source, crate::FileId(0));
6688        let mut tokens = Vec::new();
6689        loop {
6690            let tok = asi.next_token();
6691            let is_eof = matches!(tok.kind, TokenKind::Eof);
6692            tokens.push(tok);
6693            if is_eof {
6694                break;
6695            }
6696        }
6697        let diags = asi.into_diagnostics();
6698        assert!(diags.is_empty(), "unexpected lexer diagnostics: {diags:?}");
6699        tokens
6700    }
6701
6702    fn parse_str(source: &str) -> (Ast, Vec<crate::Diagnostic>) {
6703        parse(source, tokens_of(source), crate::FileId(0))
6704    }
6705
6706    fn parser_from_source(source: &str) -> Parser<'_> {
6707        Parser {
6708            source,
6709            tokens: tokens_of(source),
6710            file: F,
6711            pos: 0,
6712            ast: Ast::new(),
6713            diagnostics: Vec::new(),
6714            block_depth: 0,
6715            class_member_body_depth: 0,
6716            function_expression_body_depth: 0,
6717            recursion_depth: 0,
6718            recursion_limit_span: None,
6719            last_as_type_end: None,
6720            eof: Token::new(
6721                TokenKind::Eof,
6722                crate::Span::new(F, source.len() as u32, source.len() as u32).unwrap(),
6723            ),
6724            fatal: None,
6725        }
6726    }
6727
6728    #[test]
6729    fn recursion_budget_accepts_boundary_and_restores_depth() {
6730        let source = format!("{}true", "!".repeat(MAX_PARSE_DEPTH - 1));
6731        let mut parser = parser_from_source(&source);
6732        assert!(parser.parse_unary().is_some());
6733        assert_eq!(parser.recursion_depth, 0);
6734        assert!(parser.recursion_limit_span.is_none());
6735
6736        let source = format!("{}true", "!".repeat(MAX_PARSE_DEPTH));
6737        let mut parser = parser_from_source(&source);
6738        assert!(parser.parse_unary().is_none());
6739        assert_eq!(parser.recursion_depth, 0);
6740        assert!(parser.recursion_limit_span.is_some());
6741    }
6742
6743    #[test]
6744    fn parse_empty_input() {
6745        let (ast, diags) = parse_str("");
6746        assert!(diags.is_empty());
6747        assert!(ast.top_level.is_empty());
6748    }
6749
6750    #[test]
6751    fn any_type_is_rejected() {
6752        // `any` is not supported — the parser rejects it in every type position
6753        // (annotation, cast target, array/generic element) and points to the fix.
6754        for src in [
6755            "function main(): void { const x: any = 1; }",
6756            "function main(): void { const x = 1 as any; }",
6757            "function main(): void { const x: any[] = []; }",
6758        ] {
6759            let (_ast, diags) = parse_str(src);
6760            assert!(
6761                diags
6762                    .iter()
6763                    .any(|d| d.message.contains("`any` is not supported")),
6764                "expected an `any` rejection for {src:?}, got: {diags:?}",
6765            );
6766            assert!(
6767                diags
6768                    .iter()
6769                    .any(|d| d.help.iter().any(|h| h.contains("`unknown`"))),
6770                "expected the fix-shape help for {src:?}, got: {diags:?}",
6771            );
6772        }
6773    }
6774
6775    #[test]
6776    fn any_catch_binding_help_suggests_catch_forms() {
6777        // `unknown` is not a valid catch binding type, so the general `any`
6778        // help would send the reader to a second error.
6779        let (_ast, diags) = parse_str("function main(): void { try { } catch (e: any) { } }");
6780        let any_diags: Vec<_> = diags
6781            .iter()
6782            .filter(|d| d.message.contains("`any` is not supported"))
6783            .collect();
6784        assert_eq!(any_diags.len(), 1, "got: {diags:?}");
6785        assert!(
6786            any_diags[0].help.iter().any(|h| h.contains("`catch (e)`")),
6787            "got: {diags:?}"
6788        );
6789        assert!(
6790            !any_diags[0].help.iter().any(|h| h.contains("`unknown`")),
6791            "got: {diags:?}"
6792        );
6793    }
6794
6795    #[test]
6796    fn arrow_return_type_scanner_matches_type_grammar() {
6797        // Every form the annotation grammar accepts must also be accepted by
6798        // the arrow-disambiguator's lookahead (scan_past_type_annotation):
6799        // a miss there turns a valid arrow into `expected expression`.
6800        // When adding a type form to parse_type_array, add a row here.
6801        for ty in [
6802            "number",
6803            "void",
6804            "null",
6805            "ns.Foo",
6806            "Map<string, number>",
6807            "ns.Foo<string>[]",
6808            "number[]",
6809            "[number, number]",
6810            "[number, [string, boolean]]",
6811            "[number, string][]",
6812            "{ a: number }",
6813            "{ a: number }[]",
6814            "\"on\" | \"off\"",
6815            "1 | 2",
6816            "Point | null",
6817            "| number | string",
6818            "(n: number) => number",
6819            "(n: number) => [number, number]",
6820            "readonly number[]",
6821            "readonly number[][]",
6822            "readonly [number, string]",
6823            "readonly number[] | null",
6824            "[first: number, second: string]",
6825            "[new: number, string]",
6826            "keyof { a: number }",
6827        ] {
6828            let annotation = format!("function f(): void {{ const a: {ty} = x; }}");
6829            let (_ast, diags) = parse_str(&annotation);
6830            assert!(
6831                diags.is_empty(),
6832                "annotation grammar rejected {ty:?}: {diags:?}",
6833            );
6834
6835            let arrow = format!("function f(): void {{ const g = (): {ty} => x; }}");
6836            let (_ast, diags) = parse_str(&arrow);
6837            assert!(
6838                diags.is_empty(),
6839                "arrow lookahead rejected return type {ty:?}: {diags:?}",
6840            );
6841        }
6842
6843        // Type-predicate returns ride the same scanner.
6844        let (_ast, diags) =
6845            parse_str("function f(): void { const g = (v: unknown): v is string => x; }");
6846        assert!(
6847            diags.is_empty(),
6848            "arrow lookahead rejected predicate return: {diags:?}",
6849        );
6850    }
6851
6852    #[test]
6853    fn bare_semicolons_are_silent_empty_statements() {
6854        let (ast, diags) = parse_str(";");
6855        assert!(diags.is_empty());
6856        assert!(ast.top_level.is_empty());
6857    }
6858
6859    #[test]
6860    fn parse_single_non_expression_token_errors() {
6861        let (ast, diags) = parse_str("}");
6862        assert_eq!(diags.len(), 1);
6863        assert_eq!(diags[0].message, "expected expression");
6864        assert!(ast.top_level.is_empty());
6865    }
6866
6867    #[test]
6868    fn recovery_across_many_bad_starts() {
6869        let (ast, diags) = parse_str("} } } x;");
6870        assert_eq!(diags.len(), 3);
6871        for d in &diags {
6872            assert_eq!(d.message, "expected expression");
6873        }
6874        assert_eq!(ast.top_level.len(), 1);
6875    }
6876
6877    #[test]
6878    fn recovery_consumes_semicolon() {
6879        let mut p = parser_from_source("foo ;");
6880        p.error_at_peek("test");
6881        p.recover();
6882        assert!(matches!(
6883            p.peek().kind,
6884            TokenKind::Semicolon | TokenKind::Eof
6885        ));
6886        assert!(matches!(p.peek().kind, TokenKind::Eof));
6887    }
6888
6889    #[test]
6890    fn recovery_stops_at_right_brace() {
6891        let mut p = parser_from_source("foo }");
6892        p.error_at_peek("test");
6893        p.recover();
6894        assert!(matches!(p.peek().kind, TokenKind::RightBrace));
6895    }
6896
6897    #[test]
6898    fn recovery_stops_at_let_keyword() {
6899        let mut p = parser_from_source("foo let x");
6900        p.error_at_peek("test");
6901        p.recover();
6902        assert!(matches!(p.peek().kind, TokenKind::Let));
6903    }
6904
6905    #[test]
6906    fn recovery_stops_at_function_keyword() {
6907        let mut p = parser_from_source("foo function bar");
6908        p.error_at_peek("test");
6909        p.recover();
6910        assert!(matches!(p.peek().kind, TokenKind::Function));
6911    }
6912
6913    #[test]
6914    fn error_cap_at_20() {
6915        let source = "} ".repeat(25);
6916        let (_, diags) = parse_str(&source);
6917        assert_eq!(diags.len(), MAX_ERRORS);
6918    }
6919
6920    #[test]
6921    fn driver_makes_progress_past_unmatched_brace() {
6922        let (ast, diags) = parse_str("} x;");
6923        assert_eq!(diags.len(), 1);
6924        assert_eq!(diags[0].message, "expected expression");
6925        assert_eq!(ast.top_level.len(), 1);
6926    }
6927
6928    #[test]
6929    fn snapshot_multi_error_render() {
6930        let source = "foo bar;\nbaz qux;\nhello world;";
6931        let (_, diags) = parse_str(source);
6932        let (sources, _) = Sources::single("script.subm", source).unwrap();
6933        let rendered: String = diags
6934            .iter()
6935            .map(|d| diagnostics::render(d, &sources))
6936            .collect::<Vec<_>>()
6937            .join("\n");
6938        insta::assert_snapshot!(rendered);
6939    }
6940
6941    fn single_stmt(ast: &Ast) -> &crate::Stmt {
6942        assert_eq!(ast.top_level.len(), 1);
6943        ast.try_stmt(ast.top_level[0]).unwrap()
6944    }
6945
6946    fn expr_of_single_stmt(ast: &Ast) -> &crate::Expr {
6947        let stmt = single_stmt(ast);
6948        match stmt.kind {
6949            crate::StmtKind::Expr(id) => ast.try_expr(id).unwrap(),
6950            _ => panic!("expected expression statement, got {:?}", stmt.kind),
6951        }
6952    }
6953
6954    #[test]
6955    fn parse_number_literal() {
6956        let (ast, diags) = parse_str("42;");
6957        assert!(diags.is_empty());
6958        let expr = expr_of_single_stmt(&ast);
6959        assert_eq!(expr.kind, crate::ExprKind::Number(42.0));
6960        assert_eq!(expr.span, crate::Span::new(F, 0, 2).unwrap());
6961    }
6962
6963    #[test]
6964    fn parse_string_literal() {
6965        let (ast, diags) = parse_str(r#""s";"#);
6966        assert!(diags.is_empty());
6967        let expr = expr_of_single_stmt(&ast);
6968        assert_eq!(expr.kind, crate::ExprKind::String("s".to_string()));
6969        assert_eq!(expr.span, crate::Span::new(F, 0, 3).unwrap());
6970    }
6971
6972    #[test]
6973    fn parse_boolean_true() {
6974        let (ast, diags) = parse_str("true;");
6975        assert!(diags.is_empty());
6976        let expr = expr_of_single_stmt(&ast);
6977        assert_eq!(expr.kind, crate::ExprKind::Boolean(true));
6978        assert_eq!(expr.span, crate::Span::new(F, 0, 4).unwrap());
6979    }
6980
6981    #[test]
6982    fn parse_boolean_false() {
6983        let (ast, diags) = parse_str("false;");
6984        assert!(diags.is_empty());
6985        let expr = expr_of_single_stmt(&ast);
6986        assert_eq!(expr.kind, crate::ExprKind::Boolean(false));
6987        assert_eq!(expr.span, crate::Span::new(F, 0, 5).unwrap());
6988    }
6989
6990    #[test]
6991    fn parse_null_literal() {
6992        let (ast, diags) = parse_str("null;");
6993        assert!(diags.is_empty());
6994        let expr = expr_of_single_stmt(&ast);
6995        assert_eq!(expr.kind, crate::ExprKind::Null);
6996        assert_eq!(expr.span, crate::Span::new(F, 0, 4).unwrap());
6997    }
6998
6999    #[test]
7000    fn parse_identifier() {
7001        let (ast, diags) = parse_str("x;");
7002        assert!(diags.is_empty());
7003        let expr = expr_of_single_stmt(&ast);
7004        assert!(matches!(expr.kind, crate::ExprKind::Identifier(_)));
7005        assert_eq!(expr.span, crate::Span::new(F, 0, 1).unwrap());
7006    }
7007
7008    #[test]
7009    fn reserved_keywords_remain_invalid_declaration_names() {
7010        let (_ast, diags) = parse_str("function default(): void {}");
7011        assert_eq!(diags.len(), 1, "{diags:#?}");
7012        assert_eq!(
7013            diags[0].message,
7014            "`default` is a reserved keyword and can't be used as a name"
7015        );
7016        assert!(
7017            diags[0]
7018                .help
7019                .iter()
7020                .any(|h| h == "rename it, for example: `defaultValue`"),
7021            "expected rename help, got: {diags:?}"
7022        );
7023    }
7024
7025    #[test]
7026    fn reserved_keywords_remain_invalid_parameter_names() {
7027        let (_ast, diags) = parse_str("function f(default: string): void {}");
7028        assert_eq!(diags.len(), 1, "{diags:#?}");
7029        assert_eq!(
7030            diags[0].message,
7031            "`default` is a reserved keyword and can't be used as a name"
7032        );
7033        assert!(
7034            diags[0]
7035                .help
7036                .iter()
7037                .any(|h| h == "rename it, for example: `defaultValue`"),
7038            "expected rename help, got: {diags:?}"
7039        );
7040    }
7041
7042    #[test]
7043    fn contextual_keywords_are_valid_names() {
7044        for src in [
7045            "const type = \"x\";",
7046            "const from = 1;",
7047            "let of = 2;",
7048            "const as = 3;",
7049            "const is = 4;",
7050            "function f(from: string): void {}",
7051        ] {
7052            let (_ast, diags) = parse_str(src);
7053            assert!(
7054                diags.is_empty(),
7055                "unexpected diagnostics for {src:?}: {diags:#?}"
7056            );
7057        }
7058    }
7059
7060    #[test]
7061    fn type_alias_dispatch_table() {
7062        let (ast, diags) = parse_str("type X = number;");
7063        assert!(diags.is_empty(), "{diags:#?}");
7064        assert!(matches!(
7065            ast.try_stmt(ast.top_level[0]).unwrap().kind,
7066            crate::StmtKind::TypeAliasDecl { .. }
7067        ));
7068
7069        // A binding named `type` keeps expression/assignment statements working.
7070        for src in ["type = 5;", "type;", "type(1);", "type < 3;", "type.foo;"] {
7071            let (ast, diags) = parse_str(src);
7072            assert!(
7073                diags.is_empty(),
7074                "unexpected diagnostics for {src:?}: {diags:#?}"
7075            );
7076            assert!(
7077                !matches!(
7078                    ast.try_stmt(ast.top_level[0]).unwrap().kind,
7079                    crate::StmtKind::TypeAliasDecl { .. }
7080                ),
7081                "{src:?} must not parse as a type alias"
7082            );
7083        }
7084
7085        let (_ast, diags) = parse_str("type X;");
7086        assert!(
7087            diags
7088                .iter()
7089                .any(|d| d.message == "expected `=` after type alias name"),
7090            "bare `type X` should commit to the alias diagnostic, got: {diags:#?}"
7091        );
7092    }
7093
7094    #[test]
7095    fn export_type_alias_dispatch() {
7096        let (ast, diags) = parse_str("export type X = number;");
7097        assert!(diags.is_empty(), "{diags:#?}");
7098        assert_eq!(ast.exported_decls.len(), 1);
7099        assert!(matches!(
7100            ast.try_stmt(ast.exported_decls[0].stmt).unwrap().kind,
7101            crate::StmtKind::TypeAliasDecl { .. }
7102        ));
7103
7104        let (_ast, diags) = parse_str("export type = 5;");
7105        assert!(
7106            diags
7107                .iter()
7108                .any(|d| d.message == "expected a declaration after `export`"),
7109            "`export type = 5` must not become an exported assignment, got: {diags:#?}"
7110        );
7111    }
7112
7113    #[test]
7114    fn parse_paren_simple() {
7115        let (ast, diags) = parse_str("(x);");
7116        assert!(diags.is_empty());
7117        let expr = expr_of_single_stmt(&ast);
7118        let crate::ExprKind::Paren(inner_id) = expr.kind else {
7119            panic!("expected Paren, got {:?}", expr.kind);
7120        };
7121        assert_eq!(expr.span, crate::Span::new(F, 0, 3).unwrap());
7122        let inner = ast.try_expr(inner_id).unwrap();
7123        assert!(matches!(inner.kind, crate::ExprKind::Identifier(_)));
7124        assert_eq!(inner.span, crate::Span::new(F, 1, 2).unwrap());
7125    }
7126
7127    #[test]
7128    fn parse_paren_nested() {
7129        let (ast, diags) = parse_str("((x));");
7130        assert!(diags.is_empty());
7131        let outer = expr_of_single_stmt(&ast);
7132        let crate::ExprKind::Paren(mid_id) = outer.kind else {
7133            panic!("expected outer Paren");
7134        };
7135        assert_eq!(outer.span, crate::Span::new(F, 0, 5).unwrap());
7136        let mid = ast.try_expr(mid_id).unwrap();
7137        let crate::ExprKind::Paren(inner_id) = mid.kind else {
7138            panic!("expected inner Paren");
7139        };
7140        assert_eq!(mid.span, crate::Span::new(F, 1, 4).unwrap());
7141        let inner = ast.try_expr(inner_id).unwrap();
7142        assert!(matches!(inner.kind, crate::ExprKind::Identifier(_)));
7143        assert_eq!(inner.span, crate::Span::new(F, 2, 3).unwrap());
7144    }
7145
7146    #[test]
7147    fn parse_sequence_of_atoms() {
7148        let (ast, diags) = parse_str(r#"42; "hi"; true; null; x; (x);"#);
7149        assert!(diags.is_empty());
7150        assert_eq!(ast.top_level.len(), 6);
7151        insta::assert_debug_snapshot!(ast);
7152    }
7153
7154    #[test]
7155    fn missing_semicolon_after_expression() {
7156        let (ast, diags) = parse_str("42 43");
7157        assert_eq!(diags.len(), 1);
7158        assert_eq!(diags[0].message, "expected `;` after expression");
7159        assert!(ast.top_level.is_empty());
7160    }
7161
7162    #[test]
7163    fn empty_paren_diagnoses() {
7164        let (ast, diags) = parse_str("();");
7165        assert_eq!(diags.len(), 1);
7166        assert_eq!(diags[0].message, "expected expression");
7167        assert!(ast.top_level.is_empty());
7168    }
7169
7170    #[test]
7171    fn unterminated_paren_diagnoses() {
7172        let (ast, diags) = parse_str("(x;");
7173        assert_eq!(diags.len(), 1);
7174        assert_eq!(diags[0].message, "expected `)`");
7175        assert!(ast.top_level.is_empty());
7176    }
7177
7178    fn binary(e: &crate::Expr) -> (crate::BinOp, crate::ExprId, crate::ExprId) {
7179        match e.kind {
7180            crate::ExprKind::Binary { op, lhs, rhs } => (op, lhs, rhs),
7181            _ => panic!("expected Binary, got {:?}", e.kind),
7182        }
7183    }
7184
7185    #[test]
7186    fn parse_binary_add() {
7187        let (ast, diags) = parse_str("a + b;");
7188        assert!(diags.is_empty());
7189        let outer = expr_of_single_stmt(&ast);
7190        let (op, lhs, rhs) = binary(outer);
7191        assert_eq!(op, crate::BinOp::Add);
7192        assert!(matches!(
7193            ast.try_expr(lhs).unwrap().kind,
7194            crate::ExprKind::Identifier(_)
7195        ));
7196        assert!(matches!(
7197            ast.try_expr(rhs).unwrap().kind,
7198            crate::ExprKind::Identifier(_)
7199        ));
7200        assert_eq!(outer.span, crate::Span::new(F, 0, 5).unwrap());
7201    }
7202
7203    #[test]
7204    fn bitwise_precedence_and_shift_adjacency() {
7205        let (ast, diags) = parse_str("a | b ^ c & d == e >> f + g;");
7206        assert!(diags.is_empty(), "{diags:?}");
7207        let (op, _, mut rhs) = binary(expr_of_single_stmt(&ast));
7208        assert_eq!(op, crate::BinOp::BitOr);
7209        for expected in [
7210            crate::BinOp::BitXor,
7211            crate::BinOp::BitAnd,
7212            crate::BinOp::Eq,
7213            crate::BinOp::Shr,
7214            crate::BinOp::Add,
7215        ] {
7216            let (op, _, next) = binary(ast.try_expr(rhs).unwrap());
7217            assert_eq!(op, expected);
7218            rhs = next;
7219        }
7220        for source in ["a > > b;", "a >/*gap*/> b;", "a < < b;"] {
7221            let (_, diags) = parse_str(source);
7222            assert!(!diags.is_empty(), "{source}");
7223        }
7224    }
7225
7226    #[test]
7227    fn bitwise_shift_assignment_span() {
7228        let (ast, diags) = parse_str("a >>>= b;");
7229        assert!(diags.is_empty(), "{diags:?}");
7230        let crate::StmtKind::CompoundAssign { op, op_span, .. } = single_stmt(&ast).kind else {
7231            panic!("expected assignment");
7232        };
7233        assert_eq!(op, crate::BinOp::UnsignedShr);
7234        assert_eq!(op_span, crate::Span::new(F, 2, 6).unwrap());
7235    }
7236
7237    #[test]
7238    fn precedence_mul_binds_tighter_than_add() {
7239        let (ast, diags) = parse_str("a + b * c;");
7240        assert!(diags.is_empty());
7241        let outer = expr_of_single_stmt(&ast);
7242        let (op, lhs, rhs) = binary(outer);
7243        assert_eq!(op, crate::BinOp::Add);
7244        assert!(matches!(
7245            ast.try_expr(lhs).unwrap().kind,
7246            crate::ExprKind::Identifier(_)
7247        ));
7248        let (rop, rlhs, rrhs) = binary(ast.try_expr(rhs).unwrap());
7249        assert_eq!(rop, crate::BinOp::Mul);
7250        assert!(matches!(
7251            ast.try_expr(rlhs).unwrap().kind,
7252            crate::ExprKind::Identifier(_)
7253        ));
7254        assert!(matches!(
7255            ast.try_expr(rrhs).unwrap().kind,
7256            crate::ExprKind::Identifier(_)
7257        ));
7258    }
7259
7260    #[test]
7261    fn precedence_mul_left_of_add() {
7262        let (ast, diags) = parse_str("a * b + c;");
7263        assert!(diags.is_empty());
7264        let outer = expr_of_single_stmt(&ast);
7265        let (op, lhs, rhs) = binary(outer);
7266        assert_eq!(op, crate::BinOp::Add);
7267        let (lop, ..) = binary(ast.try_expr(lhs).unwrap());
7268        assert_eq!(lop, crate::BinOp::Mul);
7269        assert!(matches!(
7270            ast.try_expr(rhs).unwrap().kind,
7271            crate::ExprKind::Identifier(_)
7272        ));
7273    }
7274
7275    #[test]
7276    fn left_associative_same_precedence() {
7277        let (ast, diags) = parse_str("a - b - c;");
7278        assert!(diags.is_empty());
7279        let outer = expr_of_single_stmt(&ast);
7280        let (op, lhs, rhs) = binary(outer);
7281        assert_eq!(op, crate::BinOp::Sub);
7282        let (lop, llhs, lrhs) = binary(ast.try_expr(lhs).unwrap());
7283        assert_eq!(lop, crate::BinOp::Sub);
7284        assert!(matches!(
7285            ast.try_expr(llhs).unwrap().kind,
7286            crate::ExprKind::Identifier(_)
7287        ));
7288        assert!(matches!(
7289            ast.try_expr(lrhs).unwrap().kind,
7290            crate::ExprKind::Identifier(_)
7291        ));
7292        assert!(matches!(
7293            ast.try_expr(rhs).unwrap().kind,
7294            crate::ExprKind::Identifier(_)
7295        ));
7296    }
7297
7298    #[test]
7299    fn equality_inside_logical() {
7300        let (ast, diags) = parse_str("a === b && c === d;");
7301        assert!(diags.is_empty());
7302        let outer = expr_of_single_stmt(&ast);
7303        let (op, lhs, rhs) = binary(outer);
7304        assert_eq!(op, crate::BinOp::And);
7305        let (lop, ..) = binary(ast.try_expr(lhs).unwrap());
7306        assert_eq!(lop, crate::BinOp::Eq);
7307        let (rop, ..) = binary(ast.try_expr(rhs).unwrap());
7308        assert_eq!(rop, crate::BinOp::Eq);
7309    }
7310
7311    #[test]
7312    fn double_equal_is_same_variant_as_triple() {
7313        let (ast, _) = parse_str("a == b;");
7314        let outer = expr_of_single_stmt(&ast);
7315        let (op, ..) = binary(outer);
7316        assert_eq!(op, crate::BinOp::Eq);
7317    }
7318
7319    #[test]
7320    fn bang_eq_is_not_eq() {
7321        let (ast, _) = parse_str("a != b;");
7322        let outer = expr_of_single_stmt(&ast);
7323        let (op, ..) = binary(outer);
7324        assert_eq!(op, crate::BinOp::NotEq);
7325    }
7326
7327    #[test]
7328    fn comparison_has_higher_prec_than_logical() {
7329        let (ast, _) = parse_str("a < b && c;");
7330        let outer = expr_of_single_stmt(&ast);
7331        let (op, lhs, _) = binary(outer);
7332        assert_eq!(op, crate::BinOp::And);
7333        let (lop, ..) = binary(ast.try_expr(lhs).unwrap());
7334        assert_eq!(lop, crate::BinOp::Lt);
7335    }
7336
7337    #[test]
7338    fn full_precedence_chain_snapshot() {
7339        let (ast, diags) = parse_str("a || b && c === d < e + f * g;");
7340        assert!(diags.is_empty());
7341        insta::assert_debug_snapshot!(ast);
7342    }
7343
7344    #[test]
7345    fn paren_overrides_precedence() {
7346        let (ast, _) = parse_str("(a + b) * c;");
7347        let outer = expr_of_single_stmt(&ast);
7348        let (op, lhs, rhs) = binary(outer);
7349        assert_eq!(op, crate::BinOp::Mul);
7350        let crate::ExprKind::Paren(inner_id) = ast.try_expr(lhs).unwrap().kind else {
7351            panic!("expected Paren on lhs");
7352        };
7353        let (inner_op, ..) = binary(ast.try_expr(inner_id).unwrap());
7354        assert_eq!(inner_op, crate::BinOp::Add);
7355        assert!(matches!(
7356            ast.try_expr(rhs).unwrap().kind,
7357            crate::ExprKind::Identifier(_)
7358        ));
7359    }
7360
7361    #[test]
7362    fn binary_missing_rhs_diagnoses() {
7363        let (ast, diags) = parse_str("a +;");
7364        assert_eq!(diags.len(), 1);
7365        assert_eq!(diags[0].message, "expected expression");
7366        assert!(ast.top_level.is_empty());
7367    }
7368
7369    fn unary(e: &crate::Expr) -> (crate::UnOp, crate::ExprId) {
7370        match e.kind {
7371            crate::ExprKind::Unary { op, operand } => (op, operand),
7372            _ => panic!("expected Unary, got {:?}", e.kind),
7373        }
7374    }
7375
7376    #[test]
7377    fn parse_unary_not() {
7378        let (ast, _) = parse_str("!x;");
7379        let outer = expr_of_single_stmt(&ast);
7380        let (op, operand) = unary(outer);
7381        assert_eq!(op, crate::UnOp::Not);
7382        assert!(matches!(
7383            ast.try_expr(operand).unwrap().kind,
7384            crate::ExprKind::Identifier(_)
7385        ));
7386        assert_eq!(outer.span, crate::Span::new(F, 0, 2).unwrap());
7387    }
7388
7389    #[test]
7390    fn parse_unary_neg() {
7391        let (ast, _) = parse_str("-x;");
7392        let (op, _) = unary(expr_of_single_stmt(&ast));
7393        assert_eq!(op, crate::UnOp::Neg);
7394    }
7395
7396    #[test]
7397    fn parse_unary_pos() {
7398        let (ast, _) = parse_str("+x;");
7399        let (op, _) = unary(expr_of_single_stmt(&ast));
7400        assert_eq!(op, crate::UnOp::Pos);
7401    }
7402
7403    #[test]
7404    fn repeated_unary_not() {
7405        let (ast, _) = parse_str("!!x;");
7406        let outer = expr_of_single_stmt(&ast);
7407        let (op, operand) = unary(outer);
7408        assert_eq!(op, crate::UnOp::Not);
7409        let (inner_op, inner_operand) = unary(ast.try_expr(operand).unwrap());
7410        assert_eq!(inner_op, crate::UnOp::Not);
7411        assert!(matches!(
7412            ast.try_expr(inner_operand).unwrap().kind,
7413            crate::ExprKind::Identifier(_)
7414        ));
7415    }
7416
7417    #[test]
7418    fn unary_binds_tighter_than_binary() {
7419        let (ast, _) = parse_str("-x + 1;");
7420        let outer = expr_of_single_stmt(&ast);
7421        let (op, lhs, rhs) = binary(outer);
7422        assert_eq!(op, crate::BinOp::Add);
7423        let (unop, _) = unary(ast.try_expr(lhs).unwrap());
7424        assert_eq!(unop, crate::UnOp::Neg);
7425        assert_eq!(
7426            ast.try_expr(rhs).unwrap().kind,
7427            crate::ExprKind::Number(1.0)
7428        );
7429    }
7430
7431    #[test]
7432    fn unary_over_paren() {
7433        let (ast, _) = parse_str("-(x + 1);");
7434        let outer = expr_of_single_stmt(&ast);
7435        let (op, operand) = unary(outer);
7436        assert_eq!(op, crate::UnOp::Neg);
7437        let crate::ExprKind::Paren(inner_id) = ast.try_expr(operand).unwrap().kind else {
7438            panic!("expected Paren");
7439        };
7440        let (inner_op, ..) = binary(ast.try_expr(inner_id).unwrap());
7441        assert_eq!(inner_op, crate::BinOp::Add);
7442    }
7443
7444    #[test]
7445    fn parse_is_as_expression_rejected() {
7446        let (_, diags) = parse_str("x is number;");
7447        assert!(!diags.is_empty(), "expected parse error for `x is T`");
7448    }
7449
7450    fn as_cast(e: &crate::Expr) -> (crate::ExprId, &crate::TypeAnnotation) {
7451        match &e.kind {
7452            crate::ExprKind::As { expr, ty } => (*expr, ty),
7453            other => panic!("expected ExprKind::As, got {other:?}"),
7454        }
7455    }
7456
7457    #[test]
7458    fn parse_instanceof_class() {
7459        let (ast, diags) = parse_str("x instanceof Foo;");
7460        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7461        let outer = expr_of_single_stmt(&ast);
7462        let crate::ExprKind::InstanceOf { value, ty } = &outer.kind else {
7463            panic!("expected ExprKind::InstanceOf, got {:?}", outer.kind);
7464        };
7465        assert!(matches!(
7466            ast.try_expr(*value).unwrap().kind,
7467            crate::ExprKind::Identifier(_)
7468        ));
7469        assert!(matches!(ty.kind, crate::TypeAnnotationKind::Name { .. }));
7470    }
7471
7472    #[test]
7473    fn parse_instanceof_binds_tighter_than_logical_and() {
7474        // `a instanceof B && c` parses as `(a instanceof B) && c`.
7475        let (ast, diags) = parse_str("a instanceof B && c;");
7476        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7477        let outer = expr_of_single_stmt(&ast);
7478        let (op, lhs, rhs) = binary(outer);
7479        assert_eq!(op, crate::BinOp::And);
7480        assert!(matches!(
7481            ast.try_expr(lhs).unwrap().kind,
7482            crate::ExprKind::InstanceOf { .. }
7483        ));
7484        assert!(matches!(
7485            ast.try_expr(rhs).unwrap().kind,
7486            crate::ExprKind::Identifier(_)
7487        ));
7488    }
7489
7490    /// `<T>x` produces the same node as `x as T`, so everything downstream of the
7491    /// parser sees one construct rather than two.
7492    #[test]
7493    fn parse_angle_cast_lowers_to_the_as_node() {
7494        let (ast, diags) = parse_str("<number>x;");
7495        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7496        let outer = expr_of_single_stmt(&ast);
7497        let (inner, ty) = as_cast(outer);
7498        assert!(matches!(
7499            ast.try_expr(inner).unwrap().kind,
7500            crate::ExprKind::Identifier(_)
7501        ));
7502        assert!(matches!(ty.kind, crate::TypeAnnotationKind::Name { .. }));
7503    }
7504
7505    /// A `<` that means comparison always has a left operand, so it never reaches
7506    /// the cast branch. This is the regression the cast syntax could most easily
7507    /// have caused.
7508    #[test]
7509    fn a_less_than_comparison_is_not_read_as_a_cast() {
7510        let (ast, diags) = parse_str("a < b;");
7511        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7512        let e = expr_of_single_stmt(&ast);
7513        assert!(matches!(e.kind, crate::ExprKind::Binary { .. }));
7514    }
7515
7516    #[test]
7517    fn parse_typeof_in_type_position() {
7518        let (_ast, diags) = parse_str("let a: typeof b = 1;");
7519        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7520    }
7521
7522    #[test]
7523    fn parse_typeof_with_a_dotted_path() {
7524        let (_ast, diags) = parse_str("let a: typeof b.c.d = 1;");
7525        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7526    }
7527
7528    #[test]
7529    fn parse_as_primitive() {
7530        let (ast, diags) = parse_str("x as number;");
7531        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7532        let outer = expr_of_single_stmt(&ast);
7533        let (inner, ty) = as_cast(outer);
7534        assert!(matches!(
7535            ast.try_expr(inner).unwrap().kind,
7536            crate::ExprKind::Identifier(_)
7537        ));
7538        match &ty.kind {
7539            crate::TypeAnnotationKind::Name { args, .. } => {
7540                assert!(args.is_empty());
7541            }
7542            other => panic!("expected Name target, got {other:?}"),
7543        }
7544        assert_eq!(outer.span, crate::Span::new(F, 0, 11).unwrap());
7545    }
7546
7547    #[test]
7548    fn parse_as_object_literal_type() {
7549        let (ast, diags) = parse_str("x as { a: number };");
7550        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7551        let outer = expr_of_single_stmt(&ast);
7552        let (_, ty) = as_cast(outer);
7553        assert!(matches!(ty.kind, crate::TypeAnnotationKind::Object { .. }));
7554    }
7555
7556    #[test]
7557    fn parse_as_array_short() {
7558        let (ast, diags) = parse_str("x as string[];");
7559        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7560        let outer = expr_of_single_stmt(&ast);
7561        let (_, ty) = as_cast(outer);
7562        assert!(matches!(ty.kind, crate::TypeAnnotationKind::Array { .. }));
7563    }
7564
7565    #[test]
7566    fn parse_as_array_generic() {
7567        let (ast, diags) = parse_str("x as T[];");
7568        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7569        let outer = expr_of_single_stmt(&ast);
7570        let (_, ty) = as_cast(outer);
7571        assert!(matches!(ty.kind, crate::TypeAnnotationKind::Array { .. }));
7572    }
7573
7574    #[test]
7575    fn parse_as_generic_instantiation() {
7576        let (ast, diags) = parse_str("x as Foo<Bar>;");
7577        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7578        let outer = expr_of_single_stmt(&ast);
7579        let (_, ty) = as_cast(outer);
7580        match &ty.kind {
7581            crate::TypeAnnotationKind::Name { args, .. } => {
7582                assert_eq!(args.len(), 1, "Foo<Bar> has one type arg");
7583            }
7584            other => panic!("expected Name with type args, got {other:?}"),
7585        }
7586    }
7587
7588    #[test]
7589    fn parse_as_precedence_higher_than_additive() {
7590        let (ast, diags) = parse_str("x as number + 1;");
7591        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7592        let outer = expr_of_single_stmt(&ast);
7593        let (op, lhs, rhs) = binary(outer);
7594        assert_eq!(op, crate::BinOp::Add);
7595        assert!(matches!(
7596            ast.try_expr(lhs).unwrap().kind,
7597            crate::ExprKind::As { .. }
7598        ));
7599        assert!(matches!(
7600            ast.try_expr(rhs).unwrap().kind,
7601            crate::ExprKind::Number(_)
7602        ));
7603    }
7604
7605    #[test]
7606    fn parse_as_precedence_lower_than_unary() {
7607        let (ast, diags) = parse_str("!x as boolean;");
7608        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7609        let outer = expr_of_single_stmt(&ast);
7610        let (inner, _) = as_cast(outer);
7611        assert!(matches!(
7612            ast.try_expr(inner).unwrap().kind,
7613            crate::ExprKind::Unary {
7614                op: crate::UnOp::Not,
7615                ..
7616            }
7617        ));
7618    }
7619
7620    #[test]
7621    fn parse_as_left_associative_chain() {
7622        let (ast, diags) = parse_str("x as A as B;");
7623        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7624        let outer = expr_of_single_stmt(&ast);
7625        let (inner, _outer_ty) = as_cast(outer);
7626        let (innermost, _inner_ty) = as_cast(ast.try_expr(inner).unwrap());
7627        assert!(matches!(
7628            ast.try_expr(innermost).unwrap().kind,
7629            crate::ExprKind::Identifier(_)
7630        ));
7631    }
7632
7633    #[test]
7634    fn parse_as_inside_call_arg() {
7635        let (ast, diags) = parse_str("f(x as T);");
7636        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7637        let outer = expr_of_single_stmt(&ast);
7638        match &outer.kind {
7639            crate::ExprKind::Call { args, .. } => {
7640                assert_eq!(args.len(), 1);
7641                assert!(matches!(
7642                    ast.try_expr(args[0]).unwrap().kind,
7643                    crate::ExprKind::As { .. }
7644                ));
7645            }
7646            other => panic!("expected Call, got {other:?}"),
7647        }
7648    }
7649
7650    #[test]
7651    fn parse_as_after_field_access() {
7652        let (ast, diags) = parse_str("obj.field as T;");
7653        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7654        let outer = expr_of_single_stmt(&ast);
7655        let (inner, _) = as_cast(outer);
7656        assert!(matches!(
7657            ast.try_expr(inner).unwrap().kind,
7658            crate::ExprKind::FieldAccess { .. }
7659        ));
7660    }
7661
7662    #[test]
7663    fn parse_type_guard_return_annotation_on_function() {
7664        let src = "function isCircle(s: Shape): s is Circle { return true; }";
7665        let (ast, diags) = parse_str(src);
7666        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7667        let stmt = single_stmt(&ast);
7668        match stmt.kind {
7669            crate::StmtKind::Function {
7670                ref return_type,
7671                ref type_predicate,
7672                ..
7673            } => {
7674                assert!(
7675                    return_type.is_none(),
7676                    "predicate form leaves return_type empty",
7677                );
7678                let tp = type_predicate.as_ref().expect("predicate parsed");
7679                assert_eq!(tp.param.name, "s");
7680                assert!(matches!(
7681                    tp.asserted.kind,
7682                    crate::TypeAnnotationKind::Name { .. }
7683                ));
7684                assert_eq!(tp.span, crate::Span::new(F, 29, 40).unwrap());
7685            }
7686            _ => panic!("expected Function"),
7687        }
7688    }
7689
7690    #[test]
7691    fn parse_type_guard_return_annotation_on_arrow() {
7692        let src = "const f = (s: Shape): s is Circle => true;";
7693        let (ast, diags) = parse_str(src);
7694        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7695        let value = match &ast.try_stmt(ast.top_level[0]).unwrap().kind {
7696            crate::StmtKind::Const { value, .. } => *value,
7697            _ => panic!("expected const"),
7698        };
7699        match &ast.try_expr(value).unwrap().kind {
7700            crate::ExprKind::Arrow {
7701                return_type,
7702                type_predicate,
7703                ..
7704            } => {
7705                assert!(return_type.is_none());
7706                let tp = type_predicate.as_ref().expect("predicate parsed");
7707                assert_eq!(tp.param.name, "s");
7708            }
7709            other => panic!("expected Arrow, got {other:?}"),
7710        }
7711    }
7712
7713    #[test]
7714    fn parse_plain_return_type_unaffected_by_predicate_parser() {
7715        let src = "function f(s: Shape): boolean { return true; }";
7716        let (ast, diags) = parse_str(src);
7717        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7718        let stmt = single_stmt(&ast);
7719        match stmt.kind {
7720            crate::StmtKind::Function {
7721                ref return_type,
7722                ref type_predicate,
7723                ..
7724            } => {
7725                assert!(return_type.is_some(), "plain form has return_type");
7726                assert!(type_predicate.is_none(), "plain form has no type_predicate",);
7727            }
7728            _ => panic!("expected Function"),
7729        }
7730    }
7731
7732    #[test]
7733    fn parse_type_guard_rejects_non_return_position() {
7734        let (_, diags) = parse_str("let x: y is Foo = null;");
7735        assert!(
7736            !diags.is_empty(),
7737            "expected parse error for `is` outside return-type position",
7738        );
7739    }
7740
7741    #[test]
7742    fn parse_typeof_prefix() {
7743        let (ast, diags) = parse_str("typeof x;");
7744        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7745        let outer = expr_of_single_stmt(&ast);
7746        let crate::ExprKind::Typeof { operand } = outer.kind else {
7747            panic!("expected Typeof, got {:?}", outer.kind);
7748        };
7749        assert!(matches!(
7750            ast.try_expr(operand).unwrap().kind,
7751            crate::ExprKind::Identifier(_)
7752        ));
7753        assert_eq!(outer.span, crate::Span::new(F, 0, 8).unwrap());
7754    }
7755
7756    #[test]
7757    fn parse_typeof_in_equality() {
7758        let (ast, diags) = parse_str(r#"typeof x === "number";"#);
7759        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
7760        let outer = expr_of_single_stmt(&ast);
7761        let (op, lhs, rhs) = binary(outer);
7762        assert_eq!(op, crate::BinOp::Eq);
7763        assert!(matches!(
7764            ast.try_expr(lhs).unwrap().kind,
7765            crate::ExprKind::Typeof { .. }
7766        ));
7767        assert!(matches!(
7768            ast.try_expr(rhs).unwrap().kind,
7769            crate::ExprKind::String(ref s) if s == "number"
7770        ));
7771    }
7772
7773    #[test]
7774    fn parse_let_without_type() {
7775        let (ast, diags) = parse_str("let x = 1;");
7776        assert!(diags.is_empty());
7777        let stmt = single_stmt(&ast);
7778        assert_eq!(stmt.span, crate::Span::new(F, 0, 10).unwrap());
7779        match stmt.kind {
7780            crate::StmtKind::Let {
7781                ref name,
7782                ref ty,
7783                value,
7784                ..
7785            } => {
7786                assert_eq!(name.name, "x");
7787                assert_eq!(name.span, crate::Span::new(F, 4, 5).unwrap());
7788                assert!(ty.is_none());
7789                assert_eq!(
7790                    ast.try_expr(value).unwrap().kind,
7791                    crate::ExprKind::Number(1.0)
7792                );
7793            }
7794            _ => panic!("expected Let"),
7795        }
7796    }
7797
7798    #[test]
7799    fn parse_const_with_type_and_string() {
7800        let (ast, diags) = parse_str(r#"const y: string = "hi";"#);
7801        assert!(diags.is_empty());
7802        let stmt = single_stmt(&ast);
7803        match stmt.kind {
7804            crate::StmtKind::Const {
7805                ref name,
7806                ref ty,
7807                value,
7808                ..
7809            } => {
7810                assert_eq!(name.name, "y");
7811                assert_eq!(name.span, crate::Span::new(F, 6, 7).unwrap());
7812                let ty = ty.as_ref().expect("expected type annotation");
7813                assert!(matches!(ty.kind, crate::TypeAnnotationKind::Name { .. }));
7814                assert_eq!(ty.span, crate::Span::new(F, 9, 15).unwrap());
7815                assert_eq!(
7816                    ast.try_expr(value).unwrap().kind,
7817                    crate::ExprKind::String("hi".to_string())
7818                );
7819            }
7820            _ => panic!("expected Const"),
7821        }
7822    }
7823
7824    #[test]
7825    fn parse_let_with_type_and_binary_initializer() {
7826        let (ast, diags) = parse_str("let z: number = 1 + 2;");
7827        assert!(diags.is_empty());
7828        let stmt = single_stmt(&ast);
7829        match stmt.kind {
7830            crate::StmtKind::Let { ref ty, value, .. } => {
7831                assert!(ty.is_some());
7832                let (op, ..) = binary(ast.try_expr(value).unwrap());
7833                assert_eq!(op, crate::BinOp::Add);
7834            }
7835            _ => panic!("expected Let"),
7836        }
7837    }
7838
7839    #[test]
7840    fn void_type_annotation_is_accepted_syntactically() {
7841        let (ast, diags) = parse_str("let v: void = null;");
7842        assert!(diags.is_empty());
7843        let stmt = single_stmt(&ast);
7844        match stmt.kind {
7845            crate::StmtKind::Let { ref ty, .. } => {
7846                let ty = ty.as_ref().unwrap();
7847                assert!(matches!(ty.kind, crate::TypeAnnotationKind::Name { .. }));
7848                assert_eq!(ty.span, crate::Span::new(F, 7, 11).unwrap());
7849            }
7850            _ => panic!("expected Let"),
7851        }
7852    }
7853
7854    #[test]
7855    fn const_missing_initializer_diagnoses() {
7856        let (ast, diags) = parse_str("const x;");
7857        assert_eq!(diags.len(), 1);
7858        assert_eq!(
7859            diags[0].message,
7860            "`const` declaration requires an initializer"
7861        );
7862        assert!(ast.top_level.is_empty());
7863    }
7864
7865    #[test]
7866    fn parse_const_object_shorthand_pattern() {
7867        let (ast, diags) = parse_str("const { a, b } = obj;");
7868        assert!(diags.is_empty(), "unexpected: {diags:?}");
7869        let stmt = single_stmt(&ast);
7870        match stmt.kind {
7871            crate::StmtKind::ConstPattern {
7872                binding:
7873                    crate::Binding::Object {
7874                        ref fields,
7875                        ref rest,
7876                        ..
7877                    },
7878                ..
7879            } => {
7880                assert_eq!(fields.len(), 2);
7881                assert_eq!(fields[0].source.name, "a");
7882                assert_eq!(fields[0].local.name, "a");
7883                assert_eq!(fields[1].source.name, "b");
7884                assert_eq!(fields[1].local.name, "b");
7885                assert!(rest.is_none());
7886            }
7887            _ => panic!("expected ConstPattern Object, got {:?}", stmt.kind),
7888        }
7889    }
7890
7891    #[test]
7892    fn parse_const_object_pattern_keyword_source_key() {
7893        let (ast, diags) = parse_str("const { type: statusType, default: fallback } = obj;");
7894        assert!(diags.is_empty(), "unexpected: {diags:?}");
7895        let stmt = single_stmt(&ast);
7896        let crate::StmtKind::ConstPattern {
7897            binding: crate::Binding::Object { ref fields, .. },
7898            ..
7899        } = stmt.kind
7900        else {
7901            panic!("expected object const pattern");
7902        };
7903
7904        let names: Vec<(&str, &str)> = fields
7905            .iter()
7906            .map(|f| (f.source.name.as_str(), f.local.name.as_str()))
7907            .collect();
7908        assert_eq!(names, vec![("type", "statusType"), ("default", "fallback")]);
7909    }
7910
7911    #[test]
7912    fn parse_const_object_pattern_keyword_shorthand_rejected() {
7913        let (_ast, diags) = parse_str("const { default } = obj;");
7914        assert!(
7915            diags
7916                .iter()
7917                .any(|d| d.message == "expected `:` after keyword field name in object pattern"),
7918            "expected keyword-shorthand diagnostic, got: {diags:?}"
7919        );
7920    }
7921
7922    #[test]
7923    fn parse_const_object_renamed_pattern() {
7924        let (ast, diags) = parse_str("const { a: x, b: y } = obj;");
7925        assert!(diags.is_empty(), "unexpected: {diags:?}");
7926        match single_stmt(&ast).kind {
7927            crate::StmtKind::ConstPattern {
7928                binding: crate::Binding::Object { ref fields, .. },
7929                ..
7930            } => {
7931                assert_eq!(fields[0].source.name, "a");
7932                assert_eq!(fields[0].local.name, "x");
7933                assert_eq!(fields[1].source.name, "b");
7934                assert_eq!(fields[1].local.name, "y");
7935            }
7936            ref other => panic!("expected ConstPattern Object, got {other:?}"),
7937        }
7938    }
7939
7940    #[test]
7941    fn parse_const_object_with_rest() {
7942        let (ast, diags) = parse_str("const { a, ...rest } = obj;");
7943        assert!(diags.is_empty(), "unexpected: {diags:?}");
7944        match single_stmt(&ast).kind {
7945            crate::StmtKind::ConstPattern {
7946                binding:
7947                    crate::Binding::Object {
7948                        ref fields,
7949                        ref rest,
7950                        ..
7951                    },
7952                ..
7953            } => {
7954                assert_eq!(fields.len(), 1);
7955                assert_eq!(fields[0].source.name, "a");
7956                let r = rest.as_ref().expect("expected rest");
7957                assert_eq!(r.name, "rest");
7958            }
7959            ref other => panic!("expected ConstPattern Object, got {other:?}"),
7960        }
7961    }
7962
7963    #[test]
7964    fn parse_const_array_pattern() {
7965        let (ast, diags) = parse_str("const [x, y] = arr;");
7966        assert!(diags.is_empty(), "unexpected: {diags:?}");
7967        match single_stmt(&ast).kind {
7968            crate::StmtKind::ConstPattern {
7969                binding:
7970                    crate::Binding::Array {
7971                        ref elems,
7972                        ref rest,
7973                        ..
7974                    },
7975                ..
7976            } => {
7977                assert_eq!(elems.len(), 2);
7978                assert_eq!(elems[0].as_ref().unwrap().name, "x");
7979                assert_eq!(elems[1].as_ref().unwrap().name, "y");
7980                assert!(rest.is_none());
7981            }
7982            ref other => panic!("expected ConstPattern Array, got {other:?}"),
7983        }
7984    }
7985
7986    #[test]
7987    fn parse_const_array_with_hole_and_rest() {
7988        let (ast, diags) = parse_str("const [, x, ...rest] = arr;");
7989        assert!(diags.is_empty(), "unexpected: {diags:?}");
7990        match single_stmt(&ast).kind {
7991            crate::StmtKind::ConstPattern {
7992                binding:
7993                    crate::Binding::Array {
7994                        ref elems,
7995                        ref rest,
7996                        ..
7997                    },
7998                ..
7999            } => {
8000                assert_eq!(elems.len(), 2);
8001                assert!(elems[0].is_none(), "first slot should be a hole");
8002                assert_eq!(elems[1].as_ref().unwrap().name, "x");
8003                assert_eq!(rest.as_ref().unwrap().name, "rest");
8004            }
8005            ref other => panic!("expected ConstPattern Array, got {other:?}"),
8006        }
8007    }
8008
8009    #[test]
8010    fn parse_let_object_pattern() {
8011        let (ast, diags) = parse_str("let { a, b } = obj;");
8012        assert!(diags.is_empty(), "unexpected: {diags:?}");
8013        assert!(matches!(
8014            single_stmt(&ast).kind,
8015            crate::StmtKind::LetPattern {
8016                binding: crate::Binding::Object { .. },
8017                ..
8018            }
8019        ));
8020    }
8021
8022    #[test]
8023    fn parse_function_param_object_pattern() {
8024        let src = "function f({ a, b }: T): void { return; }";
8025        let (ast, diags) = parse_str(src);
8026        assert!(diags.is_empty(), "unexpected: {diags:?}");
8027        match single_stmt(&ast).kind {
8028            crate::StmtKind::Function { ref params, .. } => {
8029                assert_eq!(params.len(), 1);
8030                let p = &params[0];
8031                assert!(p.pattern.is_some(), "expected pattern param");
8032                assert_eq!(p.name.name, "", "pattern placeholder uses empty name");
8033                match p.pattern.as_ref().unwrap() {
8034                    crate::Binding::Object { fields, .. } => {
8035                        assert_eq!(fields.len(), 2);
8036                        assert_eq!(fields[0].source.name, "a");
8037                    }
8038                    other => panic!("expected Object binding, got {other:?}"),
8039                }
8040            }
8041            ref other => panic!("expected Function, got {other:?}"),
8042        }
8043    }
8044
8045    #[test]
8046    fn parse_arrow_param_array_pattern() {
8047        let src = "const g = ([x, y]: number[]): number => x + y;";
8048        let (_ast, diags) = parse_str(src);
8049        assert!(diags.is_empty(), "unexpected: {diags:?}");
8050    }
8051
8052    #[test]
8053    fn parse_destructure_nested_rejected() {
8054        let (_ast, diags) = parse_str("const { a: { b } } = obj;");
8055        assert!(
8056            diags
8057                .iter()
8058                .any(|d| d.message.contains("nested destructuring")),
8059            "expected nested-destructuring diagnostic, got: {diags:?}",
8060        );
8061    }
8062
8063    #[test]
8064    fn parse_destructure_array_nested_rejected() {
8065        let (_ast, diags) = parse_str("const [[a]] = arr;");
8066        assert!(
8067            diags
8068                .iter()
8069                .any(|d| d.message.contains("nested destructuring")),
8070            "expected nested-destructuring diagnostic, got: {diags:?}",
8071        );
8072    }
8073
8074    #[test]
8075    fn parse_destructure_pattern_defaults() {
8076        let (ast, diags) = parse_str("const { a = 1, b: c = 2 } = obj;");
8077        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8078        let crate::StmtKind::ConstPattern { ref binding, .. } = single_stmt(&ast).kind else {
8079            panic!("expected pattern")
8080        };
8081        let crate::Binding::Object { fields, .. } = binding else {
8082            panic!("expected object")
8083        };
8084        assert_eq!(fields.len(), 2);
8085        assert!(fields.iter().all(|field| field.default.is_some()));
8086        assert_eq!(fields[1].local.name, "c");
8087    }
8088
8089    #[test]
8090    fn parse_destructure_param_default() {
8091        let (ast, diags) = parse_str("function f({ a = 1 }: T = obj): void { return; }");
8092        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8093        let crate::StmtKind::Function { ref params, .. } = single_stmt(&ast).kind else {
8094            panic!("expected function")
8095        };
8096        assert!(params[0].default.is_some());
8097        assert!(params[0].pattern.is_some());
8098    }
8099
8100    #[test]
8101    fn parse_destructure_rest_not_last_rejected() {
8102        let (_ast, diags) = parse_str("const { ...rest, a } = obj;");
8103        assert!(
8104            diags
8105                .iter()
8106                .any(|d| d.message.contains("rest element must be the last element")),
8107            "expected rest-not-last diagnostic, got: {diags:?}",
8108        );
8109    }
8110
8111    #[test]
8112    fn parse_destructure_empty_object_rejected() {
8113        let (_ast, diags) = parse_str("const { } = obj;");
8114        assert!(
8115            diags
8116                .iter()
8117                .any(|d| d.message.contains("empty object destructuring pattern")),
8118            "expected empty-pattern diagnostic, got: {diags:?}",
8119        );
8120    }
8121
8122    #[test]
8123    fn parse_ternary_simple() {
8124        let (ast, diags) = parse_str("const x: number = a ? 1 : 2;");
8125        assert!(diags.is_empty(), "unexpected: {diags:?}");
8126        let stmt = single_stmt(&ast);
8127        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8128            panic!("expected Const, got {:?}", stmt.kind);
8129        };
8130        assert!(matches!(
8131            ast.try_expr(value).unwrap().kind,
8132            crate::ExprKind::Ternary { .. }
8133        ));
8134    }
8135
8136    #[test]
8137    fn parse_ternary_nested_right_assoc() {
8138        let (ast, diags) = parse_str("const x: number = a ? 1 : b ? 2 : 3;");
8139        assert!(diags.is_empty(), "unexpected: {diags:?}");
8140        let stmt = single_stmt(&ast);
8141        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8142            panic!("expected Const");
8143        };
8144        let crate::ExprKind::Ternary { else_, .. } = ast.try_expr(value).unwrap().kind else {
8145            panic!("expected outer Ternary");
8146        };
8147        assert!(matches!(
8148            ast.try_expr(else_).unwrap().kind,
8149            crate::ExprKind::Ternary { .. }
8150        ));
8151    }
8152
8153    #[test]
8154    fn parse_ternary_missing_colon_diagnoses() {
8155        let (_, diags) = parse_str("const x: number = a ? 1;");
8156        assert!(
8157            diags
8158                .iter()
8159                .any(|d| d.message.contains("expected `:` to complete ternary")),
8160            "expected colon diagnostic, got: {diags:?}",
8161        );
8162    }
8163
8164    #[test]
8165    fn parse_nullish_coalesce_binop() {
8166        let (ast, diags) = parse_str("const x: number = a ?? 1;");
8167        assert!(diags.is_empty(), "unexpected: {diags:?}");
8168        let stmt = single_stmt(&ast);
8169        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8170            panic!("expected Const");
8171        };
8172        match ast.try_expr(value).unwrap().kind {
8173            crate::ExprKind::Binary { op, .. } => {
8174                assert_eq!(op, crate::BinOp::NullishCoalesce);
8175            }
8176            ref other => panic!("expected Binary NullishCoalesce, got {other:?}"),
8177        }
8178    }
8179
8180    #[test]
8181    fn parse_nullish_left_associative() {
8182        let (ast, diags) = parse_str("const x: number = a ?? b ?? c;");
8183        assert!(diags.is_empty(), "unexpected: {diags:?}");
8184        let stmt = single_stmt(&ast);
8185        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8186            panic!("expected Const");
8187        };
8188        let crate::ExprKind::Binary { lhs, .. } = ast.try_expr(value).unwrap().kind else {
8189            panic!("expected outer Binary");
8190        };
8191        match ast.try_expr(lhs).unwrap().kind {
8192            crate::ExprKind::Binary { op, .. } => {
8193                assert_eq!(op, crate::BinOp::NullishCoalesce);
8194            }
8195            ref other => panic!("expected inner Binary, got {other:?}"),
8196        }
8197    }
8198
8199    #[test]
8200    fn parse_pow_is_right_associative() {
8201        let (ast, diags) = parse_str("const x: number = 2 ** 3 ** 2;");
8202        assert!(diags.is_empty(), "unexpected: {diags:?}");
8203        let stmt = single_stmt(&ast);
8204        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8205            panic!("expected Const");
8206        };
8207        let crate::ExprKind::Binary { op, rhs, .. } = ast.try_expr(value).unwrap().kind else {
8208            panic!("expected outer Binary");
8209        };
8210        assert_eq!(op, crate::BinOp::Pow);
8211        match ast.try_expr(rhs).unwrap().kind {
8212            crate::ExprKind::Binary { op: inner_op, .. } => {
8213                assert_eq!(inner_op, crate::BinOp::Pow);
8214            }
8215            ref other => panic!("expected inner Pow Binary on RHS, got {other:?}"),
8216        }
8217    }
8218
8219    #[test]
8220    fn parse_pow_higher_than_multiply() {
8221        let (ast, diags) = parse_str("const x: number = 2 * 3 ** 2;");
8222        assert!(diags.is_empty(), "unexpected: {diags:?}");
8223        let stmt = single_stmt(&ast);
8224        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8225            panic!("expected Const");
8226        };
8227        let crate::ExprKind::Binary { op, rhs, .. } = ast.try_expr(value).unwrap().kind else {
8228            panic!("expected outer Binary");
8229        };
8230        assert_eq!(op, crate::BinOp::Mul);
8231        match ast.try_expr(rhs).unwrap().kind {
8232            crate::ExprKind::Binary { op: inner_op, .. } => {
8233                assert_eq!(inner_op, crate::BinOp::Pow);
8234            }
8235            ref other => panic!("expected Pow on the RHS of *, got {other:?}"),
8236        }
8237    }
8238
8239    #[test]
8240    fn parse_compound_assign_ident() {
8241        let (ast, diags) = parse_str("function main(): void { let x: number = 0; x += 5; }");
8242        assert!(diags.is_empty(), "unexpected: {diags:?}");
8243        let func = ast.top_level.iter().find_map(|sid| {
8244            if let crate::StmtKind::Function { body, .. } = &ast.try_stmt(*sid).unwrap().kind {
8245                Some(*body)
8246            } else {
8247                None
8248            }
8249        });
8250        let crate::StmtKind::Block(stmts) = &ast.try_stmt(func.expect("main")).unwrap().kind else {
8251            panic!("expected function body to be a block");
8252        };
8253        let target = stmts
8254            .iter()
8255            .find_map(|sid| match &ast.try_stmt(*sid).unwrap().kind {
8256                crate::StmtKind::CompoundAssign { target, op, .. } => {
8257                    Some((target.name.clone(), *op))
8258                }
8259                _ => None,
8260            });
8261        let (name, op) = target.expect("expected a CompoundAssign in the body");
8262        assert_eq!(name, "x");
8263        assert_eq!(op, crate::BinOp::Add);
8264    }
8265
8266    #[test]
8267    fn parse_nullish_mixed_with_or_rejected() {
8268        let (_, diags) = parse_str("const x: number = a || b ?? c;");
8269        assert!(
8270            diags
8271                .iter()
8272                .any(|d| d.message.contains("mixing `??` with `||` / `&&`")),
8273            "expected mixing diagnostic, got: {diags:?}",
8274        );
8275    }
8276
8277    #[test]
8278    fn parse_nullish_mixed_with_and_rejected() {
8279        let (_, diags) = parse_str("const x: number = a && b ?? c;");
8280        assert!(
8281            diags
8282                .iter()
8283                .any(|d| d.message.contains("mixing `??` with `||` / `&&`")),
8284            "expected mixing diagnostic, got: {diags:?}",
8285        );
8286    }
8287
8288    #[test]
8289    fn parse_nullish_followed_by_logical_rejected() {
8290        for (source, help) in [
8291            (
8292                "const x: number = a ?? b || c;",
8293                "`(a ?? b) || c` or `a ?? (b || c)`",
8294            ),
8295            (
8296                "const x: number = a ?? b && c;",
8297                "`(a ?? b) && c` or `a ?? (b && c)`",
8298            ),
8299            (
8300                "const x: number = a ?? b ?? c || d;",
8301                "`(a ?? b) || c` or `a ?? (b || c)`",
8302            ),
8303            // One error per chain, however many mixes it has.
8304            (
8305                "const x: number = a || b ?? c || d;",
8306                "`(a || b) ?? c` or `a || (b ?? c)`",
8307            ),
8308        ] {
8309            let (_, diags) = parse_str(source);
8310            assert_eq!(diags.len(), 1, "diags for {source:?}: {diags:?}");
8311            assert!(
8312                diags[0].message.contains("mixing `??` with `||` / `&&`"),
8313                "message for {source:?}: {diags:?}",
8314            );
8315            assert!(
8316                diags[0].help.iter().any(|h| h.contains(help)),
8317                "help for {source:?}: {diags:?}",
8318            );
8319        }
8320    }
8321
8322    #[test]
8323    fn parse_nullish_operand_keeps_tighter_operators() {
8324        // `a ?? (b == c)`: only `||` and `&&` are excluded from a `??` operand.
8325        let (ast, diags) = parse_str("const x: boolean = a ?? b == c;");
8326        assert!(diags.is_empty(), "unexpected: {diags:?}");
8327        let stmt = single_stmt(&ast);
8328        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8329            panic!("expected Const");
8330        };
8331        let crate::ExprKind::Binary { op, rhs, .. } = ast.try_expr(value).unwrap().kind else {
8332            panic!("expected outer Binary");
8333        };
8334        assert_eq!(op, crate::BinOp::NullishCoalesce);
8335        assert!(matches!(
8336            ast.try_expr(rhs).unwrap().kind,
8337            crate::ExprKind::Binary {
8338                op: crate::BinOp::Eq,
8339                ..
8340            }
8341        ));
8342    }
8343
8344    #[test]
8345    fn parse_nullish_with_parens_ok() {
8346        let (_, diags) = parse_str("const x: number = (a || b) ?? c;");
8347        assert!(diags.is_empty(), "unexpected: {diags:?}");
8348    }
8349
8350    #[test]
8351    fn parse_optional_field_chain() {
8352        let (ast, diags) = parse_str("const x: number = a?.b;");
8353        assert!(diags.is_empty(), "unexpected: {diags:?}");
8354        let stmt = single_stmt(&ast);
8355        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8356            panic!("expected Const");
8357        };
8358        match ast.try_expr(value).unwrap().kind {
8359            crate::ExprKind::OptionalChain { ref parts, .. } => {
8360                assert_eq!(parts.len(), 1);
8361                match &parts[0] {
8362                    crate::ChainPart::Field { name, optional, .. } => {
8363                        assert_eq!(name.name, "b");
8364                        assert!(*optional);
8365                    }
8366                    other => panic!("expected Field, got {other:?}"),
8367                }
8368            }
8369            ref other => panic!("expected OptionalChain, got {other:?}"),
8370        }
8371    }
8372
8373    #[test]
8374    fn parse_optional_chain_continues_after_first_dot() {
8375        let (ast, diags) = parse_str("const x: number = a?.b.c;");
8376        assert!(diags.is_empty(), "unexpected: {diags:?}");
8377        let stmt = single_stmt(&ast);
8378        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8379            panic!("expected Const");
8380        };
8381        match ast.try_expr(value).unwrap().kind {
8382            crate::ExprKind::OptionalChain { ref parts, .. } => {
8383                assert_eq!(parts.len(), 2);
8384                if let crate::ChainPart::Field { optional, .. } = &parts[0] {
8385                    assert!(*optional);
8386                }
8387                if let crate::ChainPart::Field { optional, .. } = &parts[1] {
8388                    assert!(!*optional);
8389                }
8390            }
8391            ref other => panic!("expected OptionalChain, got {other:?}"),
8392        }
8393    }
8394
8395    #[test]
8396    fn parse_optional_chain_multiple_short_circuits() {
8397        let (ast, diags) = parse_str("const x: number = a?.b?.c;");
8398        assert!(diags.is_empty(), "unexpected: {diags:?}");
8399        let stmt = single_stmt(&ast);
8400        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8401            panic!("expected Const");
8402        };
8403        let crate::ExprKind::OptionalChain { parts, .. } = &ast.try_expr(value).unwrap().kind
8404        else {
8405            panic!("expected OptionalChain");
8406        };
8407        assert_eq!(parts.len(), 2);
8408        for p in parts {
8409            if let crate::ChainPart::Field { optional, .. } = p {
8410                assert!(*optional, "both parts should be optional");
8411            }
8412        }
8413    }
8414
8415    #[test]
8416    fn parse_optional_call_and_index() {
8417        let (_, diags) = parse_str("const x: number = f?.();");
8418        assert!(diags.is_empty(), "unexpected: {diags:?}");
8419        let (_, diags) = parse_str("const y: number = arr?.[0];");
8420        assert!(diags.is_empty(), "unexpected: {diags:?}");
8421    }
8422
8423    #[test]
8424    fn parse_optional_chain_terminates_at_questionmark_for_ternary() {
8425        let (ast, diags) = parse_str("const x: number = a?.b ? c : d;");
8426        assert!(diags.is_empty(), "unexpected: {diags:?}");
8427        let stmt = single_stmt(&ast);
8428        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8429            panic!("expected Const");
8430        };
8431        let crate::ExprKind::Ternary { cond, .. } = ast.try_expr(value).unwrap().kind else {
8432            panic!(
8433                "expected top-level Ternary, got {:?}",
8434                ast.try_expr(value).unwrap().kind
8435            );
8436        };
8437        assert!(matches!(
8438            ast.try_expr(cond).unwrap().kind,
8439            crate::ExprKind::OptionalChain { .. }
8440        ));
8441    }
8442
8443    #[test]
8444    fn parse_nullish_below_ternary() {
8445        let (ast, diags) = parse_str("const x: number = a ?? b ? c : d;");
8446        assert!(diags.is_empty(), "unexpected: {diags:?}");
8447        let stmt = single_stmt(&ast);
8448        let crate::StmtKind::Const { value, .. } = stmt.kind else {
8449            panic!("expected Const");
8450        };
8451        let crate::ExprKind::Ternary { cond, .. } = ast.try_expr(value).unwrap().kind else {
8452            panic!("expected top-level Ternary");
8453        };
8454        match ast.try_expr(cond).unwrap().kind {
8455            crate::ExprKind::Binary { op, .. } => {
8456                assert_eq!(op, crate::BinOp::NullishCoalesce);
8457            }
8458            ref other => panic!("expected Binary ??, got {other:?}"),
8459        }
8460    }
8461
8462    #[test]
8463    fn let_missing_initializer_diagnoses() {
8464        let (ast, diags) = parse_str("let x;");
8465        assert_eq!(diags.len(), 1);
8466        assert_eq!(
8467            diags[0].message,
8468            "`let` declaration requires an initializer"
8469        );
8470        assert!(ast.top_level.is_empty());
8471    }
8472
8473    #[test]
8474    fn missing_identifier_after_let_diagnoses() {
8475        let (ast, diags) = parse_str("let = 1;");
8476        assert_eq!(diags.len(), 1);
8477        assert_eq!(diags[0].message, "expected identifier after `let`/`const`");
8478        assert!(ast.top_level.is_empty());
8479    }
8480
8481    #[test]
8482    fn missing_type_after_colon_diagnoses() {
8483        let (ast, diags) = parse_str("let x: = 1;");
8484        assert_eq!(diags.len(), 1);
8485        assert_eq!(diags[0].message, "expected type");
8486        assert!(ast.top_level.is_empty());
8487    }
8488
8489    #[test]
8490    fn missing_semicolon_after_declaration_diagnoses() {
8491        let (ast, diags) = parse_str("let x = 1 y;");
8492        assert_eq!(diags.len(), 1);
8493        assert_eq!(diags[0].message, "expected `;` after declaration");
8494        assert!(ast.top_level.is_empty());
8495    }
8496
8497    #[test]
8498    fn expression_statements_still_parse() {
8499        let (ast, diags) = parse_str("42;");
8500        assert!(diags.is_empty());
8501        assert_eq!(ast.top_level.len(), 1);
8502    }
8503
8504    #[test]
8505    fn snapshot_mixed_declarations() {
8506        let (ast, diags) =
8507            parse_str(r#"let x = 1; const y: string = "hi"; let z: number = 1 + 2;"#);
8508        assert!(diags.is_empty());
8509        assert_eq!(ast.top_level.len(), 3);
8510        insta::assert_debug_snapshot!(ast);
8511    }
8512
8513    #[test]
8514    fn parse_function_typical() {
8515        let (ast, diags) = parse_str("function f(a: number, b: string): boolean { }");
8516        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8517        let stmt = single_stmt(&ast);
8518        match stmt.kind {
8519            crate::StmtKind::Function {
8520                ref name,
8521                ref generics,
8522                ref params,
8523                ref return_type,
8524                body,
8525                ..
8526            } => {
8527                assert_eq!(name.name, "f");
8528                assert_eq!(name.span, crate::Span::new(F, 9, 10).unwrap());
8529                assert!(generics.is_empty());
8530                assert_eq!(params.len(), 2);
8531                assert_eq!(params[0].name.name, "a");
8532                assert_eq!(params[0].name.span, crate::Span::new(F, 11, 12).unwrap());
8533                let p0_ty = params[0]
8534                    .ty
8535                    .as_ref()
8536                    .expect("function-decl param requires annotation");
8537                assert!(matches!(p0_ty.kind, crate::TypeAnnotationKind::Name { .. }));
8538                assert_eq!(p0_ty.span, crate::Span::new(F, 14, 20).unwrap()); // `number`
8539                assert_eq!(params[1].name.name, "b");
8540                assert_eq!(params[1].name.span, crate::Span::new(F, 22, 23).unwrap());
8541                let p1_ty = params[1]
8542                    .ty
8543                    .as_ref()
8544                    .expect("function-decl param requires annotation");
8545                assert_eq!(p1_ty.span, crate::Span::new(F, 25, 31).unwrap()); // `string`
8546                let return_type = return_type.as_ref().expect("plain return type");
8547                assert!(matches!(
8548                    return_type.kind,
8549                    crate::TypeAnnotationKind::Name { .. }
8550                ));
8551                assert_eq!(return_type.span, crate::Span::new(F, 34, 41).unwrap()); // `boolean`
8552                let block = ast.try_stmt(body).unwrap();
8553                assert!(matches!(block.kind, crate::StmtKind::Block(ref v) if v.is_empty()));
8554            }
8555            _ => panic!("expected Function"),
8556        }
8557    }
8558
8559    #[test]
8560    fn parse_function_with_doc_comment() {
8561        let (ast, diags) = parse_str(
8562            "/**\n * Sum two numbers.\n * @param a First.\n * @param b Second.\n */\nfunction add(a: number, b: number): number { return a + b; }",
8563        );
8564        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8565        let stmt = single_stmt(&ast);
8566        match &stmt.kind {
8567            crate::StmtKind::Function { doc, .. } => {
8568                let d = doc.as_ref().expect("doc attached");
8569                assert_eq!(d.summary, "Sum two numbers.");
8570                assert_eq!(d.params.len(), 2);
8571                assert_eq!(d.params[0].name, "a");
8572                assert_eq!(d.params[1].name, "b");
8573            }
8574            _ => panic!("expected Function"),
8575        }
8576    }
8577
8578    #[test]
8579    fn parse_interface_member_docs() {
8580        let (ast, diags) = parse_str(
8581            "interface Foo {\n  /** First method. */ first(): void;\n  /** A field. */ field: number;\n}",
8582        );
8583        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8584        let stmt = single_stmt(&ast);
8585        match &stmt.kind {
8586            crate::StmtKind::InterfaceDecl { members, .. } => {
8587                assert_eq!(members.len(), 2);
8588                match &members[0] {
8589                    crate::InterfaceMember::Method { doc, .. } => {
8590                        assert_eq!(doc.as_ref().unwrap().summary, "First method.");
8591                    }
8592                    _ => panic!("expected Method first"),
8593                }
8594                match &members[1] {
8595                    crate::InterfaceMember::Property { doc, .. } => {
8596                        assert_eq!(doc.as_ref().unwrap().summary, "A field.");
8597                    }
8598                    _ => panic!("expected Property second"),
8599                }
8600            }
8601            _ => panic!("expected InterfaceDecl"),
8602        }
8603    }
8604
8605    #[test]
8606    fn parse_numeric_enum_all_implicit() {
8607        let (ast, diags) = parse_str("enum Direction { Up, Down, Left, Right }");
8608        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8609        let stmt = single_stmt(&ast);
8610        match &stmt.kind {
8611            crate::StmtKind::EnumDecl { name, members, doc } => {
8612                assert_eq!(name.name, "Direction");
8613                assert!(doc.is_none());
8614                assert_eq!(members.len(), 4);
8615                for (m, expected) in members.iter().zip(["Up", "Down", "Left", "Right"]) {
8616                    assert_eq!(m.name.name, expected);
8617                    assert!(m.value.is_none(), "expected no initializer on {expected}");
8618                }
8619            }
8620            _ => panic!("expected EnumDecl"),
8621        }
8622    }
8623
8624    #[test]
8625    fn parse_string_enum_all_explicit() {
8626        let (ast, diags) =
8627            parse_str("enum Status { Active = \"active\", Inactive = \"inactive\" }");
8628        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8629        let stmt = single_stmt(&ast);
8630        match &stmt.kind {
8631            crate::StmtKind::EnumDecl { name, members, .. } => {
8632                assert_eq!(name.name, "Status");
8633                assert_eq!(members.len(), 2);
8634                match &members[0].value {
8635                    Some(crate::EnumInitializer::String { value, .. }) => {
8636                        assert_eq!(value, "active");
8637                    }
8638                    other => panic!("expected String init, got {other:?}"),
8639                }
8640                match &members[1].value {
8641                    Some(crate::EnumInitializer::String { value, .. }) => {
8642                        assert_eq!(value, "inactive");
8643                    }
8644                    other => panic!("expected String init, got {other:?}"),
8645                }
8646            }
8647            _ => panic!("expected EnumDecl"),
8648        }
8649    }
8650
8651    #[test]
8652    fn parse_numeric_enum_with_negative_init() {
8653        let (ast, diags) = parse_str("enum D { Up = 1, Down = -1 }");
8654        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8655        let stmt = single_stmt(&ast);
8656        match &stmt.kind {
8657            crate::StmtKind::EnumDecl { members, .. } => {
8658                assert_eq!(members.len(), 2);
8659                match &members[0].value {
8660                    Some(crate::EnumInitializer::Number { value, .. }) => {
8661                        assert_eq!(*value, 1.0);
8662                    }
8663                    other => panic!("expected Number init, got {other:?}"),
8664                }
8665                match &members[1].value {
8666                    Some(crate::EnumInitializer::Number { value, .. }) => {
8667                        assert_eq!(*value, -1.0);
8668                    }
8669                    other => panic!("expected Number init, got {other:?}"),
8670                }
8671            }
8672            _ => panic!("expected EnumDecl"),
8673        }
8674    }
8675
8676    #[test]
8677    fn parse_enum_mixed_kinds_is_parse_error() {
8678        let (_ast, diags) = parse_str("enum Mix { A = 1, B = \"two\" }");
8679        assert!(
8680            diags
8681                .iter()
8682                .any(|d| d.message.contains("mixed numeric and string")),
8683            "expected mixed-kind diagnostic, got: {diags:?}"
8684        );
8685    }
8686
8687    #[test]
8688    fn parse_enum_member_docs() {
8689        let (ast, diags) = parse_str(
8690            "enum D {\n  /** The first one. */ A = 1,\n  /** The second one. */ B = 2,\n}",
8691        );
8692        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8693        let stmt = single_stmt(&ast);
8694        match &stmt.kind {
8695            crate::StmtKind::EnumDecl { members, .. } => {
8696                assert_eq!(members.len(), 2);
8697                assert_eq!(
8698                    members[0].doc.as_ref().expect("first doc").summary,
8699                    "The first one."
8700                );
8701                assert_eq!(
8702                    members[1].doc.as_ref().expect("second doc").summary,
8703                    "The second one."
8704                );
8705            }
8706            _ => panic!("expected EnumDecl"),
8707        }
8708    }
8709
8710    #[test]
8711    fn parse_enum_trailing_comma_ok() {
8712        let (ast, diags) = parse_str("enum D { A, B, }");
8713        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8714        let stmt = single_stmt(&ast);
8715        match &stmt.kind {
8716            crate::StmtKind::EnumDecl { members, .. } => assert_eq!(members.len(), 2),
8717            _ => panic!("expected EnumDecl"),
8718        }
8719    }
8720
8721    #[test]
8722    fn parse_function_empty_params() {
8723        let (ast, diags) = parse_str("function noop(): void { }");
8724        assert!(diags.is_empty());
8725        let stmt = single_stmt(&ast);
8726        match stmt.kind {
8727            crate::StmtKind::Function { ref params, .. } => assert!(params.is_empty()),
8728            _ => panic!("expected Function"),
8729        }
8730    }
8731
8732    #[test]
8733    fn parse_generic_function_single_param() {
8734        let (ast, diags) = parse_str("function identity<T>(x: T): T { return x; }");
8735        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8736        let stmt = single_stmt(&ast);
8737        match stmt.kind {
8738            crate::StmtKind::Function {
8739                ref name,
8740                ref generics,
8741                ref params,
8742                ..
8743            } => {
8744                assert_eq!(name.name, "identity");
8745                assert_eq!(generics.len(), 1);
8746                assert_eq!(generics[0].name, "T");
8747                assert_eq!(params.len(), 1);
8748                assert_eq!(params[0].name.name, "x");
8749            }
8750            _ => panic!("expected Function"),
8751        }
8752    }
8753
8754    #[test]
8755    fn parse_generic_function_multiple_params() {
8756        let (ast, diags) = parse_str("function pair<T, U>(a: T, b: U): T { return a; }");
8757        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8758        let stmt = single_stmt(&ast);
8759        match stmt.kind {
8760            crate::StmtKind::Function { ref generics, .. } => {
8761                assert_eq!(generics.len(), 2);
8762                assert_eq!(generics[0].name, "T");
8763                assert_eq!(generics[1].name, "U");
8764            }
8765            _ => panic!("expected Function"),
8766        }
8767    }
8768
8769    #[test]
8770    fn parse_non_generic_function_has_empty_generics() {
8771        let (ast, diags) = parse_str("function noop(): void { }");
8772        assert!(diags.is_empty());
8773        let stmt = single_stmt(&ast);
8774        match stmt.kind {
8775            crate::StmtKind::Function { ref generics, .. } => assert!(generics.is_empty()),
8776            _ => panic!("expected Function"),
8777        }
8778    }
8779
8780    #[test]
8781    fn parse_empty_generic_list_diagnoses() {
8782        let (_ast, diags) = parse_str("function f<>(): void { }");
8783        assert!(
8784            diags
8785                .iter()
8786                .any(|d| d.message.contains("empty generic parameter list")),
8787            "expected empty-generic-list diagnostic, got: {diags:?}",
8788        );
8789    }
8790
8791    #[test]
8792    fn parse_trailing_comma_in_generic_list_ok() {
8793        let (ast, diags) = parse_str("function f<T,>(): void { }");
8794        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8795        match single_stmt(&ast).kind {
8796            crate::StmtKind::Function { ref generics, .. } => assert_eq!(generics.len(), 1),
8797            _ => panic!("expected Function"),
8798        }
8799    }
8800
8801    #[test]
8802    fn parse_generic_call_single_type_arg() {
8803        let (ast, diags) = parse_str("identity<number>(42);");
8804        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8805        let stmt = single_stmt(&ast);
8806        let crate::StmtKind::Expr(call_id) = stmt.kind else {
8807            panic!("expected Expr stmt");
8808        };
8809        match &ast.try_expr(call_id).unwrap().kind {
8810            crate::ExprKind::Call {
8811                type_args, args, ..
8812            } => {
8813                let ta = type_args.as_ref().expect("type_args set for generic call");
8814                assert_eq!(ta.len(), 1);
8815                assert_eq!(args.len(), 1);
8816            }
8817            other => panic!("expected Call, got {other:?}"),
8818        }
8819    }
8820
8821    #[test]
8822    fn parse_generic_call_trailing_comma_ok() {
8823        let (ast, diags) = parse_str("identity<number,>(42);");
8824        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8825        let stmt = single_stmt(&ast);
8826        let crate::StmtKind::Expr(call_id) = stmt.kind else {
8827            panic!("expected Expr stmt");
8828        };
8829        match &ast.try_expr(call_id).unwrap().kind {
8830            crate::ExprKind::Call {
8831                type_args, args, ..
8832            } => {
8833                assert_eq!(type_args.as_ref().expect("type_args set").len(), 1);
8834                assert_eq!(args.len(), 1);
8835            }
8836            other => panic!("expected Call, got {other:?}"),
8837        }
8838    }
8839
8840    #[test]
8841    fn parse_generic_call_multi_type_args() {
8842        let (ast, diags) = parse_str("pair<number, string>(1, \"x\");");
8843        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8844        let stmt = single_stmt(&ast);
8845        let crate::StmtKind::Expr(call_id) = stmt.kind else {
8846            panic!("expected Expr stmt");
8847        };
8848        match &ast.try_expr(call_id).unwrap().kind {
8849            crate::ExprKind::Call { type_args, .. } => {
8850                let ta = type_args.as_ref().expect("type_args set for generic call");
8851                assert_eq!(ta.len(), 2);
8852            }
8853            other => panic!("expected Call, got {other:?}"),
8854        }
8855    }
8856
8857    #[test]
8858    fn parse_comparison_chain_unaffected_by_generic_lookahead() {
8859        let (ast, diags) = parse_str("let z = a < b > c;");
8860        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8861        assert!(
8862            !ast_contains_call_with_type_args(&ast),
8863            "should not have parsed any generic call",
8864        );
8865    }
8866
8867    #[test]
8868    fn parse_lt_with_paren_rhs_is_comparison_not_generic() {
8869        let (ast, diags) = parse_str("let z = a < (b);");
8870        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8871        assert!(!ast_contains_call_with_type_args(&ast));
8872    }
8873
8874    #[test]
8875    fn parse_generic_call_no_args() {
8876        let (ast, diags) = parse_str("none<number>();");
8877        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8878        let stmt = single_stmt(&ast);
8879        let crate::StmtKind::Expr(call_id) = stmt.kind else {
8880            panic!("expected Expr stmt");
8881        };
8882        match &ast.try_expr(call_id).unwrap().kind {
8883            crate::ExprKind::Call {
8884                type_args, args, ..
8885            } => {
8886                assert_eq!(type_args.as_ref().unwrap().len(), 1);
8887                assert!(args.is_empty());
8888            }
8889            other => panic!("expected Call, got {other:?}"),
8890        }
8891    }
8892
8893    fn ast_contains_call_with_type_args(ast: &crate::Ast) -> bool {
8894        for id in 0..ast.top_level.len() {
8895            let stmt = ast.try_stmt(ast.top_level[id]).unwrap();
8896            if walk_stmt_for_typed_call(ast, stmt) {
8897                return true;
8898            }
8899        }
8900        false
8901    }
8902
8903    fn walk_stmt_for_typed_call(ast: &crate::Ast, stmt: &crate::Stmt) -> bool {
8904        match &stmt.kind {
8905            crate::StmtKind::Let { value, .. } | crate::StmtKind::Const { value, .. } => {
8906                walk_expr_for_typed_call(ast, *value)
8907            }
8908            crate::StmtKind::Expr(e) => walk_expr_for_typed_call(ast, *e),
8909            crate::StmtKind::Block(stmts) => stmts
8910                .iter()
8911                .any(|sid| walk_stmt_for_typed_call(ast, ast.try_stmt(*sid).unwrap())),
8912            _ => false,
8913        }
8914    }
8915
8916    fn walk_expr_for_typed_call(ast: &crate::Ast, id: crate::ExprId) -> bool {
8917        match &ast.try_expr(id).unwrap().kind {
8918            crate::ExprKind::Call {
8919                type_args: Some(_), ..
8920            } => true,
8921            crate::ExprKind::Call { callee, args, .. } => {
8922                walk_expr_for_typed_call(ast, *callee)
8923                    || args.iter().any(|a| walk_expr_for_typed_call(ast, *a))
8924            }
8925            crate::ExprKind::Binary { lhs, rhs, .. } => {
8926                walk_expr_for_typed_call(ast, *lhs) || walk_expr_for_typed_call(ast, *rhs)
8927            }
8928            crate::ExprKind::Unary { operand, .. } => walk_expr_for_typed_call(ast, *operand),
8929            crate::ExprKind::Paren(e) => walk_expr_for_typed_call(ast, *e),
8930            crate::ExprKind::FieldAccess { receiver, .. } => {
8931                walk_expr_for_typed_call(ast, *receiver)
8932            }
8933            crate::ExprKind::IndexAccess { receiver, index } => {
8934                walk_expr_for_typed_call(ast, *receiver) || walk_expr_for_typed_call(ast, *index)
8935            }
8936            _ => false,
8937        }
8938    }
8939
8940    #[test]
8941    fn parse_function_body_with_statements() {
8942        let (ast, diags) = parse_str("function g(x: number): number { let y = x; y; }");
8943        assert!(diags.is_empty());
8944        let stmt = single_stmt(&ast);
8945        match stmt.kind {
8946            crate::StmtKind::Function { body, .. } => {
8947                let block = ast.try_stmt(body).unwrap();
8948                match block.kind {
8949                    crate::StmtKind::Block(ref v) => assert_eq!(v.len(), 2),
8950                    _ => panic!("expected Block"),
8951                }
8952            }
8953            _ => panic!("expected Function"),
8954        }
8955    }
8956
8957    #[test]
8958    fn function_missing_return_type_diagnoses() {
8959        let (ast, diags) = parse_str("function f() { }");
8960        assert!(!diags.is_empty());
8961        assert_eq!(diags[0].message, "expected `:` and return type");
8962        assert!(ast.top_level.is_empty());
8963    }
8964
8965    #[test]
8966    fn function_missing_param_type_diagnoses() {
8967        let (ast, diags) = parse_str("function f(a): void { }");
8968        assert!(!diags.is_empty());
8969        assert_eq!(diags[0].message, "parameter requires a type annotation");
8970        assert!(ast.top_level.is_empty());
8971    }
8972
8973    #[test]
8974    fn function_missing_name_diagnoses() {
8975        let (ast, diags) = parse_str("function (x: number): void { }");
8976        assert!(!diags.is_empty());
8977        assert_eq!(diags[0].message, "expected function name");
8978        assert!(ast.top_level.is_empty());
8979    }
8980
8981    #[test]
8982    fn function_trailing_param_comma_ok() {
8983        let (ast, diags) = parse_str("function f(a: number,): void { }");
8984        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
8985        match single_stmt(&ast).kind {
8986            crate::StmtKind::Function { ref params, .. } => assert_eq!(params.len(), 1),
8987            _ => panic!("expected Function"),
8988        }
8989    }
8990
8991    #[test]
8992    fn function_missing_open_brace_diagnoses() {
8993        let (ast, diags) = parse_str("function f(): void");
8994        assert_eq!(diags.len(), 1);
8995        assert_eq!(diags[0].message, "expected `{`");
8996        assert!(ast.top_level.is_empty());
8997    }
8998
8999    #[test]
9000    fn snapshot_function_declaration() {
9001        let (ast, diags) =
9002            parse_str("function add(a: number, b: number): number { let sum = a + b; sum; }");
9003        assert!(diags.is_empty());
9004        insta::assert_debug_snapshot!(ast);
9005    }
9006
9007    #[test]
9008    fn rest_param_basic_parses() {
9009        let (_, diags) = parse_str("function sum(...nums: number[]): number { return 0; }");
9010        assert!(diags.is_empty(), "expected clean parse, got {diags:?}");
9011    }
9012
9013    #[test]
9014    fn rest_param_with_fixed_prefix_parses() {
9015        let (_, diags) =
9016            parse_str("function tag(label: string, ...vals: number[]): string { return label; }");
9017        assert!(diags.is_empty(), "expected clean parse, got {diags:?}");
9018    }
9019
9020    #[test]
9021    fn rest_param_in_arrow_parses() {
9022        let (_, diags) = parse_str("const f = (...n: number[]) => n.length;");
9023        assert!(diags.is_empty(), "expected clean parse, got {diags:?}");
9024    }
9025
9026    #[test]
9027    fn rest_param_in_function_type_annotation_parses() {
9028        let (_, diags) =
9029            parse_str("let f: (a: number, ...rest: string[]) => void = (a, ...r) => {};");
9030        assert!(diags.is_empty(), "expected clean parse, got {diags:?}");
9031    }
9032
9033    #[test]
9034    fn rest_param_not_last_rejected() {
9035        let (_, diags) = parse_str("function f(...xs: number[], y: number): void {}");
9036        assert!(
9037            diags.iter().any(|d| d
9038                .message
9039                .contains("rest parameter must be the last parameter")),
9040            "expected 'must be last' diagnostic, got {diags:?}",
9041        );
9042    }
9043
9044    #[test]
9045    fn rest_param_with_default_rejected() {
9046        let (_, diags) = parse_str("function f(...xs: number[] = []): void {}");
9047        assert!(
9048            diags.iter().any(|d| d
9049                .message
9050                .contains("rest parameter cannot have a default value")),
9051            "expected default-rejection diagnostic, got {diags:?}",
9052        );
9053    }
9054
9055    #[test]
9056    fn rest_param_without_annotation_rejected() {
9057        let (_, diags) = parse_str("function f(...xs): void {}");
9058        assert!(
9059            diags.iter().any(|d| d
9060                .message
9061                .contains("rest parameter requires a type annotation")),
9062            "expected missing-annotation diagnostic, got {diags:?}",
9063        );
9064    }
9065
9066    #[test]
9067    fn rest_param_destructured_rejected() {
9068        let (_, diags) = parse_str("function f(...{a, b}): void {}");
9069        assert!(
9070            diags
9071                .iter()
9072                .any(|d| d.message.contains("rest parameter cannot be destructured")),
9073            "expected destructured-rejection diagnostic, got {diags:?}",
9074        );
9075    }
9076
9077    #[test]
9078    fn rest_param_in_function_type_with_non_array_rejected() {
9079        let (_, diags) = parse_str("let f: (...xs: number) => void = () => {};");
9080        assert!(
9081            diags
9082                .iter()
9083                .any(|d| d.message.contains("rest parameter type must be an array")),
9084            "expected non-array-type diagnostic, got {diags:?}",
9085        );
9086    }
9087
9088    fn call(e: &crate::Expr) -> (crate::ExprId, &[crate::ExprId]) {
9089        match e.kind {
9090            crate::ExprKind::Call {
9091                callee, ref args, ..
9092            } => (callee, args.as_slice()),
9093            _ => panic!("expected Call, got {:?}", e.kind),
9094        }
9095    }
9096
9097    #[test]
9098    fn parse_call_no_args() {
9099        let (ast, diags) = parse_str("f();");
9100        assert!(diags.is_empty());
9101        let outer = expr_of_single_stmt(&ast);
9102        let (callee, args) = call(outer);
9103        assert!(matches!(
9104            ast.try_expr(callee).unwrap().kind,
9105            crate::ExprKind::Identifier(_)
9106        ));
9107        assert!(args.is_empty());
9108        assert_eq!(outer.span, crate::Span::new(F, 0, 3).unwrap());
9109    }
9110
9111    #[test]
9112    fn parse_call_one_arg() {
9113        let (ast, diags) = parse_str("f(1);");
9114        assert!(diags.is_empty());
9115        let outer = expr_of_single_stmt(&ast);
9116        let (callee, args) = call(outer);
9117        assert!(matches!(
9118            ast.try_expr(callee).unwrap().kind,
9119            crate::ExprKind::Identifier(_)
9120        ));
9121        assert_eq!(args.len(), 1);
9122        assert_eq!(
9123            ast.try_expr(args[0]).unwrap().kind,
9124            crate::ExprKind::Number(1.0)
9125        );
9126    }
9127
9128    #[test]
9129    fn parse_call_three_args() {
9130        let (ast, diags) = parse_str("f(1, 2, 3);");
9131        assert!(diags.is_empty());
9132        let outer = expr_of_single_stmt(&ast);
9133        let (_callee, args) = call(outer);
9134        assert_eq!(args.len(), 3);
9135        assert_eq!(
9136            ast.try_expr(args[0]).unwrap().kind,
9137            crate::ExprKind::Number(1.0)
9138        );
9139        assert_eq!(
9140            ast.try_expr(args[1]).unwrap().kind,
9141            crate::ExprKind::Number(2.0)
9142        );
9143        assert_eq!(
9144            ast.try_expr(args[2]).unwrap().kind,
9145            crate::ExprKind::Number(3.0)
9146        );
9147    }
9148
9149    #[test]
9150    fn parse_call_nested() {
9151        let (ast, diags) = parse_str("f(g(x));");
9152        assert!(diags.is_empty());
9153        let outer = expr_of_single_stmt(&ast);
9154        let (_callee, args) = call(outer);
9155        assert_eq!(args.len(), 1);
9156        let (_inner_callee, inner_args) = call(ast.try_expr(args[0]).unwrap());
9157        assert_eq!(inner_args.len(), 1);
9158        assert!(matches!(
9159            ast.try_expr(inner_args[0]).unwrap().kind,
9160            crate::ExprKind::Identifier(_)
9161        ));
9162    }
9163
9164    #[test]
9165    fn parse_curried_call() {
9166        let (ast, diags) = parse_str("f()(x);");
9167        assert!(diags.is_empty());
9168        let outer = expr_of_single_stmt(&ast);
9169        let (callee, args) = call(outer);
9170        assert_eq!(args.len(), 1);
9171        let (_inner_callee, inner_args) = call(ast.try_expr(callee).unwrap());
9172        assert!(inner_args.is_empty());
9173    }
9174
9175    #[test]
9176    fn unary_binds_looser_than_call() {
9177        let (ast, diags) = parse_str("-f();");
9178        assert!(diags.is_empty());
9179        let outer = expr_of_single_stmt(&ast);
9180        let (op, operand) = unary(outer);
9181        assert_eq!(op, crate::UnOp::Neg);
9182        let (_callee, args) = call(ast.try_expr(operand).unwrap());
9183        assert!(args.is_empty());
9184    }
9185
9186    #[test]
9187    fn call_inside_binary() {
9188        let (ast, diags) = parse_str("f() + 1;");
9189        assert!(diags.is_empty());
9190        let outer = expr_of_single_stmt(&ast);
9191        let (op, lhs, rhs) = binary(outer);
9192        assert_eq!(op, crate::BinOp::Add);
9193        let (_callee, args) = call(ast.try_expr(lhs).unwrap());
9194        assert!(args.is_empty());
9195        assert_eq!(
9196            ast.try_expr(rhs).unwrap().kind,
9197            crate::ExprKind::Number(1.0)
9198        );
9199    }
9200
9201    #[test]
9202    fn call_with_complex_args() {
9203        let (ast, diags) = parse_str("f(1 + 2, g(x));");
9204        assert!(diags.is_empty());
9205        let outer = expr_of_single_stmt(&ast);
9206        let (_callee, args) = call(outer);
9207        assert_eq!(args.len(), 2);
9208        let (op, ..) = binary(ast.try_expr(args[0]).unwrap());
9209        assert_eq!(op, crate::BinOp::Add);
9210        let (_, inner_args) = call(ast.try_expr(args[1]).unwrap());
9211        assert_eq!(inner_args.len(), 1);
9212    }
9213
9214    #[test]
9215    fn call_trailing_comma_ok() {
9216        let (ast, diags) = parse_str("f(1, 2,);");
9217        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9218        let (_callee, args) = call(expr_of_single_stmt(&ast));
9219        assert_eq!(args.len(), 2);
9220    }
9221
9222    #[test]
9223    fn unclosed_call_diagnoses() {
9224        let (ast, diags) = parse_str("f(1, 2");
9225        assert!(!diags.is_empty());
9226        assert!(diags[0].message == "expected `,` or `)`" || diags[0].message == "expected `)`");
9227        assert!(ast.top_level.is_empty());
9228    }
9229
9230    #[test]
9231    fn call_expression_statement() {
9232        let (ast, diags) = parse_str(r#"greet("world");"#);
9233        assert!(diags.is_empty());
9234        let outer = expr_of_single_stmt(&ast);
9235        let (callee, args) = call(outer);
9236        assert!(matches!(
9237            ast.try_expr(callee).unwrap().kind,
9238            crate::ExprKind::Identifier(_)
9239        ));
9240        assert_eq!(args.len(), 1);
9241        assert_eq!(
9242            ast.try_expr(args[0]).unwrap().kind,
9243            crate::ExprKind::String("world".to_string())
9244        );
9245    }
9246
9247    #[test]
9248    fn snapshot_multi_call_program() {
9249        let (ast, diags) = parse_str("f(); g(1); h(f(2), 3);");
9250        assert!(diags.is_empty());
9251        assert_eq!(ast.top_level.len(), 3);
9252        insta::assert_debug_snapshot!(ast);
9253    }
9254
9255    #[test]
9256    fn parse_if_simple() {
9257        let (ast, diags) = parse_str("if (a) { }");
9258        assert!(diags.is_empty());
9259        let stmt = single_stmt(&ast);
9260        match stmt.kind {
9261            crate::StmtKind::If {
9262                condition,
9263                then_block,
9264                else_block,
9265            } => {
9266                assert!(matches!(
9267                    ast.try_expr(condition).unwrap().kind,
9268                    crate::ExprKind::Identifier(_)
9269                ));
9270                assert!(matches!(
9271                    ast.try_stmt(then_block).unwrap().kind,
9272                    crate::StmtKind::Block(ref v) if v.is_empty()
9273                ));
9274                assert!(else_block.is_none());
9275            }
9276            _ => panic!("expected If"),
9277        }
9278    }
9279
9280    #[test]
9281    fn parse_if_else() {
9282        let (ast, diags) = parse_str("if (a) { } else { }");
9283        assert!(diags.is_empty());
9284        let stmt = single_stmt(&ast);
9285        match stmt.kind {
9286            crate::StmtKind::If { else_block, .. } => {
9287                let else_id = else_block.expect("expected else block");
9288                assert!(matches!(
9289                    ast.try_stmt(else_id).unwrap().kind,
9290                    crate::StmtKind::Block(ref v) if v.is_empty()
9291                ));
9292            }
9293            _ => panic!("expected If"),
9294        }
9295    }
9296
9297    #[test]
9298    fn parse_else_if_chain() {
9299        let (ast, diags) = parse_str("if (a) { } else if (b) { } else { }");
9300        assert!(diags.is_empty());
9301        let stmt = single_stmt(&ast);
9302        let crate::StmtKind::If {
9303            else_block: Some(inner_id),
9304            ..
9305        } = stmt.kind
9306        else {
9307            panic!("expected outer If with else_block");
9308        };
9309        let inner = ast.try_stmt(inner_id).unwrap();
9310        let crate::StmtKind::If {
9311            else_block: Some(tail_id),
9312            ..
9313        } = inner.kind
9314        else {
9315            panic!("expected inner If for `else if`");
9316        };
9317        assert!(matches!(
9318            ast.try_stmt(tail_id).unwrap().kind,
9319            crate::StmtKind::Block(_)
9320        ));
9321    }
9322
9323    #[test]
9324    fn parse_if_with_body_statements() {
9325        let (ast, diags) = parse_str("if (a) { let x = 1; }");
9326        assert!(diags.is_empty());
9327        let stmt = single_stmt(&ast);
9328        match stmt.kind {
9329            crate::StmtKind::If { then_block, .. } => {
9330                let crate::StmtKind::Block(ref body) = ast.try_stmt(then_block).unwrap().kind
9331                else {
9332                    panic!("expected Block")
9333                };
9334                assert_eq!(body.len(), 1);
9335            }
9336            _ => panic!("expected If"),
9337        }
9338    }
9339
9340    #[test]
9341    fn parse_if_braceless_body() {
9342        let (ast, diags) = parse_str("if (a) return b;");
9343        assert!(diags.is_empty(), "{diags:?}");
9344        let stmt = single_stmt(&ast);
9345        let crate::StmtKind::If {
9346            then_block,
9347            else_block,
9348            ..
9349        } = stmt.kind
9350        else {
9351            panic!("expected If");
9352        };
9353        let crate::StmtKind::Block(ref body) = ast.try_stmt(then_block).unwrap().kind else {
9354            panic!("braceless body should wrap in a Block");
9355        };
9356        assert_eq!(body.len(), 1);
9357        assert!(matches!(
9358            ast.try_stmt(body[0]).unwrap().kind,
9359            crate::StmtKind::Return(_)
9360        ));
9361        assert!(else_block.is_none());
9362    }
9363
9364    #[test]
9365    fn parse_braceless_dangling_else_binds_nearest_if() {
9366        // `else` attaches to the inner `if`, not the outer one.
9367        let (ast, diags) = parse_str("if (a) if (b) x(); else y();");
9368        assert!(diags.is_empty(), "{diags:?}");
9369        let crate::StmtKind::If {
9370            then_block,
9371            else_block: outer_else,
9372            ..
9373        } = single_stmt(&ast).kind
9374        else {
9375            panic!("expected outer If");
9376        };
9377        assert!(outer_else.is_none(), "else must bind to the inner if");
9378        let crate::StmtKind::Block(ref body) = ast.try_stmt(then_block).unwrap().kind else {
9379            panic!("expected wrapped Block");
9380        };
9381        let crate::StmtKind::If {
9382            else_block: Some(_),
9383            ..
9384        } = ast.try_stmt(body[0]).unwrap().kind
9385        else {
9386            panic!("inner If should own the else");
9387        };
9388    }
9389
9390    #[test]
9391    fn parse_while_braceless_body() {
9392        let (ast, diags) = parse_str("while (a) x();");
9393        assert!(diags.is_empty(), "{diags:?}");
9394        let crate::StmtKind::While { body, .. } = single_stmt(&ast).kind else {
9395            panic!("expected While");
9396        };
9397        assert!(matches!(
9398            ast.try_stmt(body).unwrap().kind,
9399            crate::StmtKind::Block(ref v) if v.len() == 1
9400        ));
9401    }
9402
9403    #[test]
9404    fn parse_loop_with_empty_body() {
9405        for src in [
9406            "while (a());",
9407            "for (const x of xs);",
9408            "for (let i = 0; i < 3; i++);",
9409            "do ; while (a());",
9410        ] {
9411            let (ast, diags) = parse_str(src);
9412            assert!(diags.is_empty(), "{src}: {diags:?}");
9413            let body = match single_stmt(&ast).kind {
9414                crate::StmtKind::While { body, .. }
9415                | crate::StmtKind::DoWhile { body, .. }
9416                | crate::StmtKind::For { body, .. }
9417                | crate::StmtKind::ForOf { body, .. } => body,
9418                ref other => panic!("{src}: expected a loop, got {other:?}"),
9419            };
9420            assert!(
9421                matches!(ast.try_stmt(body).unwrap().kind, crate::StmtKind::Block(ref v) if v.is_empty()),
9422                "{src}"
9423            );
9424        }
9425    }
9426
9427    #[test]
9428    fn parse_if_with_empty_body_is_an_error() {
9429        let (_ast, diags) = parse_str("if (a);");
9430        assert_eq!(diags[0].message, "`if` has an empty body");
9431    }
9432
9433    #[test]
9434    fn parse_empty_branch_body_reports_once() {
9435        let (_ast, diags) = parse_str("if (a) ; else b();");
9436        assert_eq!(diags.len(), 1, "{diags:?}");
9437        assert_eq!(diags[0].message, "`if` has an empty body");
9438        let (_ast, diags) = parse_str("if (a) b(); else ;");
9439        assert_eq!(diags.len(), 1, "{diags:?}");
9440        assert_eq!(diags[0].message, "`else` has an empty body");
9441        // One diagnostic per stray `;`, and the `else if` chain still parses.
9442        let (_ast, diags) = parse_str("if (a) ; else if (b) ; else ;");
9443        let messages: Vec<_> = diags.iter().map(|d| d.message.as_str()).collect();
9444        assert_eq!(
9445            messages,
9446            [
9447                "`if` has an empty body",
9448                "`if` has an empty body",
9449                "`else` has an empty body"
9450            ]
9451        );
9452    }
9453
9454    #[test]
9455    fn parse_for_of_braceless_body() {
9456        let (ast, diags) = parse_str("for (const x of xs) f(x);");
9457        assert!(diags.is_empty(), "{diags:?}");
9458        let crate::StmtKind::ForOf { body, .. } = single_stmt(&ast).kind else {
9459            panic!("expected ForOf");
9460        };
9461        assert!(matches!(
9462            ast.try_stmt(body).unwrap().kind,
9463            crate::StmtKind::Block(ref v) if v.len() == 1
9464        ));
9465    }
9466
9467    #[test]
9468    fn parse_nested_if() {
9469        let (ast, diags) = parse_str("if (a) { if (b) { } }");
9470        assert!(diags.is_empty());
9471        let outer = single_stmt(&ast);
9472        let crate::StmtKind::If { then_block, .. } = outer.kind else {
9473            panic!("expected outer If");
9474        };
9475        let crate::StmtKind::Block(ref body) = ast.try_stmt(then_block).unwrap().kind else {
9476            panic!("expected Block")
9477        };
9478        assert_eq!(body.len(), 1);
9479        assert!(matches!(
9480            ast.try_stmt(body[0]).unwrap().kind,
9481            crate::StmtKind::If { .. }
9482        ));
9483    }
9484
9485    #[test]
9486    fn if_missing_open_paren_diagnoses() {
9487        let (ast, diags) = parse_str("if a { }");
9488        assert!(!diags.is_empty());
9489        assert_eq!(diags[0].message, "expected `(`");
9490        assert!(ast.top_level.is_empty());
9491    }
9492
9493    #[test]
9494    fn if_missing_close_paren_diagnoses() {
9495        let (ast, diags) = parse_str("if (a { }");
9496        assert!(!diags.is_empty());
9497        assert_eq!(diags[0].message, "expected `)`");
9498        assert!(ast.top_level.is_empty());
9499    }
9500
9501    #[test]
9502    fn if_braceless_body_parses() {
9503        let (ast, diags) = parse_str("if (a) x;");
9504        assert!(diags.is_empty(), "{diags:?}");
9505        let crate::StmtKind::If { then_block, .. } = single_stmt(&ast).kind else {
9506            panic!("expected If");
9507        };
9508        assert!(matches!(
9509            ast.try_stmt(then_block).unwrap().kind,
9510            crate::StmtKind::Block(ref v) if v.len() == 1
9511        ));
9512    }
9513
9514    #[test]
9515    fn snapshot_else_if_chain() {
9516        let (ast, diags) = parse_str("if (a) { } else if (b) { } else { }");
9517        assert!(diags.is_empty());
9518        insta::assert_debug_snapshot!(ast);
9519    }
9520
9521    #[test]
9522    fn parse_while_simple() {
9523        let (ast, diags) = parse_str("while (a) { }");
9524        assert!(diags.is_empty());
9525        let stmt = single_stmt(&ast);
9526        match stmt.kind {
9527            crate::StmtKind::While { condition, body } => {
9528                assert!(matches!(
9529                    ast.try_expr(condition).unwrap().kind,
9530                    crate::ExprKind::Identifier(_)
9531                ));
9532                assert!(matches!(
9533                    ast.try_stmt(body).unwrap().kind,
9534                    crate::StmtKind::Block(ref v) if v.is_empty()
9535                ));
9536            }
9537            _ => panic!("expected While"),
9538        }
9539    }
9540
9541    #[test]
9542    fn parse_while_with_body() {
9543        let (ast, diags) = parse_str("while (a) { x; y; }");
9544        assert!(diags.is_empty());
9545        let stmt = single_stmt(&ast);
9546        let crate::StmtKind::While { body, .. } = stmt.kind else {
9547            panic!("expected While");
9548        };
9549        let crate::StmtKind::Block(ref stmts) = ast.try_stmt(body).unwrap().kind else {
9550            panic!("expected Block")
9551        };
9552        assert_eq!(stmts.len(), 2);
9553    }
9554
9555    #[test]
9556    fn while_missing_paren_diagnoses() {
9557        let (ast, diags) = parse_str("while a { }");
9558        assert!(!diags.is_empty());
9559        assert_eq!(diags[0].message, "expected `(`");
9560        assert!(ast.top_level.is_empty());
9561    }
9562
9563    #[test]
9564    fn parse_for_all_slots() {
9565        let (ast, diags) = parse_str("for (let i = 0; i < 10; i = i + 1) { x; }");
9566        assert!(diags.is_empty(), "{diags:?}");
9567        let stmt = single_stmt(&ast);
9568        let crate::StmtKind::For {
9569            init,
9570            condition,
9571            update,
9572            body,
9573        } = stmt.kind
9574        else {
9575            panic!("expected For");
9576        };
9577        assert!(init.is_some(), "init missing");
9578        assert!(condition.is_some(), "condition missing");
9579        assert!(update.is_some(), "update missing");
9580        assert!(matches!(
9581            ast.try_stmt(body).unwrap().kind,
9582            crate::StmtKind::Block(_)
9583        ));
9584    }
9585
9586    #[test]
9587    fn parse_for_empty_slots() {
9588        let (ast, diags) = parse_str("for (;;) { break; }");
9589        assert!(diags.is_empty(), "{diags:?}");
9590        let stmt = single_stmt(&ast);
9591        let crate::StmtKind::For {
9592            init,
9593            condition,
9594            update,
9595            ..
9596        } = stmt.kind
9597        else {
9598            panic!("expected For");
9599        };
9600        assert!(init.is_none() && condition.is_none() && update.is_none());
9601    }
9602
9603    #[test]
9604    fn parse_for_of_const_binding() {
9605        let (ast, diags) = parse_str("for (const x of arr) { y; }");
9606        assert!(diags.is_empty(), "{diags:?}");
9607        let stmt = single_stmt(&ast);
9608        let crate::StmtKind::ForOf {
9609            binding_kind,
9610            ref name,
9611            ref ty,
9612            ..
9613        } = stmt.kind
9614        else {
9615            panic!("expected ForOf");
9616        };
9617        assert!(matches!(binding_kind, crate::BindingKind::Const));
9618        assert_eq!(name.name, "x");
9619        assert!(ty.is_none());
9620    }
9621
9622    #[test]
9623    fn parse_for_of_let_with_annotation() {
9624        let (ast, diags) = parse_str("for (let x: number of arr) { y; }");
9625        assert!(diags.is_empty(), "{diags:?}");
9626        let stmt = single_stmt(&ast);
9627        let crate::StmtKind::ForOf {
9628            binding_kind,
9629            ref ty,
9630            ..
9631        } = stmt.kind
9632        else {
9633            panic!("expected ForOf");
9634        };
9635        assert!(matches!(binding_kind, crate::BindingKind::Let));
9636        assert!(ty.is_some());
9637    }
9638
9639    #[test]
9640    fn parse_do_while_simple() {
9641        let (ast, diags) = parse_str("do { x; } while (a);");
9642        assert!(diags.is_empty(), "{diags:?}");
9643        let stmt = single_stmt(&ast);
9644        assert!(matches!(stmt.kind, crate::StmtKind::DoWhile { .. }));
9645    }
9646
9647    #[test]
9648    fn parse_break_and_continue() {
9649        let (ast, diags) = parse_str("while (a) { break; continue; }");
9650        assert!(diags.is_empty(), "{diags:?}");
9651        let stmt = single_stmt(&ast);
9652        let crate::StmtKind::While { body, .. } = stmt.kind else {
9653            panic!("expected While");
9654        };
9655        let crate::StmtKind::Block(ref stmts) = ast.try_stmt(body).unwrap().kind else {
9656            panic!("expected Block");
9657        };
9658        assert!(matches!(
9659            ast.try_stmt(stmts[0]).unwrap().kind,
9660            crate::StmtKind::Break
9661        ));
9662        assert!(matches!(
9663            ast.try_stmt(stmts[1]).unwrap().kind,
9664            crate::StmtKind::Continue
9665        ));
9666    }
9667
9668    #[test]
9669    fn parse_bare_return_in_function() {
9670        let (ast, diags) = parse_str("function f(): void { return; }");
9671        assert!(diags.is_empty());
9672        let f = single_stmt(&ast);
9673        let crate::StmtKind::Function { body, .. } = f.kind else {
9674            panic!("expected Function");
9675        };
9676        let crate::StmtKind::Block(ref stmts) = ast.try_stmt(body).unwrap().kind else {
9677            panic!("expected Block")
9678        };
9679        assert_eq!(stmts.len(), 1);
9680        match ast.try_stmt(stmts[0]).unwrap().kind {
9681            crate::StmtKind::Return(None) => {}
9682            _ => panic!("expected Return(None)"),
9683        }
9684    }
9685
9686    #[test]
9687    fn parse_return_with_value() {
9688        let (ast, diags) = parse_str("function f(): number { return 1; }");
9689        assert!(diags.is_empty());
9690        let f = single_stmt(&ast);
9691        let crate::StmtKind::Function { body, .. } = f.kind else {
9692            panic!("expected Function");
9693        };
9694        let crate::StmtKind::Block(ref stmts) = ast.try_stmt(body).unwrap().kind else {
9695            panic!("expected Block")
9696        };
9697        match ast.try_stmt(stmts[0]).unwrap().kind {
9698            crate::StmtKind::Return(Some(value)) => {
9699                assert_eq!(
9700                    ast.try_expr(value).unwrap().kind,
9701                    crate::ExprKind::Number(1.0)
9702                );
9703            }
9704            _ => panic!("expected Return(Some)"),
9705        }
9706    }
9707
9708    #[test]
9709    fn parse_return_with_expression() {
9710        let (ast, diags) = parse_str("function f(): number { return 1 + 2; }");
9711        assert!(diags.is_empty());
9712        let f = single_stmt(&ast);
9713        let crate::StmtKind::Function { body, .. } = f.kind else {
9714            panic!("expected Function");
9715        };
9716        let crate::StmtKind::Block(ref stmts) = ast.try_stmt(body).unwrap().kind else {
9717            panic!("expected Block")
9718        };
9719        match ast.try_stmt(stmts[0]).unwrap().kind {
9720            crate::StmtKind::Return(Some(value)) => {
9721                let (op, ..) = binary(ast.try_expr(value).unwrap());
9722                assert_eq!(op, crate::BinOp::Add);
9723            }
9724            _ => panic!("expected Return(Some(Binary))"),
9725        }
9726    }
9727
9728    #[test]
9729    fn return_missing_semicolon_diagnoses() {
9730        let (_, diags) = parse_str("function f(): number { return x x; }");
9731        assert!(!diags.is_empty());
9732        assert_eq!(diags[0].message, "expected `;` after return");
9733    }
9734
9735    #[test]
9736    fn parse_bare_block() {
9737        let (ast, diags) = parse_str("{ x; y; }");
9738        assert!(diags.is_empty());
9739        let stmt = single_stmt(&ast);
9740        let crate::StmtKind::Block(ref stmts) = stmt.kind else {
9741            panic!("expected Block");
9742        };
9743        assert_eq!(stmts.len(), 2);
9744    }
9745
9746    #[test]
9747    fn parse_empty_bare_block() {
9748        let (ast, diags) = parse_str("{ }");
9749        assert!(diags.is_empty());
9750        let stmt = single_stmt(&ast);
9751        let crate::StmtKind::Block(ref stmts) = stmt.kind else {
9752            panic!("expected Block");
9753        };
9754        assert!(stmts.is_empty());
9755    }
9756
9757    #[test]
9758    fn parse_nested_bare_blocks() {
9759        let (ast, diags) = parse_str("{ { } }");
9760        assert!(diags.is_empty());
9761        let outer = single_stmt(&ast);
9762        let crate::StmtKind::Block(ref stmts) = outer.kind else {
9763            panic!("expected outer Block");
9764        };
9765        assert_eq!(stmts.len(), 1);
9766        assert!(matches!(
9767            ast.try_stmt(stmts[0]).unwrap().kind,
9768            crate::StmtKind::Block(ref v) if v.is_empty()
9769        ));
9770    }
9771
9772    #[test]
9773    fn snapshot_function_with_if_and_return() {
9774        let source = "function isPositive(n: number): boolean {\n  if (n > 0) {\n    return true;\n  } else {\n    return false;\n  }\n}";
9775        let (ast, diags) = parse_str(source);
9776        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9777        insta::assert_debug_snapshot!(ast);
9778    }
9779
9780    fn object_literal(e: &crate::Expr) -> &[crate::ObjectLiteralMember] {
9781        match e.kind {
9782            crate::ExprKind::ObjectLiteral { ref members } => members.as_slice(),
9783            _ => panic!("expected ObjectLiteral, got {:?}", e.kind),
9784        }
9785    }
9786
9787    fn object_literal_field(m: &crate::ObjectLiteralMember) -> &crate::ObjectLiteralField {
9788        match m {
9789            crate::ObjectLiteralMember::Field(f) => f,
9790            crate::ObjectLiteralMember::Spread { .. }
9791            | crate::ObjectLiteralMember::Computed { .. } => {
9792                panic!("expected Field member, got Spread")
9793            }
9794        }
9795    }
9796
9797    fn array_literal(e: &crate::Expr) -> &[crate::ArrayLiteralElement] {
9798        match e.kind {
9799            crate::ExprKind::ArrayLiteral { ref elements } => elements.as_slice(),
9800            _ => panic!("expected ArrayLiteral, got {:?}", e.kind),
9801        }
9802    }
9803
9804    fn array_literal_value(el: &crate::ArrayLiteralElement) -> crate::ExprId {
9805        match el {
9806            crate::ArrayLiteralElement::Value(id) => *id,
9807            crate::ArrayLiteralElement::Spread { .. } => {
9808                panic!("expected Value element, got Spread")
9809            }
9810        }
9811    }
9812
9813    #[test]
9814    fn parse_empty_object_literal() {
9815        let (ast, diags) = parse_str("let x = {};");
9816        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9817        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9818            panic!("expected Let");
9819        };
9820        assert!(object_literal(ast.try_expr(value).unwrap()).is_empty());
9821    }
9822
9823    #[test]
9824    fn parse_object_literal_simple() {
9825        let (ast, diags) = parse_str("let p = { x: 1, y: 2 };");
9826        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9827        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9828            panic!("expected Let");
9829        };
9830        let members = object_literal(ast.try_expr(value).unwrap());
9831        assert_eq!(members.len(), 2);
9832        let f0 = object_literal_field(&members[0]);
9833        let f1 = object_literal_field(&members[1]);
9834        assert_eq!(f0.name.name, "x");
9835        assert_eq!(f1.name.name, "y");
9836        assert_eq!(
9837            ast.try_expr(f0.value).unwrap().kind,
9838            crate::ExprKind::Number(1.0)
9839        );
9840        assert_eq!(
9841            ast.try_expr(f1.value).unwrap().kind,
9842            crate::ExprKind::Number(2.0)
9843        );
9844    }
9845
9846    #[test]
9847    fn parse_object_literal_keyword_keys() {
9848        let (ast, diags) = parse_str("let p = { type: 1, default: 2, null: 3, true: 4 };");
9849        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9850        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9851            panic!("expected Let");
9852        };
9853        let names: Vec<&str> = object_literal(ast.try_expr(value).unwrap())
9854            .iter()
9855            .map(object_literal_field)
9856            .map(|f| f.name.name.as_str())
9857            .collect();
9858        assert_eq!(names, vec!["type", "default", "null", "true"]);
9859    }
9860
9861    #[test]
9862    fn parse_object_literal_string_key() {
9863        let (ast, diags) = parse_str(r#"let p = { "hello": 1 };"#);
9864        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9865        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9866            panic!("expected Let");
9867        };
9868        let members = object_literal(ast.try_expr(value).unwrap());
9869        assert_eq!(members.len(), 1);
9870        let f0 = object_literal_field(&members[0]);
9871        assert_eq!(f0.name.name, "hello");
9872    }
9873
9874    #[test]
9875    fn parse_object_literal_string_key_decodes_escape() {
9876        let (ast, diags) = parse_str(r#"let p = { "content\u002dtype": 1 };"#);
9877        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9878        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9879            panic!("expected Let");
9880        };
9881        let members = object_literal(ast.try_expr(value).unwrap());
9882        assert_eq!(members.len(), 1);
9883        let f0 = object_literal_field(&members[0]);
9884        assert_eq!(f0.name.name, "content-type");
9885    }
9886
9887    #[test]
9888    fn parse_object_literal_shorthand() {
9889        let (ast, diags) = parse_str("let p = { x, y: 2 };");
9890        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9891        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9892            panic!("expected Let");
9893        };
9894        let members = object_literal(ast.try_expr(value).unwrap());
9895        assert_eq!(members.len(), 2);
9896        let f0 = object_literal_field(&members[0]);
9897        assert_eq!(f0.name.name, "x");
9898        // `{ x }` desugars to `{ x: x }` — value is an identifier reference.
9899        assert_eq!(
9900            ast.try_expr(f0.value).unwrap().kind,
9901            crate::ExprKind::Identifier(crate::Ident {
9902                name: "x".into(),
9903                span: f0.name.span,
9904            })
9905        );
9906        let f1 = object_literal_field(&members[1]);
9907        assert_eq!(f1.name.name, "y");
9908        assert_eq!(
9909            ast.try_expr(f1.value).unwrap().kind,
9910            crate::ExprKind::Number(2.0)
9911        );
9912    }
9913
9914    #[test]
9915    fn parse_object_literal_string_key_shorthand_rejected() {
9916        let (_ast, diags) = parse_str(r#"let p = { "x" };"#);
9917        assert!(
9918            diags
9919                .iter()
9920                .any(|d| d.message.contains("expected `:` after field name")),
9921            "string-key shorthand should be rejected: {diags:?}"
9922        );
9923    }
9924
9925    #[test]
9926    fn parse_object_literal_trailing_comma() {
9927        let (ast, diags) = parse_str("let p = { x: 1, y: 2, };");
9928        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9929        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9930            panic!("expected Let");
9931        };
9932        assert_eq!(object_literal(ast.try_expr(value).unwrap()).len(), 2);
9933    }
9934
9935    #[test]
9936    fn parse_object_literal_duplicate_key_diagnoses() {
9937        let (_, diags) = parse_str("let p = { x: 1, x: 2 };");
9938        assert_eq!(diags.len(), 1);
9939        assert_eq!(diags[0].message, "duplicate field `x` in object literal");
9940    }
9941
9942    #[test]
9943    fn parse_empty_array_literal() {
9944        let (ast, diags) = parse_str("let xs = [];");
9945        assert!(diags.is_empty());
9946        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9947            panic!("expected Let");
9948        };
9949        assert!(array_literal(ast.try_expr(value).unwrap()).is_empty());
9950    }
9951
9952    #[test]
9953    fn parse_array_literal_simple() {
9954        let (ast, diags) = parse_str("let xs = [1, 2, 3];");
9955        assert!(diags.is_empty());
9956        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9957            panic!("expected Let");
9958        };
9959        let elements = array_literal(ast.try_expr(value).unwrap());
9960        assert_eq!(elements.len(), 3);
9961        assert_eq!(
9962            ast.try_expr(array_literal_value(&elements[0]))
9963                .unwrap()
9964                .kind,
9965            crate::ExprKind::Number(1.0)
9966        );
9967        assert_eq!(
9968            ast.try_expr(array_literal_value(&elements[2]))
9969                .unwrap()
9970                .kind,
9971            crate::ExprKind::Number(3.0)
9972        );
9973    }
9974
9975    #[test]
9976    fn parse_array_literal_trailing_comma() {
9977        let (ast, diags) = parse_str("let xs = [1, 2,];");
9978        assert!(diags.is_empty());
9979        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9980            panic!("expected Let");
9981        };
9982        assert_eq!(array_literal(ast.try_expr(value).unwrap()).len(), 2);
9983    }
9984
9985    #[test]
9986    fn parse_nested_array_in_object() {
9987        let (ast, diags) = parse_str("let p = { items: [1, 2] };");
9988        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
9989        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
9990            panic!("expected Let");
9991        };
9992        let members = object_literal(ast.try_expr(value).unwrap());
9993        let f0 = object_literal_field(&members[0]);
9994        assert_eq!(f0.name.name, "items");
9995        let inner = array_literal(ast.try_expr(f0.value).unwrap());
9996        assert_eq!(inner.len(), 2);
9997    }
9998
9999    #[test]
10000    fn parse_nested_object_in_array() {
10001        let (ast, diags) = parse_str("let xs = [{ x: 1 }, { x: 2 }];");
10002        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10003        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
10004            panic!("expected Let");
10005        };
10006        let elements = array_literal(ast.try_expr(value).unwrap());
10007        assert_eq!(elements.len(), 2);
10008        assert_eq!(
10009            object_literal(ast.try_expr(array_literal_value(&elements[0])).unwrap()).len(),
10010            1
10011        );
10012        assert_eq!(
10013            object_literal(ast.try_expr(array_literal_value(&elements[1])).unwrap()).len(),
10014            1
10015        );
10016    }
10017
10018    #[test]
10019    fn brace_at_statement_start_is_block_not_object() {
10020        let (_, diags) = parse_str("{ x: 1 }");
10021        assert!(
10022            !diags.is_empty(),
10023            "expected a diagnostic for `x:` inside block"
10024        );
10025    }
10026
10027    fn field_access(e: &crate::Expr) -> (crate::ExprId, &str) {
10028        match e.kind {
10029            crate::ExprKind::FieldAccess { receiver, ref name } => (receiver, name.name.as_str()),
10030            _ => panic!("expected FieldAccess, got {:?}", e.kind),
10031        }
10032    }
10033
10034    fn index_access(e: &crate::Expr) -> (crate::ExprId, crate::ExprId) {
10035        match e.kind {
10036            crate::ExprKind::IndexAccess { receiver, index } => (receiver, index),
10037            _ => panic!("expected IndexAccess, got {:?}", e.kind),
10038        }
10039    }
10040
10041    #[test]
10042    fn parse_field_access_simple() {
10043        let (ast, diags) = parse_str("a.b;");
10044        assert!(diags.is_empty());
10045        let outer = expr_of_single_stmt(&ast);
10046        let (receiver, name) = field_access(outer);
10047        assert!(matches!(
10048            ast.try_expr(receiver).unwrap().kind,
10049            crate::ExprKind::Identifier(_)
10050        ));
10051        assert_eq!(name, "b");
10052    }
10053
10054    #[test]
10055    fn parse_field_access_keyword_name() {
10056        let (ast, diags) = parse_str("status.type.default.null;");
10057        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10058        let outer = expr_of_single_stmt(&ast);
10059        let (receiver, name) = field_access(outer);
10060        assert_eq!(name, "null");
10061        let (receiver, name) = field_access(ast.try_expr(receiver).unwrap());
10062        assert_eq!(name, "default");
10063        let (_receiver, name) = field_access(ast.try_expr(receiver).unwrap());
10064        assert_eq!(name, "type");
10065    }
10066
10067    #[test]
10068    fn parse_field_access_chain_left_assoc() {
10069        let (ast, diags) = parse_str("a.b.c;");
10070        assert!(diags.is_empty());
10071        let outer = expr_of_single_stmt(&ast);
10072        let (mid_id, c) = field_access(outer);
10073        assert_eq!(c, "c");
10074        let (a_id, b) = field_access(ast.try_expr(mid_id).unwrap());
10075        assert_eq!(b, "b");
10076        assert!(matches!(
10077            ast.try_expr(a_id).unwrap().kind,
10078            crate::ExprKind::Identifier(_)
10079        ));
10080    }
10081
10082    #[test]
10083    fn parse_index_access_simple() {
10084        let (ast, diags) = parse_str("a[0];");
10085        assert!(diags.is_empty());
10086        let outer = expr_of_single_stmt(&ast);
10087        let (receiver, idx) = index_access(outer);
10088        assert!(matches!(
10089            ast.try_expr(receiver).unwrap().kind,
10090            crate::ExprKind::Identifier(_)
10091        ));
10092        assert_eq!(
10093            ast.try_expr(idx).unwrap().kind,
10094            crate::ExprKind::Number(0.0)
10095        );
10096    }
10097
10098    #[test]
10099    fn parse_index_access_chain() {
10100        let (ast, diags) = parse_str("a[0][1];");
10101        assert!(diags.is_empty());
10102        let outer = expr_of_single_stmt(&ast);
10103        let (mid_id, one) = index_access(outer);
10104        assert_eq!(
10105            ast.try_expr(one).unwrap().kind,
10106            crate::ExprKind::Number(1.0)
10107        );
10108        let (a_id, zero) = index_access(ast.try_expr(mid_id).unwrap());
10109        assert_eq!(
10110            ast.try_expr(zero).unwrap().kind,
10111            crate::ExprKind::Number(0.0)
10112        );
10113        assert!(matches!(
10114            ast.try_expr(a_id).unwrap().kind,
10115            crate::ExprKind::Identifier(_)
10116        ));
10117    }
10118
10119    #[test]
10120    fn parse_mixed_access_and_call_chain() {
10121        let (ast, diags) = parse_str("a.b[c].d();");
10122        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10123        let outer = expr_of_single_stmt(&ast);
10124        let (callee, args) = call(outer);
10125        assert!(args.is_empty());
10126        let (idx_id, d_name) = field_access(ast.try_expr(callee).unwrap());
10127        assert_eq!(d_name, "d");
10128        let (b_id, c_idx) = index_access(ast.try_expr(idx_id).unwrap());
10129        assert!(matches!(
10130            ast.try_expr(c_idx).unwrap().kind,
10131            crate::ExprKind::Identifier(_)
10132        ));
10133        let (a_id, b_name) = field_access(ast.try_expr(b_id).unwrap());
10134        assert_eq!(b_name, "b");
10135        assert!(matches!(
10136            ast.try_expr(a_id).unwrap().kind,
10137            crate::ExprKind::Identifier(_)
10138        ));
10139    }
10140
10141    #[test]
10142    fn parse_call_then_field() {
10143        let (ast, diags) = parse_str("f().x;");
10144        assert!(diags.is_empty());
10145        let outer = expr_of_single_stmt(&ast);
10146        let (call_id, x_name) = field_access(outer);
10147        assert_eq!(x_name, "x");
10148        let (_callee, args) = call(ast.try_expr(call_id).unwrap());
10149        assert!(args.is_empty());
10150    }
10151
10152    #[test]
10153    fn parse_index_with_complex_expression() {
10154        let (ast, diags) = parse_str("a[i + 1];");
10155        assert!(diags.is_empty());
10156        let outer = expr_of_single_stmt(&ast);
10157        let (_, idx) = index_access(outer);
10158        let (op, ..) = binary(ast.try_expr(idx).unwrap());
10159        assert_eq!(op, crate::BinOp::Add);
10160    }
10161
10162    #[test]
10163    fn field_access_missing_name_diagnoses() {
10164        let (_, diags) = parse_str("a.;");
10165        assert!(!diags.is_empty());
10166        assert_eq!(diags[0].message, "expected field name after `.`");
10167    }
10168
10169    #[test]
10170    fn parse_postfix_increment() {
10171        let (ast, diags) = parse_str("x++;");
10172        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10173        let expr = expr_of_single_stmt(&ast);
10174        let crate::ExprKind::PostfixUnary { op, operand } = expr.kind else {
10175            panic!("expected PostfixUnary, got {:?}", expr.kind);
10176        };
10177        assert_eq!(op, crate::PostfixOp::Inc);
10178        assert!(matches!(
10179            ast.try_expr(operand).unwrap().kind,
10180            crate::ExprKind::Identifier(_)
10181        ));
10182        assert_eq!(expr.span, crate::Span::new(F, 0, 3).unwrap());
10183    }
10184
10185    #[test]
10186    fn parse_postfix_decrement() {
10187        let (ast, diags) = parse_str("x--;");
10188        assert!(diags.is_empty());
10189        let expr = expr_of_single_stmt(&ast);
10190        let crate::ExprKind::PostfixUnary { op, .. } = expr.kind else {
10191            panic!("expected PostfixUnary");
10192        };
10193        assert_eq!(op, crate::PostfixOp::Dec);
10194    }
10195
10196    #[test]
10197    fn parse_postfix_non_null_assertion() {
10198        let (ast, diags) = parse_str("x!;");
10199        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10200        let expr = expr_of_single_stmt(&ast);
10201        let crate::ExprKind::PostfixUnary { op, operand } = expr.kind else {
10202            panic!("expected PostfixUnary, got {:?}", expr.kind);
10203        };
10204        assert_eq!(op, crate::PostfixOp::NonNullAssert);
10205        assert!(matches!(
10206            ast.try_expr(operand).unwrap().kind,
10207            crate::ExprKind::Identifier(_)
10208        ));
10209        assert_eq!(expr.span, crate::Span::new(F, 0, 2).unwrap());
10210    }
10211
10212    #[test]
10213    fn parse_non_null_assertion_continues_chain() {
10214        let (ast, diags) = parse_str("x!.y;");
10215        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10216        let expr = expr_of_single_stmt(&ast);
10217        let crate::ExprKind::FieldAccess { receiver, name } = &expr.kind else {
10218            panic!("expected FieldAccess, got {:?}", expr.kind);
10219        };
10220        assert_eq!(name.name, "y");
10221        assert!(matches!(
10222            ast.try_expr(*receiver).unwrap().kind,
10223            crate::ExprKind::PostfixUnary {
10224                op: crate::PostfixOp::NonNullAssert,
10225                ..
10226            }
10227        ));
10228    }
10229
10230    #[test]
10231    fn parse_postfix_on_field_access() {
10232        let (ast, diags) = parse_str("o.f++;");
10233        assert!(diags.is_empty());
10234        let expr = expr_of_single_stmt(&ast);
10235        let crate::ExprKind::PostfixUnary { operand, .. } = expr.kind else {
10236            panic!("expected PostfixUnary");
10237        };
10238        assert!(matches!(
10239            ast.try_expr(operand).unwrap().kind,
10240            crate::ExprKind::FieldAccess { .. }
10241        ));
10242    }
10243
10244    #[test]
10245    fn parse_postfix_on_index_access() {
10246        let (ast, diags) = parse_str("xs[0]++;");
10247        assert!(diags.is_empty());
10248        let expr = expr_of_single_stmt(&ast);
10249        let crate::ExprKind::PostfixUnary { operand, .. } = expr.kind else {
10250            panic!("expected PostfixUnary");
10251        };
10252        assert!(matches!(
10253            ast.try_expr(operand).unwrap().kind,
10254            crate::ExprKind::IndexAccess { .. }
10255        ));
10256    }
10257
10258    #[test]
10259    fn parse_postfix_terminates_chain() {
10260        let (_, diags) = parse_str("x++.y;");
10261        assert!(!diags.is_empty());
10262        assert_eq!(diags[0].message, "expected `;` after expression");
10263    }
10264
10265    #[test]
10266    fn parse_postfix_in_binary_continues() {
10267        let (ast, diags) = parse_str("let y = x++ + 1;");
10268        assert!(diags.is_empty(), "unexpected: {diags:?}");
10269        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
10270            panic!("expected Let");
10271        };
10272        let (op, lhs, _) = binary(ast.try_expr(value).unwrap());
10273        assert_eq!(op, crate::BinOp::Add);
10274        assert!(matches!(
10275            ast.try_expr(lhs).unwrap().kind,
10276            crate::ExprKind::PostfixUnary { .. }
10277        ));
10278    }
10279
10280    #[test]
10281    fn parse_index_assignment() {
10282        let (ast, diags) = parse_str("xs[0] = 1;");
10283        assert!(diags.is_empty(), "unexpected: {diags:?}");
10284        let stmt = single_stmt(&ast);
10285        let crate::StmtKind::AssignIndex {
10286            receiver,
10287            index,
10288            value,
10289        } = stmt.kind
10290        else {
10291            panic!("expected AssignIndex, got {:?}", stmt.kind);
10292        };
10293        assert!(matches!(
10294            ast.try_expr(receiver).unwrap().kind,
10295            crate::ExprKind::Identifier(_)
10296        ));
10297        assert_eq!(
10298            ast.try_expr(index).unwrap().kind,
10299            crate::ExprKind::Number(0.0)
10300        );
10301        assert_eq!(
10302            ast.try_expr(value).unwrap().kind,
10303            crate::ExprKind::Number(1.0)
10304        );
10305    }
10306
10307    #[test]
10308    fn parse_for_update_postfix() {
10309        let (ast, diags) = parse_str("function main(): void { for (let i = 0; i < 3; i++) {} }");
10310        assert!(diags.is_empty(), "unexpected: {diags:?}");
10311        assert_eq!(ast.top_level.len(), 1);
10312    }
10313
10314    #[test]
10315    fn parse_for_update_index_assignment() {
10316        let (ast, diags) =
10317            parse_str("function main(): void { for (let i = 0; i < 3; xs[i] = i) {} }");
10318        assert!(diags.is_empty(), "unexpected: {diags:?}");
10319        assert_eq!(ast.top_level.len(), 1);
10320    }
10321
10322    #[test]
10323    fn index_access_missing_close_bracket_diagnoses() {
10324        let (_, diags) = parse_str("a[1;");
10325        assert!(!diags.is_empty());
10326        assert_eq!(diags[0].message, "expected `]`");
10327    }
10328
10329    #[test]
10330    fn field_access_inside_object_literal_value_does_not_steal_dot() {
10331        let (ast, diags) = parse_str("let x = { a: 1 }.foo;");
10332        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10333        let crate::StmtKind::Let { value, .. } = single_stmt(&ast).kind else {
10334            panic!("expected Let");
10335        };
10336        let (recv_id, name) = field_access(ast.try_expr(value).unwrap());
10337        assert_eq!(name, "foo");
10338        assert_eq!(object_literal(ast.try_expr(recv_id).unwrap()).len(), 1);
10339    }
10340
10341    fn type_of_let(stmt: &crate::Stmt) -> &crate::TypeAnnotation {
10342        match stmt.kind {
10343            crate::StmtKind::Let { ref ty, .. } => ty.as_ref().expect("expected type annotation"),
10344            _ => panic!("expected Let"),
10345        }
10346    }
10347
10348    #[test]
10349    fn parse_array_type_annotation() {
10350        let (ast, diags) = parse_str("let xs: number[] = null;");
10351        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10352        let ty = type_of_let(single_stmt(&ast));
10353        match ty.kind {
10354            crate::TypeAnnotationKind::Array(ref inner) => {
10355                assert!(matches!(inner.kind, crate::TypeAnnotationKind::Name { .. }));
10356            }
10357            _ => panic!("expected Array, got {:?}", ty.kind),
10358        }
10359    }
10360
10361    #[test]
10362    fn parse_nested_array_type_annotation() {
10363        let (ast, diags) = parse_str("let xs: number[][] = null;");
10364        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10365        let ty = type_of_let(single_stmt(&ast));
10366        let crate::TypeAnnotationKind::Array(ref inner) = ty.kind else {
10367            panic!("expected outer Array");
10368        };
10369        let crate::TypeAnnotationKind::Array(ref inner2) = inner.kind else {
10370            panic!("expected inner Array");
10371        };
10372        assert!(matches!(
10373            inner2.kind,
10374            crate::TypeAnnotationKind::Name { .. }
10375        ));
10376    }
10377
10378    #[test]
10379    fn parse_object_type_annotation_simple() {
10380        let (ast, diags) = parse_str("let p: { x: number; y: number } = null;");
10381        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10382        let ty = type_of_let(single_stmt(&ast));
10383        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10384            panic!("expected Object, got {:?}", ty.kind);
10385        };
10386        assert_eq!(fields.len(), 2);
10387        assert_eq!(fields[0].name.name, "x");
10388        assert_eq!(fields[1].name.name, "y");
10389    }
10390
10391    #[test]
10392    fn readonly_is_rejected_on_methods_accessors_and_method_signatures() {
10393        let source = "class K {\n\
10394             readonly m(): number { return 1; }\n\
10395             public readonly n<T>(): number { return 1; }\n\
10396             readonly get g(): number { return 1; }\n\
10397             readonly p: number = 1;\n\
10398             }\n\
10399             type T = { readonly r(a: number): boolean; readonly f: () => void };\n\
10400             interface I { readonly s(): void; readonly q: number; }\n";
10401        let (_, diags) = parse_str(source);
10402        let lines: Vec<usize> = diags
10403            .iter()
10404            .map(|d| {
10405                assert_eq!(
10406                    d.message,
10407                    "`readonly` can only modify a property or index signature"
10408                );
10409                source[..d.span.start as usize].matches('\n').count() + 1
10410            })
10411            .collect();
10412        assert_eq!(lines, vec![2, 3, 4, 7, 8]);
10413    }
10414
10415    #[test]
10416    fn parse_object_type_method_members() {
10417        // `m(): T` is the same member as `m: () => T`, so it parses to a function-typed
10418        // field — including through the `?` modifier.
10419        let (ast, diags) =
10420            parse_str("let p: { m(): number; opt?(): string; r(a: number): boolean } = null;");
10421        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10422        let ty = type_of_let(single_stmt(&ast));
10423        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10424            panic!("expected Object, got {:?}", ty.kind);
10425        };
10426        assert_eq!(fields.len(), 3);
10427        for field in fields {
10428            assert!(
10429                matches!(field.ty.kind, crate::TypeAnnotationKind::Function { .. }),
10430                "member `{}` should be function-typed, got {:?}",
10431                field.name.name,
10432                field.ty.kind,
10433            );
10434        }
10435        assert!(fields[1].optional, "`opt?()` is an optional member");
10436        let crate::TypeAnnotationKind::Function { ref params, .. } = fields[2].ty.kind else {
10437            unreachable!("checked above");
10438        };
10439        assert_eq!(params.len(), 1);
10440        assert_eq!(params[0].name.name, "a");
10441    }
10442
10443    #[test]
10444    fn parse_object_type_annotation_with_commas() {
10445        let (ast, diags) = parse_str("let p: { x: number, y: number } = null;");
10446        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10447        let ty = type_of_let(single_stmt(&ast));
10448        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10449            panic!("expected Object");
10450        };
10451        assert_eq!(fields.len(), 2);
10452    }
10453
10454    #[test]
10455    fn parse_object_type_annotation_empty() {
10456        let (ast, diags) = parse_str("let p: {} = null;");
10457        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10458        let ty = type_of_let(single_stmt(&ast));
10459        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10460            panic!("expected Object");
10461        };
10462        assert!(fields.is_empty());
10463    }
10464
10465    #[test]
10466    fn parse_object_type_with_optional_field() {
10467        let (ast, diags) = parse_str("let p: { id: number; nick?: string } = null;");
10468        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10469        let ty = type_of_let(single_stmt(&ast));
10470        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10471            panic!("expected Object");
10472        };
10473        assert_eq!(fields.len(), 2);
10474        assert_eq!(fields[0].name.name, "id");
10475        assert!(!fields[0].optional);
10476        assert_eq!(fields[1].name.name, "nick");
10477        assert!(fields[1].optional);
10478    }
10479
10480    #[test]
10481    fn parse_object_type_with_readonly_fields() {
10482        let (ast, diags) = parse_str(
10483            "let issue: { readonly id: string; readonly title?: string; body: string } = null;",
10484        );
10485        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10486        let ty = type_of_let(single_stmt(&ast));
10487        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10488            panic!("expected Object");
10489        };
10490        let fields: Vec<(&str, bool, bool)> = fields
10491            .iter()
10492            .map(|field| (field.name.name.as_str(), field.optional, field.readonly))
10493            .collect();
10494        assert_eq!(
10495            fields,
10496            vec![
10497                ("id", false, true),
10498                ("title", true, true),
10499                ("body", false, false),
10500            ]
10501        );
10502    }
10503
10504    #[test]
10505    fn parse_object_type_mixed_optional_and_required() {
10506        let (ast, diags) = parse_str("let p: { a?: number; b: string; c?: boolean } = null;");
10507        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10508        let ty = type_of_let(single_stmt(&ast));
10509        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10510            panic!("expected Object");
10511        };
10512        let opt: Vec<(&str, bool)> = fields
10513            .iter()
10514            .map(|f| (f.name.name.as_str(), f.optional))
10515            .collect();
10516        assert_eq!(opt, vec![("a", true), ("b", false), ("c", true)]);
10517    }
10518
10519    #[test]
10520    fn parse_object_type_keyword_fields() {
10521        let (ast, diags) =
10522            parse_str("let p: { type: string; default?: number; null: boolean } = null;");
10523        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10524        let ty = type_of_let(single_stmt(&ast));
10525        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10526            panic!("expected Object");
10527        };
10528        let opt: Vec<(&str, bool)> = fields
10529            .iter()
10530            .map(|f| (f.name.name.as_str(), f.optional))
10531            .collect();
10532        assert_eq!(
10533            opt,
10534            vec![("type", false), ("default", true), ("null", false)]
10535        );
10536    }
10537
10538    #[test]
10539    fn parse_object_type_quoted_fields() {
10540        let (ast, diags) =
10541            parse_str(r#"let p: { "content-type": string; "x\u002drequest-id"?: string } = null;"#);
10542        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10543        let ty = type_of_let(single_stmt(&ast));
10544        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10545            panic!("expected Object");
10546        };
10547        let opt: Vec<(&str, bool)> = fields
10548            .iter()
10549            .map(|f| (f.name.name.as_str(), f.optional))
10550            .collect();
10551        assert_eq!(opt, vec![("content-type", false), ("x-request-id", true)]);
10552    }
10553
10554    #[test]
10555    fn parse_interface_with_optional_property() {
10556        let (ast, diags) = parse_str("interface User { id: number; deletedAt?: string; }");
10557        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10558        let stmt = single_stmt(&ast);
10559        let crate::StmtKind::InterfaceDecl { ref members, .. } = stmt.kind else {
10560            panic!("expected interface decl");
10561        };
10562        let opt: Vec<(&str, bool)> = members
10563            .iter()
10564            .map(|m| match m {
10565                crate::InterfaceMember::Property { name, optional, .. } => {
10566                    (name.name.as_str(), *optional)
10567                }
10568                _ => panic!("expected property"),
10569            })
10570            .collect();
10571        assert_eq!(opt, vec![("id", false), ("deletedAt", true)]);
10572    }
10573
10574    #[test]
10575    fn parse_interface_with_readonly_properties() {
10576        let (ast, diags) = parse_str(
10577            "interface Issue { readonly id: string; readonly title?: string; body: string; }",
10578        );
10579        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10580        let stmt = single_stmt(&ast);
10581        let crate::StmtKind::InterfaceDecl { ref members, .. } = stmt.kind else {
10582            panic!("expected interface decl");
10583        };
10584        let opt: Vec<(&str, bool, bool)> = members
10585            .iter()
10586            .map(|member| match member {
10587                crate::InterfaceMember::Property {
10588                    name,
10589                    optional,
10590                    readonly,
10591                    ..
10592                } => (name.name.as_str(), *optional, *readonly),
10593                _ => panic!("expected property"),
10594            })
10595            .collect();
10596        assert_eq!(
10597            opt,
10598            vec![
10599                ("id", false, true),
10600                ("title", true, true),
10601                ("body", false, false),
10602            ]
10603        );
10604    }
10605
10606    #[test]
10607    fn parse_readonly_property_name_without_modifier() {
10608        let (ast, diags) = parse_str(
10609            "interface Meta { readonly: boolean; }\nlet meta: { readonly: boolean } = null;",
10610        );
10611        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10612        let crate::StmtKind::InterfaceDecl { ref members, .. } =
10613            ast.try_stmt(ast.top_level[0]).unwrap().kind
10614        else {
10615            panic!("expected interface decl");
10616        };
10617        let crate::InterfaceMember::Property {
10618            ref name, readonly, ..
10619        } = members[0]
10620        else {
10621            panic!("expected property");
10622        };
10623        assert_eq!(name.name, "readonly");
10624        assert!(
10625            !readonly,
10626            "`readonly` here is the property name, not a modifier"
10627        );
10628
10629        let ty = type_of_let(ast.try_stmt(ast.top_level[1]).unwrap());
10630        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10631            panic!("expected Object");
10632        };
10633        assert_eq!(fields[0].name.name, "readonly");
10634        assert!(!fields[0].readonly);
10635    }
10636
10637    #[test]
10638    fn parse_interface_keyword_members() {
10639        let (ast, diags) =
10640            parse_str("interface IssueStatus { type: string; default(): string; null: boolean; }");
10641        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10642        let stmt = single_stmt(&ast);
10643        let crate::StmtKind::InterfaceDecl { ref members, .. } = stmt.kind else {
10644            panic!("expected interface decl");
10645        };
10646        let names: Vec<&str> = members
10647            .iter()
10648            .map(|m| match m {
10649                crate::InterfaceMember::Method { name, .. }
10650                | crate::InterfaceMember::Property { name, .. } => name.name.as_str(),
10651                crate::InterfaceMember::IndexSignature(_) => panic!("unexpected index signature"),
10652            })
10653            .collect();
10654        assert_eq!(names, vec!["type", "default", "null"]);
10655    }
10656
10657    #[test]
10658    fn parse_interface_quoted_property_members() {
10659        let (ast, diags) = parse_str(
10660            r#"interface Headers { "content-type": string; "x\u002drequest-id"?: string; }"#,
10661        );
10662        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10663        let stmt = single_stmt(&ast);
10664        let crate::StmtKind::InterfaceDecl { ref members, .. } = stmt.kind else {
10665            panic!("expected interface decl");
10666        };
10667        let names: Vec<(&str, bool)> = members
10668            .iter()
10669            .map(|m| match m {
10670                crate::InterfaceMember::Property { name, optional, .. } => {
10671                    (name.name.as_str(), *optional)
10672                }
10673                _ => panic!("expected property"),
10674            })
10675            .collect();
10676        assert_eq!(names, vec![("content-type", false), ("x-request-id", true)]);
10677    }
10678
10679    #[test]
10680    fn parse_param_optional_marker() {
10681        let (ast, diags) = parse_str("function f(x?: number): void {}");
10682        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10683        let crate::StmtKind::Function { ref params, .. } = single_stmt(&ast).kind else {
10684            panic!("expected function")
10685        };
10686        assert!(params[0].optional);
10687        assert!(params[0].default.is_none());
10688    }
10689
10690    #[test]
10691    fn parse_interface_optional_method() {
10692        let (ast, diags) = parse_str("interface F { foo?(x?: number): void; }");
10693        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10694        let crate::StmtKind::InterfaceDecl { ref members, .. } = single_stmt(&ast).kind else {
10695            panic!("expected interface")
10696        };
10697        let crate::InterfaceMember::Property { optional, ty, .. } = &members[0] else {
10698            panic!("expected callable property")
10699        };
10700        assert!(*optional);
10701        let crate::TypeAnnotationKind::Function { params, .. } = &ty.kind else {
10702            panic!("expected function type")
10703        };
10704        assert!(params[0].optional);
10705    }
10706
10707    #[test]
10708    fn parse_interface_with_call_signature() {
10709        let (ast, diags) = parse_str("interface F { (s: string): number; }");
10710        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10711        let stmt = single_stmt(&ast);
10712        let crate::StmtKind::InterfaceDecl { ref members, .. } = stmt.kind else {
10713            panic!("expected interface decl");
10714        };
10715        assert_eq!(members.len(), 1);
10716        match &members[0] {
10717            crate::InterfaceMember::Method { name, params, .. } => {
10718                assert_eq!(name.name, "@call");
10719                assert_eq!(params.len(), 1);
10720                assert_eq!(params[0].name.name, "s");
10721            }
10722            other => panic!("expected method (call signature), got {other:?}"),
10723        }
10724    }
10725
10726    #[test]
10727    fn parse_interface_mixed_call_signature_and_method() {
10728        let (ast, diags) = parse_str("interface F { greet(): string; (x: number): number; }");
10729        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10730        let stmt = single_stmt(&ast);
10731        let crate::StmtKind::InterfaceDecl { ref members, .. } = stmt.kind else {
10732            panic!("expected interface decl");
10733        };
10734        let names: Vec<&str> = members
10735            .iter()
10736            .map(|m| match m {
10737                crate::InterfaceMember::Method { name, .. } => name.name.as_str(),
10738                _ => "<property>",
10739            })
10740            .collect();
10741        assert_eq!(names, vec!["greet", "@call"]);
10742    }
10743
10744    #[test]
10745    fn parse_interface_rejects_generic_call_signature() {
10746        let (_ast, diags) = parse_str("interface F { <T>(x: T): T; }");
10747        assert!(
10748            diags
10749                .iter()
10750                .any(|d| d.message.contains("generic call signatures")),
10751            "expected generic-call-signature diagnostic, got {diags:?}"
10752        );
10753    }
10754
10755    #[test]
10756    fn parse_object_type_with_array_field() {
10757        let (ast, diags) = parse_str("let p: { xs: number[] } = null;");
10758        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10759        let ty = type_of_let(single_stmt(&ast));
10760        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
10761            panic!("expected Object");
10762        };
10763        assert_eq!(fields[0].name.name, "xs");
10764        assert!(matches!(
10765            fields[0].ty.kind,
10766            crate::TypeAnnotationKind::Array(_)
10767        ));
10768    }
10769
10770    #[test]
10771    fn parse_array_of_objects() {
10772        let (ast, diags) = parse_str("let xs: { x: number }[] = null;");
10773        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10774        let ty = type_of_let(single_stmt(&ast));
10775        let crate::TypeAnnotationKind::Array(ref inner) = ty.kind else {
10776            panic!("expected Array");
10777        };
10778        assert!(matches!(
10779            inner.kind,
10780            crate::TypeAnnotationKind::Object { .. }
10781        ));
10782    }
10783
10784    #[test]
10785    fn parse_tuple_type_annotation() {
10786        let (ast, diags) = parse_str("let p: [string, number] = null;");
10787        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10788        let ty = type_of_let(single_stmt(&ast));
10789        let crate::TypeAnnotationKind::Tuple(ref elems) = ty.kind else {
10790            panic!("expected Tuple, got {:?}", ty.kind);
10791        };
10792        assert_eq!(elems.len(), 2);
10793        assert!(matches!(
10794            elems[0].kind,
10795            crate::TypeAnnotationKind::Name { .. }
10796        ));
10797        assert!(matches!(
10798            elems[1].kind,
10799            crate::TypeAnnotationKind::Name { .. }
10800        ));
10801    }
10802
10803    #[test]
10804    fn parse_singleton_tuple_type() {
10805        let (ast, diags) = parse_str("let p: [number] = null;");
10806        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10807        let ty = type_of_let(single_stmt(&ast));
10808        let crate::TypeAnnotationKind::Tuple(ref elems) = ty.kind else {
10809            panic!("expected Tuple");
10810        };
10811        assert_eq!(elems.len(), 1);
10812    }
10813
10814    #[test]
10815    fn parse_nested_tuple_type() {
10816        let (ast, diags) = parse_str("let p: [[number, string], boolean] = null;");
10817        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10818        let ty = type_of_let(single_stmt(&ast));
10819        let crate::TypeAnnotationKind::Tuple(ref outer) = ty.kind else {
10820            panic!("expected outer Tuple");
10821        };
10822        assert_eq!(outer.len(), 2);
10823        let crate::TypeAnnotationKind::Tuple(ref inner) = outer[0].kind else {
10824            panic!("expected inner Tuple");
10825        };
10826        assert_eq!(inner.len(), 2);
10827    }
10828
10829    #[test]
10830    fn parse_array_of_tuple_type() {
10831        let (ast, diags) = parse_str("let xs: [string, number][] = null;");
10832        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10833        let ty = type_of_let(single_stmt(&ast));
10834        let crate::TypeAnnotationKind::Array(ref inner) = ty.kind else {
10835            panic!("expected outer Array, got {:?}", ty.kind);
10836        };
10837        assert!(matches!(inner.kind, crate::TypeAnnotationKind::Tuple(_)));
10838    }
10839
10840    #[test]
10841    fn parse_trailing_comma_tuple_type() {
10842        let (ast, diags) = parse_str("let p: [number, string,] = null;");
10843        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10844        let ty = type_of_let(single_stmt(&ast));
10845        let crate::TypeAnnotationKind::Tuple(ref elems) = ty.kind else {
10846            panic!("expected Tuple");
10847        };
10848        assert_eq!(elems.len(), 2);
10849    }
10850
10851    #[test]
10852    fn parse_empty_tuple_type_rejected() {
10853        let (_ast, diags) = parse_str("let p: [] = null;");
10854        assert!(
10855            diags.iter().any(|d| d
10856                .message
10857                .contains("tuple types must have at least one element")),
10858            "expected empty-tuple diagnostic, got: {diags:?}"
10859        );
10860    }
10861
10862    #[test]
10863    fn parse_labeled_tuple_drops_labels() {
10864        let (ast, diags) = parse_str("let p: [first: number, string] = null;");
10865        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10866        let ty = type_of_let(single_stmt(&ast));
10867        let crate::TypeAnnotationKind::Tuple(ref elems) = ty.kind else {
10868            panic!("expected Tuple, got {:?}", ty.kind);
10869        };
10870        assert_eq!(elems.len(), 2);
10871        assert!(
10872            elems
10873                .iter()
10874                .all(|e| matches!(e.kind, crate::TypeAnnotationKind::Name { .. }))
10875        );
10876    }
10877
10878    #[test]
10879    fn parse_labeled_optional_tuple_element() {
10880        let (ast, diags) = parse_str("let p: [first: number, second?: string] = null;");
10881        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10882        let ty = type_of_let(single_stmt(&ast));
10883        let crate::TypeAnnotationKind::Tuple(elements) = &ty.kind else {
10884            panic!("expected tuple")
10885        };
10886        assert!(matches!(
10887            elements[1].kind,
10888            crate::TypeAnnotationKind::Optional(_)
10889        ));
10890    }
10891
10892    #[test]
10893    fn parse_readonly_binds_to_the_postfix_type() {
10894        let (ast, diags) = parse_str("let p: readonly number[][] | null = null;");
10895        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10896        let ty = type_of_let(single_stmt(&ast));
10897        let crate::TypeAnnotationKind::Union(ref members) = ty.kind else {
10898            panic!("expected Union, got {:?}", ty.kind);
10899        };
10900        let crate::TypeAnnotationKind::Readonly(ref operand) = members[0].kind else {
10901            panic!("expected Readonly, got {:?}", members[0].kind);
10902        };
10903        let crate::TypeAnnotationKind::Array(ref inner) = operand.kind else {
10904            panic!("expected Array, got {:?}", operand.kind);
10905        };
10906        assert!(matches!(inner.kind, crate::TypeAnnotationKind::Array(_)));
10907    }
10908
10909    #[test]
10910    fn parse_readonly_as_a_type_name_is_not_the_operator() {
10911        let (ast, diags) = parse_str("let p: readonly = null;");
10912        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10913        let ty = type_of_let(single_stmt(&ast));
10914        assert!(matches!(ty.kind, crate::TypeAnnotationKind::Name { .. }));
10915    }
10916
10917    #[test]
10918    fn parse_generic_one_arg() {
10919        let (ast, diags) = parse_str("let x: Foo<number> = null;");
10920        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10921        let ty = type_of_let(single_stmt(&ast));
10922        let crate::TypeAnnotationKind::Name { ref name, ref args } = ty.kind else {
10923            panic!("expected Name, got {:?}", ty.kind);
10924        };
10925        assert_eq!(name.span, crate::Span::new(F, 7, 10).unwrap());
10926        assert_eq!(ty.span, crate::Span::new(F, 7, 18).unwrap());
10927        assert_eq!(args.len(), 1);
10928        assert!(matches!(
10929            args[0].kind,
10930            crate::TypeAnnotationKind::Name { .. }
10931        ));
10932    }
10933
10934    #[test]
10935    fn parse_generic_two_args() {
10936        let (ast, diags) = parse_str("let m: Map<string, number> = null;");
10937        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10938        let ty = type_of_let(single_stmt(&ast));
10939        let crate::TypeAnnotationKind::Name { ref args, .. } = ty.kind else {
10940            panic!("expected Name");
10941        };
10942        assert_eq!(args.len(), 2);
10943    }
10944
10945    #[test]
10946    fn parse_generic_nested() {
10947        let (ast, diags) = parse_str("let b: Box<Box<T>> = null;");
10948        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10949        let ty = type_of_let(single_stmt(&ast));
10950        let crate::TypeAnnotationKind::Name { ref args, .. } = ty.kind else {
10951            panic!("expected outer Name");
10952        };
10953        assert_eq!(args.len(), 1);
10954        let crate::TypeAnnotationKind::Name {
10955            args: ref inner, ..
10956        } = args[0].kind
10957        else {
10958            panic!("expected inner Name");
10959        };
10960        assert_eq!(inner.len(), 1);
10961    }
10962
10963    #[test]
10964    fn parse_generic_array_postfix() {
10965        let (ast, diags) = parse_str("let xs: Foo<number>[] = null;");
10966        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10967        let ty = type_of_let(single_stmt(&ast));
10968        let crate::TypeAnnotationKind::Array(ref inner) = ty.kind else {
10969            panic!("expected Array");
10970        };
10971        assert!(
10972            matches!(inner.kind, crate::TypeAnnotationKind::Name { ref args, .. } if args.len() == 1)
10973        );
10974    }
10975
10976    #[test]
10977    fn parse_generic_empty_diagnoses() {
10978        let (_ast, diags) = parse_str("let x: Foo<> = null;");
10979        assert!(
10980            diags
10981                .iter()
10982                .any(|d| d.message.contains("empty generic argument list")),
10983            "expected empty-generic-args diagnostic, got: {diags:?}"
10984        );
10985    }
10986
10987    #[test]
10988    fn parse_generic_trailing_comma_ok() {
10989        let (_ast, diags) = parse_str("let x: Foo<number,> = null;");
10990        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
10991    }
10992
10993    #[test]
10994    fn parse_generic_missing_close_diagnoses() {
10995        let (_ast, diags) = parse_str("let x: Foo<number = null;");
10996        assert!(
10997            diags
10998                .iter()
10999                .any(|d| d.message.contains("expected `,` or `>`")),
11000            "expected unterminated-generic diagnostic, got: {diags:?}"
11001        );
11002    }
11003
11004    #[test]
11005    fn parse_qualified_two_segments() {
11006        let (ast, diags) = parse_str("let x: Foo.Bar = null;");
11007        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11008        let ty = type_of_let(single_stmt(&ast));
11009        let crate::TypeAnnotationKind::Qualified { ref path, ref args } = ty.kind else {
11010            panic!("expected Qualified, got {:?}", ty.kind);
11011        };
11012        assert_eq!(path.len(), 2);
11013        // `Foo` at 7..10, `Bar` at 11..14.
11014        assert_eq!(path[0].span, crate::Span::new(F, 7, 10).unwrap());
11015        assert_eq!(path[1].span, crate::Span::new(F, 11, 14).unwrap());
11016        assert!(args.is_empty());
11017        assert_eq!(ty.span, crate::Span::new(F, 7, 14).unwrap());
11018    }
11019
11020    #[test]
11021    fn parse_qualified_three_segments() {
11022        let (ast, diags) = parse_str("let x: Foo.Bar.Baz = null;");
11023        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11024        let ty = type_of_let(single_stmt(&ast));
11025        let crate::TypeAnnotationKind::Qualified { ref path, .. } = ty.kind else {
11026            panic!("expected Qualified, got {:?}", ty.kind);
11027        };
11028        assert_eq!(path.len(), 3);
11029    }
11030
11031    #[test]
11032    fn parse_qualified_with_generic_args() {
11033        let (ast, diags) = parse_str("let x: Foo.Box<number> = null;");
11034        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11035        let ty = type_of_let(single_stmt(&ast));
11036        let crate::TypeAnnotationKind::Qualified { ref path, ref args } = ty.kind else {
11037            panic!("expected Qualified, got {:?}", ty.kind);
11038        };
11039        assert_eq!(path.len(), 2);
11040        assert_eq!(args.len(), 1);
11041        assert!(matches!(
11042            args[0].kind,
11043            crate::TypeAnnotationKind::Name { .. }
11044        ));
11045    }
11046
11047    #[test]
11048    fn parse_qualified_with_array_postfix() {
11049        let (ast, diags) = parse_str("let xs: Foo.Bar[] = null;");
11050        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11051        let ty = type_of_let(single_stmt(&ast));
11052        let crate::TypeAnnotationKind::Array(ref inner) = ty.kind else {
11053            panic!("expected Array, got {:?}", ty.kind);
11054        };
11055        assert!(matches!(
11056            inner.kind,
11057            crate::TypeAnnotationKind::Qualified { .. }
11058        ));
11059    }
11060
11061    #[test]
11062    fn parse_qualified_trailing_dot_diagnoses() {
11063        let (_ast, diags) = parse_str("let x: Foo. = null;");
11064        assert!(
11065            diags.iter().any(|d| d
11066                .message
11067                .contains("expected identifier after `.` in type name")),
11068            "expected dotted-name diagnostic, got: {diags:?}"
11069        );
11070    }
11071
11072    #[test]
11073    fn parse_qualified_void_root_rejected_dot() {
11074        let (ast, diags) = parse_str("function f(): void.Bar { }");
11075        assert!(!diags.is_empty(), "expected a diagnostic for `void.Bar`");
11076        let _ = ast;
11077    }
11078
11079    fn union_members(ty: &crate::TypeAnnotation) -> &[crate::TypeAnnotation] {
11080        match &ty.kind {
11081            crate::TypeAnnotationKind::Union(members) => members,
11082            other => panic!("expected Union, got {other:?}"),
11083        }
11084    }
11085
11086    #[test]
11087    fn parse_union_two_members() {
11088        let (ast, diags) = parse_str("let x: number | string = null;");
11089        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11090        let ty = type_of_let(single_stmt(&ast));
11091        let members = union_members(ty);
11092        assert_eq!(members.len(), 2);
11093        assert!(matches!(
11094            members[0].kind,
11095            crate::TypeAnnotationKind::Name { .. }
11096        ));
11097        assert_eq!(members[0].span, crate::Span::new(F, 7, 13).unwrap()); // `number`
11098        assert!(matches!(
11099            members[1].kind,
11100            crate::TypeAnnotationKind::Name { .. }
11101        ));
11102        assert_eq!(members[1].span, crate::Span::new(F, 16, 22).unwrap()); // `string`
11103        assert_eq!(ty.span, crate::Span::new(F, 7, 22).unwrap());
11104    }
11105
11106    #[test]
11107    fn parse_union_three_members() {
11108        let (ast, diags) = parse_str("let x: A | B | C = null;");
11109        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11110        let ty = type_of_let(single_stmt(&ast));
11111        let members = union_members(ty);
11112        assert_eq!(members.len(), 3);
11113        for m in members {
11114            assert!(matches!(m.kind, crate::TypeAnnotationKind::Name { .. }));
11115        }
11116    }
11117
11118    #[test]
11119    fn parse_union_lower_than_array() {
11120        let (ast, diags) = parse_str("let x: number[] | string = null;");
11121        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11122        let ty = type_of_let(single_stmt(&ast));
11123        let members = union_members(ty);
11124        assert_eq!(members.len(), 2);
11125        assert!(
11126            matches!(members[0].kind, crate::TypeAnnotationKind::Array(_)),
11127            "expected first member to be Array, got {:?}",
11128            members[0].kind
11129        );
11130        assert!(matches!(
11131            members[1].kind,
11132            crate::TypeAnnotationKind::Name { .. }
11133        ));
11134    }
11135
11136    #[test]
11137    fn parse_union_with_null_member() {
11138        let (ast, diags) = parse_str("let x: number | null = null;");
11139        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11140        let ty = type_of_let(single_stmt(&ast));
11141        let members = union_members(ty);
11142        assert_eq!(members.len(), 2);
11143        assert_eq!(members[0].span, crate::Span::new(F, 7, 13).unwrap()); // `number`
11144        assert!(matches!(
11145            members[1].kind,
11146            crate::TypeAnnotationKind::Name { .. }
11147        ));
11148        assert_eq!(members[1].span, crate::Span::new(F, 16, 20).unwrap()); // `null`
11149    }
11150
11151    #[test]
11152    fn parse_union_in_function_return_type() {
11153        let (ast, diags) = parse_str("function f(): number | string { }");
11154        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11155        let stmt = single_stmt(&ast);
11156        match stmt.kind {
11157            crate::StmtKind::Function {
11158                ref return_type, ..
11159            } => {
11160                let return_type = return_type.as_ref().expect("plain return type");
11161                let members = union_members(return_type);
11162                assert_eq!(members.len(), 2);
11163            }
11164            _ => panic!("expected Function"),
11165        }
11166    }
11167
11168    #[test]
11169    fn parse_union_in_function_param_type() {
11170        let (ast, diags) = parse_str("function f(x: number | string): void { }");
11171        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11172        let stmt = single_stmt(&ast);
11173        match stmt.kind {
11174            crate::StmtKind::Function { ref params, .. } => {
11175                let p_ty = params[0].ty.as_ref().expect("param type");
11176                let members = union_members(p_ty);
11177                assert_eq!(members.len(), 2);
11178            }
11179            _ => panic!("expected Function"),
11180        }
11181    }
11182
11183    #[test]
11184    fn parse_union_in_object_field_type() {
11185        let (ast, diags) = parse_str("let p: { x: number | string } = null;");
11186        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11187        let ty = type_of_let(single_stmt(&ast));
11188        let crate::TypeAnnotationKind::Object { ref fields, .. } = ty.kind else {
11189            panic!("expected Object, got {:?}", ty.kind);
11190        };
11191        assert_eq!(fields.len(), 1);
11192        let members = union_members(&fields[0].ty);
11193        assert_eq!(members.len(), 2);
11194    }
11195
11196    #[test]
11197    fn parse_union_leading_pipe_is_forgiven() {
11198        let (ast, diags) = parse_str("let x: | number | string = null;");
11199        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11200        let ty = type_of_let(single_stmt(&ast));
11201        let members = union_members(ty);
11202        assert_eq!(members.len(), 2);
11203    }
11204
11205    #[test]
11206    fn parse_union_leading_pipe_single_member_unwraps() {
11207        let (ast, diags) = parse_str("let x: | number = null;");
11208        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11209        let ty = type_of_let(single_stmt(&ast));
11210        assert!(matches!(ty.kind, crate::TypeAnnotationKind::Name { .. }));
11211    }
11212
11213    #[test]
11214    fn parse_union_trailing_pipe_diagnoses() {
11215        let (_, diags) = parse_str("let x: number | = null;");
11216        assert!(!diags.is_empty());
11217        assert_eq!(diags[0].message, "expected type");
11218    }
11219
11220    #[test]
11221    fn snapshot_union_type_mix() {
11222        let (ast, diags) = parse_str("let xs: number[] | string | null = null;");
11223        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11224        insta::assert_debug_snapshot!(ast);
11225    }
11226
11227    #[test]
11228    fn parse_object_type_duplicate_field_diagnoses() {
11229        let (_, diags) = parse_str("let p: { readonly x: number; x: string } = null;");
11230        assert!(!diags.is_empty());
11231        assert!(
11232            diags
11233                .iter()
11234                .any(|d| d.message == "duplicate field `x` in object type"),
11235            "expected duplicate-field diagnostic, got: {diags:?}"
11236        );
11237    }
11238
11239    #[test]
11240    fn snapshot_object_and_array_literals() {
11241        let (ast, diags) = parse_str(r#"let mix = [{ a: 1, b: "hi" }, {}];"#);
11242        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11243        insta::assert_debug_snapshot!(ast);
11244    }
11245
11246    #[test]
11247    fn snapshot_function_with_while() {
11248        let source = "function loop(n: number): void {\n  while (n > 0) {\n    log(n);\n  }\n}";
11249        let (ast, diags) = parse_str(source);
11250        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11251        insta::assert_debug_snapshot!(ast);
11252    }
11253
11254    fn parse_arrow_const(
11255        source: &str,
11256    ) -> (
11257        Ast,
11258        Vec<crate::ParamDecl>,
11259        Option<crate::TypeAnnotation>,
11260        crate::ArrowBody,
11261    ) {
11262        let (ast, diags) = parse_str(source);
11263        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11264        assert_eq!(ast.top_level.len(), 1);
11265        let stmt = ast.try_stmt(ast.top_level[0]).unwrap();
11266        let value = match &stmt.kind {
11267            StmtKind::Const { value, .. } => *value,
11268            _ => panic!("expected const declaration"),
11269        };
11270        let value = match ast.try_expr(value).unwrap().kind {
11271            ExprKind::FunctionExpression { function, .. } => function,
11272            _ => value,
11273        };
11274        let expr = ast.try_expr(value).unwrap().clone();
11275        let (params, return_type, body) = match expr.kind {
11276            ExprKind::Arrow {
11277                params,
11278                return_type,
11279                body,
11280                ..
11281            } => (params, return_type, body),
11282            other => panic!("expected Arrow, got {other:?}"),
11283        };
11284        (ast, params, return_type, body)
11285    }
11286
11287    #[test]
11288    fn parses_anonymous_function_expression_as_arrow() {
11289        let (ast, params, return_type, body) =
11290            parse_arrow_const("const f = function (x: number): number { return x + 1; };");
11291        assert_eq!(params.len(), 1);
11292        assert_eq!(params[0].name.name, "x");
11293        assert!(return_type.is_some());
11294        assert!(matches!(body, crate::ArrowBody::Block(_)));
11295        let _ = ast;
11296    }
11297
11298    #[test]
11299    fn parses_function_expression_with_no_params() {
11300        let (_ast, params, _return_type, body) =
11301            parse_arrow_const("const f = function (): void {};");
11302        assert!(params.is_empty());
11303        assert!(matches!(body, crate::ArrowBody::Block(_)));
11304    }
11305
11306    /// The named wrapper preserves its body signature.
11307    #[test]
11308    fn parses_named_function_expression_signature() {
11309        let (_ast, params, _return_type, _body) =
11310            parse_arrow_const("const f = function named(x: number): number { return x + 1; };");
11311        assert_eq!(params.len(), 1);
11312    }
11313
11314    /// Self references are resolved in the function expression scope.
11315    #[test]
11316    fn accepts_a_named_function_expression_that_calls_itself() {
11317        let (_ast, diags) =
11318            parse_str("const f = function bar(x: number): number { return bar(x); };");
11319        assert!(diags.is_empty(), "{diags:?}");
11320    }
11321
11322    /// A member name after `.` is never a reference to the function itself.
11323    #[test]
11324    fn accepts_a_named_function_expression_using_the_name_as_a_property() {
11325        let (_ast, diags) = parse_str("const f = function bar(o: O): number { return o.bar; };");
11326        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11327    }
11328
11329    /// Nor is a key before `:`.
11330    #[test]
11331    fn accepts_a_named_function_expression_using_the_name_as_an_object_key() {
11332        let (_ast, diags) = parse_str(
11333            "const f = function bar(x: number): number { const o = { bar: 1 }; return o.bar + x; };",
11334        );
11335        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11336    }
11337
11338    /// A string that happens to spell the name is not a reference.
11339    #[test]
11340    fn accepts_a_named_function_expression_with_its_name_in_a_string() {
11341        let (_ast, diags) =
11342            parse_str("const f = function bar(x: number): string { return \"bar\"; };");
11343        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11344    }
11345
11346    /// A self reference in a template substitution remains a normal expression.
11347    #[test]
11348    fn accepts_a_self_call_inside_a_template_substitution() {
11349        let (_ast, diags) =
11350            parse_str("const f = function bar(x: number): string { return `${bar(0)}`; };");
11351        assert!(diags.is_empty(), "{diags:?}");
11352    }
11353
11354    #[test]
11355    fn rejects_a_generic_function_expression() {
11356        let (_ast, diags) = parse_str("const f = function <T>(x: T): T { return x; };");
11357        assert!(
11358            diags
11359                .iter()
11360                .any(|d| d.message.contains("generic function expressions")),
11361            "generic function expression should be rejected: {diags:?}"
11362        );
11363    }
11364
11365    #[test]
11366    fn accepts_this_inside_a_function_expression_in_a_class_method() {
11367        let (_ast, diags) = parse_str(
11368            "class C { x: number = 1; m(): number { \
11369             const f = function (this: { x: number }): number { return this.x; }; return f(); } }",
11370        );
11371        assert!(diags.is_empty(), "{diags:?}");
11372    }
11373
11374    #[test]
11375    fn retains_this_boundary_inside_method_shorthand() {
11376        let (ast, diags) = parse_str(
11377            "class C { x: number = 1; m(): number { \
11378             const o = { x: 2, g(): number { return this.x; } }; return o.g(); } }",
11379        );
11380        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11381        assert!((0..ast.exprs_len()).any(|index| matches!(
11382            ast.try_expr(crate::ExprId(index as u32)).unwrap().kind,
11383            ExprKind::ThisOutsideReceiver
11384        )));
11385    }
11386
11387    #[test]
11388    fn this_still_parses_in_an_ordinary_class_method() {
11389        let (_ast, diags) = parse_str("class C { x: number = 1; m(): number { return this.x; } }");
11390        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11391    }
11392
11393    /// `function` is a valid property name; the expression form must not shadow that.
11394    #[test]
11395    fn function_keyword_still_parses_as_an_object_key() {
11396        let (_ast, diags) = parse_str("let o = { function: 1 };");
11397        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11398    }
11399
11400    fn object_literal_member_values(source: &str) -> (Ast, Vec<crate::ExprId>) {
11401        let (ast, diags) = parse_str(source);
11402        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11403        let stmt = ast.try_stmt(ast.top_level[0]).unwrap();
11404        let value = match &stmt.kind {
11405            StmtKind::Const { value, .. } => *value,
11406            other => panic!("expected a const declaration, got {other:?}"),
11407        };
11408        let members = match &ast.try_expr(value).unwrap().kind {
11409            ExprKind::ObjectLiteral { members } => members
11410                .iter()
11411                .map(crate::ObjectLiteralMember::value)
11412                .collect::<Vec<_>>(),
11413            other => panic!("expected ObjectLiteral, got {other:?}"),
11414        };
11415        (ast, members)
11416    }
11417
11418    #[test]
11419    fn parses_object_literal_method_shorthand_as_arrow() {
11420        let (ast, members) =
11421            object_literal_member_values("const o = { m(x: number): number { return x; } };");
11422        assert_eq!(members.len(), 1);
11423        match &ast.try_expr(members[0]).unwrap().kind {
11424            ExprKind::Arrow { params, body, .. } => {
11425                assert_eq!(params.len(), 1);
11426                assert_eq!(params[0].name.name, "x");
11427                assert!(matches!(body, crate::ArrowBody::Block(_)));
11428            }
11429            other => panic!("expected Arrow, got {other:?}"),
11430        }
11431    }
11432
11433    /// A keyword is a valid method name, just as it is a valid property name.
11434    #[test]
11435    fn parses_method_shorthand_with_a_keyword_name() {
11436        let (ast, members) =
11437            object_literal_member_values("const o = { if(x: number): number { return x; } };");
11438        assert!(matches!(
11439            ast.try_expr(members[0]).unwrap().kind,
11440            ExprKind::Arrow { .. }
11441        ));
11442    }
11443
11444    #[test]
11445    fn parses_method_shorthand_alongside_plain_and_shorthand_properties() {
11446        let (ast, members) =
11447            object_literal_member_values("const o = { a: 1, m(): void {}, b: 2 };");
11448        assert_eq!(members.len(), 3);
11449        assert!(matches!(
11450            ast.try_expr(members[1]).unwrap().kind,
11451            ExprKind::Arrow { .. }
11452        ));
11453    }
11454
11455    #[test]
11456    fn parses_bare_ident_arrow_expression_body() {
11457        let (_ast, params, return_type, body) = parse_arrow_const("const f = x => x + 1;");
11458        assert_eq!(params.len(), 1);
11459        assert_eq!(params[0].name.name, "x");
11460        assert!(params[0].ty.is_none());
11461        assert!(return_type.is_none());
11462        assert!(matches!(body, crate::ArrowBody::Expr(_)));
11463    }
11464
11465    #[test]
11466    fn parses_zero_param_arrow() {
11467        let (_ast, params, return_type, body) = parse_arrow_const("const f = () => 0;");
11468        assert!(params.is_empty());
11469        assert!(return_type.is_none());
11470        assert!(matches!(body, crate::ArrowBody::Expr(_)));
11471    }
11472
11473    #[test]
11474    fn parses_paren_unannotated_arrow() {
11475        let (_ast, params, _return_type, _body) = parse_arrow_const("const f = (x) => x;");
11476        assert_eq!(params.len(), 1);
11477        assert_eq!(params[0].name.name, "x");
11478        assert!(params[0].ty.is_none());
11479    }
11480
11481    #[test]
11482    fn parses_typed_param_arrow() {
11483        let (_ast, params, return_type, body) =
11484            parse_arrow_const("const f = (x: number) => x * 2;");
11485        assert_eq!(params.len(), 1);
11486        assert!(params[0].ty.is_some());
11487        assert!(return_type.is_none());
11488        assert!(matches!(body, crate::ArrowBody::Expr(_)));
11489    }
11490
11491    #[test]
11492    fn parses_typed_param_and_return_arrow() {
11493        let (_ast, params, return_type, body) =
11494            parse_arrow_const("const f = (x: number): number => x * 2;");
11495        assert_eq!(params.len(), 1);
11496        assert!(params[0].ty.is_some());
11497        assert!(return_type.is_some());
11498        assert!(matches!(body, crate::ArrowBody::Expr(_)));
11499    }
11500
11501    #[test]
11502    fn parses_multi_param_unannotated_arrow() {
11503        let (_ast, params, _return_type, _body) = parse_arrow_const("const f = (x, y) => x + y;");
11504        assert_eq!(params.len(), 2);
11505        assert_eq!(params[0].name.name, "x");
11506        assert_eq!(params[1].name.name, "y");
11507        assert!(params[0].ty.is_none());
11508        assert!(params[1].ty.is_none());
11509    }
11510
11511    #[test]
11512    fn parses_multi_param_typed_arrow() {
11513        let (_ast, params, _return_type, _body) =
11514            parse_arrow_const("const f = (x: number, y: number) => x + y;");
11515        assert_eq!(params.len(), 2);
11516        assert!(params[0].ty.is_some());
11517        assert!(params[1].ty.is_some());
11518    }
11519
11520    #[test]
11521    fn parses_block_body_arrow() {
11522        let (_ast, params, _return_type, body) =
11523            parse_arrow_const("const f = (x: number) => { return x; };");
11524        assert_eq!(params.len(), 1);
11525        assert!(matches!(body, crate::ArrowBody::Block(_)));
11526    }
11527
11528    #[test]
11529    fn rejects_typed_bare_ident_arrow() {
11530        let (_ast, diags) = parse_str("const f = x: number => x;");
11531        assert!(
11532            !diags.is_empty(),
11533            "expected diagnostics for `x: number => …`",
11534        );
11535    }
11536
11537    #[test]
11538    fn arrow_inside_call_argument_parses() {
11539        let (ast, diags) = parse_str("f((x: number) => x + 1, 2);");
11540        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11541        let stmt = ast.try_stmt(ast.top_level[0]).unwrap();
11542        let call = match &stmt.kind {
11543            StmtKind::Expr(eid) => ast.try_expr(*eid).unwrap(),
11544            _ => panic!("expected expression statement"),
11545        };
11546        let args = match &call.kind {
11547            ExprKind::Call { args, .. } => args.clone(),
11548            other => panic!("expected Call, got {other:?}"),
11549        };
11550        assert_eq!(args.len(), 2);
11551        assert!(matches!(
11552            ast.try_expr(args[0]).unwrap().kind,
11553            ExprKind::Arrow { .. }
11554        ));
11555    }
11556
11557    #[test]
11558    fn function_decl_param_still_requires_annotation() {
11559        let (_ast, diags) = parse_str("function f(x): number { return x; }");
11560        assert!(
11561            diags.iter().any(|d| d.message.contains("type annotation")),
11562            "expected `parameter requires a type annotation` diagnostic, got {diags:?}",
11563        );
11564    }
11565
11566    #[test]
11567    fn paren_expression_still_parses_when_no_arrow() {
11568        let (ast, diags) = parse_str("const a = (1 + 2);");
11569        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11570        let stmt = ast.try_stmt(ast.top_level[0]).unwrap();
11571        let value = match &stmt.kind {
11572            StmtKind::Const { value, .. } => *value,
11573            _ => panic!("expected const declaration"),
11574        };
11575        assert!(matches!(
11576            ast.try_expr(value).unwrap().kind,
11577            ExprKind::Paren(_)
11578        ));
11579    }
11580
11581    #[test]
11582    fn parse_named_import_single() {
11583        let (ast, diags) = parse_str("import { v4 } from \"submilli:uuid\";");
11584        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11585        assert_eq!(ast.top_level.len(), 1);
11586        let stmt = ast.try_stmt(ast.top_level[0]).unwrap();
11587        let (module, kind) = match &stmt.kind {
11588            StmtKind::Import { module, kind, .. } => (module, kind),
11589            other => panic!("expected Import, got {other:?}"),
11590        };
11591        assert_eq!(module, "submilli:uuid");
11592        let specs = match kind {
11593            ImportKind::Named(specs) => specs,
11594            other => panic!("expected Named, got {other:?}"),
11595        };
11596        assert_eq!(specs.len(), 1);
11597        assert_eq!(specs[0].imported_name.name, "v4");
11598        assert_eq!(specs[0].local_name.name, "v4");
11599    }
11600
11601    #[test]
11602    fn parse_named_import_multi_and_alias() {
11603        let (ast, diags) =
11604            parse_str("import { v4, v7 as makeId, validate } from \"submilli:uuid\";");
11605        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11606        let specs = match &ast.try_stmt(ast.top_level[0]).unwrap().kind {
11607            StmtKind::Import {
11608                kind: ImportKind::Named(s),
11609                ..
11610            } => s.clone(),
11611            other => panic!("expected named import, got {other:?}"),
11612        };
11613        assert_eq!(specs.len(), 3);
11614        assert_eq!(specs[0].imported_name.name, "v4");
11615        assert_eq!(specs[0].local_name.name, "v4");
11616        assert_eq!(specs[1].imported_name.name, "v7");
11617        assert_eq!(specs[1].local_name.name, "makeId");
11618        assert_eq!(specs[2].imported_name.name, "validate");
11619        assert_eq!(specs[2].local_name.name, "validate");
11620    }
11621
11622    #[test]
11623    fn parse_namespace_import() {
11624        let (ast, diags) = parse_str("import uuid from \"submilli:uuid\";");
11625        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11626        let kind = match &ast.try_stmt(ast.top_level[0]).unwrap().kind {
11627            StmtKind::Import { kind, .. } => kind.clone(),
11628            other => panic!("expected import, got {other:?}"),
11629        };
11630        let local = match kind {
11631            ImportKind::Namespace { local_name } => local_name,
11632            other => panic!("expected Namespace, got {other:?}"),
11633        };
11634        assert_eq!(local.name, "uuid");
11635    }
11636
11637    #[test]
11638    fn parse_wildcard_namespace_import_synonym() {
11639        let (ast, diags) = parse_str("import * as uuid from \"submilli:uuid\";");
11640        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
11641        let kind = match &ast.try_stmt(ast.top_level[0]).unwrap().kind {
11642            StmtKind::Import { kind, .. } => kind.clone(),
11643            other => panic!("expected import, got {other:?}"),
11644        };
11645        let local = match kind {
11646            ImportKind::Namespace { local_name } => local_name,
11647            other => panic!("expected Namespace, got {other:?}"),
11648        };
11649        assert_eq!(local.name, "uuid");
11650    }
11651
11652    #[test]
11653    fn parse_wildcard_namespace_import_without_as_rejected() {
11654        let (_ast, diags) = parse_str("import * from \"submilli:uuid\";");
11655        assert!(
11656            diags
11657                .iter()
11658                .any(|d| d.message.contains("expected `as <name>`")),
11659            "expected missing-as diagnostic, got: {diags:?}",
11660        );
11661    }
11662
11663    #[test]
11664    fn parse_combined_default_and_named_rejected() {
11665        let (_ast, diags) = parse_str("import uuid, { v4 } from \"submilli:uuid\";");
11666        assert!(
11667            diags
11668                .iter()
11669                .any(|d| d.message.contains("combining default and named")),
11670            "expected combined-import diagnostic, got: {diags:?}",
11671        );
11672    }
11673
11674    #[test]
11675    fn parse_side_effect_import_rejected() {
11676        let (_ast, diags) = parse_str("import \"submilli:uuid\";");
11677        assert!(
11678            diags
11679                .iter()
11680                .any(|d| d.message.contains("side-effect-only imports")),
11681            "expected side-effect-import diagnostic, got: {diags:?}",
11682        );
11683    }
11684
11685    #[test]
11686    fn parse_empty_specifier_list_rejected() {
11687        let (_ast, diags) = parse_str("import {} from \"submilli:uuid\";");
11688        assert!(
11689            diags
11690                .iter()
11691                .any(|d| d.message.contains("empty import specifier list")),
11692            "expected empty-specifier diagnostic, got: {diags:?}",
11693        );
11694    }
11695
11696    #[test]
11697    fn parse_import_inside_function_body_rejected() {
11698        let (_ast, diags) =
11699            parse_str("function main(): void { import { v4 } from \"submilli:uuid\"; }");
11700        assert!(
11701            diags
11702                .iter()
11703                .any(|d| d.message.contains("must appear at the top of the file")),
11704            "expected top-level diagnostic, got: {diags:?}",
11705        );
11706    }
11707
11708    #[test]
11709    fn parse_missing_from_rejected() {
11710        let (_ast, diags) = parse_str("import { v4 } \"submilli:uuid\";");
11711        assert!(
11712            diags.iter().any(|d| d.message.contains("expected `from`")),
11713            "expected missing-from diagnostic, got: {diags:?}",
11714        );
11715    }
11716
11717    #[test]
11718    fn parse_missing_module_specifier_rejected() {
11719        let (_ast, diags) = parse_str("import { v4 } from;");
11720        assert!(
11721            diags
11722                .iter()
11723                .any(|d| d.message.contains("expected module specifier")),
11724            "expected missing-module diagnostic, got: {diags:?}",
11725        );
11726    }
11727
11728    #[test]
11729    fn parse_export_function_marks_exported() {
11730        // `export function main() {}` parses to a single function declaration in
11731        // `top_level`, plus an `exported_decls` entry pointing at it.
11732        let (ast, diags) = parse_str("export function main(): void {}");
11733        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11734        let stmt = single_stmt(&ast);
11735        assert!(
11736            matches!(stmt.kind, crate::StmtKind::Function { .. }),
11737            "expected Function decl, got {:?}",
11738            stmt.kind,
11739        );
11740        assert_eq!(ast.exported_decls.len(), 1, "one exported decl");
11741        assert_eq!(ast.exported_decls[0].stmt, ast.top_level[0]);
11742    }
11743
11744    #[test]
11745    fn parse_export_const_type_interface_enum_are_marked() {
11746        for src in [
11747            "export const X: number = 1;",
11748            "export type Id = string;",
11749            "export interface P { x: number; }",
11750            "export enum E { A, B }",
11751        ] {
11752            let (ast, diags) = parse_str(src);
11753            assert!(diags.is_empty(), "unexpected diags for {src:?}: {diags:?}");
11754            assert_eq!(ast.top_level.len(), 1, "one decl for {src:?}");
11755            assert_eq!(ast.exported_decls.len(), 1, "one exported decl for {src:?}");
11756        }
11757    }
11758
11759    #[test]
11760    fn parse_export_default_is_rejected_with_focused_message() {
11761        let (_ast, diags) = parse_str("export default function main(): void {}");
11762        assert!(
11763            diags
11764                .iter()
11765                .any(|d| d.message.contains("`export default` is not supported")),
11766            "expected focused export-default diagnostic, got: {diags:?}",
11767        );
11768        assert!(
11769            !diags.iter().any(|d| d.message.contains("expected `;`")),
11770            "the spurious missing-semicolon error must be gone: {diags:?}",
11771        );
11772    }
11773
11774    #[test]
11775    fn parse_export_list_parses_as_export_from() {
11776        // Form 2 (`export { x };`, no `from`) now parses cleanly — the
11777        // single-file gating happens in the typechecker, not the parser.
11778        let (ast, diags) = parse_str("function main(): void {}\nexport { main };");
11779        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11780        assert_eq!(ast.top_level.len(), 2, "function + export-from");
11781        let export = ast.try_stmt(ast.top_level[1]).unwrap();
11782        let crate::StmtKind::ExportFrom { specs, source, .. } = &export.kind else {
11783            panic!("expected ExportFrom, got {:?}", export.kind);
11784        };
11785        assert!(source.is_none(), "bare `export {{ x }}` has no source");
11786        assert_eq!(specs.len(), 1);
11787        assert_eq!(specs[0].imported_name.name, "main");
11788        assert_eq!(specs[0].local_name.name, "main");
11789    }
11790
11791    #[test]
11792    fn parse_export_from_with_source_and_alias_parses() {
11793        let (ast, diags) = parse_str("export { foo, bar as baz } from \"./util\";");
11794        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11795        let export = ast.try_stmt(ast.top_level[0]).unwrap();
11796        let crate::StmtKind::ExportFrom { specs, source, .. } = &export.kind else {
11797            panic!("expected ExportFrom, got {:?}", export.kind);
11798        };
11799        let Some((module, _)) = source else {
11800            panic!("expected a `from` source");
11801        };
11802        assert_eq!(module, "./util");
11803        assert_eq!(specs.len(), 2);
11804        assert_eq!(specs[1].imported_name.name, "bar");
11805        assert_eq!(specs[1].local_name.name, "baz");
11806    }
11807
11808    #[test]
11809    fn parse_export_inside_function_body_rejected() {
11810        let (_ast, diags) = parse_str("function main(): void { export const X: number = 1; }");
11811        assert!(
11812            diags
11813                .iter()
11814                .any(|d| d.message.contains("must appear at the top of the file")),
11815            "expected top-level diagnostic, got: {diags:?}",
11816        );
11817    }
11818
11819    #[test]
11820    fn parse_throw_with_expression() {
11821        let (ast, diags) = parse_str(r#"throw new Error("oops");"#);
11822        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11823        let stmt = single_stmt(&ast);
11824        let crate::StmtKind::Throw { value } = stmt.kind else {
11825            panic!("expected Throw, got {:?}", stmt.kind);
11826        };
11827        assert!(matches!(
11828            ast.try_expr(value).unwrap().kind,
11829            crate::ExprKind::New { .. }
11830        ));
11831    }
11832
11833    #[test]
11834    fn parse_new_with_dotted_callee() {
11835        let (ast, diags) = parse_str("new Temporal.Duration({});");
11836        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11837        let crate::StmtKind::Expr(expr_id) = single_stmt(&ast).kind else {
11838            panic!("expected expression statement");
11839        };
11840        let crate::ExprKind::New { callee, .. } = &ast.try_expr(expr_id).unwrap().kind else {
11841            panic!("expected New");
11842        };
11843        let crate::ExprKind::FieldAccess { name, .. } = &ast.try_expr(*callee).unwrap().kind else {
11844            panic!("expected FieldAccess callee");
11845        };
11846        assert_eq!(name.name, "Duration");
11847    }
11848
11849    #[test]
11850    fn parse_new_with_three_level_dotted_callee() {
11851        let (ast, diags) = parse_str("new Foo.Bar.Baz();");
11852        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11853        let crate::StmtKind::Expr(expr_id) = single_stmt(&ast).kind else {
11854            panic!("expected expression statement");
11855        };
11856        let crate::ExprKind::New { callee, .. } = &ast.try_expr(expr_id).unwrap().kind else {
11857            panic!("expected New");
11858        };
11859        let crate::ExprKind::FieldAccess { receiver, name } = &ast.try_expr(*callee).unwrap().kind
11860        else {
11861            panic!("expected outer FieldAccess");
11862        };
11863        assert_eq!(name.name, "Baz");
11864        let crate::ExprKind::FieldAccess { name: mid_name, .. } =
11865            &ast.try_expr(*receiver).unwrap().kind
11866        else {
11867            panic!("expected inner FieldAccess");
11868        };
11869        assert_eq!(mid_name.name, "Bar");
11870    }
11871
11872    #[test]
11873    fn parse_new_with_trailing_dot_diagnoses() {
11874        let (_ast, diags) = parse_str("new Foo.;");
11875        assert!(
11876            diags.iter().any(|d| d
11877                .message
11878                .contains("expected identifier after `.` in constructor name")),
11879            "expected trailing-dot diagnostic, got: {diags:?}",
11880        );
11881    }
11882
11883    #[test]
11884    fn throw_without_expression_diagnoses() {
11885        let (_ast, diags) = parse_str("throw;");
11886        assert!(
11887            diags
11888                .iter()
11889                .any(|d| d.message == "expected expression after `throw`"),
11890            "expected bare-throw diagnostic, got: {diags:?}",
11891        );
11892    }
11893
11894    #[test]
11895    fn parse_try_catch() {
11896        let (ast, diags) = parse_str("try { } catch (e: Error) { }");
11897        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11898        let stmt = single_stmt(&ast);
11899        let crate::StmtKind::Try {
11900            body,
11901            ref catches,
11902            ref finally,
11903        } = stmt.kind
11904        else {
11905            panic!("expected Try, got {:?}", stmt.kind);
11906        };
11907        assert!(matches!(
11908            ast.try_stmt(body).unwrap().kind,
11909            crate::StmtKind::Block(ref v) if v.is_empty()
11910        ));
11911        let [clause] = catches.as_slice() else {
11912            panic!("expected one catch clause, got {catches:?}");
11913        };
11914        assert_eq!(clause.binding.name, "e");
11915        assert!(clause.ty.is_some());
11916        assert!(finally.is_none());
11917    }
11918
11919    #[test]
11920    fn parse_try_finally() {
11921        let (ast, diags) = parse_str("try { } finally { }");
11922        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11923        let stmt = single_stmt(&ast);
11924        let crate::StmtKind::Try {
11925            ref catches,
11926            ref finally,
11927            ..
11928        } = stmt.kind
11929        else {
11930            panic!("expected Try");
11931        };
11932        assert!(catches.is_empty());
11933        let finally_id = finally.expect("finally block");
11934        assert!(matches!(
11935            ast.try_stmt(finally_id).unwrap().kind,
11936            crate::StmtKind::Block(_)
11937        ));
11938    }
11939
11940    #[test]
11941    fn parse_try_catch_finally() {
11942        let (ast, diags) = parse_str("try { } catch (e: Error) { } finally { }");
11943        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11944        let stmt = single_stmt(&ast);
11945        let crate::StmtKind::Try {
11946            ref catches,
11947            ref finally,
11948            ..
11949        } = stmt.kind
11950        else {
11951            panic!("expected Try");
11952        };
11953        assert_eq!(catches.len(), 1);
11954        assert!(finally.is_some());
11955    }
11956
11957    #[test]
11958    fn bare_try_diagnoses() {
11959        let (_ast, diags) = parse_str("try { }");
11960        assert!(
11961            diags
11962                .iter()
11963                .any(|d| d.message == "try requires at least one of `catch` or `finally`"),
11964            "expected bare-try diagnostic, got: {diags:?}",
11965        );
11966    }
11967
11968    #[test]
11969    fn parse_untyped_catch() {
11970        let (ast, diags) = parse_str("try { } catch (e) { }");
11971        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11972        let stmt = single_stmt(&ast);
11973        let crate::StmtKind::Try {
11974            ref catches,
11975            ref finally,
11976            ..
11977        } = stmt.kind
11978        else {
11979            panic!("expected Try");
11980        };
11981        let [clause] = catches.as_slice() else {
11982            panic!("expected one catch clause, got {catches:?}");
11983        };
11984        assert_eq!(clause.binding.name, "e");
11985        assert!(clause.ty.is_none());
11986        assert!(finally.is_none());
11987    }
11988
11989    #[test]
11990    fn parse_multiple_catch_clauses() {
11991        let (ast, diags) = parse_str("try { } catch (e: HttpError) { } catch (f) { }");
11992        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
11993        let stmt = single_stmt(&ast);
11994        let crate::StmtKind::Try {
11995            ref catches,
11996            ref finally,
11997            ..
11998        } = stmt.kind
11999        else {
12000            panic!("expected Try");
12001        };
12002        let [first, second] = catches.as_slice() else {
12003            panic!("expected two catch clauses, got {catches:?}");
12004        };
12005        assert_eq!(first.binding.name, "e");
12006        assert!(first.ty.is_some());
12007        assert_eq!(second.binding.name, "f");
12008        assert!(second.ty.is_none());
12009        assert!(finally.is_none());
12010    }
12011
12012    #[test]
12013    fn catch_after_finally_diagnoses() {
12014        let (_ast, diags) = parse_str("try { } finally { } catch (e: Error) { }");
12015        assert!(
12016            !diags.is_empty(),
12017            "expected diagnostic for catch-after-finally"
12018        );
12019    }
12020
12021    #[test]
12022    fn catch_missing_open_paren_diagnoses() {
12023        let (_ast, diags) = parse_str("try { } catch e: Error) { }");
12024        assert!(
12025            diags
12026                .iter()
12027                .any(|d| d.message == "expected `(` after `catch`"),
12028            "expected open-paren diagnostic, got: {diags:?}",
12029        );
12030    }
12031
12032    fn class_members(ast: &Ast) -> &[crate::ClassMember] {
12033        match &single_stmt(ast).kind {
12034            StmtKind::ClassDecl { members, .. } => members,
12035            other => panic!("expected ClassDecl, got {other:?}"),
12036        }
12037    }
12038
12039    #[test]
12040    fn parse_animal_class() {
12041        let src = r#"
12042class Animal {
12043  name: string;
12044  private sound: string;
12045  readonly species: string;
12046
12047  constructor(name: string, sound: string, species: string) {
12048    this.name = name;
12049    this.sound = sound;
12050    this.species = species;
12051  }
12052
12053  speak(): string {
12054    return this.name + " says " + this.sound;
12055  }
12056}
12057"#;
12058        let (ast, diags) = parse_str(src);
12059        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12060        let StmtKind::ClassDecl {
12061            name,
12062            generics,
12063            extends,
12064            implements,
12065            members,
12066            ..
12067        } = &single_stmt(&ast).kind
12068        else {
12069            panic!("expected ClassDecl");
12070        };
12071        assert_eq!(name.name, "Animal");
12072        assert!(generics.is_empty());
12073        assert!(extends.is_none());
12074        assert!(implements.is_empty());
12075        assert_eq!(members.len(), 5);
12076
12077        let crate::ClassMember::Field {
12078            name, modifiers, ..
12079        } = &members[0]
12080        else {
12081            panic!("member 0 should be a field");
12082        };
12083        assert_eq!(name.name, "name");
12084        assert_eq!(modifiers.visibility, crate::Visibility::Public);
12085        assert!(modifiers.readonly.is_none());
12086
12087        let crate::ClassMember::Field { modifiers, .. } = &members[1] else {
12088            panic!("member 1 should be a field");
12089        };
12090        assert_eq!(modifiers.visibility, crate::Visibility::Private);
12091
12092        let crate::ClassMember::Field { modifiers, .. } = &members[2] else {
12093            panic!("member 2 should be a field");
12094        };
12095        assert!(modifiers.readonly.is_some());
12096        assert_eq!(modifiers.visibility, crate::Visibility::Public);
12097
12098        assert!(matches!(
12099            &members[3],
12100            crate::ClassMember::Constructor { .. }
12101        ));
12102
12103        let crate::ClassMember::Method { name, .. } = &members[4] else {
12104            panic!("member 4 should be a method");
12105        };
12106        assert_eq!(name.name, "speak");
12107    }
12108
12109    #[test]
12110    fn parse_dog_class_extends_super_and_void_method() {
12111        let src = r#"
12112class Dog extends Animal {
12113  private tricks: string[];
12114
12115  constructor(name: string) {
12116    super(name, "woof", "canis familiaris");
12117    this.tricks = [];
12118  }
12119
12120  learn(trick: string): void {
12121    this.tricks.push(trick);
12122  }
12123}
12124"#;
12125        let (ast, diags) = parse_str(src);
12126        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12127        let StmtKind::ClassDecl {
12128            extends, members, ..
12129        } = &single_stmt(&ast).kind
12130        else {
12131            panic!("expected ClassDecl");
12132        };
12133        assert!(extends.is_some(), "Dog should extend Animal");
12134        assert_eq!(members.len(), 3);
12135        assert!(matches!(&members[0], crate::ClassMember::Field { .. }));
12136        assert!(matches!(
12137            &members[1],
12138            crate::ClassMember::Constructor { .. }
12139        ));
12140        assert!(matches!(&members[2], crate::ClassMember::Method { .. }));
12141    }
12142
12143    #[test]
12144    fn parse_class_implements_list() {
12145        let (ast, diags) = parse_str("class C implements I, J { run(): void {} }");
12146        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12147        let StmtKind::ClassDecl { implements, .. } = &single_stmt(&ast).kind else {
12148            panic!("expected ClassDecl");
12149        };
12150        assert_eq!(implements.len(), 2);
12151    }
12152
12153    #[test]
12154    fn parse_class_extends_and_implements() {
12155        let (ast, diags) = parse_str("class C extends B implements I { run(): void {} }");
12156        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12157        let StmtKind::ClassDecl {
12158            extends,
12159            implements,
12160            ..
12161        } = &single_stmt(&ast).kind
12162        else {
12163            panic!("expected ClassDecl");
12164        };
12165        assert!(extends.is_some());
12166        assert_eq!(implements.len(), 1);
12167    }
12168
12169    #[test]
12170    fn parse_class_generics_optional_field_and_initializer() {
12171        let (ast, diags) = parse_str("class Box<T> { value?: T; items: T[] = []; }");
12172        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12173        let StmtKind::ClassDecl {
12174            generics, members, ..
12175        } = &single_stmt(&ast).kind
12176        else {
12177            panic!("expected ClassDecl");
12178        };
12179        assert_eq!(generics.len(), 1);
12180        assert_eq!(generics[0].name, "T");
12181
12182        let crate::ClassMember::Field {
12183            optional,
12184            initializer,
12185            ..
12186        } = &members[0]
12187        else {
12188            panic!("member 0 should be a field");
12189        };
12190        assert!(*optional);
12191        assert!(initializer.is_none());
12192
12193        let crate::ClassMember::Field { initializer, .. } = &members[1] else {
12194            panic!("member 1 should be a field");
12195        };
12196        assert!(initializer.is_some());
12197    }
12198
12199    #[test]
12200    fn export_class_is_marked() {
12201        let (ast, diags) = parse_str("export class Foo {}");
12202        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12203        assert_eq!(ast.top_level.len(), 1);
12204        assert_eq!(ast.exported_decls.len(), 1);
12205        assert!(matches!(
12206            &single_stmt(&ast).kind,
12207            StmtKind::ClassDecl { .. }
12208        ));
12209    }
12210
12211    #[test]
12212    fn parameterless_index_signature_is_reported_from_its_member_start() {
12213        for (source, expected) in [
12214            ("interface P { readonly []: number }", "readonly []"),
12215            ("let p: { readonly []: number } = {};", "readonly []"),
12216            ("let q: { []: number } = {};", "[]"),
12217        ] {
12218            let (_ast, diags) = parse_str(source);
12219            let diag = diags
12220                .iter()
12221                .find(|d| d.message.contains("must declare exactly one parameter"))
12222                .unwrap_or_else(|| panic!("no diagnostic for {source}"));
12223            let reported = &source[diag.span.start as usize..diag.span.end as usize];
12224            assert_eq!(reported, expected, "{source}");
12225        }
12226    }
12227
12228    #[test]
12229    fn unterminated_object_type_expects_closing_brace() {
12230        let (_ast, diags) = parse_str("let p: { a: number\n");
12231        assert!(
12232            diags.iter().any(|d| d.message == "expected `}`"),
12233            "{diags:?}"
12234        );
12235    }
12236
12237    #[test]
12238    fn conflicting_visibility_modifiers_diagnose() {
12239        let (_ast, diags) = parse_str("class C { public private x: number; }");
12240        assert!(
12241            diags
12242                .iter()
12243                .any(|d| d.message.contains("at most one visibility modifier")),
12244            "got: {diags:?}",
12245        );
12246    }
12247
12248    #[test]
12249    fn duplicate_readonly_modifier_diagnoses() {
12250        let (_ast, diags) = parse_str("class C { readonly readonly x: number; }");
12251        assert!(
12252            diags
12253                .iter()
12254                .any(|d| d.message.contains("duplicate `readonly`")),
12255            "got: {diags:?}",
12256        );
12257    }
12258
12259    #[test]
12260    fn field_named_like_a_modifier_is_allowed() {
12261        let (ast, diags) = parse_str("class C { private: number; }");
12262        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12263        let crate::ClassMember::Field {
12264            name, modifiers, ..
12265        } = &class_members(&ast)[0]
12266        else {
12267            panic!("expected a field");
12268        };
12269        assert_eq!(name.name, "private");
12270        assert_eq!(modifiers.visibility, crate::Visibility::Public);
12271    }
12272
12273    #[test]
12274    fn method_named_get_is_allowed() {
12275        let (ast, diags) = parse_str("class C { get(): number { return 1; } }");
12276        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12277        let crate::ClassMember::Method { name, .. } = &class_members(&ast)[0] else {
12278            panic!("expected a method");
12279        };
12280        assert_eq!(name.name, "get");
12281    }
12282
12283    #[test]
12284    fn static_method_parses() {
12285        let (ast, diags) = parse_str("class C { static f(): number { return 1; } }");
12286        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12287        let crate::ClassMember::Method {
12288            name, modifiers, ..
12289        } = &class_members(&ast)[0]
12290        else {
12291            panic!("expected a method");
12292        };
12293        assert_eq!(name.name, "f");
12294        assert!(modifiers.static_span.is_some());
12295    }
12296
12297    #[test]
12298    fn static_readonly_field_parses() {
12299        let (ast, diags) = parse_str("class C { static readonly x: number = 1; }");
12300        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12301        let crate::ClassMember::Field {
12302            name,
12303            modifiers,
12304            initializer,
12305            ..
12306        } = &class_members(&ast)[0]
12307        else {
12308            panic!("expected a field");
12309        };
12310        assert_eq!(name.name, "x");
12311        assert!(modifiers.static_span.is_some());
12312        assert!(modifiers.readonly.is_some());
12313        assert!(initializer.is_some());
12314    }
12315
12316    #[test]
12317    fn private_static_method_parses() {
12318        let (ast, diags) = parse_str("class C { private static f(): void {} }");
12319        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12320        let crate::ClassMember::Method { modifiers, .. } = &class_members(&ast)[0] else {
12321            panic!("expected a method");
12322        };
12323        assert_eq!(modifiers.visibility, crate::Visibility::Private);
12324        assert!(modifiers.static_span.is_some());
12325    }
12326
12327    #[test]
12328    fn method_named_static_is_allowed() {
12329        let (ast, diags) = parse_str("class C { static(): number { return 1; } }");
12330        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12331        let crate::ClassMember::Method {
12332            name, modifiers, ..
12333        } = &class_members(&ast)[0]
12334        else {
12335            panic!("expected a method");
12336        };
12337        assert_eq!(name.name, "static");
12338        assert!(modifiers.static_span.is_none());
12339    }
12340
12341    #[test]
12342    fn visibility_after_static_diagnoses() {
12343        let (_ast, diags) = parse_str("class C { static private f(): void {} }");
12344        assert!(
12345            diags
12346                .iter()
12347                .any(|d| d.message.contains("`private` must come before `static`")),
12348            "got: {diags:?}",
12349        );
12350    }
12351
12352    #[test]
12353    fn duplicate_static_modifier_diagnoses() {
12354        let (_ast, diags) = parse_str("class C { static static f(): void {} }");
12355        assert!(
12356            diags
12357                .iter()
12358                .any(|d| d.message.contains("duplicate `static` modifier")),
12359            "got: {diags:?}",
12360        );
12361    }
12362
12363    #[test]
12364    fn static_accessor_is_rejected() {
12365        let (_ast, diags) = parse_str("class C { static get x(): number { return 1; } }");
12366        assert!(
12367            diags
12368                .iter()
12369                .any(|d| d.message.contains("static accessors are not supported")),
12370            "got: {diags:?}",
12371        );
12372    }
12373
12374    #[test]
12375    fn static_constructor_is_rejected() {
12376        let (_ast, diags) = parse_str("class C { static constructor() {} }");
12377        assert!(
12378            diags
12379                .iter()
12380                .any(|d| d.message.contains("a constructor cannot be `static`")),
12381            "got: {diags:?}",
12382        );
12383    }
12384
12385    #[test]
12386    fn optional_static_field_is_rejected() {
12387        let (_ast, diags) = parse_str("class C { static x?: number; }");
12388        assert!(
12389            diags
12390                .iter()
12391                .any(|d| d.message.contains("a static field cannot be optional")),
12392            "got: {diags:?}",
12393        );
12394    }
12395
12396    #[test]
12397    fn protected_member_is_rejected() {
12398        let (_ast, diags) = parse_str("class C { protected x: number; }");
12399        assert!(
12400            diags
12401                .iter()
12402                .any(|d| d.message.contains("`protected` is not supported")),
12403            "got: {diags:?}",
12404        );
12405    }
12406
12407    #[test]
12408    fn abstract_member_is_rejected() {
12409        let (_ast, diags) = parse_str("class C { abstract run(): void; }");
12410        assert!(
12411            diags
12412                .iter()
12413                .any(|d| d.message.contains("abstract classes are not supported")),
12414            "got: {diags:?}",
12415        );
12416    }
12417
12418    #[test]
12419    fn getter_parses_as_accessor() {
12420        let (ast, diags) = parse_str("class C { get value(): number { return 1; } }");
12421        assert!(diags.is_empty(), "got: {diags:?}");
12422        let crate::ClassMember::Accessor { kind, name, .. } = &class_members(&ast)[0] else {
12423            panic!("expected an accessor member");
12424        };
12425        assert_eq!(*kind, crate::AccessorKind::Get);
12426        assert_eq!(name.name, "value");
12427    }
12428
12429    #[test]
12430    fn getter_without_return_type_is_rejected() {
12431        let (ast, diags) = parse_str("class C { get value() { return 1; } }");
12432        assert_eq!(diags.len(), 1, "got: {diags:?}");
12433        assert_eq!(diags[0].message, "expected `:` and return type");
12434        assert_eq!(diags[0].help, vec!["get name(): T { … }".to_string()]);
12435        assert!(
12436            matches!(
12437                &class_members(&ast)[0],
12438                crate::ClassMember::Accessor {
12439                    return_type: None,
12440                    ..
12441                }
12442            ),
12443            "the getter is still recovered as a member"
12444        );
12445    }
12446
12447    #[test]
12448    fn setter_parses_as_accessor() {
12449        let (ast, diags) = parse_str("class C { set value(v: number) {} }");
12450        assert!(diags.is_empty(), "got: {diags:?}");
12451        let crate::ClassMember::Accessor { kind, param, .. } = &class_members(&ast)[0] else {
12452            panic!("expected an accessor member");
12453        };
12454        assert_eq!(*kind, crate::AccessorKind::Set);
12455        assert!(param.is_some(), "setter should have a parameter");
12456    }
12457
12458    #[test]
12459    fn param_property_modifier_outside_constructor_is_rejected() {
12460        let (_ast, diags) = parse_str("function f(public x: number): void {}");
12461        assert!(
12462            diags.iter().any(|d| d
12463                .message
12464                .contains("parameter properties are only allowed in a constructor")),
12465            "got: {diags:?}",
12466        );
12467    }
12468
12469    #[test]
12470    fn constructor_return_type_is_rejected() {
12471        let (_ast, diags) = parse_str("class C { constructor(): void {} }");
12472        assert!(
12473            diags.iter().any(|d| d
12474                .message
12475                .contains("constructor cannot declare a return type")),
12476            "got: {diags:?}",
12477        );
12478    }
12479
12480    #[test]
12481    fn field_initializer_without_type_is_rejected() {
12482        let (_ast, diags) = parse_str("class C { x = 1; }");
12483        assert!(
12484            diags
12485                .iter()
12486                .any(|d| d.message.contains("class fields require a type annotation")),
12487            "got: {diags:?}",
12488        );
12489    }
12490
12491    #[test]
12492    fn this_outside_method_is_left_to_typechecker() {
12493        let (_ast, diags) = parse_str("function f(): string { return this.name; }");
12494        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12495    }
12496
12497    #[test]
12498    fn super_outside_method_is_rejected() {
12499        let (_ast, diags) = parse_str("function f(): void { super(1); }");
12500        assert!(
12501            diags.iter().any(|d| d
12502                .message
12503                .contains("`super` is only valid inside a class method or constructor body")),
12504            "got: {diags:?}",
12505        );
12506    }
12507
12508    #[test]
12509    fn this_inside_method_is_accepted_by_parser() {
12510        let (_ast, diags) = parse_str("class C { x: number; read(): number { return this.x; } }");
12511        assert!(diags.is_empty(), "unexpected diags: {diags:?}");
12512    }
12513    #[test]
12514    fn parse_void_expression_preserves_operand_and_precedence() {
12515        let (ast, diags) = parse_str("void effect() === undefined;");
12516        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
12517        let expression = expr_of_single_stmt(&ast);
12518        let crate::ExprKind::Binary { lhs, rhs, .. } = expression.kind else {
12519            panic!("expected equality")
12520        };
12521        let crate::ExprKind::Void { operand } = ast.try_expr(lhs).unwrap().kind else {
12522            panic!("expected void")
12523        };
12524        assert!(matches!(
12525            ast.try_expr(operand).unwrap().kind,
12526            crate::ExprKind::Call { .. }
12527        ));
12528        assert!(matches!(
12529            ast.try_expr(rhs).unwrap().kind,
12530            crate::ExprKind::Identifier(_)
12531        ));
12532    }
12533
12534    #[test]
12535    fn parse_void_newline_and_object_operands() {
12536        for source in [
12537            "void\n0;",
12538            "void { value: 1 };",
12539            "void\n{ value: 1 };",
12540            "void /x/;",
12541            "void void 0;",
12542        ] {
12543            let (_, diags) = parse_str(source);
12544            assert!(diags.is_empty(), "{source}: {diags:?}");
12545        }
12546        let (_, diags) = parse_str("void; 0;");
12547        assert!(
12548            !diags.is_empty(),
12549            "an explicit semicolon cannot split void from its operand"
12550        );
12551    }
12552
12553    #[test]
12554    fn parse_optional_parameters_and_defaults_in_callable_forms() {
12555        for source in [
12556            "function f(x = 1): number { return x; }",
12557            "function f(x: number = 1, y: number): number { return y; }",
12558            "function f({ x } = { x: 1 }): number { return x; }",
12559            "const f = (x?: number) => x;",
12560            "const f = (x: number = effect()) => x;",
12561            "const f = function(x: number = effect()) { return x; };",
12562            "const f = { m(x: number = effect()): number { return x; } };",
12563            "const f = ([x = 1]: number[] = values) => x;",
12564            "let f: (x?: number, ...rest: string[]) => void = value;",
12565            "let f: { method?(x?: number): void } = value;",
12566            "function f(x: number = 1, y?: number): void {}",
12567        ] {
12568            let (_, diags) = parse_str(source);
12569            assert!(diags.is_empty(), "{source}: {diags:?}");
12570        }
12571    }
12572
12573    #[test]
12574    fn parse_invalid_optional_parameters() {
12575        for source in [
12576            "function f(x?: number, y: number): void {}",
12577            "const f = (x?: number, y: number) => y;",
12578            "let f: (x?: number, y: number) => void = value;",
12579            "function f(...x?: number[]): void {}",
12580            "function f(x?: number = 1): void {}",
12581            "const f = (x?: number = 1) => x;",
12582            "function f({x}?: T): void {}",
12583        ] {
12584            let (_, diags) = parse_str(source);
12585            assert!(!diags.is_empty(), "expected diagnostic for {source}");
12586        }
12587    }
12588
12589    #[test]
12590    fn parse_optional_tuple_forms_and_order() {
12591        for source in [
12592            "let x: [number, string?] = value;",
12593            "let x: [x?: number, y?: string] = value;",
12594            "let x: readonly [(number | null)?, string?] = value;",
12595        ] {
12596            let (_, diags) = parse_str(source);
12597            assert!(diags.is_empty(), "{source}: {diags:?}");
12598        }
12599        for source in [
12600            "let x: [number?, string] = value;",
12601            "let x: [x: number?] = value;",
12602        ] {
12603            let (_, diags) = parse_str(source);
12604            assert!(!diags.is_empty(), "expected diagnostic for {source}");
12605        }
12606    }
12607
12608    #[test]
12609    fn parse_array_pattern_defaults_preserve_holes() {
12610        let (ast, diags) = parse_str("const [, a = effect(), b] = values;");
12611        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
12612        let crate::StmtKind::ConstPattern { ref binding, .. } = single_stmt(&ast).kind else {
12613            panic!("expected pattern")
12614        };
12615        let crate::Binding::Array {
12616            elems, defaults, ..
12617        } = binding
12618        else {
12619            panic!("expected array")
12620        };
12621        assert_eq!(elems.len(), 3);
12622        assert_eq!(defaults.len(), elems.len());
12623        assert!(elems[0].is_none());
12624        assert!(defaults[0].is_none());
12625        assert!(defaults[1].is_some());
12626        assert!(defaults[2].is_none());
12627    }
12628
12629    #[test]
12630    fn parse_optional_class_method_forms() {
12631        let (ast, diags) =
12632            parse_str("class C { declared?(x?: number): void; implemented?(): void\n{} }");
12633        assert!(diags.is_empty(), "unexpected diagnostics: {diags:?}");
12634        let members = class_members(&ast);
12635        assert!(matches!(
12636            &members[0],
12637            crate::ClassMember::Field {
12638                optional: true,
12639                initializer: None,
12640                ty: crate::TypeAnnotation {
12641                    kind: crate::TypeAnnotationKind::Function { .. },
12642                    ..
12643                },
12644                ..
12645            }
12646        ));
12647        assert!(matches!(
12648            &members[1],
12649            crate::ClassMember::Method { optional: true, .. }
12650        ));
12651    }
12652    #[test]
12653    fn defaults_retain_their_callable_receiver_scope() {
12654        for source in [
12655            "class C { value: number = 1; method(value: number = this.value): number { return value; } }",
12656            "class C { constructor(value: number = this.value) {} value: number = 1; }",
12657            "const f = function(this: { value: number }, value: number = this.value): number { return value; };",
12658        ] {
12659            let (ast, diagnostics) = parse_str(source);
12660            assert!(diagnostics.is_empty(), "{source}: {diagnostics:?}");
12661            assert!(
12662                ast.source_expressions()
12663                    .iter()
12664                    .any(|expr| matches!(expr.kind, ExprKind::This))
12665            );
12666            assert!(
12667                !ast.source_expressions()
12668                    .iter()
12669                    .any(|expr| matches!(expr.kind, ExprKind::ThisOutsideReceiver))
12670            );
12671        }
12672    }
12673}