Skip to main content

submilli_engine/
lower_patterns.rs

1//! Pre-infer pass eliminating destructuring patterns from [`Ast`].
2//! Runs between [`crate::parse`] and [`crate::infer`]; downstream sees only plain
3//! `Let`/`Const`/`ObjectRest`/`ForOf` and `ParamDecl` with `pattern: None`.
4//! Synthesised array-pattern `IndexAccess` nodes are tagged in [`Ast::pattern_origins`]
5//! so the typechecker can rephrase index errors as destructure-specific diagnostics.
6
7use crate::compiler_error::{CompilerFailure, CompilerStage};
8use crate::tree_height;
9
10use crate::{
11    Ast, Binding, BindingKind, ClassMember, Expr, ExprId, ExprKind, Ident, ParamDecl,
12    PatternOrigin, Span, Stmt, StmtId, StmtKind,
13};
14
15pub fn lower(mut ast: Ast) -> Result<Ast, CompilerFailure> {
16    // Callers may supply an AST that did not come from `parse_checked`. Pattern
17    // lowering reports its failures in the inference stage it feeds.
18    tree_height::check_syntax(&ast).map_err(|failure| failure.with_stage(CompilerStage::Infer))?;
19    let mut ctx = LowerCtx { next_tmp: 0 };
20    ctx.lower_arrows(&mut ast)?;
21    ctx.lower_function_params(&mut ast)?;
22    ctx.lower_class_member_params(&mut ast)?;
23    ctx.lower_for_of_patterns(&mut ast)?;
24    ctx.lower_for_pattern_inits(&mut ast)?;
25    ctx.lower_pattern_stmts(&mut ast)?;
26    Ok(ast)
27}
28
29/// Starts every `fresh` name, so none can collide with a source identifier.
30const FRESH_MARKER: &str = "#pattern_";
31
32/// The `fresh` prefix of a lowered destructured parameter.
33const PATTERN_PARAM: &str = "p";
34
35/// Whether `name` stands in for a destructured parameter, which has no source
36/// name: either the fresh `#pattern_p_N` from lowering, or the parser's empty
37/// placeholder on an interface method signature, which has no body to lower.
38pub(crate) fn is_pattern_param(name: &str) -> bool {
39    name.is_empty()
40        || name
41            .strip_prefix(FRESH_MARKER)
42            .and_then(|rest| rest.strip_prefix(PATTERN_PARAM))
43            .is_some_and(|rest| rest.starts_with('_'))
44}
45
46struct LowerCtx {
47    next_tmp: u32,
48}
49
50/// What a pattern's bindings inherit from the declaration that holds it.
51#[derive(Clone, Copy)]
52struct PatternRoot {
53    is_const: bool,
54    /// Whether the declaration annotates the pattern's type, which its
55    /// defaults must then fit.
56    annotated: bool,
57}
58
59impl LowerCtx {
60    fn fresh(&mut self, prefix: &str, span: Span) -> Result<Ident, CompilerFailure> {
61        let n = self.next_tmp;
62        self.next_tmp = self
63            .next_tmp
64            .checked_add(1)
65            .ok_or_else(|| CompilerFailure::Limit {
66                stage: CompilerStage::Infer,
67                span: None,
68                message: "pattern temporary capacity exceeded".into(),
69                help: vec![],
70            })?;
71        Ok(Ident {
72            // Distinct from source identifiers and the later desugar pass.
73            name: format!("{FRESH_MARKER}{prefix}_{n}"),
74            span,
75        })
76    }
77
78    fn lower_arrows(&mut self, ast: &mut Ast) -> Result<(), CompilerFailure> {
79        for id in ast
80            .expr_ids()
81            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
82        {
83            let has_pattern = match &ast
84                .try_expr(id)
85                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
86                .kind
87            {
88                ExprKind::Arrow { params, .. } => params.iter().any(|p| p.pattern.is_some()),
89                _ => false,
90            };
91            if has_pattern {
92                self.lower_arrow(ast, id)?;
93            }
94        }
95
96        Ok(())
97    }
98
99    fn lower_arrow(&mut self, ast: &mut Ast, id: ExprId) -> Result<(), CompilerFailure> {
100        // Clone the kind to release the read borrow before we start
101        // mutating other arena slots.
102        let kind = ast
103            .try_expr(id)
104            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
105            .kind
106            .clone();
107        let ExprKind::Arrow {
108            mut params,
109            return_type,
110            type_predicate,
111            body,
112        } = kind
113        else {
114            return Err(lowering_failure(
115                "unexpected node kind during pattern lowering",
116            ));
117        };
118
119        self.materialise_pattern_params(ast, &mut params)?;
120
121        ast.try_expr_mut(id)
122            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
123            .kind = ExprKind::Arrow {
124            params,
125            return_type,
126            type_predicate,
127            body,
128        };
129
130        Ok(())
131    }
132
133    fn lower_function_params(&mut self, ast: &mut Ast) -> Result<(), CompilerFailure> {
134        for id in ast
135            .stmt_ids()
136            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
137        {
138            let has_pattern = match &ast
139                .try_stmt(id)
140                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
141                .kind
142            {
143                StmtKind::Function { params, .. } => params.iter().any(|p| p.pattern.is_some()),
144                _ => false,
145            };
146            if has_pattern {
147                self.lower_function(ast, id)?;
148            }
149        }
150
151        Ok(())
152    }
153
154    fn lower_function(&mut self, ast: &mut Ast, id: StmtId) -> Result<(), CompilerFailure> {
155        let stmt_span = ast
156            .try_stmt(id)
157            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
158            .span;
159        let kind = std::mem::replace(
160            &mut ast
161                .try_stmt_mut(id)
162                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
163                .kind,
164            StmtKind::Block(Vec::new()), // sentinel, restored before return
165        );
166        let StmtKind::Function {
167            name,
168            generics,
169            mut params,
170            return_type,
171            type_predicate,
172            body,
173            doc,
174        } = kind
175        else {
176            return Err(lowering_failure(
177                "unexpected node kind during pattern lowering",
178            ));
179        };
180
181        self.materialise_pattern_params(ast, &mut params)?;
182
183        ast.try_stmt_mut(id)
184            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
185            .kind = StmtKind::Function {
186            name,
187            generics,
188            params,
189            return_type,
190            type_predicate,
191            body,
192            doc,
193        };
194        // Span is unchanged, but reassert for the linter.
195        let _ = stmt_span;
196
197        Ok(())
198    }
199
200    fn lower_class_member_params(&mut self, ast: &mut Ast) -> Result<(), CompilerFailure> {
201        for id in ast
202            .stmt_ids()
203            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
204        {
205            let StmtKind::ClassDecl { members, .. } = &mut ast
206                .try_stmt_mut(id)
207                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
208                .kind
209            else {
210                continue;
211            };
212            let mut taken = std::mem::take(members);
213            let lowered = self.lower_member_params(ast, &mut taken);
214            // Restore the members before reporting a failure, so the class
215            // keeps its shape either way.
216            let StmtKind::ClassDecl { members, .. } = &mut ast
217                .try_stmt_mut(id)
218                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
219                .kind
220            else {
221                return Err(lowering_failure(
222                    "unexpected node kind during pattern lowering",
223                ));
224            };
225            *members = taken;
226            lowered?;
227        }
228
229        Ok(())
230    }
231
232    fn lower_member_params(
233        &mut self,
234        ast: &mut Ast,
235        members: &mut [ClassMember],
236    ) -> Result<(), CompilerFailure> {
237        for member in members {
238            let params = match member {
239                ClassMember::Method { params, .. } | ClassMember::Constructor { params, .. } => {
240                    params.as_mut_slice()
241                }
242                ClassMember::Accessor {
243                    param: Some(param), ..
244                } => std::slice::from_mut(param.as_mut()),
245                ClassMember::Accessor { param: None, .. } | ClassMember::Field { .. } => continue,
246            };
247            self.materialise_pattern_params(ast, params)?;
248        }
249
250        Ok(())
251    }
252
253    fn lower_for_of_patterns(&mut self, ast: &mut Ast) -> Result<(), CompilerFailure> {
254        for id in ast
255            .stmt_ids()
256            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
257        {
258            if !matches!(
259                ast.try_stmt(id)
260                    .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
261                    .kind,
262                StmtKind::ForOfPattern { .. }
263            ) {
264                continue;
265            }
266            self.expand_for_of_pattern(ast, id)?;
267        }
268
269        Ok(())
270    }
271
272    fn expand_for_of_pattern(&mut self, ast: &mut Ast, id: StmtId) -> Result<(), CompilerFailure> {
273        let stmt_span = ast
274            .try_stmt(id)
275            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
276            .span;
277        let kind = std::mem::replace(
278            &mut ast
279                .try_stmt_mut(id)
280                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
281                .kind,
282            StmtKind::Block(Vec::new()),
283        );
284        let StmtKind::ForOfPattern {
285            binding_kind,
286            binding,
287            ty,
288            iter,
289            body,
290        } = kind
291        else {
292            return Err(lowering_failure(
293                "unexpected node kind during pattern lowering",
294            ));
295        };
296
297        let pattern_span = binding.span();
298        let dst = self.fresh("dst", pattern_span)?;
299        let is_const = matches!(binding_kind, BindingKind::Const);
300        let root = PatternRoot {
301            is_const,
302            annotated: ty.is_some(),
303        };
304        let mut decompose = self.emit_decompose(
305            ast,
306            binding,
307            dst.clone(),
308            root,
309            /*doc=*/ None,
310            /*tuple_len=*/ None,
311        )?;
312        let mut source_bindings = Vec::new();
313        for &id in &decompose {
314            match &ast
315                .try_stmt(id)
316                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
317                .kind
318            {
319                StmtKind::Let { name, .. }
320                | StmtKind::Const { name, .. }
321                | StmtKind::ObjectRest { name, .. } => source_bindings.push(name.clone()),
322                _ => {}
323            }
324        }
325        ast.for_of_pattern_bindings.insert(id, source_bindings);
326        // The loop head and the user's body are distinct lexical scopes.
327        decompose.push(body);
328        let body = ast
329            .try_push_stmt(Stmt {
330                kind: StmtKind::Block(decompose),
331                span: ast
332                    .try_stmt(body)
333                    .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
334                    .span,
335            })
336            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
337
338        ast.try_stmt_mut(id)
339            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
340            .kind = StmtKind::ForOf {
341            binding_kind,
342            name: dst,
343            ty,
344            iter,
345            body,
346        };
347        let _ = stmt_span;
348
349        Ok(())
350    }
351
352    fn lower_for_pattern_inits(&mut self, ast: &mut Ast) -> Result<(), CompilerFailure> {
353        for id in ast
354            .stmt_ids()
355            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
356        {
357            let StmtKind::For {
358                init: Some(init), ..
359            } = ast
360                .try_stmt(id)
361                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
362                .kind
363            else {
364                continue;
365            };
366            let is_let = match ast
367                .try_stmt(init)
368                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
369                .kind
370            {
371                StmtKind::LetPattern { .. } => true,
372                StmtKind::ConstPattern { .. } => false,
373                _ => continue,
374            };
375            self.hoist_for_pattern_init(ast, id, init, is_let)?;
376        }
377
378        Ok(())
379    }
380
381    /// `for (let [i, j] = a; c; u) body` becomes
382    /// `{ const dst = a; let i = dst[0]; let j = dst[1]; for (; c; u) body }`.
383    /// The bindings keep the loop's scope but lose the per-iteration copies a
384    /// `for` head's `let` gets, which only a closure capturing them observes;
385    /// inference rejects that case using [`Ast::for_pattern_init_bindings`].
386    fn hoist_for_pattern_init(
387        &mut self,
388        ast: &mut Ast,
389        id: StmtId,
390        init: StmtId,
391        is_let: bool,
392    ) -> Result<(), CompilerFailure> {
393        let mut hoisted = self.expand_pattern_stmt(ast, init)?;
394        let loop_stmt = ast
395            .try_stmt_mut(id)
396            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
397        let span = loop_stmt.span;
398        let kind = std::mem::replace(&mut loop_stmt.kind, StmtKind::Block(Vec::new()));
399        let StmtKind::For {
400            condition,
401            update,
402            body,
403            ..
404        } = kind
405        else {
406            return Err(lowering_failure(
407                "unexpected node kind during pattern lowering",
408            ));
409        };
410        let lowered_loop = ast
411            .try_push_stmt(Stmt {
412                kind: StmtKind::For {
413                    init: None,
414                    condition,
415                    update,
416                    body,
417                },
418                span,
419            })
420            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
421        if is_let {
422            let bindings = let_names(ast, &hoisted)?;
423            if !bindings.is_empty() {
424                ast.for_pattern_init_bindings.insert(lowered_loop, bindings);
425            }
426        }
427        hoisted.push(lowered_loop);
428        ast.try_stmt_mut(id)
429            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
430            .kind = StmtKind::Block(hoisted);
431
432        Ok(())
433    }
434
435    fn lower_pattern_stmts(&mut self, ast: &mut Ast) -> Result<(), CompilerFailure> {
436        let top = std::mem::take(&mut ast.top_level);
437        ast.top_level = self.expand_list(ast, top)?;
438
439        for id in ast
440            .stmt_ids()
441            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
442        {
443            let needs = matches!(
444                ast.try_stmt(id)
445                    .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
446                    .kind,
447                StmtKind::Block(_)
448            );
449            if !needs {
450                continue;
451            }
452            let StmtKind::Block(taken) = std::mem::replace(
453                &mut ast
454                    .try_stmt_mut(id)
455                    .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
456                    .kind,
457                StmtKind::Block(Vec::new()),
458            ) else {
459                return Err(lowering_failure(
460                    "unexpected node kind during pattern lowering",
461                ));
462            };
463            let expanded = self.expand_list(ast, taken)?;
464            ast.try_stmt_mut(id)
465                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
466                .kind = StmtKind::Block(expanded);
467        }
468
469        Ok(())
470    }
471
472    fn expand_list(
473        &mut self,
474        ast: &mut Ast,
475        ids: Vec<StmtId>,
476    ) -> Result<Vec<StmtId>, CompilerFailure> {
477        let mut out = Vec::with_capacity(ids.len());
478        for id in ids {
479            let is_pattern = matches!(
480                ast.try_stmt(id)
481                    .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
482                    .kind,
483                StmtKind::LetPattern { .. } | StmtKind::ConstPattern { .. }
484            );
485            if !is_pattern {
486                out.push(id);
487                continue;
488            }
489            let expanded = self.expand_pattern_stmt(ast, id)?;
490            out.extend(expanded);
491        }
492        Ok(out)
493    }
494
495    fn expand_pattern_stmt(
496        &mut self,
497        ast: &mut Ast,
498        id: StmtId,
499    ) -> Result<Vec<StmtId>, CompilerFailure> {
500        let span = ast
501            .try_stmt(id)
502            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
503            .span;
504        let kind = std::mem::replace(
505            &mut ast
506                .try_stmt_mut(id)
507                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
508                .kind,
509            StmtKind::Block(Vec::new()), // sentinel; the slot is unreachable post-lowering
510        );
511        let (is_const, binding, ty, value, doc) = match kind {
512            StmtKind::LetPattern {
513                binding,
514                ty,
515                value,
516                doc,
517            } => (false, binding, ty, value, doc),
518            StmtKind::ConstPattern {
519                binding,
520                ty,
521                value,
522                doc,
523            } => (true, binding, ty, value, doc),
524            _ => {
525                return Err(lowering_failure(
526                    "unexpected node kind during pattern lowering",
527                ));
528            }
529        };
530
531        let tuple_len = match ty {
532            None => tuple_source_len(ast, &binding, value)?,
533            Some(_) => None,
534        };
535        if let Some(len) = tuple_len {
536            pad_tuple_source(ast, value, len)?;
537            ast.tuple_pattern_sources.insert(value);
538        }
539        let pattern_span = binding.span();
540        let dst = self.fresh("dst", pattern_span)?;
541        let annotated = ty.is_some();
542
543        // temp is always `const` even for `let` patterns — it's never re-assigned
544        let dst_stmt = ast
545            .try_push_stmt(Stmt {
546                kind: StmtKind::Const {
547                    name: dst.clone(),
548                    ty,
549                    value,
550                    doc: None,
551                },
552                span,
553            })
554            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
555
556        let mut out = vec![dst_stmt];
557        let root = PatternRoot {
558            is_const,
559            annotated,
560        };
561        out.extend(self.emit_decompose(ast, binding, dst, root, doc, tuple_len)?);
562        Ok(out)
563    }
564
565    fn materialise_pattern_params(
566        &mut self,
567        ast: &mut Ast,
568        params: &mut [ParamDecl],
569    ) -> Result<(), CompilerFailure> {
570        for param in params.iter_mut() {
571            let Some(pattern) = param.pattern.take() else {
572                continue;
573            };
574            let fresh = self.fresh(PATTERN_PARAM, pattern.span())?;
575            param.name = fresh.clone();
576            let root = PatternRoot {
577                is_const: false,
578                annotated: param.ty.is_some(),
579            };
580            let statements = self.emit_decompose(
581                ast, pattern, fresh, root, /*doc=*/ None, /*tuple_len=*/ None,
582            )?;
583            if ast
584                .parameter_bindings
585                .insert(param.name.span, statements)
586                .is_some()
587            {
588                return Err(lowering_failure(
589                    "duplicate parameter binding span during pattern lowering",
590                ));
591            }
592        }
593
594        Ok(())
595    }
596
597    /// `doc` attaches to the first emitted stmt only. `tuple_len` is the length
598    /// of the tuple-typed array literal `source` holds, if it holds one.
599    fn emit_decompose(
600        &mut self,
601        ast: &mut Ast,
602        binding: Binding,
603        source: Ident,
604        root: PatternRoot,
605        mut doc: Option<crate::DocComment>,
606        tuple_len: Option<usize>,
607    ) -> Result<Vec<StmtId>, CompilerFailure> {
608        let is_const = root.is_const;
609        let mut out = Vec::new();
610        match binding {
611            Binding::Object {
612                fields,
613                rest,
614                span: _,
615            } => {
616                let bound_names: Vec<Ident> = fields.iter().map(|f| f.source.clone()).collect();
617                for field in fields {
618                    let recv = ast
619                        .try_push_expr(Expr {
620                            kind: ExprKind::Identifier(source.clone()),
621                            span: field.span,
622                        })
623                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
624                    let access = ast
625                        .try_push_expr(Expr {
626                            kind: ExprKind::FieldAccess {
627                                receiver: recv,
628                                name: field.source.clone(),
629                            },
630                            span: field.span,
631                        })
632                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
633                    let value = self.apply_binding_default(
634                        ast,
635                        access,
636                        field.default,
637                        root.annotated,
638                        field.span,
639                        &mut out,
640                    )?;
641                    let decl = ast
642                        .try_push_stmt(Stmt {
643                            kind: make_decl(is_const, field.local, value, doc.take()),
644                            span: field.span,
645                        })
646                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
647                    out.push(decl);
648                }
649                if let Some(rest_ident) = rest {
650                    let src_expr = ast
651                        .try_push_expr(Expr {
652                            kind: ExprKind::Identifier(source.clone()),
653                            span: rest_ident.span,
654                        })
655                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
656                    let decl = ast
657                        .try_push_stmt(Stmt {
658                            kind: StmtKind::ObjectRest {
659                                is_const,
660                                name: rest_ident.clone(),
661                                source: src_expr,
662                                exclude: bound_names,
663                                ty: None,
664                                doc: doc.take(),
665                            },
666                            span: rest_ident.span,
667                        })
668                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
669                    out.push(decl);
670                }
671            }
672            Binding::Array {
673                elems,
674                defaults,
675                rest,
676                span: pattern_span,
677            } => {
678                if elems.len() != defaults.len() {
679                    return Err(lowering_failure(
680                        "array pattern defaults do not match element positions",
681                    ));
682                }
683                let elems_len = elems.len();
684                for (i, (slot, default)) in elems.into_iter().zip(defaults).enumerate() {
685                    let Some(local) = slot else {
686                        if default.is_some() {
687                            return Err(lowering_failure("array pattern hole has a default value"));
688                        }
689                        continue;
690                    };
691                    let recv = ast
692                        .try_push_expr(Expr {
693                            kind: ExprKind::Identifier(source.clone()),
694                            span: local.span,
695                        })
696                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
697                    let idx = ast
698                        .try_push_expr(Expr {
699                            kind: ExprKind::Number(i as f64),
700                            span: local.span,
701                        })
702                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
703                    let access = ast
704                        .try_push_expr(Expr {
705                            kind: ExprKind::IndexAccess {
706                                receiver: recv,
707                                index: idx,
708                            },
709                            span: local.span,
710                        })
711                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
712                    // tag so typechecker rephrases index errors as destructure-specific
713                    ast.pattern_origins.insert(
714                        access,
715                        PatternOrigin {
716                            pattern_span,
717                            slot_arity: elems_len,
718                        },
719                    );
720                    let access = if default.is_some() {
721                        self.guard_defaulted_array_read(ast, recv, idx, access, local.span)?
722                    } else {
723                        access
724                    };
725                    let value = self.apply_binding_default(
726                        ast,
727                        access,
728                        default,
729                        root.annotated,
730                        local.span,
731                        &mut out,
732                    )?;
733                    let decl = ast
734                        .try_push_stmt(Stmt {
735                            kind: make_decl(is_const, local.clone(), value, doc.take()),
736                            span: local.span,
737                        })
738                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
739                    out.push(decl);
740                }
741                if let Some(rest_ident) = rest {
742                    let rest_value = match tuple_len {
743                        Some(len) if len > elems_len => {
744                            remaining_elements(ast, &source, elems_len..len, rest_ident.span)?
745                        }
746                        _ => array_rest(ast, &source, elems_len, rest_ident.span)?,
747                    };
748                    let decl = ast
749                        .try_push_stmt(Stmt {
750                            kind: make_decl(is_const, rest_ident.clone(), rest_value, doc.take()),
751                            span: rest_ident.span,
752                        })
753                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
754                    out.push(decl);
755                }
756            }
757        }
758        Ok(out)
759    }
760
761    /// A pattern default treats an absent element as undefined, while ordinary
762    /// index expressions retain their bounds checks.
763    fn guard_defaulted_array_read(
764        &mut self,
765        ast: &mut Ast,
766        receiver: ExprId,
767        index: ExprId,
768        access: ExprId,
769        span: Span,
770    ) -> Result<ExprId, CompilerFailure> {
771        let length = push_expr(
772            ast,
773            ExprKind::FieldAccess {
774                receiver,
775                name: Ident {
776                    name: "length".to_string(),
777                    span,
778                },
779            },
780            span,
781        )?;
782        let in_bounds = push_expr(
783            ast,
784            ExprKind::Binary {
785                op: crate::BinOp::Lt,
786                lhs: index,
787                rhs: length,
788            },
789            span,
790        )?;
791        let zero = push_expr(ast, ExprKind::Number(0.0), span)?;
792        let undefined = push_expr(ast, ExprKind::Void { operand: zero }, span)?;
793        push_expr(
794            ast,
795            ExprKind::Ternary {
796                cond: in_bounds,
797                then_: access,
798                else_: undefined,
799            },
800            span,
801        )
802    }
803
804    /// Save the property/index read once, then evaluate its default only for undefined.
805    /// `void 0` is immune to a source binding named `undefined`.
806    fn apply_binding_default(
807        &mut self,
808        ast: &mut Ast,
809        access: ExprId,
810        default: Option<ExprId>,
811        annotated: bool,
812        span: Span,
813        out: &mut Vec<StmtId>,
814    ) -> Result<ExprId, CompilerFailure> {
815        let Some(default) = default else {
816            return Ok(access);
817        };
818        let saved = self.fresh("value", span)?;
819        let declaration = ast
820            .try_push_stmt(Stmt {
821                kind: make_decl(true, saved.clone(), access, None),
822                span,
823            })
824            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
825        out.push(declaration);
826        let saved_value = push_expr(ast, ExprKind::Identifier(saved.clone()), span)?;
827        let zero = push_expr(ast, ExprKind::Number(0.0), span)?;
828        let undefined = push_expr(ast, ExprKind::Void { operand: zero }, span)?;
829        let condition = push_expr(
830            ast,
831            ExprKind::Binary {
832                op: crate::BinOp::Eq,
833                lhs: saved_value,
834                rhs: undefined,
835            },
836            span,
837        )?;
838        let selected = push_expr(
839            ast,
840            ExprKind::Ternary {
841                cond: condition,
842                then_: default,
843                else_: saved_value,
844            },
845            span,
846        )?;
847        ast.binding_defaults.insert(
848            selected,
849            crate::ast::BindingDefault {
850                saved,
851                default,
852                annotated,
853            },
854        );
855        Ok(selected)
856    }
857}
858
859/// The length of `value` when it is an array literal without spreads that
860/// `binding`, an array pattern, takes apart slot by slot, so TypeScript types
861/// it as a tuple. A pattern of only a rest element takes the literal whole, as
862/// an array. When every slot past the literal's end has a default or is a
863/// hole, the length is the pattern's: [`pad_tuple_source`] fills those slots
864/// with `undefined`, as tsc pads the tuple (`const [a, b = a] = [1]`).
865fn tuple_source_len(
866    ast: &Ast,
867    binding: &Binding,
868    value: ExprId,
869) -> Result<Option<usize>, CompilerFailure> {
870    let Binding::Array {
871        elems, defaults, ..
872    } = binding
873    else {
874        return Ok(None);
875    };
876    if elems.is_empty() {
877        return Ok(None);
878    }
879    let ExprKind::ArrayLiteral { elements } = &ast
880        .try_expr(value)
881        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
882        .kind
883    else {
884        return Ok(None);
885    };
886    let has_spread = elements
887        .iter()
888        .any(|element| matches!(element, crate::ArrayLiteralElement::Spread { .. }));
889    if has_spread {
890        return Ok(None);
891    }
892    let padded = elems.len() > elements.len()
893        && (elements.len()..elems.len()).all(|slot| {
894            elems.get(slot).is_some_and(Option::is_none)
895                || defaults.get(slot).is_some_and(Option::is_some)
896        });
897    Ok(Some(if padded { elems.len() } else { elements.len() }))
898}
899
900/// Append `undefined` to the array literal `value` until it holds `len`
901/// elements. The literal only feeds a pattern's hidden source, so the padding
902/// is never observed; it gives each defaulted or hole slot an element to read.
903fn pad_tuple_source(ast: &mut Ast, value: ExprId, len: usize) -> Result<(), CompilerFailure> {
904    let expr = ast
905        .try_expr(value)
906        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
907    let span = expr.span;
908    let ExprKind::ArrayLiteral { elements } = &expr.kind else {
909        return Ok(());
910    };
911    let missing = len.saturating_sub(elements.len());
912    if missing == 0 {
913        return Ok(());
914    }
915    let mut padding = Vec::with_capacity(missing);
916    for _ in 0..missing {
917        let undefined = Ident {
918            name: "undefined".to_string(),
919            span,
920        };
921        let element = push_expr(ast, ExprKind::Identifier(undefined), span)?;
922        ast.synthetic_undefined.insert(element);
923        padding.push(crate::ArrayLiteralElement::Value(element));
924    }
925    // The same literal, borrowed mutably now that the padding is allocated.
926    if let ExprKind::ArrayLiteral { elements } = &mut ast
927        .try_expr_mut(value)
928        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
929        .kind
930    {
931        elements.extend(padding);
932    }
933    Ok(())
934}
935
936/// `source.slice(from, source.length)`: the elements an array pattern's rest
937/// element collects.
938fn array_rest(
939    ast: &mut Ast,
940    source: &Ident,
941    from: usize,
942    span: Span,
943) -> Result<ExprId, CompilerFailure> {
944    let from_lit = push_expr(ast, ExprKind::Number(from as f64), span)?;
945    let slice_receiver = push_expr(ast, ExprKind::Identifier(source.clone()), span)?;
946    let slice_callee = push_expr(
947        ast,
948        ExprKind::FieldAccess {
949            receiver: slice_receiver,
950            name: Ident {
951                name: "slice".to_string(),
952                span,
953            },
954        },
955        span,
956    )?;
957    let length_receiver = push_expr(ast, ExprKind::Identifier(source.clone()), span)?;
958    let length_access = push_expr(
959        ast,
960        ExprKind::FieldAccess {
961            receiver: length_receiver,
962            name: Ident {
963                name: "length".to_string(),
964                span,
965            },
966        },
967        span,
968    )?;
969    let slice_call = push_expr(
970        ast,
971        ExprKind::Call {
972            callee: slice_callee,
973            type_args: None,
974            args: vec![from_lit, length_access],
975        },
976        span,
977    )?;
978    Ok(slice_call)
979}
980
981/// `[source[from], …, source[to - 1]].slice(0, to - from)`: the rest of a
982/// tuple-typed literal. The remaining elements form a tuple of their own, and
983/// slicing it yields an array of their types: one that can grow, where
984/// TypeScript types the rest as a tuple.
985fn remaining_elements(
986    ast: &mut Ast,
987    source: &Ident,
988    indices: std::ops::Range<usize>,
989    span: Span,
990) -> Result<ExprId, CompilerFailure> {
991    let count = indices.len();
992    let mut elements = Vec::with_capacity(count);
993    for i in indices {
994        let receiver = push_expr(ast, ExprKind::Identifier(source.clone()), span)?;
995        let index = push_expr(ast, ExprKind::Number(i as f64), span)?;
996        let access = push_expr(ast, ExprKind::IndexAccess { receiver, index }, span)?;
997        elements.push(crate::ArrayLiteralElement::Value(access));
998    }
999    let remaining = push_expr(ast, ExprKind::ArrayLiteral { elements }, span)?;
1000    ast.tuple_pattern_sources.insert(remaining);
1001    let slice = push_expr(
1002        ast,
1003        ExprKind::FieldAccess {
1004            receiver: remaining,
1005            name: Ident {
1006                name: "slice".to_string(),
1007                span,
1008            },
1009        },
1010        span,
1011    )?;
1012    let from = push_expr(ast, ExprKind::Number(0.0), span)?;
1013    let to = push_expr(ast, ExprKind::Number(count as f64), span)?;
1014    push_expr(
1015        ast,
1016        ExprKind::Call {
1017            callee: slice,
1018            type_args: None,
1019            args: vec![from, to],
1020        },
1021        span,
1022    )
1023}
1024
1025fn push_expr(ast: &mut Ast, kind: ExprKind, span: Span) -> Result<ExprId, CompilerFailure> {
1026    ast.try_push_expr(Expr { kind, span })
1027        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))
1028}
1029
1030fn make_decl(
1031    is_const: bool,
1032    name: Ident,
1033    value: ExprId,
1034    doc: Option<crate::DocComment>,
1035) -> StmtKind {
1036    if is_const {
1037        StmtKind::Const {
1038            name,
1039            ty: None,
1040            value,
1041            doc,
1042        }
1043    } else {
1044        StmtKind::Let {
1045            name,
1046            ty: None,
1047            value,
1048            doc,
1049        }
1050    }
1051}
1052
1053/// The names the `let` statements among `stmts` declare.
1054fn let_names(ast: &Ast, stmts: &[StmtId]) -> Result<Vec<Ident>, CompilerFailure> {
1055    let mut names = Vec::new();
1056    for &stmt in stmts {
1057        if let StmtKind::Let { name, .. } = &ast
1058            .try_stmt(stmt)
1059            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
1060            .kind
1061        {
1062            names.push(name.clone());
1063        }
1064    }
1065    Ok(names)
1066}
1067
1068fn lowering_failure(message: &str) -> CompilerFailure {
1069    CompilerFailure::Internal {
1070        stage: CompilerStage::Infer,
1071        span: None,
1072        message: message.into(),
1073    }
1074}
1075
1076#[cfg(test)]
1077mod tests {
1078    use super::lower;
1079    use crate::{Asi, Binding, Stmt, StmtKind, Token, TokenKind, parse};
1080
1081    fn pipeline(source: &str) -> crate::Ast {
1082        let mut asi = Asi::new(source, crate::FileId(0));
1083        let mut tokens: Vec<Token> = Vec::new();
1084        loop {
1085            let t = asi.next_token();
1086            let is_eof = matches!(t.kind, TokenKind::Eof);
1087            tokens.push(t);
1088            if is_eof {
1089                break;
1090            }
1091        }
1092        let _ = asi.into_diagnostics();
1093        let (mut ast, diags) = parse(source, tokens, crate::FileId(0));
1094        assert!(diags.is_empty(), "unexpected parse diags: {diags:?}");
1095        ast = lower(ast).unwrap();
1096        ast
1097    }
1098
1099    fn no_patterns_left(ast: &crate::Ast) {
1100        for i in 0..ast.stmts_len() {
1101            let s = ast.try_stmt(crate::StmtId(i as u32)).unwrap();
1102            assert!(
1103                !matches!(
1104                    s.kind,
1105                    StmtKind::LetPattern { .. }
1106                        | StmtKind::ConstPattern { .. }
1107                        | StmtKind::ForOfPattern { .. }
1108                ),
1109                "found unlowered pattern at stmt #{i}: {:?}",
1110                s.kind,
1111            );
1112            if let StmtKind::Function { params, .. } = &s.kind {
1113                for (pi, p) in params.iter().enumerate() {
1114                    assert!(
1115                        p.pattern.is_none(),
1116                        "function param #{pi} still has a pattern after lowering",
1117                    );
1118                }
1119            }
1120        }
1121        for i in 0..ast.exprs_len() {
1122            let e = ast.try_expr(crate::ExprId(i as u32)).unwrap();
1123            if let crate::ExprKind::Arrow { params, .. } = &e.kind {
1124                for (pi, p) in params.iter().enumerate() {
1125                    assert!(
1126                        p.pattern.is_none(),
1127                        "arrow param #{pi} still has a pattern after lowering",
1128                    );
1129                }
1130            }
1131        }
1132    }
1133
1134    #[test]
1135    fn lowers_simple_object_destructure() {
1136        let ast = pipeline("const { a, b } = obj;");
1137        no_patterns_left(&ast);
1138        // top_level: __dst, a-decl, b-decl
1139        assert_eq!(ast.top_level.len(), 3);
1140        let names: Vec<String> = ast
1141            .top_level
1142            .iter()
1143            .map(|id| match &ast.try_stmt(*id).unwrap().kind {
1144                StmtKind::Const { name, .. } => name.name.clone(),
1145                other => panic!("expected Const, got {other:?}"),
1146            })
1147            .collect();
1148        assert!(names[0].starts_with("#pattern_dst_"));
1149        assert_eq!(names[1], "a");
1150        assert_eq!(names[2], "b");
1151    }
1152
1153    #[test]
1154    fn lowers_renamed_object_destructure() {
1155        let ast = pipeline("const { a: x, b: y } = obj;");
1156        no_patterns_left(&ast);
1157        let names: Vec<String> = ast
1158            .top_level
1159            .iter()
1160            .map(|id| match &ast.try_stmt(*id).unwrap().kind {
1161                StmtKind::Const { name, .. } => name.name.clone(),
1162                other => panic!("expected Const, got {other:?}"),
1163            })
1164            .collect();
1165        assert_eq!(names[1], "x");
1166        assert_eq!(names[2], "y");
1167    }
1168
1169    #[test]
1170    fn lowers_array_destructure() {
1171        let ast = pipeline("const [x, y] = arr;");
1172        no_patterns_left(&ast);
1173        assert_eq!(ast.top_level.len(), 3);
1174        if let StmtKind::Const { value, .. } = &ast.try_stmt(ast.top_level[1]).unwrap().kind {
1175            assert!(matches!(
1176                ast.try_expr(*value).unwrap().kind,
1177                crate::ExprKind::IndexAccess { .. }
1178            ));
1179        }
1180    }
1181
1182    #[test]
1183    fn lowers_array_with_hole_and_rest() {
1184        let ast = pipeline("const [, x, ...rest] = arr;");
1185        no_patterns_left(&ast);
1186        // __dst, x (= arr[1]), rest (= arr.slice(2))
1187        assert_eq!(ast.top_level.len(), 3);
1188        if let StmtKind::Const { name, value, .. } = &ast.try_stmt(ast.top_level[1]).unwrap().kind {
1189            assert_eq!(name.name, "x");
1190            if let crate::ExprKind::IndexAccess { index, .. } = &ast.try_expr(*value).unwrap().kind
1191            {
1192                assert_eq!(
1193                    ast.try_expr(*index).unwrap().kind,
1194                    crate::ExprKind::Number(1.0)
1195                );
1196            } else {
1197                panic!("expected IndexAccess");
1198            }
1199        }
1200        if let StmtKind::Const { name, value, .. } = &ast.try_stmt(ast.top_level[2]).unwrap().kind {
1201            assert_eq!(name.name, "rest");
1202            match &ast.try_expr(*value).unwrap().kind {
1203                crate::ExprKind::Call { callee, args, .. } => {
1204                    assert_eq!(args.len(), 2, "slice takes (start, end)");
1205                    assert_eq!(
1206                        ast.try_expr(args[0]).unwrap().kind,
1207                        crate::ExprKind::Number(2.0)
1208                    );
1209                    match &ast.try_expr(*callee).unwrap().kind {
1210                        crate::ExprKind::FieldAccess { name, .. } => {
1211                            assert_eq!(name.name, "slice");
1212                        }
1213                        other => panic!("expected slice FieldAccess, got {other:?}"),
1214                    }
1215                }
1216                other => panic!("expected Call, got {other:?}"),
1217            }
1218        }
1219    }
1220
1221    #[test]
1222    fn lowers_object_rest_to_object_rest() {
1223        let ast = pipeline("const { a, ...rest } = obj;");
1224        no_patterns_left(&ast);
1225        assert_eq!(ast.top_level.len(), 3);
1226        match &ast.try_stmt(ast.top_level[2]).unwrap().kind {
1227            StmtKind::ObjectRest {
1228                is_const,
1229                name,
1230                exclude,
1231                ..
1232            } => {
1233                assert!(*is_const);
1234                assert_eq!(name.name, "rest");
1235                assert_eq!(exclude.len(), 1);
1236                assert_eq!(exclude[0].name, "a");
1237            }
1238            other => panic!("expected ObjectRest, got {other:?}"),
1239        }
1240    }
1241
1242    #[test]
1243    fn lowers_function_param_pattern() {
1244        let src = "function f({ a, b }: T): number { return a + b; }";
1245        let ast = pipeline(src);
1246        no_patterns_left(&ast);
1247        let Stmt {
1248            kind: StmtKind::Function { body, params, .. },
1249            ..
1250        } = ast.try_stmt(ast.top_level[0]).unwrap().clone()
1251        else {
1252            panic!("expected Function");
1253        };
1254        assert_eq!(params.len(), 1);
1255        assert!(params[0].pattern.is_none());
1256        assert!(params[0].name.name.starts_with("#pattern_p_"));
1257        let body_stmts = match &ast.try_stmt(body).unwrap().kind {
1258            StmtKind::Block(s) => s.clone(),
1259            _ => panic!("body is not a Block"),
1260        };
1261        assert_eq!(body_stmts.len(), 1);
1262        let bindings = ast.parameter_bindings.get(&params[0].name.span).unwrap();
1263        assert_eq!(bindings.len(), 2);
1264        if let StmtKind::Let { name, .. } = &ast.try_stmt(bindings[0]).unwrap().kind {
1265            assert_eq!(name.name, "a");
1266        } else {
1267            panic!("expected Let a");
1268        }
1269        if let StmtKind::Let { name, .. } = &ast.try_stmt(bindings[1]).unwrap().kind {
1270            assert_eq!(name.name, "b");
1271        } else {
1272            panic!("expected Let b");
1273        }
1274        assert!(matches!(
1275            ast.try_stmt(body_stmts[0]).unwrap().kind,
1276            StmtKind::Return(_)
1277        ));
1278    }
1279
1280    #[test]
1281    fn parameter_object_rest_has_a_mutable_authored_binding() {
1282        let ast = pipeline("function f({ head, ...tail }: T): number { return tail.value; }");
1283        let StmtKind::Function { params, .. } = &ast.try_stmt(ast.top_level[0]).unwrap().kind
1284        else {
1285            panic!("expected function");
1286        };
1287        let bindings = ast.parameter_bindings.get(&params[0].name.span).unwrap();
1288        assert_eq!(bindings.len(), 2);
1289        let StmtKind::ObjectRest { is_const, name, .. } = &ast.try_stmt(bindings[1]).unwrap().kind
1290        else {
1291            panic!("expected object rest binding");
1292        };
1293        assert!(!*is_const);
1294        assert_eq!(name.name, "tail");
1295    }
1296
1297    #[test]
1298    fn lowers_arrow_expr_body_with_pattern() {
1299        let src = "const f = ({a, b}: T): number => a + b;";
1300        let ast = pipeline(src);
1301        no_patterns_left(&ast);
1302        let mut found = false;
1303        for i in 0..ast.exprs_len() {
1304            let e = ast.try_expr(crate::ExprId(i as u32)).unwrap();
1305            if let crate::ExprKind::Arrow { params, body, .. } = &e.kind {
1306                assert!(matches!(body, crate::ArrowBody::Expr(_)));
1307                assert_eq!(
1308                    ast.parameter_bindings
1309                        .get(&params[0].name.span)
1310                        .unwrap()
1311                        .len(),
1312                    2
1313                );
1314                found = true;
1315            }
1316        }
1317        assert!(found, "no Arrow found in expr arena");
1318    }
1319
1320    #[test]
1321    fn lowers_for_of_array_pattern() {
1322        // `for (const [a, b] of pairs) { … }` becomes
1323        // `for (const #pattern_dst_N of pairs) { const a = #pattern_dst_N[0];
1324        // const b = #pattern_dst_N[1]; { ... } }` after lowering.
1325        let src = "function main(): void { for (const [a, b] of pairs) { use(a, b); } }";
1326        let ast = pipeline(src);
1327        no_patterns_left(&ast);
1328
1329        let Stmt {
1330            kind: StmtKind::Function { body, .. },
1331            ..
1332        } = ast.try_stmt(ast.top_level[0]).unwrap().clone()
1333        else {
1334            panic!("expected Function");
1335        };
1336        let body_stmts = match &ast.try_stmt(body).unwrap().kind {
1337            StmtKind::Block(s) => s.clone(),
1338            _ => panic!("function body is not a Block"),
1339        };
1340        assert_eq!(body_stmts.len(), 1, "main body has just the for-of");
1341
1342        let (loop_name, loop_body) = match &ast.try_stmt(body_stmts[0]).unwrap().kind {
1343            StmtKind::ForOf { name, body, .. } => (name.name.clone(), *body),
1344            other => panic!("expected ForOf, got {other:?}"),
1345        };
1346        assert!(
1347            loop_name.starts_with("#pattern_dst_"),
1348            "loop var was rewritten to a synth temp; got {loop_name}",
1349        );
1350
1351        let loop_body_stmts = match &ast.try_stmt(loop_body).unwrap().kind {
1352            StmtKind::Block(s) => s.clone(),
1353            _ => panic!("for-of body is not a Block"),
1354        };
1355        assert_eq!(loop_body_stmts.len(), 3);
1356        if let StmtKind::Const { name, value, .. } = &ast.try_stmt(loop_body_stmts[0]).unwrap().kind
1357        {
1358            assert_eq!(name.name, "a");
1359            assert!(matches!(
1360                ast.try_expr(*value).unwrap().kind,
1361                crate::ExprKind::IndexAccess { .. }
1362            ));
1363            // Origin-tagged so the typechecker can rephrase index errors.
1364            assert!(ast.pattern_origins.contains_key(value));
1365        } else {
1366            panic!("expected Const a");
1367        }
1368        if let StmtKind::Const { name, .. } = &ast.try_stmt(loop_body_stmts[1]).unwrap().kind {
1369            assert_eq!(name.name, "b");
1370        } else {
1371            panic!("expected Const b");
1372        }
1373    }
1374
1375    #[test]
1376    fn lowers_let_pattern_preserves_mutability() {
1377        // __dst is const; per-field bindings follow source kind (`let` stays `let`)
1378        let ast = pipeline("let { a } = obj;");
1379        no_patterns_left(&ast);
1380        // __dst is Const; `a` is Let.
1381        assert!(matches!(
1382            ast.try_stmt(ast.top_level[0]).unwrap().kind,
1383            StmtKind::Const { .. }
1384        ));
1385        assert!(matches!(
1386            ast.try_stmt(ast.top_level[1]).unwrap().kind,
1387            StmtKind::Let { .. }
1388        ));
1389    }
1390
1391    #[test]
1392    fn no_op_on_plain_program() {
1393        let src = "const x = 1; function main(): void { }";
1394        let before = {
1395            let mut asi = Asi::new(src, crate::FileId(0));
1396            let mut tokens: Vec<Token> = Vec::new();
1397            loop {
1398                let t = asi.next_token();
1399                let is_eof = matches!(t.kind, TokenKind::Eof);
1400                tokens.push(t);
1401                if is_eof {
1402                    break;
1403                }
1404            }
1405            let _ = asi.into_diagnostics();
1406            let (ast, _) = parse(src, tokens, crate::FileId(0));
1407            ast
1408        };
1409        let after = pipeline(src);
1410        assert_eq!(format!("{before:?}"), format!("{after:?}"));
1411    }
1412
1413    #[test]
1414    fn lowers_pattern_in_nested_block() {
1415        let src = "function main(): void { const { a } = obj; }";
1416        let ast = pipeline(src);
1417        no_patterns_left(&ast);
1418    }
1419
1420    #[test]
1421    fn idempotent() {
1422        let src = "const { a, b } = obj;";
1423        let mut ast1 = pipeline(src);
1424        let snapshot = format!("{ast1:?}");
1425        ast1 = lower(ast1).unwrap();
1426        assert_eq!(snapshot, format!("{ast1:?}"));
1427    }
1428
1429    #[test]
1430    fn binding_span_preserved() {
1431        let (ast, _) = parse(
1432            "const { a } = obj;",
1433            tokens_of("const { a } = obj;"),
1434            crate::FileId(0),
1435        );
1436        match &ast.try_stmt(ast.top_level[0]).unwrap().kind {
1437            crate::StmtKind::ConstPattern {
1438                binding: Binding::Object { span, .. },
1439                ..
1440            } => {
1441                // `{ a }` starts at offset 6 and ends at 11 (inclusive of `}`).
1442                assert_eq!(*span, crate::Span::new(crate::FileId(0), 6, 11).unwrap());
1443            }
1444            other => panic!("expected ConstPattern Object, got {other:?}"),
1445        }
1446    }
1447
1448    #[test]
1449    fn defaults_save_each_access_once_and_test_undefined() {
1450        for source in [
1451            "const { a = fallback() } = source();",
1452            "const [a = fallback()] = source();",
1453        ] {
1454            let ast = pipeline(source);
1455            assert_eq!(ast.top_level.len(), 3);
1456            let StmtKind::Const {
1457                value: saved_access,
1458                ..
1459            } = ast.try_stmt(ast.top_level[1]).unwrap().kind
1460            else {
1461                panic!("expected saved read")
1462            };
1463            assert!(matches!(
1464                ast.try_expr(saved_access).unwrap().kind,
1465                crate::ExprKind::FieldAccess { .. } | crate::ExprKind::Ternary { .. }
1466            ));
1467            let StmtKind::Const { value, .. } = ast.try_stmt(ast.top_level[2]).unwrap().kind else {
1468                panic!("expected binding")
1469            };
1470            let crate::ExprKind::Ternary { cond, then_, else_ } = ast.try_expr(value).unwrap().kind
1471            else {
1472                panic!("expected lazy default")
1473            };
1474            let crate::ExprKind::Binary {
1475                op: crate::BinOp::Eq,
1476                lhs,
1477                rhs,
1478            } = ast.try_expr(cond).unwrap().kind
1479            else {
1480                panic!("expected undefined comparison")
1481            };
1482            assert_eq!(lhs, else_);
1483            assert!(matches!(
1484                ast.try_expr(rhs).unwrap().kind,
1485                crate::ExprKind::Void { .. }
1486            ));
1487            assert!(matches!(
1488                ast.try_expr(then_).unwrap().kind,
1489                crate::ExprKind::Call { .. }
1490            ));
1491        }
1492    }
1493
1494    #[test]
1495    fn array_defaults_keep_original_indices_and_origin_metadata() {
1496        let ast = pipeline("const [, a = 1] = values;");
1497        let StmtKind::Const { value, .. } = ast.try_stmt(ast.top_level[1]).unwrap().kind else {
1498            panic!("expected saved index read")
1499        };
1500        let crate::ExprKind::Ternary {
1501            cond,
1502            then_: access,
1503            else_,
1504        } = ast.try_expr(value).unwrap().kind
1505        else {
1506            panic!("expected bounds guard")
1507        };
1508        assert!(matches!(
1509            ast.try_expr(cond).unwrap().kind,
1510            crate::ExprKind::Binary {
1511                op: crate::BinOp::Lt,
1512                ..
1513            }
1514        ));
1515        assert!(matches!(
1516            ast.try_expr(else_).unwrap().kind,
1517            crate::ExprKind::Void { .. }
1518        ));
1519        let crate::ExprKind::IndexAccess { index, .. } = ast.try_expr(access).unwrap().kind else {
1520            panic!("expected guarded index")
1521        };
1522        assert!(matches!(
1523            ast.try_expr(index).unwrap().kind,
1524            crate::ExprKind::Number(1.0)
1525        ));
1526        assert_eq!(ast.pattern_origins.get(&access).unwrap().slot_arity, 2);
1527    }
1528
1529    #[test]
1530    fn lowers_class_method_parameter_defaults() {
1531        let ast = pipeline("class C { method({ x = 1 }: T): number { return x; } }");
1532        let StmtKind::ClassDecl { ref members, .. } = ast.try_stmt(ast.top_level[0]).unwrap().kind
1533        else {
1534            panic!("expected class")
1535        };
1536        let crate::ClassMember::Method { params, body, .. } = &members[0] else {
1537            panic!("expected method")
1538        };
1539        assert!(params[0].pattern.is_none());
1540        let StmtKind::Block(ref statements) = ast.try_stmt(*body).unwrap().kind else {
1541            panic!("expected block")
1542        };
1543        assert_eq!(statements.len(), 1);
1544        assert_eq!(
1545            ast.parameter_bindings
1546                .get(&params[0].name.span)
1547                .unwrap()
1548                .len(),
1549            2
1550        );
1551    }
1552
1553    fn tokens_of(source: &str) -> Vec<Token> {
1554        let mut asi = Asi::new(source, crate::FileId(0));
1555        let mut tokens = Vec::new();
1556        loop {
1557            let t = asi.next_token();
1558            let is_eof = matches!(t.kind, TokenKind::Eof);
1559            tokens.push(t);
1560            if is_eof {
1561                break;
1562            }
1563        }
1564        let _ = asi.into_diagnostics();
1565        tokens
1566    }
1567}