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 std::collections::{BTreeMap, BTreeSet};
8
9use crate::compiler_error::{CompilerFailure, CompilerStage};
10use crate::tree_height;
11
12use crate::{
13    Ast, Binding, BindingKind, ClassMember, Expr, ExprId, ExprKind, Ident, ParamDecl,
14    PatternOrigin, Span, Stmt, StmtId, StmtKind,
15};
16
17pub fn lower(mut ast: Ast) -> Result<Ast, CompilerFailure> {
18    // Callers may supply an AST that did not come from `parse_checked`. Pattern
19    // lowering reports its failures in the inference stage it feeds.
20    tree_height::check_syntax(&ast).map_err(|failure| failure.with_stage(CompilerStage::Infer))?;
21    let mut ctx = LowerCtx { next_tmp: 0 };
22    ctx.lower_arrows(&mut ast)?;
23    ctx.lower_function_params(&mut ast)?;
24    ctx.lower_class_member_params(&mut ast)?;
25    ctx.lower_for_of_patterns(&mut ast)?;
26    ctx.lower_for_pattern_inits(&mut ast)?;
27    ctx.lower_pattern_stmts(&mut ast)?;
28    Ok(ast)
29}
30
31/// Starts every `fresh` name, so none can collide with a source identifier.
32const FRESH_MARKER: &str = "#pattern_";
33
34/// The `fresh` prefix of a lowered destructured parameter.
35const PATTERN_PARAM: &str = "p";
36
37/// Whether `name` stands in for a destructured parameter, which has no source
38/// name: either the fresh `#pattern_p_N` from lowering, or the parser's empty
39/// placeholder on an interface method signature, which has no body to lower.
40pub(crate) fn is_pattern_param(name: &str) -> bool {
41    name.is_empty()
42        || name
43            .strip_prefix(FRESH_MARKER)
44            .and_then(|rest| rest.strip_prefix(PATTERN_PARAM))
45            .is_some_and(|rest| rest.starts_with('_'))
46}
47
48/// The `fresh` prefix of the value a destructuring declaration unpacks.
49const PATTERN_VALUE: &str = "dst";
50
51/// Whether `name` is the fresh `#pattern_dst_N` that holds the value a
52/// destructuring declaration unpacks.
53pub(crate) fn is_pattern_value(name: &str) -> bool {
54    name.strip_prefix(FRESH_MARKER)
55        .and_then(|rest| rest.strip_prefix(PATTERN_VALUE))
56        .is_some_and(|rest| rest.starts_with('_'))
57}
58
59struct LowerCtx {
60    next_tmp: u32,
61}
62
63/// What a pattern's bindings inherit from the declaration that holds it.
64#[derive(Clone, Copy)]
65struct PatternRoot {
66    is_const: bool,
67    /// Whether the declaration annotates the pattern's type, which its
68    /// defaults must then fit.
69    annotated: bool,
70}
71
72impl LowerCtx {
73    fn fresh(&mut self, prefix: &str, span: Span) -> Result<Ident, CompilerFailure> {
74        let n = self.next_tmp;
75        self.next_tmp = self
76            .next_tmp
77            .checked_add(1)
78            .ok_or_else(|| CompilerFailure::Limit {
79                stage: CompilerStage::Infer,
80                span: None,
81                message: "pattern temporary capacity exceeded".into(),
82                help: vec![],
83            })?;
84        Ok(Ident {
85            // Distinct from source identifiers and the later desugar pass.
86            name: format!("{FRESH_MARKER}{prefix}_{n}"),
87            span,
88        })
89    }
90
91    fn lower_arrows(&mut self, ast: &mut Ast) -> Result<(), CompilerFailure> {
92        for id in ast
93            .expr_ids()
94            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
95        {
96            let has_pattern = match &ast
97                .try_expr(id)
98                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
99                .kind
100            {
101                ExprKind::Arrow { params, .. } => params.iter().any(|p| p.pattern.is_some()),
102                _ => false,
103            };
104            if has_pattern {
105                self.lower_arrow(ast, id)?;
106            }
107        }
108
109        Ok(())
110    }
111
112    fn lower_arrow(&mut self, ast: &mut Ast, id: ExprId) -> Result<(), CompilerFailure> {
113        // Clone the kind to release the read borrow before we start
114        // mutating other arena slots.
115        let kind = ast
116            .try_expr(id)
117            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
118            .kind
119            .clone();
120        let ExprKind::Arrow {
121            mut params,
122            return_type,
123            type_predicate,
124            body,
125        } = kind
126        else {
127            return Err(lowering_failure(
128                "unexpected node kind during pattern lowering",
129            ));
130        };
131
132        self.materialise_pattern_params(ast, &mut params)?;
133
134        ast.try_expr_mut(id)
135            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
136            .kind = ExprKind::Arrow {
137            params,
138            return_type,
139            type_predicate,
140            body,
141        };
142
143        Ok(())
144    }
145
146    fn lower_function_params(&mut self, ast: &mut Ast) -> Result<(), CompilerFailure> {
147        for id in ast
148            .stmt_ids()
149            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
150        {
151            let has_pattern = match &ast
152                .try_stmt(id)
153                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
154                .kind
155            {
156                StmtKind::Function { params, .. } => params.iter().any(|p| p.pattern.is_some()),
157                _ => false,
158            };
159            if has_pattern {
160                self.lower_function(ast, id)?;
161            }
162        }
163
164        Ok(())
165    }
166
167    fn lower_function(&mut self, ast: &mut Ast, id: StmtId) -> Result<(), CompilerFailure> {
168        let stmt_span = ast
169            .try_stmt(id)
170            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
171            .span;
172        let kind = std::mem::replace(
173            &mut ast
174                .try_stmt_mut(id)
175                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
176                .kind,
177            StmtKind::Block(Vec::new()), // sentinel, restored before return
178        );
179        let StmtKind::Function {
180            name,
181            generics,
182            mut params,
183            return_type,
184            type_predicate,
185            body,
186            doc,
187        } = kind
188        else {
189            return Err(lowering_failure(
190                "unexpected node kind during pattern lowering",
191            ));
192        };
193
194        self.materialise_pattern_params(ast, &mut params)?;
195
196        ast.try_stmt_mut(id)
197            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
198            .kind = StmtKind::Function {
199            name,
200            generics,
201            params,
202            return_type,
203            type_predicate,
204            body,
205            doc,
206        };
207        // Span is unchanged, but reassert for the linter.
208        let _ = stmt_span;
209
210        Ok(())
211    }
212
213    fn lower_class_member_params(&mut self, ast: &mut Ast) -> Result<(), CompilerFailure> {
214        for id in ast
215            .stmt_ids()
216            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
217        {
218            let StmtKind::ClassDecl { members, .. } = &mut ast
219                .try_stmt_mut(id)
220                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
221                .kind
222            else {
223                continue;
224            };
225            let mut taken = std::mem::take(members);
226            let lowered = self.lower_member_params(ast, &mut taken);
227            // Restore the members before reporting a failure, so the class
228            // keeps its shape either way.
229            let StmtKind::ClassDecl { members, .. } = &mut ast
230                .try_stmt_mut(id)
231                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
232                .kind
233            else {
234                return Err(lowering_failure(
235                    "unexpected node kind during pattern lowering",
236                ));
237            };
238            *members = taken;
239            lowered?;
240        }
241
242        Ok(())
243    }
244
245    fn lower_member_params(
246        &mut self,
247        ast: &mut Ast,
248        members: &mut [ClassMember],
249    ) -> Result<(), CompilerFailure> {
250        for member in members {
251            let params = match member {
252                ClassMember::Method { params, .. } | ClassMember::Constructor { params, .. } => {
253                    params.as_mut_slice()
254                }
255                ClassMember::Accessor {
256                    param: Some(param), ..
257                } => std::slice::from_mut(param.as_mut()),
258                ClassMember::Accessor { param: None, .. } | ClassMember::Field { .. } => continue,
259            };
260            self.materialise_pattern_params(ast, params)?;
261        }
262
263        Ok(())
264    }
265
266    fn lower_for_of_patterns(&mut self, ast: &mut Ast) -> Result<(), CompilerFailure> {
267        for id in ast
268            .stmt_ids()
269            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
270        {
271            if !matches!(
272                ast.try_stmt(id)
273                    .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
274                    .kind,
275                StmtKind::ForOfPattern { .. }
276            ) {
277                continue;
278            }
279            self.expand_for_of_pattern(ast, id)?;
280        }
281
282        Ok(())
283    }
284
285    fn expand_for_of_pattern(&mut self, ast: &mut Ast, id: StmtId) -> Result<(), CompilerFailure> {
286        let stmt_span = ast
287            .try_stmt(id)
288            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
289            .span;
290        let kind = std::mem::replace(
291            &mut ast
292                .try_stmt_mut(id)
293                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
294                .kind,
295            StmtKind::Block(Vec::new()),
296        );
297        let StmtKind::ForOfPattern {
298            binding_kind,
299            binding,
300            ty,
301            iter,
302            body,
303        } = kind
304        else {
305            return Err(lowering_failure(
306                "unexpected node kind during pattern lowering",
307            ));
308        };
309
310        let pattern_span = binding.span();
311        let dst = self.fresh(PATTERN_VALUE, pattern_span)?;
312        let is_const = matches!(binding_kind, BindingKind::Const);
313        let root = PatternRoot {
314            is_const,
315            annotated: ty.is_some(),
316        };
317        let mut decompose = self.emit_decompose(
318            ast,
319            binding,
320            dst.clone(),
321            root,
322            /*doc=*/ None,
323            /*literal=*/ None,
324        )?;
325        let mut source_bindings = Vec::new();
326        for &id in &decompose {
327            match &ast
328                .try_stmt(id)
329                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
330                .kind
331            {
332                StmtKind::Let { name, .. }
333                | StmtKind::Const { name, .. }
334                | StmtKind::ObjectRest { name, .. } => source_bindings.push(name.clone()),
335                _ => {}
336            }
337        }
338        ast.for_of_pattern_bindings.insert(id, source_bindings);
339        // The loop head and the user's body are distinct lexical scopes.
340        decompose.push(body);
341        let body = ast
342            .try_push_stmt(Stmt {
343                kind: StmtKind::Block(decompose),
344                span: ast
345                    .try_stmt(body)
346                    .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
347                    .span,
348            })
349            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
350
351        ast.try_stmt_mut(id)
352            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
353            .kind = StmtKind::ForOf {
354            binding_kind,
355            name: dst,
356            ty,
357            iter,
358            body,
359        };
360        let _ = stmt_span;
361
362        Ok(())
363    }
364
365    fn lower_for_pattern_inits(&mut self, ast: &mut Ast) -> Result<(), CompilerFailure> {
366        for id in ast
367            .stmt_ids()
368            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
369        {
370            let StmtKind::For { init, .. } = &ast
371                .try_stmt(id)
372                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
373                .kind
374            else {
375                continue;
376            };
377            let init = init.clone();
378            let mut has_pattern = false;
379            for &declarator in &init {
380                has_pattern |= matches!(
381                    ast.try_stmt(declarator)
382                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
383                        .kind,
384                    StmtKind::LetPattern { .. } | StmtKind::ConstPattern { .. }
385                );
386            }
387            if has_pattern {
388                self.hoist_for_pattern_init(ast, id, init)?;
389            }
390        }
391
392        Ok(())
393    }
394
395    /// `for (let [i, j] = a, k = 0; c; u) body` becomes
396    /// `{ const dst = a; let i = dst[0]; let j = dst[1]; let k = 0; for (; c; u) body }`.
397    /// The bindings keep the loop's scope but lose the per-iteration copies a
398    /// `for` head's `let` gets, which only a closure capturing them observes;
399    /// inference rejects that case using [`Ast::for_pattern_init_bindings`].
400    fn hoist_for_pattern_init(
401        &mut self,
402        ast: &mut Ast,
403        id: StmtId,
404        init: Vec<StmtId>,
405    ) -> Result<(), CompilerFailure> {
406        let mut hoisted = self.expand_list(ast, init)?;
407        let loop_stmt = ast
408            .try_stmt_mut(id)
409            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
410        let span = loop_stmt.span;
411        let kind = std::mem::replace(&mut loop_stmt.kind, StmtKind::Block(Vec::new()));
412        let StmtKind::For {
413            condition,
414            update,
415            body,
416            ..
417        } = kind
418        else {
419            return Err(lowering_failure(
420                "unexpected node kind during pattern lowering",
421            ));
422        };
423        let lowered_loop = ast
424            .try_push_stmt(Stmt {
425                kind: StmtKind::For {
426                    init: Vec::new(),
427                    condition,
428                    update,
429                    body,
430                },
431                span,
432            })
433            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
434        let bindings = let_names(ast, &hoisted)?;
435        if !bindings.is_empty() {
436            ast.for_pattern_init_bindings.insert(lowered_loop, bindings);
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 literal = match ty {
543            None => mark_literal_source(ast, &binding, value, is_const)?,
544            Some(_) => None,
545        };
546        let pattern_span = binding.span();
547        let dst = self.fresh(PATTERN_VALUE, pattern_span)?;
548        let annotated = ty.is_some();
549
550        // temp is always `const` even for `let` patterns — it's never re-assigned
551        let dst_stmt = ast
552            .try_push_stmt(Stmt {
553                kind: StmtKind::Const {
554                    name: dst.clone(),
555                    ty,
556                    value,
557                    doc: None,
558                },
559                span,
560            })
561            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
562
563        let mut out = vec![dst_stmt];
564        let root = PatternRoot {
565            is_const,
566            annotated,
567        };
568        out.extend(self.emit_decompose(ast, binding, dst, root, doc, literal.as_ref())?);
569        Ok(out)
570    }
571
572    fn materialise_pattern_params(
573        &mut self,
574        ast: &mut Ast,
575        params: &mut [ParamDecl],
576    ) -> Result<(), CompilerFailure> {
577        // A padding default is typed before the patterns bind and before any
578        // later parameter does, so one that reads such a name would resolve
579        // to an outer binding: its slot and the tuple slots after it stay out
580        // of the padding. An earlier plain parameter is already bound.
581        let pattern_names: BTreeSet<String> = params
582            .iter()
583            .filter_map(|param| param.pattern.as_ref())
584            .flat_map(|pattern| bound_names(pattern).into_iter().map(str::to_string))
585            .collect();
586        let mut unbound_by_param = Vec::with_capacity(params.len());
587        let mut unbound = pattern_names;
588        for param in params.iter().rev() {
589            unbound_by_param.push(unbound.clone());
590            if param.pattern.is_none() {
591                unbound.insert(param.name.name.clone());
592            }
593        }
594        unbound_by_param.reverse();
595        for (param, unbound) in params.iter_mut().zip(unbound_by_param) {
596            let Some(pattern) = param.pattern.take() else {
597                continue;
598            };
599            let fresh = self.fresh(PATTERN_PARAM, pattern.span())?;
600            param.name = fresh.clone();
601            let root = PatternRoot {
602                is_const: false,
603                annotated: param.ty.is_some(),
604            };
605            // An unannotated parameter's default is typed as a declaration's
606            // initializer is. The argument, not the default, is what the
607            // pattern takes apart: a caller or a contextual type may supply
608            // more elements or properties than the default has.
609            let literal = match (&param.ty, param.default) {
610                (None, Some(default)) => mark_literal_source(ast, &pattern, default, false)?,
611                _ => None,
612            };
613            if let Some(default) = param.default {
614                let literal_id = peel_parens(ast, default)?;
615                match &literal {
616                    Some(LiteralSource::Tuple(len)) => {
617                        if let Some(mut pad_defaults) = defaults_past_end(&pattern, *len) {
618                            let mut kept = 0;
619                            for slot in &pad_defaults {
620                                if let Some(slot_default) = slot
621                                    && reads_any_of(ast, *slot_default, &unbound)?
622                                {
623                                    break;
624                                }
625                                kept += 1;
626                            }
627                            pad_defaults.truncate(kept);
628                            if !pad_defaults.is_empty() {
629                                ast.tuple_pattern_pad_defaults
630                                    .insert(literal_id, pad_defaults);
631                            }
632                        }
633                    }
634                    Some(LiteralSource::Object(names)) => {
635                        let mut pad_defaults = Vec::new();
636                        for (name, pad_default) in defaults_absent_from(&pattern, names) {
637                            if !reads_any_of(ast, pad_default, &unbound)? {
638                                pad_defaults.push((name, pad_default));
639                            }
640                        }
641                        if !pad_defaults.is_empty() {
642                            ast.object_pattern_pad_defaults
643                                .insert(literal_id, pad_defaults);
644                        }
645                    }
646                    None => {}
647                }
648            }
649            let statements =
650                self.emit_decompose(ast, pattern, fresh, root, /*doc=*/ None, None)?;
651            if ast
652                .parameter_bindings
653                .insert(param.name.span, statements)
654                .is_some()
655            {
656                return Err(lowering_failure(
657                    "duplicate parameter binding span during pattern lowering",
658                ));
659            }
660        }
661
662        Ok(())
663    }
664
665    /// `doc` attaches to the first emitted stmt only. `literal` describes the
666    /// literal `source` holds, if the pattern types one.
667    fn emit_decompose(
668        &mut self,
669        ast: &mut Ast,
670        binding: Binding,
671        source: Ident,
672        root: PatternRoot,
673        mut doc: Option<crate::DocComment>,
674        literal: Option<&LiteralSource>,
675    ) -> Result<Vec<StmtId>, CompilerFailure> {
676        let is_const = root.is_const;
677        let mut out = Vec::new();
678        match binding {
679            Binding::Object {
680                fields,
681                rest,
682                span: _,
683            } => {
684                let bound_names: Vec<Ident> = fields.iter().map(|f| f.source.clone()).collect();
685                for field in fields {
686                    // A defaulted property the literal lacks reads `undefined`,
687                    // as tsc reads it, unless the literal inherits it.
688                    let absent = matches!(
689                        literal,
690                        Some(LiteralSource::Object(names)) if !names.contains(&field.source.name)
691                    ) && !OBJECT_PROTOTYPE_MEMBERS
692                        .contains(&field.source.name.as_str());
693                    let access = if absent && field.default.is_some() {
694                        push_void_zero(ast, field.span)?
695                    } else {
696                        let recv =
697                            push_expr(ast, ExprKind::Identifier(source.clone()), field.span)?;
698                        push_expr(
699                            ast,
700                            ExprKind::FieldAccess {
701                                receiver: recv,
702                                name: field.source.clone(),
703                            },
704                            field.span,
705                        )?
706                    };
707                    let value = self.apply_binding_default(
708                        ast,
709                        access,
710                        field.default,
711                        root.annotated,
712                        field.span,
713                        &mut out,
714                    )?;
715                    let decl = ast
716                        .try_push_stmt(Stmt {
717                            kind: make_decl(is_const, field.local, value, doc.take()),
718                            span: field.span,
719                        })
720                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
721                    out.push(decl);
722                }
723                if let Some(rest_ident) = rest {
724                    let src_expr = ast
725                        .try_push_expr(Expr {
726                            kind: ExprKind::Identifier(source.clone()),
727                            span: rest_ident.span,
728                        })
729                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
730                    let decl = ast
731                        .try_push_stmt(Stmt {
732                            kind: StmtKind::ObjectRest {
733                                is_const,
734                                name: rest_ident.clone(),
735                                source: src_expr,
736                                exclude: bound_names,
737                                ty: None,
738                                doc: doc.take(),
739                            },
740                            span: rest_ident.span,
741                        })
742                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
743                    out.push(decl);
744                }
745            }
746            Binding::Array {
747                elems,
748                defaults,
749                rest,
750                span: pattern_span,
751            } => {
752                if elems.len() != defaults.len() {
753                    return Err(lowering_failure(
754                        "array pattern defaults do not match element positions",
755                    ));
756                }
757                let elems_len = elems.len();
758                for (i, (slot, default)) in elems.into_iter().zip(defaults).enumerate() {
759                    let Some(local) = slot else {
760                        if default.is_some() {
761                            return Err(lowering_failure("array pattern hole has a default value"));
762                        }
763                        continue;
764                    };
765                    // A tuple literal holds nothing past its end, so a defaulted
766                    // slot there reads `undefined`, as tsc reads it, and the
767                    // literal keeps its own length.
768                    let past_tuple_end =
769                        matches!(literal, Some(LiteralSource::Tuple(len)) if i >= *len);
770                    let access = if default.is_some() && past_tuple_end {
771                        push_void_zero(ast, local.span)?
772                    } else {
773                        let origin = PatternOrigin {
774                            pattern_span,
775                            slot_arity: elems_len,
776                            defaulted: default.is_some(),
777                        };
778                        self.array_slot_read(ast, &source, i, origin, local.span)?
779                    };
780                    let value = self.apply_binding_default(
781                        ast,
782                        access,
783                        default,
784                        root.annotated,
785                        local.span,
786                        &mut out,
787                    )?;
788                    let decl = ast
789                        .try_push_stmt(Stmt {
790                            kind: make_decl(is_const, local.clone(), value, doc.take()),
791                            span: local.span,
792                        })
793                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
794                    out.push(decl);
795                }
796                if let Some(rest_ident) = rest {
797                    let rest_value = match literal {
798                        Some(&LiteralSource::Tuple(len)) if len > elems_len => {
799                            remaining_elements(ast, &source, elems_len..len, rest_ident.span)?
800                        }
801                        _ => array_rest(ast, &source, elems_len, rest_ident.span)?,
802                    };
803                    let decl = ast
804                        .try_push_stmt(Stmt {
805                            kind: make_decl(is_const, rest_ident.clone(), rest_value, doc.take()),
806                            span: rest_ident.span,
807                        })
808                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
809                    out.push(decl);
810                }
811            }
812        }
813        Ok(out)
814    }
815
816    /// `source[index]`, tagged with `origin` so the typechecker words index
817    /// errors for the pattern. A defaulted slot's read is guarded by length.
818    fn array_slot_read(
819        &mut self,
820        ast: &mut Ast,
821        source: &Ident,
822        index: usize,
823        origin: PatternOrigin,
824        span: Span,
825    ) -> Result<ExprId, CompilerFailure> {
826        let receiver = push_expr(ast, ExprKind::Identifier(source.clone()), span)?;
827        let index = push_expr(ast, ExprKind::Number(index as f64), span)?;
828        let access = push_expr(ast, ExprKind::IndexAccess { receiver, index }, span)?;
829        let defaulted = origin.defaulted;
830        ast.pattern_origins.insert(access, origin);
831        if !defaulted {
832            return Ok(access);
833        }
834        self.guard_defaulted_array_read(ast, receiver, index, access, span)
835    }
836
837    /// A pattern default treats an absent element as undefined, while ordinary
838    /// index expressions retain their bounds checks.
839    fn guard_defaulted_array_read(
840        &mut self,
841        ast: &mut Ast,
842        receiver: ExprId,
843        index: ExprId,
844        access: ExprId,
845        span: Span,
846    ) -> Result<ExprId, CompilerFailure> {
847        let length = push_expr(
848            ast,
849            ExprKind::FieldAccess {
850                receiver,
851                name: Ident {
852                    name: "length".to_string(),
853                    span,
854                },
855            },
856            span,
857        )?;
858        let in_bounds = push_expr(
859            ast,
860            ExprKind::Binary {
861                op: crate::BinOp::Lt,
862                lhs: index,
863                rhs: length,
864            },
865            span,
866        )?;
867        let undefined = push_void_zero(ast, span)?;
868        push_expr(
869            ast,
870            ExprKind::Ternary {
871                cond: in_bounds,
872                then_: access,
873                else_: undefined,
874            },
875            span,
876        )
877    }
878
879    /// Save the property/index read once, then evaluate its default only for undefined.
880    /// `void 0` is immune to a source binding named `undefined`.
881    fn apply_binding_default(
882        &mut self,
883        ast: &mut Ast,
884        access: ExprId,
885        default: Option<ExprId>,
886        annotated: bool,
887        span: Span,
888        out: &mut Vec<StmtId>,
889    ) -> Result<ExprId, CompilerFailure> {
890        let Some(default) = default else {
891            return Ok(access);
892        };
893        let saved = self.fresh("value", span)?;
894        let declaration = ast
895            .try_push_stmt(Stmt {
896                kind: make_decl(true, saved.clone(), access, None),
897                span,
898            })
899            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
900        out.push(declaration);
901        let saved_value = push_expr(ast, ExprKind::Identifier(saved.clone()), span)?;
902        let undefined = push_void_zero(ast, span)?;
903        let condition = push_expr(
904            ast,
905            ExprKind::Binary {
906                op: crate::BinOp::Eq,
907                lhs: saved_value,
908                rhs: undefined,
909            },
910            span,
911        )?;
912        let selected = push_expr(
913            ast,
914            ExprKind::Ternary {
915                cond: condition,
916                then_: default,
917                else_: saved_value,
918            },
919            span,
920        )?;
921        ast.binding_defaults.insert(
922            selected,
923            crate::ast::BindingDefault {
924                saved,
925                default,
926                annotated,
927            },
928        );
929        Ok(selected)
930    }
931}
932
933/// A literal a pattern takes apart, which TypeScript types from the pattern.
934enum LiteralSource {
935    /// An array literal of this many elements, typed as a tuple.
936    Tuple(usize),
937    /// An object literal with these property names.
938    Object(BTreeSet<String>),
939}
940
941/// Describe the literal `value` that `binding` takes apart, and record how
942/// inference types it: an array literal as a tuple, and an object literal
943/// with each defaulted property the pattern reads optional, as tsc types it.
944fn mark_literal_source(
945    ast: &mut Ast,
946    binding: &Binding,
947    value: ExprId,
948    is_const: bool,
949) -> Result<Option<LiteralSource>, CompilerFailure> {
950    let value = peel_parens(ast, value)?;
951    let bound = bound_names(binding);
952    let context_free =
953        |default: ExprId| -> Result<bool, CompilerFailure> { names_none_of(ast, default, &bound) };
954    if let Some(len) = tuple_source_len(ast, binding, value)? {
955        if let Binding::Array { defaults, .. } = binding {
956            let mut filled: BTreeMap<usize, ExprId> = BTreeMap::new();
957            for (index, default) in defaults.iter().take(len).enumerate() {
958                if let Some(default) = *default
959                    && context_free(default)?
960                {
961                    filled.insert(index, default);
962                }
963            }
964            if !filled.is_empty() {
965                ast.tuple_pattern_defaults.insert(value, filled);
966            }
967        }
968        ast.tuple_pattern_sources.insert(value);
969        if is_const {
970            ast.const_pattern_sources.insert(value);
971        }
972        return Ok(Some(LiteralSource::Tuple(len)));
973    }
974    if let Binding::Object { fields, .. } = binding
975        && let Some(names) = object_literal_names(ast, value)?
976    {
977        let mut defaulted: BTreeMap<String, Option<ExprId>> = BTreeMap::new();
978        for field in fields
979            .iter()
980            .filter(|field| names.contains(&field.source.name))
981        {
982            if let Some(default) = field.default {
983                let context = context_free(default)?.then_some(default);
984                defaulted.insert(field.source.name.clone(), context);
985            }
986        }
987        if !defaulted.is_empty() {
988            ast.object_pattern_sources.insert(value, defaulted);
989        }
990        if is_const {
991            ast.const_pattern_sources.insert(value);
992        }
993        return Ok(Some(LiteralSource::Object(names)));
994    }
995    mark_operand_tuple_sources(ast, binding, value)?;
996    Ok(None)
997}
998
999/// The names `binding` declares.
1000fn bound_names(binding: &Binding) -> BTreeSet<&str> {
1001    match binding {
1002        Binding::Object { fields, rest, .. } => fields
1003            .iter()
1004            .map(|field| field.local.name.as_str())
1005            .chain(rest.iter().map(|rest| rest.name.as_str()))
1006            .collect(),
1007        Binding::Array { elems, rest, .. } => elems
1008            .iter()
1009            .flatten()
1010            .chain(rest.iter())
1011            .map(|name| name.name.as_str())
1012            .collect(),
1013    }
1014}
1015
1016/// Whether `default` can be typed before its pattern binds `names`, as the
1017/// context of the pattern source's literal: a literal, or a reference whose
1018/// root is none of them. Any other default may read a name the pattern binds,
1019/// which a trial ahead of the pattern would resolve to an outer binding.
1020fn names_none_of(
1021    ast: &Ast,
1022    default: ExprId,
1023    names: &BTreeSet<&str>,
1024) -> Result<bool, CompilerFailure> {
1025    let mut expr = peel_parens(ast, default)?;
1026    loop {
1027        let kind = &ast
1028            .try_expr(expr)
1029            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
1030            .kind;
1031        match kind {
1032            ExprKind::Identifier(name) => return Ok(!names.contains(name.name.as_str())),
1033            ExprKind::FieldAccess { receiver, .. } => expr = peel_parens(ast, *receiver)?,
1034            ExprKind::Unary { operand, .. } | ExprKind::As { expr: operand, .. } => {
1035                expr = peel_parens(ast, *operand)?;
1036            }
1037            ExprKind::Number(_) | ExprKind::String(_) | ExprKind::Boolean(_) | ExprKind::Null => {
1038                return Ok(true);
1039            }
1040            _ => return Ok(false),
1041        }
1042    }
1043}
1044
1045/// Whether `expr` mentions any of `names`, inside a nested function too,
1046/// other than where a nested function's parameter or own name shadows it.
1047/// A nested `let` or `const` of the name still counts as a mention.
1048fn reads_any_of(
1049    ast: &Ast,
1050    expr: ExprId,
1051    names: &BTreeSet<String>,
1052) -> Result<bool, CompilerFailure> {
1053    let span = ast
1054        .try_expr(expr)
1055        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
1056        .span;
1057    let mut shadowing: Vec<(Span, &str)> = Vec::new();
1058    let mut mentions: Vec<(Span, &str)> = Vec::new();
1059    for inner in ast.exprs_within(span) {
1060        match &inner.kind {
1061            ExprKind::Identifier(name) if names.contains(&name.name) => {
1062                mentions.push((inner.span, name.name.as_str()));
1063            }
1064            ExprKind::Arrow { params, .. } => {
1065                for param in params {
1066                    let declared = match &param.pattern {
1067                        Some(pattern) => bound_names(pattern),
1068                        None => BTreeSet::from([param.name.name.as_str()]),
1069                    };
1070                    shadowing.extend(declared.into_iter().map(|name| (inner.span, name)));
1071                }
1072            }
1073            ExprKind::FunctionExpression {
1074                name: Some(name), ..
1075            } => shadowing.push((inner.span, name.name.as_str())),
1076            _ => {}
1077        }
1078    }
1079    Ok(mentions.iter().any(|(at, name)| {
1080        !shadowing
1081            .iter()
1082            .any(|(region, shadowed)| shadowed == name && region.encloses(*at))
1083    }))
1084}
1085
1086/// The properties every object literal inherits from `Object.prototype`.
1087pub(crate) const OBJECT_PROTOTYPE_MEMBERS: &[&str] = &[
1088    "constructor",
1089    "hasOwnProperty",
1090    "isPrototypeOf",
1091    "propertyIsEnumerable",
1092    "toLocaleString",
1093    "toString",
1094    "valueOf",
1095    "__proto__",
1096    "__defineGetter__",
1097    "__defineSetter__",
1098    "__lookupGetter__",
1099    "__lookupSetter__",
1100];
1101
1102/// The defaults of an array pattern's slots from `len` on, when each is a
1103/// named slot with a default.
1104fn defaults_past_end(binding: &Binding, len: usize) -> Option<Vec<Option<ExprId>>> {
1105    let Binding::Array {
1106        elems, defaults, ..
1107    } = binding
1108    else {
1109        return None;
1110    };
1111    // A hole reads nothing, so it pads with no default. A named slot without a
1112    // default leaves the tuple as it is: reading past its end is an error.
1113    let past: Vec<Option<ExprId>> = elems
1114        .iter()
1115        .zip(defaults)
1116        .skip(len)
1117        .map(|(slot, default)| match slot {
1118            None => Some(None),
1119            Some(_) => default.map(Some),
1120        })
1121        .collect::<Option<_>>()?;
1122    past.iter().any(Option::is_some).then_some(past)
1123}
1124
1125/// The properties an object pattern gives defaults that a literal with
1126/// `names` lacks and doesn't inherit, with their defaults.
1127fn defaults_absent_from(binding: &Binding, names: &BTreeSet<String>) -> Vec<(String, ExprId)> {
1128    let Binding::Object { fields, .. } = binding else {
1129        return Vec::new();
1130    };
1131    fields
1132        .iter()
1133        .filter(|field| {
1134            !names.contains(&field.source.name)
1135                && !OBJECT_PROTOTYPE_MEMBERS.contains(&field.source.name.as_str())
1136        })
1137        .filter_map(|field| Some((field.source.name.clone(), field.default?)))
1138        .collect()
1139}
1140
1141fn peel_parens(ast: &Ast, value: ExprId) -> Result<ExprId, CompilerFailure> {
1142    ast.try_peel_parens(value)
1143        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))
1144}
1145
1146/// An array literal the pattern may take apart as one operand of `value`, a
1147/// fallback (`x ?? []`, `x || []`) or a conditional's branch, is a tuple as in
1148/// tsc, where the pattern is the context of every such operand.
1149fn mark_operand_tuple_sources(
1150    ast: &mut Ast,
1151    binding: &Binding,
1152    value: ExprId,
1153) -> Result<(), CompilerFailure> {
1154    let operands = match &ast
1155        .try_expr(value)
1156        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
1157        .kind
1158    {
1159        ExprKind::Paren(inner) => vec![*inner],
1160        ExprKind::Binary {
1161            op: crate::BinOp::NullishCoalesce | crate::BinOp::Or,
1162            rhs,
1163            ..
1164        } => vec![*rhs],
1165        ExprKind::Ternary { then_, else_, .. } => vec![*then_, *else_],
1166        _ => return Ok(()),
1167    };
1168    for operand in operands {
1169        if tuple_source_len(ast, binding, operand)?.is_some() {
1170            ast.tuple_pattern_sources.insert(operand);
1171        } else {
1172            mark_operand_tuple_sources(ast, binding, operand)?;
1173        }
1174    }
1175    Ok(())
1176}
1177
1178/// The property names of `value` when it is an object literal whose every
1179/// property is named.
1180fn object_literal_names(
1181    ast: &Ast,
1182    value: ExprId,
1183) -> Result<Option<BTreeSet<String>>, CompilerFailure> {
1184    let ExprKind::ObjectLiteral { members } = &ast
1185        .try_expr(value)
1186        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
1187        .kind
1188    else {
1189        return Ok(None);
1190    };
1191    let mut names = BTreeSet::new();
1192    for member in members {
1193        let crate::ObjectLiteralMember::Field(field) = member else {
1194            return Ok(None);
1195        };
1196        names.insert(field.name.name.clone());
1197    }
1198    Ok(Some(names))
1199}
1200
1201/// The length of `value` when it is an array literal without spreads that
1202/// `binding`, an array pattern, takes apart slot by slot, so TypeScript types
1203/// it as a tuple. A pattern of only a rest element takes the literal whole, as
1204/// an array.
1205fn tuple_source_len(
1206    ast: &Ast,
1207    binding: &Binding,
1208    value: ExprId,
1209) -> Result<Option<usize>, CompilerFailure> {
1210    let Binding::Array { elems, .. } = binding else {
1211        return Ok(None);
1212    };
1213    if elems.is_empty() {
1214        return Ok(None);
1215    }
1216    let ExprKind::ArrayLiteral { elements } = &ast
1217        .try_expr(value)
1218        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
1219        .kind
1220    else {
1221        return Ok(None);
1222    };
1223    let has_spread = elements
1224        .iter()
1225        .any(|element| matches!(element, crate::ArrayLiteralElement::Spread { .. }));
1226    Ok((!has_spread).then_some(elements.len()))
1227}
1228
1229/// `source.slice(from, source.length)`: the elements an array pattern's rest
1230/// element collects.
1231fn array_rest(
1232    ast: &mut Ast,
1233    source: &Ident,
1234    from: usize,
1235    span: Span,
1236) -> Result<ExprId, CompilerFailure> {
1237    let from_lit = push_expr(ast, ExprKind::Number(from as f64), span)?;
1238    let slice_receiver = push_expr(ast, ExprKind::Identifier(source.clone()), span)?;
1239    let slice_callee = push_expr(
1240        ast,
1241        ExprKind::FieldAccess {
1242            receiver: slice_receiver,
1243            name: Ident {
1244                name: "slice".to_string(),
1245                span,
1246            },
1247        },
1248        span,
1249    )?;
1250    let length_receiver = push_expr(ast, ExprKind::Identifier(source.clone()), span)?;
1251    let length_access = push_expr(
1252        ast,
1253        ExprKind::FieldAccess {
1254            receiver: length_receiver,
1255            name: Ident {
1256                name: "length".to_string(),
1257                span,
1258            },
1259        },
1260        span,
1261    )?;
1262    let slice_call = push_expr(
1263        ast,
1264        ExprKind::Call {
1265            callee: slice_callee,
1266            type_args: None,
1267            args: vec![from_lit, length_access],
1268        },
1269        span,
1270    )?;
1271    Ok(slice_call)
1272}
1273
1274/// `[source[from], …, source[to - 1]].slice(0, to - from)`: the rest of a
1275/// tuple-typed literal. The remaining elements form a tuple of their own, and
1276/// slicing it yields an array of their types: one that can grow, where
1277/// TypeScript types the rest as a tuple.
1278fn remaining_elements(
1279    ast: &mut Ast,
1280    source: &Ident,
1281    indices: std::ops::Range<usize>,
1282    span: Span,
1283) -> Result<ExprId, CompilerFailure> {
1284    let count = indices.len();
1285    let mut elements = Vec::with_capacity(count);
1286    for i in indices {
1287        let receiver = push_expr(ast, ExprKind::Identifier(source.clone()), span)?;
1288        let index = push_expr(ast, ExprKind::Number(i as f64), span)?;
1289        let access = push_expr(ast, ExprKind::IndexAccess { receiver, index }, span)?;
1290        elements.push(crate::ArrayLiteralElement::Value(access));
1291    }
1292    let remaining = push_expr(ast, ExprKind::ArrayLiteral { elements }, span)?;
1293    ast.tuple_pattern_sources.insert(remaining);
1294    let slice = push_expr(
1295        ast,
1296        ExprKind::FieldAccess {
1297            receiver: remaining,
1298            name: Ident {
1299                name: "slice".to_string(),
1300                span,
1301            },
1302        },
1303        span,
1304    )?;
1305    let from = push_expr(ast, ExprKind::Number(0.0), span)?;
1306    let to = push_expr(ast, ExprKind::Number(count as f64), span)?;
1307    push_expr(
1308        ast,
1309        ExprKind::Call {
1310            callee: slice,
1311            type_args: None,
1312            args: vec![from, to],
1313        },
1314        span,
1315    )
1316}
1317
1318fn push_expr(ast: &mut Ast, kind: ExprKind, span: Span) -> Result<ExprId, CompilerFailure> {
1319    ast.try_push_expr(Expr { kind, span })
1320        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))
1321}
1322
1323/// `void 0`, which is `undefined` even where a source binding shadows the name.
1324fn push_void_zero(ast: &mut Ast, span: Span) -> Result<ExprId, CompilerFailure> {
1325    let zero = push_expr(ast, ExprKind::Number(0.0), span)?;
1326    push_expr(ast, ExprKind::Void { operand: zero }, span)
1327}
1328
1329fn make_decl(
1330    is_const: bool,
1331    name: Ident,
1332    value: ExprId,
1333    doc: Option<crate::DocComment>,
1334) -> StmtKind {
1335    if is_const {
1336        StmtKind::Const {
1337            name,
1338            ty: None,
1339            value,
1340            doc,
1341        }
1342    } else {
1343        StmtKind::Let {
1344            name,
1345            ty: None,
1346            value,
1347            doc,
1348        }
1349    }
1350}
1351
1352/// The names the `let` statements among `stmts` declare.
1353fn let_names(ast: &Ast, stmts: &[StmtId]) -> Result<Vec<Ident>, CompilerFailure> {
1354    let mut names = Vec::new();
1355    for &stmt in stmts {
1356        if let StmtKind::Let { name, .. } = &ast
1357            .try_stmt(stmt)
1358            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
1359            .kind
1360        {
1361            names.push(name.clone());
1362        }
1363    }
1364    Ok(names)
1365}
1366
1367fn lowering_failure(message: &str) -> CompilerFailure {
1368    CompilerFailure::Internal {
1369        stage: CompilerStage::Infer,
1370        span: None,
1371        message: message.into(),
1372    }
1373}
1374
1375#[cfg(test)]
1376mod tests {
1377    use super::lower;
1378    use crate::{Asi, Binding, Stmt, StmtKind, Token, TokenKind, parse};
1379
1380    fn pipeline(source: &str) -> crate::Ast {
1381        let mut asi = Asi::new(source, crate::FileId(0));
1382        let mut tokens: Vec<Token> = Vec::new();
1383        loop {
1384            let t = asi.next_token();
1385            let is_eof = matches!(t.kind, TokenKind::Eof);
1386            tokens.push(t);
1387            if is_eof {
1388                break;
1389            }
1390        }
1391        let _ = asi.into_diagnostics();
1392        let (mut ast, diags) = parse(source, tokens, crate::FileId(0));
1393        assert!(diags.is_empty(), "unexpected parse diags: {diags:?}");
1394        ast = lower(ast).unwrap();
1395        ast
1396    }
1397
1398    fn no_patterns_left(ast: &crate::Ast) {
1399        for i in 0..ast.stmts_len() {
1400            let s = ast.try_stmt(crate::StmtId(i as u32)).unwrap();
1401            assert!(
1402                !matches!(
1403                    s.kind,
1404                    StmtKind::LetPattern { .. }
1405                        | StmtKind::ConstPattern { .. }
1406                        | StmtKind::ForOfPattern { .. }
1407                ),
1408                "found unlowered pattern at stmt #{i}: {:?}",
1409                s.kind,
1410            );
1411            if let StmtKind::Function { params, .. } = &s.kind {
1412                for (pi, p) in params.iter().enumerate() {
1413                    assert!(
1414                        p.pattern.is_none(),
1415                        "function param #{pi} still has a pattern after lowering",
1416                    );
1417                }
1418            }
1419        }
1420        for i in 0..ast.exprs_len() {
1421            let e = ast.try_expr(crate::ExprId(i as u32)).unwrap();
1422            if let crate::ExprKind::Arrow { params, .. } = &e.kind {
1423                for (pi, p) in params.iter().enumerate() {
1424                    assert!(
1425                        p.pattern.is_none(),
1426                        "arrow param #{pi} still has a pattern after lowering",
1427                    );
1428                }
1429            }
1430        }
1431    }
1432
1433    #[test]
1434    fn lowers_simple_object_destructure() {
1435        let ast = pipeline("const { a, b } = obj;");
1436        no_patterns_left(&ast);
1437        // top_level: __dst, a-decl, b-decl
1438        assert_eq!(ast.top_level.len(), 3);
1439        let names: Vec<String> = ast
1440            .top_level
1441            .iter()
1442            .map(|id| match &ast.try_stmt(*id).unwrap().kind {
1443                StmtKind::Const { name, .. } => name.name.clone(),
1444                other => panic!("expected Const, got {other:?}"),
1445            })
1446            .collect();
1447        assert!(names[0].starts_with("#pattern_dst_"));
1448        assert_eq!(names[1], "a");
1449        assert_eq!(names[2], "b");
1450    }
1451
1452    #[test]
1453    fn lowers_renamed_object_destructure() {
1454        let ast = pipeline("const { a: x, b: y } = obj;");
1455        no_patterns_left(&ast);
1456        let names: Vec<String> = ast
1457            .top_level
1458            .iter()
1459            .map(|id| match &ast.try_stmt(*id).unwrap().kind {
1460                StmtKind::Const { name, .. } => name.name.clone(),
1461                other => panic!("expected Const, got {other:?}"),
1462            })
1463            .collect();
1464        assert_eq!(names[1], "x");
1465        assert_eq!(names[2], "y");
1466    }
1467
1468    #[test]
1469    fn lowers_array_destructure() {
1470        let ast = pipeline("const [x, y] = arr;");
1471        no_patterns_left(&ast);
1472        assert_eq!(ast.top_level.len(), 3);
1473        if let StmtKind::Const { value, .. } = &ast.try_stmt(ast.top_level[1]).unwrap().kind {
1474            assert!(matches!(
1475                ast.try_expr(*value).unwrap().kind,
1476                crate::ExprKind::IndexAccess { .. }
1477            ));
1478        }
1479    }
1480
1481    #[test]
1482    fn lowers_array_with_hole_and_rest() {
1483        let ast = pipeline("const [, x, ...rest] = arr;");
1484        no_patterns_left(&ast);
1485        // __dst, x (= arr[1]), rest (= arr.slice(2))
1486        assert_eq!(ast.top_level.len(), 3);
1487        if let StmtKind::Const { name, value, .. } = &ast.try_stmt(ast.top_level[1]).unwrap().kind {
1488            assert_eq!(name.name, "x");
1489            if let crate::ExprKind::IndexAccess { index, .. } = &ast.try_expr(*value).unwrap().kind
1490            {
1491                assert_eq!(
1492                    ast.try_expr(*index).unwrap().kind,
1493                    crate::ExprKind::Number(1.0)
1494                );
1495            } else {
1496                panic!("expected IndexAccess");
1497            }
1498        }
1499        if let StmtKind::Const { name, value, .. } = &ast.try_stmt(ast.top_level[2]).unwrap().kind {
1500            assert_eq!(name.name, "rest");
1501            match &ast.try_expr(*value).unwrap().kind {
1502                crate::ExprKind::Call { callee, args, .. } => {
1503                    assert_eq!(args.len(), 2, "slice takes (start, end)");
1504                    assert_eq!(
1505                        ast.try_expr(args[0]).unwrap().kind,
1506                        crate::ExprKind::Number(2.0)
1507                    );
1508                    match &ast.try_expr(*callee).unwrap().kind {
1509                        crate::ExprKind::FieldAccess { name, .. } => {
1510                            assert_eq!(name.name, "slice");
1511                        }
1512                        other => panic!("expected slice FieldAccess, got {other:?}"),
1513                    }
1514                }
1515                other => panic!("expected Call, got {other:?}"),
1516            }
1517        }
1518    }
1519
1520    #[test]
1521    fn lowers_object_rest_to_object_rest() {
1522        let ast = pipeline("const { a, ...rest } = obj;");
1523        no_patterns_left(&ast);
1524        assert_eq!(ast.top_level.len(), 3);
1525        match &ast.try_stmt(ast.top_level[2]).unwrap().kind {
1526            StmtKind::ObjectRest {
1527                is_const,
1528                name,
1529                exclude,
1530                ..
1531            } => {
1532                assert!(*is_const);
1533                assert_eq!(name.name, "rest");
1534                assert_eq!(exclude.len(), 1);
1535                assert_eq!(exclude[0].name, "a");
1536            }
1537            other => panic!("expected ObjectRest, got {other:?}"),
1538        }
1539    }
1540
1541    #[test]
1542    fn lowers_function_param_pattern() {
1543        let src = "function f({ a, b }: T): number { return a + b; }";
1544        let ast = pipeline(src);
1545        no_patterns_left(&ast);
1546        let Stmt {
1547            kind: StmtKind::Function { body, params, .. },
1548            ..
1549        } = ast.try_stmt(ast.top_level[0]).unwrap().clone()
1550        else {
1551            panic!("expected Function");
1552        };
1553        assert_eq!(params.len(), 1);
1554        assert!(params[0].pattern.is_none());
1555        assert!(params[0].name.name.starts_with("#pattern_p_"));
1556        let body_stmts = match &ast.try_stmt(body).unwrap().kind {
1557            StmtKind::Block(s) => s.clone(),
1558            _ => panic!("body is not a Block"),
1559        };
1560        assert_eq!(body_stmts.len(), 1);
1561        let bindings = ast.parameter_bindings.get(&params[0].name.span).unwrap();
1562        assert_eq!(bindings.len(), 2);
1563        if let StmtKind::Let { name, .. } = &ast.try_stmt(bindings[0]).unwrap().kind {
1564            assert_eq!(name.name, "a");
1565        } else {
1566            panic!("expected Let a");
1567        }
1568        if let StmtKind::Let { name, .. } = &ast.try_stmt(bindings[1]).unwrap().kind {
1569            assert_eq!(name.name, "b");
1570        } else {
1571            panic!("expected Let b");
1572        }
1573        assert!(matches!(
1574            ast.try_stmt(body_stmts[0]).unwrap().kind,
1575            StmtKind::Return(_)
1576        ));
1577    }
1578
1579    #[test]
1580    fn parameter_object_rest_has_a_mutable_authored_binding() {
1581        let ast = pipeline("function f({ head, ...tail }: T): number { return tail.value; }");
1582        let StmtKind::Function { params, .. } = &ast.try_stmt(ast.top_level[0]).unwrap().kind
1583        else {
1584            panic!("expected function");
1585        };
1586        let bindings = ast.parameter_bindings.get(&params[0].name.span).unwrap();
1587        assert_eq!(bindings.len(), 2);
1588        let StmtKind::ObjectRest { is_const, name, .. } = &ast.try_stmt(bindings[1]).unwrap().kind
1589        else {
1590            panic!("expected object rest binding");
1591        };
1592        assert!(!*is_const);
1593        assert_eq!(name.name, "tail");
1594    }
1595
1596    #[test]
1597    fn lowers_arrow_expr_body_with_pattern() {
1598        let src = "const f = ({a, b}: T): number => a + b;";
1599        let ast = pipeline(src);
1600        no_patterns_left(&ast);
1601        let mut found = false;
1602        for i in 0..ast.exprs_len() {
1603            let e = ast.try_expr(crate::ExprId(i as u32)).unwrap();
1604            if let crate::ExprKind::Arrow { params, body, .. } = &e.kind {
1605                assert!(matches!(body, crate::ArrowBody::Expr(_)));
1606                assert_eq!(
1607                    ast.parameter_bindings
1608                        .get(&params[0].name.span)
1609                        .unwrap()
1610                        .len(),
1611                    2
1612                );
1613                found = true;
1614            }
1615        }
1616        assert!(found, "no Arrow found in expr arena");
1617    }
1618
1619    #[test]
1620    fn lowers_for_of_array_pattern() {
1621        // `for (const [a, b] of pairs) { … }` becomes
1622        // `for (const #pattern_dst_N of pairs) { const a = #pattern_dst_N[0];
1623        // const b = #pattern_dst_N[1]; { ... } }` after lowering.
1624        let src = "function main(): void { for (const [a, b] of pairs) { use(a, b); } }";
1625        let ast = pipeline(src);
1626        no_patterns_left(&ast);
1627
1628        let Stmt {
1629            kind: StmtKind::Function { body, .. },
1630            ..
1631        } = ast.try_stmt(ast.top_level[0]).unwrap().clone()
1632        else {
1633            panic!("expected Function");
1634        };
1635        let body_stmts = match &ast.try_stmt(body).unwrap().kind {
1636            StmtKind::Block(s) => s.clone(),
1637            _ => panic!("function body is not a Block"),
1638        };
1639        assert_eq!(body_stmts.len(), 1, "main body has just the for-of");
1640
1641        let (loop_name, loop_body) = match &ast.try_stmt(body_stmts[0]).unwrap().kind {
1642            StmtKind::ForOf { name, body, .. } => (name.name.clone(), *body),
1643            other => panic!("expected ForOf, got {other:?}"),
1644        };
1645        assert!(
1646            loop_name.starts_with("#pattern_dst_"),
1647            "loop var was rewritten to a synth temp; got {loop_name}",
1648        );
1649
1650        let loop_body_stmts = match &ast.try_stmt(loop_body).unwrap().kind {
1651            StmtKind::Block(s) => s.clone(),
1652            _ => panic!("for-of body is not a Block"),
1653        };
1654        assert_eq!(loop_body_stmts.len(), 3);
1655        if let StmtKind::Const { name, value, .. } = &ast.try_stmt(loop_body_stmts[0]).unwrap().kind
1656        {
1657            assert_eq!(name.name, "a");
1658            assert!(matches!(
1659                ast.try_expr(*value).unwrap().kind,
1660                crate::ExprKind::IndexAccess { .. }
1661            ));
1662            // Origin-tagged so the typechecker can rephrase index errors.
1663            assert!(ast.pattern_origins.contains_key(value));
1664        } else {
1665            panic!("expected Const a");
1666        }
1667        if let StmtKind::Const { name, .. } = &ast.try_stmt(loop_body_stmts[1]).unwrap().kind {
1668            assert_eq!(name.name, "b");
1669        } else {
1670            panic!("expected Const b");
1671        }
1672    }
1673
1674    #[test]
1675    fn lowers_let_pattern_preserves_mutability() {
1676        // __dst is const; per-field bindings follow source kind (`let` stays `let`)
1677        let ast = pipeline("let { a } = obj;");
1678        no_patterns_left(&ast);
1679        // __dst is Const; `a` is Let.
1680        assert!(matches!(
1681            ast.try_stmt(ast.top_level[0]).unwrap().kind,
1682            StmtKind::Const { .. }
1683        ));
1684        assert!(matches!(
1685            ast.try_stmt(ast.top_level[1]).unwrap().kind,
1686            StmtKind::Let { .. }
1687        ));
1688    }
1689
1690    #[test]
1691    fn no_op_on_plain_program() {
1692        let src = "const x = 1; function main(): void { }";
1693        let before = {
1694            let mut asi = Asi::new(src, crate::FileId(0));
1695            let mut tokens: Vec<Token> = Vec::new();
1696            loop {
1697                let t = asi.next_token();
1698                let is_eof = matches!(t.kind, TokenKind::Eof);
1699                tokens.push(t);
1700                if is_eof {
1701                    break;
1702                }
1703            }
1704            let _ = asi.into_diagnostics();
1705            let (ast, _) = parse(src, tokens, crate::FileId(0));
1706            ast
1707        };
1708        let after = pipeline(src);
1709        assert_eq!(format!("{before:?}"), format!("{after:?}"));
1710    }
1711
1712    #[test]
1713    fn lowers_pattern_in_nested_block() {
1714        let src = "function main(): void { const { a } = obj; }";
1715        let ast = pipeline(src);
1716        no_patterns_left(&ast);
1717    }
1718
1719    #[test]
1720    fn idempotent() {
1721        let src = "const { a, b } = obj;";
1722        let mut ast1 = pipeline(src);
1723        let snapshot = format!("{ast1:?}");
1724        ast1 = lower(ast1).unwrap();
1725        assert_eq!(snapshot, format!("{ast1:?}"));
1726    }
1727
1728    #[test]
1729    fn binding_span_preserved() {
1730        let (ast, _) = parse(
1731            "const { a } = obj;",
1732            tokens_of("const { a } = obj;"),
1733            crate::FileId(0),
1734        );
1735        match &ast.try_stmt(ast.top_level[0]).unwrap().kind {
1736            crate::StmtKind::ConstPattern {
1737                binding: Binding::Object { span, .. },
1738                ..
1739            } => {
1740                // `{ a }` starts at offset 6 and ends at 11 (inclusive of `}`).
1741                assert_eq!(*span, crate::Span::new(crate::FileId(0), 6, 11).unwrap());
1742            }
1743            other => panic!("expected ConstPattern Object, got {other:?}"),
1744        }
1745    }
1746
1747    #[test]
1748    fn defaults_save_each_access_once_and_test_undefined() {
1749        for source in [
1750            "const { a = fallback() } = source();",
1751            "const [a = fallback()] = source();",
1752        ] {
1753            let ast = pipeline(source);
1754            assert_eq!(ast.top_level.len(), 3);
1755            let StmtKind::Const {
1756                value: saved_access,
1757                ..
1758            } = ast.try_stmt(ast.top_level[1]).unwrap().kind
1759            else {
1760                panic!("expected saved read")
1761            };
1762            assert!(matches!(
1763                ast.try_expr(saved_access).unwrap().kind,
1764                crate::ExprKind::FieldAccess { .. } | crate::ExprKind::Ternary { .. }
1765            ));
1766            let StmtKind::Const { value, .. } = ast.try_stmt(ast.top_level[2]).unwrap().kind else {
1767                panic!("expected binding")
1768            };
1769            let crate::ExprKind::Ternary { cond, then_, else_ } = ast.try_expr(value).unwrap().kind
1770            else {
1771                panic!("expected lazy default")
1772            };
1773            let crate::ExprKind::Binary {
1774                op: crate::BinOp::Eq,
1775                lhs,
1776                rhs,
1777            } = ast.try_expr(cond).unwrap().kind
1778            else {
1779                panic!("expected undefined comparison")
1780            };
1781            assert_eq!(lhs, else_);
1782            assert!(matches!(
1783                ast.try_expr(rhs).unwrap().kind,
1784                crate::ExprKind::Void { .. }
1785            ));
1786            assert!(matches!(
1787                ast.try_expr(then_).unwrap().kind,
1788                crate::ExprKind::Call { .. }
1789            ));
1790        }
1791    }
1792
1793    #[test]
1794    fn array_defaults_keep_original_indices_and_origin_metadata() {
1795        let ast = pipeline("const [, a = 1] = values;");
1796        let StmtKind::Const { value, .. } = ast.try_stmt(ast.top_level[1]).unwrap().kind else {
1797            panic!("expected saved index read")
1798        };
1799        let crate::ExprKind::Ternary {
1800            cond,
1801            then_: access,
1802            else_,
1803        } = ast.try_expr(value).unwrap().kind
1804        else {
1805            panic!("expected bounds guard")
1806        };
1807        assert!(matches!(
1808            ast.try_expr(cond).unwrap().kind,
1809            crate::ExprKind::Binary {
1810                op: crate::BinOp::Lt,
1811                ..
1812            }
1813        ));
1814        assert!(matches!(
1815            ast.try_expr(else_).unwrap().kind,
1816            crate::ExprKind::Void { .. }
1817        ));
1818        let crate::ExprKind::IndexAccess { index, .. } = ast.try_expr(access).unwrap().kind else {
1819            panic!("expected guarded index")
1820        };
1821        assert!(matches!(
1822            ast.try_expr(index).unwrap().kind,
1823            crate::ExprKind::Number(1.0)
1824        ));
1825        assert_eq!(ast.pattern_origins.get(&access).unwrap().slot_arity, 2);
1826    }
1827
1828    #[test]
1829    fn lowers_class_method_parameter_defaults() {
1830        let ast = pipeline("class C { method({ x = 1 }: T): number { return x; } }");
1831        let StmtKind::ClassDecl { ref members, .. } = ast.try_stmt(ast.top_level[0]).unwrap().kind
1832        else {
1833            panic!("expected class")
1834        };
1835        let crate::ClassMember::Method { params, body, .. } = &members[0] else {
1836            panic!("expected method")
1837        };
1838        assert!(params[0].pattern.is_none());
1839        let StmtKind::Block(ref statements) = ast.try_stmt(*body).unwrap().kind else {
1840            panic!("expected block")
1841        };
1842        assert_eq!(statements.len(), 1);
1843        assert_eq!(
1844            ast.parameter_bindings
1845                .get(&params[0].name.span)
1846                .unwrap()
1847                .len(),
1848            2
1849        );
1850    }
1851
1852    fn tokens_of(source: &str) -> Vec<Token> {
1853        let mut asi = Asi::new(source, crate::FileId(0));
1854        let mut tokens = Vec::new();
1855        loop {
1856            let t = asi.next_token();
1857            let is_eof = matches!(t.kind, TokenKind::Eof);
1858            tokens.push(t);
1859            if is_eof {
1860                break;
1861            }
1862        }
1863        let _ = asi.into_diagnostics();
1864        tokens
1865    }
1866}