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