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 {
371                init: Some(init), ..
372            } = ast
373                .try_stmt(id)
374                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
375                .kind
376            else {
377                continue;
378            };
379            let is_let = match ast
380                .try_stmt(init)
381                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
382                .kind
383            {
384                StmtKind::LetPattern { .. } => true,
385                StmtKind::ConstPattern { .. } => false,
386                _ => continue,
387            };
388            self.hoist_for_pattern_init(ast, id, init, is_let)?;
389        }
390
391        Ok(())
392    }
393
394    /// `for (let [i, j] = a; c; u) body` becomes
395    /// `{ const dst = a; let i = dst[0]; let j = dst[1]; for (; c; u) body }`.
396    /// The bindings keep the loop's scope but lose the per-iteration copies a
397    /// `for` head's `let` gets, which only a closure capturing them observes;
398    /// inference rejects that case using [`Ast::for_pattern_init_bindings`].
399    fn hoist_for_pattern_init(
400        &mut self,
401        ast: &mut Ast,
402        id: StmtId,
403        init: StmtId,
404        is_let: bool,
405    ) -> Result<(), CompilerFailure> {
406        let mut hoisted = self.expand_pattern_stmt(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: None,
427                    condition,
428                    update,
429                    body,
430                },
431                span,
432            })
433            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
434        if is_let {
435            let bindings = let_names(ast, &hoisted)?;
436            if !bindings.is_empty() {
437                ast.for_pattern_init_bindings.insert(lowered_loop, bindings);
438            }
439        }
440        hoisted.push(lowered_loop);
441        ast.try_stmt_mut(id)
442            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
443            .kind = StmtKind::Block(hoisted);
444
445        Ok(())
446    }
447
448    fn lower_pattern_stmts(&mut self, ast: &mut Ast) -> Result<(), CompilerFailure> {
449        let top = std::mem::take(&mut ast.top_level);
450        ast.top_level = self.expand_list(ast, top)?;
451
452        for id in ast
453            .stmt_ids()
454            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
455        {
456            let needs = matches!(
457                ast.try_stmt(id)
458                    .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
459                    .kind,
460                StmtKind::Block(_)
461            );
462            if !needs {
463                continue;
464            }
465            let StmtKind::Block(taken) = std::mem::replace(
466                &mut ast
467                    .try_stmt_mut(id)
468                    .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
469                    .kind,
470                StmtKind::Block(Vec::new()),
471            ) else {
472                return Err(lowering_failure(
473                    "unexpected node kind during pattern lowering",
474                ));
475            };
476            let expanded = self.expand_list(ast, taken)?;
477            ast.try_stmt_mut(id)
478                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
479                .kind = StmtKind::Block(expanded);
480        }
481
482        Ok(())
483    }
484
485    fn expand_list(
486        &mut self,
487        ast: &mut Ast,
488        ids: Vec<StmtId>,
489    ) -> Result<Vec<StmtId>, CompilerFailure> {
490        let mut out = Vec::with_capacity(ids.len());
491        for id in ids {
492            let is_pattern = matches!(
493                ast.try_stmt(id)
494                    .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
495                    .kind,
496                StmtKind::LetPattern { .. } | StmtKind::ConstPattern { .. }
497            );
498            if !is_pattern {
499                out.push(id);
500                continue;
501            }
502            let expanded = self.expand_pattern_stmt(ast, id)?;
503            out.extend(expanded);
504        }
505        Ok(out)
506    }
507
508    fn expand_pattern_stmt(
509        &mut self,
510        ast: &mut Ast,
511        id: StmtId,
512    ) -> Result<Vec<StmtId>, CompilerFailure> {
513        let span = ast
514            .try_stmt(id)
515            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
516            .span;
517        let kind = std::mem::replace(
518            &mut ast
519                .try_stmt_mut(id)
520                .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
521                .kind,
522            StmtKind::Block(Vec::new()), // sentinel; the slot is unreachable post-lowering
523        );
524        let (is_const, binding, ty, value, doc) = match kind {
525            StmtKind::LetPattern {
526                binding,
527                ty,
528                value,
529                doc,
530            } => (false, binding, ty, value, doc),
531            StmtKind::ConstPattern {
532                binding,
533                ty,
534                value,
535                doc,
536            } => (true, binding, ty, value, doc),
537            _ => {
538                return Err(lowering_failure(
539                    "unexpected node kind during pattern lowering",
540                ));
541            }
542        };
543
544        let literal = match ty {
545            None => mark_literal_source(ast, &binding, value, is_const)?,
546            Some(_) => None,
547        };
548        let pattern_span = binding.span();
549        let dst = self.fresh(PATTERN_VALUE, pattern_span)?;
550        let annotated = ty.is_some();
551
552        // temp is always `const` even for `let` patterns — it's never re-assigned
553        let dst_stmt = ast
554            .try_push_stmt(Stmt {
555                kind: StmtKind::Const {
556                    name: dst.clone(),
557                    ty,
558                    value,
559                    doc: None,
560                },
561                span,
562            })
563            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
564
565        let mut out = vec![dst_stmt];
566        let root = PatternRoot {
567            is_const,
568            annotated,
569        };
570        out.extend(self.emit_decompose(ast, binding, dst, root, doc, literal.as_ref())?);
571        Ok(out)
572    }
573
574    fn materialise_pattern_params(
575        &mut self,
576        ast: &mut Ast,
577        params: &mut [ParamDecl],
578    ) -> Result<(), CompilerFailure> {
579        // A padding default is typed before the patterns bind and before any
580        // later parameter does, so one that reads such a name would resolve
581        // to an outer binding: its slot and the tuple slots after it stay out
582        // of the padding. An earlier plain parameter is already bound.
583        let pattern_names: BTreeSet<String> = params
584            .iter()
585            .filter_map(|param| param.pattern.as_ref())
586            .flat_map(|pattern| bound_names(pattern).into_iter().map(str::to_string))
587            .collect();
588        let mut unbound_by_param = Vec::with_capacity(params.len());
589        let mut unbound = pattern_names;
590        for param in params.iter().rev() {
591            unbound_by_param.push(unbound.clone());
592            if param.pattern.is_none() {
593                unbound.insert(param.name.name.clone());
594            }
595        }
596        unbound_by_param.reverse();
597        for (param, unbound) in params.iter_mut().zip(unbound_by_param) {
598            let Some(pattern) = param.pattern.take() else {
599                continue;
600            };
601            let fresh = self.fresh(PATTERN_PARAM, pattern.span())?;
602            param.name = fresh.clone();
603            let root = PatternRoot {
604                is_const: false,
605                annotated: param.ty.is_some(),
606            };
607            // An unannotated parameter's default is typed as a declaration's
608            // initializer is. The argument, not the default, is what the
609            // pattern takes apart: a caller or a contextual type may supply
610            // more elements or properties than the default has.
611            let literal = match (&param.ty, param.default) {
612                (None, Some(default)) => mark_literal_source(ast, &pattern, default, false)?,
613                _ => None,
614            };
615            if let Some(default) = param.default {
616                let literal_id = peel_parens(ast, default)?;
617                match &literal {
618                    Some(LiteralSource::Tuple(len)) => {
619                        if let Some(mut pad_defaults) = defaults_past_end(&pattern, *len) {
620                            let mut kept = 0;
621                            for slot in &pad_defaults {
622                                if let Some(slot_default) = slot
623                                    && reads_any_of(ast, *slot_default, &unbound)?
624                                {
625                                    break;
626                                }
627                                kept += 1;
628                            }
629                            pad_defaults.truncate(kept);
630                            if !pad_defaults.is_empty() {
631                                ast.tuple_pattern_pad_defaults
632                                    .insert(literal_id, pad_defaults);
633                            }
634                        }
635                    }
636                    Some(LiteralSource::Object(names)) => {
637                        let mut pad_defaults = Vec::new();
638                        for (name, pad_default) in defaults_absent_from(&pattern, names) {
639                            if !reads_any_of(ast, pad_default, &unbound)? {
640                                pad_defaults.push((name, pad_default));
641                            }
642                        }
643                        if !pad_defaults.is_empty() {
644                            ast.object_pattern_pad_defaults
645                                .insert(literal_id, pad_defaults);
646                        }
647                    }
648                    None => {}
649                }
650            }
651            let statements =
652                self.emit_decompose(ast, pattern, fresh, root, /*doc=*/ None, None)?;
653            if ast
654                .parameter_bindings
655                .insert(param.name.span, statements)
656                .is_some()
657            {
658                return Err(lowering_failure(
659                    "duplicate parameter binding span during pattern lowering",
660                ));
661            }
662        }
663
664        Ok(())
665    }
666
667    /// `doc` attaches to the first emitted stmt only. `literal` describes the
668    /// literal `source` holds, if the pattern types one.
669    fn emit_decompose(
670        &mut self,
671        ast: &mut Ast,
672        binding: Binding,
673        source: Ident,
674        root: PatternRoot,
675        mut doc: Option<crate::DocComment>,
676        literal: Option<&LiteralSource>,
677    ) -> Result<Vec<StmtId>, CompilerFailure> {
678        let is_const = root.is_const;
679        let mut out = Vec::new();
680        match binding {
681            Binding::Object {
682                fields,
683                rest,
684                span: _,
685            } => {
686                let bound_names: Vec<Ident> = fields.iter().map(|f| f.source.clone()).collect();
687                for field in fields {
688                    // A defaulted property the literal lacks reads `undefined`,
689                    // as tsc reads it, unless the literal inherits it.
690                    let absent = matches!(
691                        literal,
692                        Some(LiteralSource::Object(names)) if !names.contains(&field.source.name)
693                    ) && !OBJECT_PROTOTYPE_MEMBERS
694                        .contains(&field.source.name.as_str());
695                    let access = if absent && field.default.is_some() {
696                        push_void_zero(ast, field.span)?
697                    } else {
698                        let recv =
699                            push_expr(ast, ExprKind::Identifier(source.clone()), field.span)?;
700                        push_expr(
701                            ast,
702                            ExprKind::FieldAccess {
703                                receiver: recv,
704                                name: field.source.clone(),
705                            },
706                            field.span,
707                        )?
708                    };
709                    let value = self.apply_binding_default(
710                        ast,
711                        access,
712                        field.default,
713                        root.annotated,
714                        field.span,
715                        &mut out,
716                    )?;
717                    let decl = ast
718                        .try_push_stmt(Stmt {
719                            kind: make_decl(is_const, field.local, value, doc.take()),
720                            span: field.span,
721                        })
722                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
723                    out.push(decl);
724                }
725                if let Some(rest_ident) = rest {
726                    let src_expr = ast
727                        .try_push_expr(Expr {
728                            kind: ExprKind::Identifier(source.clone()),
729                            span: rest_ident.span,
730                        })
731                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
732                    let decl = ast
733                        .try_push_stmt(Stmt {
734                            kind: StmtKind::ObjectRest {
735                                is_const,
736                                name: rest_ident.clone(),
737                                source: src_expr,
738                                exclude: bound_names,
739                                ty: None,
740                                doc: doc.take(),
741                            },
742                            span: rest_ident.span,
743                        })
744                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
745                    out.push(decl);
746                }
747            }
748            Binding::Array {
749                elems,
750                defaults,
751                rest,
752                span: pattern_span,
753            } => {
754                if elems.len() != defaults.len() {
755                    return Err(lowering_failure(
756                        "array pattern defaults do not match element positions",
757                    ));
758                }
759                let elems_len = elems.len();
760                for (i, (slot, default)) in elems.into_iter().zip(defaults).enumerate() {
761                    let Some(local) = slot else {
762                        if default.is_some() {
763                            return Err(lowering_failure("array pattern hole has a default value"));
764                        }
765                        continue;
766                    };
767                    // A tuple literal holds nothing past its end, so a defaulted
768                    // slot there reads `undefined`, as tsc reads it, and the
769                    // literal keeps its own length.
770                    let past_tuple_end =
771                        matches!(literal, Some(LiteralSource::Tuple(len)) if i >= *len);
772                    let access = if default.is_some() && past_tuple_end {
773                        push_void_zero(ast, local.span)?
774                    } else {
775                        let origin = PatternOrigin {
776                            pattern_span,
777                            slot_arity: elems_len,
778                            defaulted: default.is_some(),
779                        };
780                        self.array_slot_read(ast, &source, i, origin, local.span)?
781                    };
782                    let value = self.apply_binding_default(
783                        ast,
784                        access,
785                        default,
786                        root.annotated,
787                        local.span,
788                        &mut out,
789                    )?;
790                    let decl = ast
791                        .try_push_stmt(Stmt {
792                            kind: make_decl(is_const, local.clone(), value, doc.take()),
793                            span: local.span,
794                        })
795                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
796                    out.push(decl);
797                }
798                if let Some(rest_ident) = rest {
799                    let rest_value = match literal {
800                        Some(&LiteralSource::Tuple(len)) if len > elems_len => {
801                            remaining_elements(ast, &source, elems_len..len, rest_ident.span)?
802                        }
803                        _ => array_rest(ast, &source, elems_len, rest_ident.span)?,
804                    };
805                    let decl = ast
806                        .try_push_stmt(Stmt {
807                            kind: make_decl(is_const, rest_ident.clone(), rest_value, doc.take()),
808                            span: rest_ident.span,
809                        })
810                        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
811                    out.push(decl);
812                }
813            }
814        }
815        Ok(out)
816    }
817
818    /// `source[index]`, tagged with `origin` so the typechecker words index
819    /// errors for the pattern. A defaulted slot's read is guarded by length.
820    fn array_slot_read(
821        &mut self,
822        ast: &mut Ast,
823        source: &Ident,
824        index: usize,
825        origin: PatternOrigin,
826        span: Span,
827    ) -> Result<ExprId, CompilerFailure> {
828        let receiver = push_expr(ast, ExprKind::Identifier(source.clone()), span)?;
829        let index = push_expr(ast, ExprKind::Number(index as f64), span)?;
830        let access = push_expr(ast, ExprKind::IndexAccess { receiver, index }, span)?;
831        let defaulted = origin.defaulted;
832        ast.pattern_origins.insert(access, origin);
833        if !defaulted {
834            return Ok(access);
835        }
836        self.guard_defaulted_array_read(ast, receiver, index, access, span)
837    }
838
839    /// A pattern default treats an absent element as undefined, while ordinary
840    /// index expressions retain their bounds checks.
841    fn guard_defaulted_array_read(
842        &mut self,
843        ast: &mut Ast,
844        receiver: ExprId,
845        index: ExprId,
846        access: ExprId,
847        span: Span,
848    ) -> Result<ExprId, CompilerFailure> {
849        let length = push_expr(
850            ast,
851            ExprKind::FieldAccess {
852                receiver,
853                name: Ident {
854                    name: "length".to_string(),
855                    span,
856                },
857            },
858            span,
859        )?;
860        let in_bounds = push_expr(
861            ast,
862            ExprKind::Binary {
863                op: crate::BinOp::Lt,
864                lhs: index,
865                rhs: length,
866            },
867            span,
868        )?;
869        let undefined = push_void_zero(ast, span)?;
870        push_expr(
871            ast,
872            ExprKind::Ternary {
873                cond: in_bounds,
874                then_: access,
875                else_: undefined,
876            },
877            span,
878        )
879    }
880
881    /// Save the property/index read once, then evaluate its default only for undefined.
882    /// `void 0` is immune to a source binding named `undefined`.
883    fn apply_binding_default(
884        &mut self,
885        ast: &mut Ast,
886        access: ExprId,
887        default: Option<ExprId>,
888        annotated: bool,
889        span: Span,
890        out: &mut Vec<StmtId>,
891    ) -> Result<ExprId, CompilerFailure> {
892        let Some(default) = default else {
893            return Ok(access);
894        };
895        let saved = self.fresh("value", span)?;
896        let declaration = ast
897            .try_push_stmt(Stmt {
898                kind: make_decl(true, saved.clone(), access, None),
899                span,
900            })
901            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?;
902        out.push(declaration);
903        let saved_value = push_expr(ast, ExprKind::Identifier(saved.clone()), span)?;
904        let undefined = push_void_zero(ast, span)?;
905        let condition = push_expr(
906            ast,
907            ExprKind::Binary {
908                op: crate::BinOp::Eq,
909                lhs: saved_value,
910                rhs: undefined,
911            },
912            span,
913        )?;
914        let selected = push_expr(
915            ast,
916            ExprKind::Ternary {
917                cond: condition,
918                then_: default,
919                else_: saved_value,
920            },
921            span,
922        )?;
923        ast.binding_defaults.insert(
924            selected,
925            crate::ast::BindingDefault {
926                saved,
927                default,
928                annotated,
929            },
930        );
931        Ok(selected)
932    }
933}
934
935/// A literal a pattern takes apart, which TypeScript types from the pattern.
936enum LiteralSource {
937    /// An array literal of this many elements, typed as a tuple.
938    Tuple(usize),
939    /// An object literal with these property names.
940    Object(BTreeSet<String>),
941}
942
943/// Describe the literal `value` that `binding` takes apart, and record how
944/// inference types it: an array literal as a tuple, and an object literal
945/// with each defaulted property the pattern reads optional, as tsc types it.
946fn mark_literal_source(
947    ast: &mut Ast,
948    binding: &Binding,
949    value: ExprId,
950    is_const: bool,
951) -> Result<Option<LiteralSource>, CompilerFailure> {
952    let value = peel_parens(ast, value)?;
953    let bound = bound_names(binding);
954    let context_free =
955        |default: ExprId| -> Result<bool, CompilerFailure> { names_none_of(ast, default, &bound) };
956    if let Some(len) = tuple_source_len(ast, binding, value)? {
957        if let Binding::Array { defaults, .. } = binding {
958            let mut filled: BTreeMap<usize, ExprId> = BTreeMap::new();
959            for (index, default) in defaults.iter().take(len).enumerate() {
960                if let Some(default) = *default
961                    && context_free(default)?
962                {
963                    filled.insert(index, default);
964                }
965            }
966            if !filled.is_empty() {
967                ast.tuple_pattern_defaults.insert(value, filled);
968            }
969        }
970        ast.tuple_pattern_sources.insert(value);
971        if is_const {
972            ast.const_pattern_sources.insert(value);
973        }
974        return Ok(Some(LiteralSource::Tuple(len)));
975    }
976    if let Binding::Object { fields, .. } = binding
977        && let Some(names) = object_literal_names(ast, value)?
978    {
979        let mut defaulted: BTreeMap<String, Option<ExprId>> = BTreeMap::new();
980        for field in fields
981            .iter()
982            .filter(|field| names.contains(&field.source.name))
983        {
984            if let Some(default) = field.default {
985                let context = context_free(default)?.then_some(default);
986                defaulted.insert(field.source.name.clone(), context);
987            }
988        }
989        if !defaulted.is_empty() {
990            ast.object_pattern_sources.insert(value, defaulted);
991        }
992        if is_const {
993            ast.const_pattern_sources.insert(value);
994        }
995        return Ok(Some(LiteralSource::Object(names)));
996    }
997    mark_operand_tuple_sources(ast, binding, value)?;
998    Ok(None)
999}
1000
1001/// The names `binding` declares.
1002fn bound_names(binding: &Binding) -> BTreeSet<&str> {
1003    match binding {
1004        Binding::Object { fields, rest, .. } => fields
1005            .iter()
1006            .map(|field| field.local.name.as_str())
1007            .chain(rest.iter().map(|rest| rest.name.as_str()))
1008            .collect(),
1009        Binding::Array { elems, rest, .. } => elems
1010            .iter()
1011            .flatten()
1012            .chain(rest.iter())
1013            .map(|name| name.name.as_str())
1014            .collect(),
1015    }
1016}
1017
1018/// Whether `default` can be typed before its pattern binds `names`, as the
1019/// context of the pattern source's literal: a literal, or a reference whose
1020/// root is none of them. Any other default may read a name the pattern binds,
1021/// which a trial ahead of the pattern would resolve to an outer binding.
1022fn names_none_of(
1023    ast: &Ast,
1024    default: ExprId,
1025    names: &BTreeSet<&str>,
1026) -> Result<bool, CompilerFailure> {
1027    let mut expr = peel_parens(ast, default)?;
1028    loop {
1029        let kind = &ast
1030            .try_expr(expr)
1031            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
1032            .kind;
1033        match kind {
1034            ExprKind::Identifier(name) => return Ok(!names.contains(name.name.as_str())),
1035            ExprKind::FieldAccess { receiver, .. } => expr = peel_parens(ast, *receiver)?,
1036            ExprKind::Unary { operand, .. } | ExprKind::As { expr: operand, .. } => {
1037                expr = peel_parens(ast, *operand)?;
1038            }
1039            ExprKind::Number(_) | ExprKind::String(_) | ExprKind::Boolean(_) | ExprKind::Null => {
1040                return Ok(true);
1041            }
1042            _ => return Ok(false),
1043        }
1044    }
1045}
1046
1047/// Whether `expr` mentions any of `names`, inside a nested function too,
1048/// other than where a nested function's parameter or own name shadows it.
1049/// A nested `let` or `const` of the name still counts as a mention.
1050fn reads_any_of(
1051    ast: &Ast,
1052    expr: ExprId,
1053    names: &BTreeSet<String>,
1054) -> Result<bool, CompilerFailure> {
1055    let span = ast
1056        .try_expr(expr)
1057        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
1058        .span;
1059    let mut shadowing: Vec<(Span, &str)> = Vec::new();
1060    let mut mentions: Vec<(Span, &str)> = Vec::new();
1061    for inner in ast.exprs_within(span) {
1062        match &inner.kind {
1063            ExprKind::Identifier(name) if names.contains(&name.name) => {
1064                mentions.push((inner.span, name.name.as_str()));
1065            }
1066            ExprKind::Arrow { params, .. } => {
1067                for param in params {
1068                    let declared = match &param.pattern {
1069                        Some(pattern) => bound_names(pattern),
1070                        None => BTreeSet::from([param.name.name.as_str()]),
1071                    };
1072                    shadowing.extend(declared.into_iter().map(|name| (inner.span, name)));
1073                }
1074            }
1075            ExprKind::FunctionExpression {
1076                name: Some(name), ..
1077            } => shadowing.push((inner.span, name.name.as_str())),
1078            _ => {}
1079        }
1080    }
1081    Ok(mentions.iter().any(|(at, name)| {
1082        !shadowing
1083            .iter()
1084            .any(|(region, shadowed)| shadowed == name && region.encloses(*at))
1085    }))
1086}
1087
1088/// The properties every object literal inherits from `Object.prototype`.
1089pub(crate) const OBJECT_PROTOTYPE_MEMBERS: &[&str] = &[
1090    "constructor",
1091    "hasOwnProperty",
1092    "isPrototypeOf",
1093    "propertyIsEnumerable",
1094    "toLocaleString",
1095    "toString",
1096    "valueOf",
1097    "__proto__",
1098    "__defineGetter__",
1099    "__defineSetter__",
1100    "__lookupGetter__",
1101    "__lookupSetter__",
1102];
1103
1104/// The defaults of an array pattern's slots from `len` on, when each is a
1105/// named slot with a default.
1106fn defaults_past_end(binding: &Binding, len: usize) -> Option<Vec<Option<ExprId>>> {
1107    let Binding::Array {
1108        elems, defaults, ..
1109    } = binding
1110    else {
1111        return None;
1112    };
1113    // A hole reads nothing, so it pads with no default. A named slot without a
1114    // default leaves the tuple as it is: reading past its end is an error.
1115    let past: Vec<Option<ExprId>> = elems
1116        .iter()
1117        .zip(defaults)
1118        .skip(len)
1119        .map(|(slot, default)| match slot {
1120            None => Some(None),
1121            Some(_) => default.map(Some),
1122        })
1123        .collect::<Option<_>>()?;
1124    past.iter().any(Option::is_some).then_some(past)
1125}
1126
1127/// The properties an object pattern gives defaults that a literal with
1128/// `names` lacks and doesn't inherit, with their defaults.
1129fn defaults_absent_from(binding: &Binding, names: &BTreeSet<String>) -> Vec<(String, ExprId)> {
1130    let Binding::Object { fields, .. } = binding else {
1131        return Vec::new();
1132    };
1133    fields
1134        .iter()
1135        .filter(|field| {
1136            !names.contains(&field.source.name)
1137                && !OBJECT_PROTOTYPE_MEMBERS.contains(&field.source.name.as_str())
1138        })
1139        .filter_map(|field| Some((field.source.name.clone(), field.default?)))
1140        .collect()
1141}
1142
1143fn peel_parens(ast: &Ast, value: ExprId) -> Result<ExprId, CompilerFailure> {
1144    ast.try_peel_parens(value)
1145        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))
1146}
1147
1148/// An array literal the pattern may take apart as one operand of `value`, a
1149/// fallback (`x ?? []`, `x || []`) or a conditional's branch, is a tuple as in
1150/// tsc, where the pattern is the context of every such operand.
1151fn mark_operand_tuple_sources(
1152    ast: &mut Ast,
1153    binding: &Binding,
1154    value: ExprId,
1155) -> Result<(), CompilerFailure> {
1156    let operands = match &ast
1157        .try_expr(value)
1158        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
1159        .kind
1160    {
1161        ExprKind::Paren(inner) => vec![*inner],
1162        ExprKind::Binary {
1163            op: crate::BinOp::NullishCoalesce | crate::BinOp::Or,
1164            rhs,
1165            ..
1166        } => vec![*rhs],
1167        ExprKind::Ternary { then_, else_, .. } => vec![*then_, *else_],
1168        _ => return Ok(()),
1169    };
1170    for operand in operands {
1171        if tuple_source_len(ast, binding, operand)?.is_some() {
1172            ast.tuple_pattern_sources.insert(operand);
1173        } else {
1174            mark_operand_tuple_sources(ast, binding, operand)?;
1175        }
1176    }
1177    Ok(())
1178}
1179
1180/// The property names of `value` when it is an object literal whose every
1181/// property is named.
1182fn object_literal_names(
1183    ast: &Ast,
1184    value: ExprId,
1185) -> Result<Option<BTreeSet<String>>, CompilerFailure> {
1186    let ExprKind::ObjectLiteral { members } = &ast
1187        .try_expr(value)
1188        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
1189        .kind
1190    else {
1191        return Ok(None);
1192    };
1193    let mut names = BTreeSet::new();
1194    for member in members {
1195        let crate::ObjectLiteralMember::Field(field) = member else {
1196            return Ok(None);
1197        };
1198        names.insert(field.name.name.clone());
1199    }
1200    Ok(Some(names))
1201}
1202
1203/// The length of `value` when it is an array literal without spreads that
1204/// `binding`, an array pattern, takes apart slot by slot, so TypeScript types
1205/// it as a tuple. A pattern of only a rest element takes the literal whole, as
1206/// an array.
1207fn tuple_source_len(
1208    ast: &Ast,
1209    binding: &Binding,
1210    value: ExprId,
1211) -> Result<Option<usize>, CompilerFailure> {
1212    let Binding::Array { elems, .. } = binding else {
1213        return Ok(None);
1214    };
1215    if elems.is_empty() {
1216        return Ok(None);
1217    }
1218    let ExprKind::ArrayLiteral { elements } = &ast
1219        .try_expr(value)
1220        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
1221        .kind
1222    else {
1223        return Ok(None);
1224    };
1225    let has_spread = elements
1226        .iter()
1227        .any(|element| matches!(element, crate::ArrayLiteralElement::Spread { .. }));
1228    Ok((!has_spread).then_some(elements.len()))
1229}
1230
1231/// `source.slice(from, source.length)`: the elements an array pattern's rest
1232/// element collects.
1233fn array_rest(
1234    ast: &mut Ast,
1235    source: &Ident,
1236    from: usize,
1237    span: Span,
1238) -> Result<ExprId, CompilerFailure> {
1239    let from_lit = push_expr(ast, ExprKind::Number(from as f64), span)?;
1240    let slice_receiver = push_expr(ast, ExprKind::Identifier(source.clone()), span)?;
1241    let slice_callee = push_expr(
1242        ast,
1243        ExprKind::FieldAccess {
1244            receiver: slice_receiver,
1245            name: Ident {
1246                name: "slice".to_string(),
1247                span,
1248            },
1249        },
1250        span,
1251    )?;
1252    let length_receiver = push_expr(ast, ExprKind::Identifier(source.clone()), span)?;
1253    let length_access = push_expr(
1254        ast,
1255        ExprKind::FieldAccess {
1256            receiver: length_receiver,
1257            name: Ident {
1258                name: "length".to_string(),
1259                span,
1260            },
1261        },
1262        span,
1263    )?;
1264    let slice_call = push_expr(
1265        ast,
1266        ExprKind::Call {
1267            callee: slice_callee,
1268            type_args: None,
1269            args: vec![from_lit, length_access],
1270        },
1271        span,
1272    )?;
1273    Ok(slice_call)
1274}
1275
1276/// `[source[from], …, source[to - 1]].slice(0, to - from)`: the rest of a
1277/// tuple-typed literal. The remaining elements form a tuple of their own, and
1278/// slicing it yields an array of their types: one that can grow, where
1279/// TypeScript types the rest as a tuple.
1280fn remaining_elements(
1281    ast: &mut Ast,
1282    source: &Ident,
1283    indices: std::ops::Range<usize>,
1284    span: Span,
1285) -> Result<ExprId, CompilerFailure> {
1286    let count = indices.len();
1287    let mut elements = Vec::with_capacity(count);
1288    for i in indices {
1289        let receiver = push_expr(ast, ExprKind::Identifier(source.clone()), span)?;
1290        let index = push_expr(ast, ExprKind::Number(i as f64), span)?;
1291        let access = push_expr(ast, ExprKind::IndexAccess { receiver, index }, span)?;
1292        elements.push(crate::ArrayLiteralElement::Value(access));
1293    }
1294    let remaining = push_expr(ast, ExprKind::ArrayLiteral { elements }, span)?;
1295    ast.tuple_pattern_sources.insert(remaining);
1296    let slice = push_expr(
1297        ast,
1298        ExprKind::FieldAccess {
1299            receiver: remaining,
1300            name: Ident {
1301                name: "slice".to_string(),
1302                span,
1303            },
1304        },
1305        span,
1306    )?;
1307    let from = push_expr(ast, ExprKind::Number(0.0), span)?;
1308    let to = push_expr(ast, ExprKind::Number(count as f64), span)?;
1309    push_expr(
1310        ast,
1311        ExprKind::Call {
1312            callee: slice,
1313            type_args: None,
1314            args: vec![from, to],
1315        },
1316        span,
1317    )
1318}
1319
1320fn push_expr(ast: &mut Ast, kind: ExprKind, span: Span) -> Result<ExprId, CompilerFailure> {
1321    ast.try_push_expr(Expr { kind, span })
1322        .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))
1323}
1324
1325/// `void 0`, which is `undefined` even where a source binding shadows the name.
1326fn push_void_zero(ast: &mut Ast, span: Span) -> Result<ExprId, CompilerFailure> {
1327    let zero = push_expr(ast, ExprKind::Number(0.0), span)?;
1328    push_expr(ast, ExprKind::Void { operand: zero }, span)
1329}
1330
1331fn make_decl(
1332    is_const: bool,
1333    name: Ident,
1334    value: ExprId,
1335    doc: Option<crate::DocComment>,
1336) -> StmtKind {
1337    if is_const {
1338        StmtKind::Const {
1339            name,
1340            ty: None,
1341            value,
1342            doc,
1343        }
1344    } else {
1345        StmtKind::Let {
1346            name,
1347            ty: None,
1348            value,
1349            doc,
1350        }
1351    }
1352}
1353
1354/// The names the `let` statements among `stmts` declare.
1355fn let_names(ast: &Ast, stmts: &[StmtId]) -> Result<Vec<Ident>, CompilerFailure> {
1356    let mut names = Vec::new();
1357    for &stmt in stmts {
1358        if let StmtKind::Let { name, .. } = &ast
1359            .try_stmt(stmt)
1360            .map_err(|error| error.into_compiler_failure(CompilerStage::Infer))?
1361            .kind
1362        {
1363            names.push(name.clone());
1364        }
1365    }
1366    Ok(names)
1367}
1368
1369fn lowering_failure(message: &str) -> CompilerFailure {
1370    CompilerFailure::Internal {
1371        stage: CompilerStage::Infer,
1372        span: None,
1373        message: message.into(),
1374    }
1375}
1376
1377#[cfg(test)]
1378mod tests {
1379    use super::lower;
1380    use crate::{Asi, Binding, Stmt, StmtKind, Token, TokenKind, parse};
1381
1382    fn pipeline(source: &str) -> crate::Ast {
1383        let mut asi = Asi::new(source, crate::FileId(0));
1384        let mut tokens: Vec<Token> = Vec::new();
1385        loop {
1386            let t = asi.next_token();
1387            let is_eof = matches!(t.kind, TokenKind::Eof);
1388            tokens.push(t);
1389            if is_eof {
1390                break;
1391            }
1392        }
1393        let _ = asi.into_diagnostics();
1394        let (mut ast, diags) = parse(source, tokens, crate::FileId(0));
1395        assert!(diags.is_empty(), "unexpected parse diags: {diags:?}");
1396        ast = lower(ast).unwrap();
1397        ast
1398    }
1399
1400    fn no_patterns_left(ast: &crate::Ast) {
1401        for i in 0..ast.stmts_len() {
1402            let s = ast.try_stmt(crate::StmtId(i as u32)).unwrap();
1403            assert!(
1404                !matches!(
1405                    s.kind,
1406                    StmtKind::LetPattern { .. }
1407                        | StmtKind::ConstPattern { .. }
1408                        | StmtKind::ForOfPattern { .. }
1409                ),
1410                "found unlowered pattern at stmt #{i}: {:?}",
1411                s.kind,
1412            );
1413            if let StmtKind::Function { params, .. } = &s.kind {
1414                for (pi, p) in params.iter().enumerate() {
1415                    assert!(
1416                        p.pattern.is_none(),
1417                        "function param #{pi} still has a pattern after lowering",
1418                    );
1419                }
1420            }
1421        }
1422        for i in 0..ast.exprs_len() {
1423            let e = ast.try_expr(crate::ExprId(i as u32)).unwrap();
1424            if let crate::ExprKind::Arrow { params, .. } = &e.kind {
1425                for (pi, p) in params.iter().enumerate() {
1426                    assert!(
1427                        p.pattern.is_none(),
1428                        "arrow param #{pi} still has a pattern after lowering",
1429                    );
1430                }
1431            }
1432        }
1433    }
1434
1435    #[test]
1436    fn lowers_simple_object_destructure() {
1437        let ast = pipeline("const { a, b } = obj;");
1438        no_patterns_left(&ast);
1439        // top_level: __dst, a-decl, b-decl
1440        assert_eq!(ast.top_level.len(), 3);
1441        let names: Vec<String> = ast
1442            .top_level
1443            .iter()
1444            .map(|id| match &ast.try_stmt(*id).unwrap().kind {
1445                StmtKind::Const { name, .. } => name.name.clone(),
1446                other => panic!("expected Const, got {other:?}"),
1447            })
1448            .collect();
1449        assert!(names[0].starts_with("#pattern_dst_"));
1450        assert_eq!(names[1], "a");
1451        assert_eq!(names[2], "b");
1452    }
1453
1454    #[test]
1455    fn lowers_renamed_object_destructure() {
1456        let ast = pipeline("const { a: x, b: y } = obj;");
1457        no_patterns_left(&ast);
1458        let names: Vec<String> = ast
1459            .top_level
1460            .iter()
1461            .map(|id| match &ast.try_stmt(*id).unwrap().kind {
1462                StmtKind::Const { name, .. } => name.name.clone(),
1463                other => panic!("expected Const, got {other:?}"),
1464            })
1465            .collect();
1466        assert_eq!(names[1], "x");
1467        assert_eq!(names[2], "y");
1468    }
1469
1470    #[test]
1471    fn lowers_array_destructure() {
1472        let ast = pipeline("const [x, y] = arr;");
1473        no_patterns_left(&ast);
1474        assert_eq!(ast.top_level.len(), 3);
1475        if let StmtKind::Const { value, .. } = &ast.try_stmt(ast.top_level[1]).unwrap().kind {
1476            assert!(matches!(
1477                ast.try_expr(*value).unwrap().kind,
1478                crate::ExprKind::IndexAccess { .. }
1479            ));
1480        }
1481    }
1482
1483    #[test]
1484    fn lowers_array_with_hole_and_rest() {
1485        let ast = pipeline("const [, x, ...rest] = arr;");
1486        no_patterns_left(&ast);
1487        // __dst, x (= arr[1]), rest (= arr.slice(2))
1488        assert_eq!(ast.top_level.len(), 3);
1489        if let StmtKind::Const { name, value, .. } = &ast.try_stmt(ast.top_level[1]).unwrap().kind {
1490            assert_eq!(name.name, "x");
1491            if let crate::ExprKind::IndexAccess { index, .. } = &ast.try_expr(*value).unwrap().kind
1492            {
1493                assert_eq!(
1494                    ast.try_expr(*index).unwrap().kind,
1495                    crate::ExprKind::Number(1.0)
1496                );
1497            } else {
1498                panic!("expected IndexAccess");
1499            }
1500        }
1501        if let StmtKind::Const { name, value, .. } = &ast.try_stmt(ast.top_level[2]).unwrap().kind {
1502            assert_eq!(name.name, "rest");
1503            match &ast.try_expr(*value).unwrap().kind {
1504                crate::ExprKind::Call { callee, args, .. } => {
1505                    assert_eq!(args.len(), 2, "slice takes (start, end)");
1506                    assert_eq!(
1507                        ast.try_expr(args[0]).unwrap().kind,
1508                        crate::ExprKind::Number(2.0)
1509                    );
1510                    match &ast.try_expr(*callee).unwrap().kind {
1511                        crate::ExprKind::FieldAccess { name, .. } => {
1512                            assert_eq!(name.name, "slice");
1513                        }
1514                        other => panic!("expected slice FieldAccess, got {other:?}"),
1515                    }
1516                }
1517                other => panic!("expected Call, got {other:?}"),
1518            }
1519        }
1520    }
1521
1522    #[test]
1523    fn lowers_object_rest_to_object_rest() {
1524        let ast = pipeline("const { a, ...rest } = obj;");
1525        no_patterns_left(&ast);
1526        assert_eq!(ast.top_level.len(), 3);
1527        match &ast.try_stmt(ast.top_level[2]).unwrap().kind {
1528            StmtKind::ObjectRest {
1529                is_const,
1530                name,
1531                exclude,
1532                ..
1533            } => {
1534                assert!(*is_const);
1535                assert_eq!(name.name, "rest");
1536                assert_eq!(exclude.len(), 1);
1537                assert_eq!(exclude[0].name, "a");
1538            }
1539            other => panic!("expected ObjectRest, got {other:?}"),
1540        }
1541    }
1542
1543    #[test]
1544    fn lowers_function_param_pattern() {
1545        let src = "function f({ a, b }: T): number { return a + b; }";
1546        let ast = pipeline(src);
1547        no_patterns_left(&ast);
1548        let Stmt {
1549            kind: StmtKind::Function { body, params, .. },
1550            ..
1551        } = ast.try_stmt(ast.top_level[0]).unwrap().clone()
1552        else {
1553            panic!("expected Function");
1554        };
1555        assert_eq!(params.len(), 1);
1556        assert!(params[0].pattern.is_none());
1557        assert!(params[0].name.name.starts_with("#pattern_p_"));
1558        let body_stmts = match &ast.try_stmt(body).unwrap().kind {
1559            StmtKind::Block(s) => s.clone(),
1560            _ => panic!("body is not a Block"),
1561        };
1562        assert_eq!(body_stmts.len(), 1);
1563        let bindings = ast.parameter_bindings.get(&params[0].name.span).unwrap();
1564        assert_eq!(bindings.len(), 2);
1565        if let StmtKind::Let { name, .. } = &ast.try_stmt(bindings[0]).unwrap().kind {
1566            assert_eq!(name.name, "a");
1567        } else {
1568            panic!("expected Let a");
1569        }
1570        if let StmtKind::Let { name, .. } = &ast.try_stmt(bindings[1]).unwrap().kind {
1571            assert_eq!(name.name, "b");
1572        } else {
1573            panic!("expected Let b");
1574        }
1575        assert!(matches!(
1576            ast.try_stmt(body_stmts[0]).unwrap().kind,
1577            StmtKind::Return(_)
1578        ));
1579    }
1580
1581    #[test]
1582    fn parameter_object_rest_has_a_mutable_authored_binding() {
1583        let ast = pipeline("function f({ head, ...tail }: T): number { return tail.value; }");
1584        let StmtKind::Function { params, .. } = &ast.try_stmt(ast.top_level[0]).unwrap().kind
1585        else {
1586            panic!("expected function");
1587        };
1588        let bindings = ast.parameter_bindings.get(&params[0].name.span).unwrap();
1589        assert_eq!(bindings.len(), 2);
1590        let StmtKind::ObjectRest { is_const, name, .. } = &ast.try_stmt(bindings[1]).unwrap().kind
1591        else {
1592            panic!("expected object rest binding");
1593        };
1594        assert!(!*is_const);
1595        assert_eq!(name.name, "tail");
1596    }
1597
1598    #[test]
1599    fn lowers_arrow_expr_body_with_pattern() {
1600        let src = "const f = ({a, b}: T): number => a + b;";
1601        let ast = pipeline(src);
1602        no_patterns_left(&ast);
1603        let mut found = false;
1604        for i in 0..ast.exprs_len() {
1605            let e = ast.try_expr(crate::ExprId(i as u32)).unwrap();
1606            if let crate::ExprKind::Arrow { params, body, .. } = &e.kind {
1607                assert!(matches!(body, crate::ArrowBody::Expr(_)));
1608                assert_eq!(
1609                    ast.parameter_bindings
1610                        .get(&params[0].name.span)
1611                        .unwrap()
1612                        .len(),
1613                    2
1614                );
1615                found = true;
1616            }
1617        }
1618        assert!(found, "no Arrow found in expr arena");
1619    }
1620
1621    #[test]
1622    fn lowers_for_of_array_pattern() {
1623        // `for (const [a, b] of pairs) { … }` becomes
1624        // `for (const #pattern_dst_N of pairs) { const a = #pattern_dst_N[0];
1625        // const b = #pattern_dst_N[1]; { ... } }` after lowering.
1626        let src = "function main(): void { for (const [a, b] of pairs) { use(a, b); } }";
1627        let ast = pipeline(src);
1628        no_patterns_left(&ast);
1629
1630        let Stmt {
1631            kind: StmtKind::Function { body, .. },
1632            ..
1633        } = ast.try_stmt(ast.top_level[0]).unwrap().clone()
1634        else {
1635            panic!("expected Function");
1636        };
1637        let body_stmts = match &ast.try_stmt(body).unwrap().kind {
1638            StmtKind::Block(s) => s.clone(),
1639            _ => panic!("function body is not a Block"),
1640        };
1641        assert_eq!(body_stmts.len(), 1, "main body has just the for-of");
1642
1643        let (loop_name, loop_body) = match &ast.try_stmt(body_stmts[0]).unwrap().kind {
1644            StmtKind::ForOf { name, body, .. } => (name.name.clone(), *body),
1645            other => panic!("expected ForOf, got {other:?}"),
1646        };
1647        assert!(
1648            loop_name.starts_with("#pattern_dst_"),
1649            "loop var was rewritten to a synth temp; got {loop_name}",
1650        );
1651
1652        let loop_body_stmts = match &ast.try_stmt(loop_body).unwrap().kind {
1653            StmtKind::Block(s) => s.clone(),
1654            _ => panic!("for-of body is not a Block"),
1655        };
1656        assert_eq!(loop_body_stmts.len(), 3);
1657        if let StmtKind::Const { name, value, .. } = &ast.try_stmt(loop_body_stmts[0]).unwrap().kind
1658        {
1659            assert_eq!(name.name, "a");
1660            assert!(matches!(
1661                ast.try_expr(*value).unwrap().kind,
1662                crate::ExprKind::IndexAccess { .. }
1663            ));
1664            // Origin-tagged so the typechecker can rephrase index errors.
1665            assert!(ast.pattern_origins.contains_key(value));
1666        } else {
1667            panic!("expected Const a");
1668        }
1669        if let StmtKind::Const { name, .. } = &ast.try_stmt(loop_body_stmts[1]).unwrap().kind {
1670            assert_eq!(name.name, "b");
1671        } else {
1672            panic!("expected Const b");
1673        }
1674    }
1675
1676    #[test]
1677    fn lowers_let_pattern_preserves_mutability() {
1678        // __dst is const; per-field bindings follow source kind (`let` stays `let`)
1679        let ast = pipeline("let { a } = obj;");
1680        no_patterns_left(&ast);
1681        // __dst is Const; `a` is Let.
1682        assert!(matches!(
1683            ast.try_stmt(ast.top_level[0]).unwrap().kind,
1684            StmtKind::Const { .. }
1685        ));
1686        assert!(matches!(
1687            ast.try_stmt(ast.top_level[1]).unwrap().kind,
1688            StmtKind::Let { .. }
1689        ));
1690    }
1691
1692    #[test]
1693    fn no_op_on_plain_program() {
1694        let src = "const x = 1; function main(): void { }";
1695        let before = {
1696            let mut asi = Asi::new(src, crate::FileId(0));
1697            let mut tokens: Vec<Token> = Vec::new();
1698            loop {
1699                let t = asi.next_token();
1700                let is_eof = matches!(t.kind, TokenKind::Eof);
1701                tokens.push(t);
1702                if is_eof {
1703                    break;
1704                }
1705            }
1706            let _ = asi.into_diagnostics();
1707            let (ast, _) = parse(src, tokens, crate::FileId(0));
1708            ast
1709        };
1710        let after = pipeline(src);
1711        assert_eq!(format!("{before:?}"), format!("{after:?}"));
1712    }
1713
1714    #[test]
1715    fn lowers_pattern_in_nested_block() {
1716        let src = "function main(): void { const { a } = obj; }";
1717        let ast = pipeline(src);
1718        no_patterns_left(&ast);
1719    }
1720
1721    #[test]
1722    fn idempotent() {
1723        let src = "const { a, b } = obj;";
1724        let mut ast1 = pipeline(src);
1725        let snapshot = format!("{ast1:?}");
1726        ast1 = lower(ast1).unwrap();
1727        assert_eq!(snapshot, format!("{ast1:?}"));
1728    }
1729
1730    #[test]
1731    fn binding_span_preserved() {
1732        let (ast, _) = parse(
1733            "const { a } = obj;",
1734            tokens_of("const { a } = obj;"),
1735            crate::FileId(0),
1736        );
1737        match &ast.try_stmt(ast.top_level[0]).unwrap().kind {
1738            crate::StmtKind::ConstPattern {
1739                binding: Binding::Object { span, .. },
1740                ..
1741            } => {
1742                // `{ a }` starts at offset 6 and ends at 11 (inclusive of `}`).
1743                assert_eq!(*span, crate::Span::new(crate::FileId(0), 6, 11).unwrap());
1744            }
1745            other => panic!("expected ConstPattern Object, got {other:?}"),
1746        }
1747    }
1748
1749    #[test]
1750    fn defaults_save_each_access_once_and_test_undefined() {
1751        for source in [
1752            "const { a = fallback() } = source();",
1753            "const [a = fallback()] = source();",
1754        ] {
1755            let ast = pipeline(source);
1756            assert_eq!(ast.top_level.len(), 3);
1757            let StmtKind::Const {
1758                value: saved_access,
1759                ..
1760            } = ast.try_stmt(ast.top_level[1]).unwrap().kind
1761            else {
1762                panic!("expected saved read")
1763            };
1764            assert!(matches!(
1765                ast.try_expr(saved_access).unwrap().kind,
1766                crate::ExprKind::FieldAccess { .. } | crate::ExprKind::Ternary { .. }
1767            ));
1768            let StmtKind::Const { value, .. } = ast.try_stmt(ast.top_level[2]).unwrap().kind else {
1769                panic!("expected binding")
1770            };
1771            let crate::ExprKind::Ternary { cond, then_, else_ } = ast.try_expr(value).unwrap().kind
1772            else {
1773                panic!("expected lazy default")
1774            };
1775            let crate::ExprKind::Binary {
1776                op: crate::BinOp::Eq,
1777                lhs,
1778                rhs,
1779            } = ast.try_expr(cond).unwrap().kind
1780            else {
1781                panic!("expected undefined comparison")
1782            };
1783            assert_eq!(lhs, else_);
1784            assert!(matches!(
1785                ast.try_expr(rhs).unwrap().kind,
1786                crate::ExprKind::Void { .. }
1787            ));
1788            assert!(matches!(
1789                ast.try_expr(then_).unwrap().kind,
1790                crate::ExprKind::Call { .. }
1791            ));
1792        }
1793    }
1794
1795    #[test]
1796    fn array_defaults_keep_original_indices_and_origin_metadata() {
1797        let ast = pipeline("const [, a = 1] = values;");
1798        let StmtKind::Const { value, .. } = ast.try_stmt(ast.top_level[1]).unwrap().kind else {
1799            panic!("expected saved index read")
1800        };
1801        let crate::ExprKind::Ternary {
1802            cond,
1803            then_: access,
1804            else_,
1805        } = ast.try_expr(value).unwrap().kind
1806        else {
1807            panic!("expected bounds guard")
1808        };
1809        assert!(matches!(
1810            ast.try_expr(cond).unwrap().kind,
1811            crate::ExprKind::Binary {
1812                op: crate::BinOp::Lt,
1813                ..
1814            }
1815        ));
1816        assert!(matches!(
1817            ast.try_expr(else_).unwrap().kind,
1818            crate::ExprKind::Void { .. }
1819        ));
1820        let crate::ExprKind::IndexAccess { index, .. } = ast.try_expr(access).unwrap().kind else {
1821            panic!("expected guarded index")
1822        };
1823        assert!(matches!(
1824            ast.try_expr(index).unwrap().kind,
1825            crate::ExprKind::Number(1.0)
1826        ));
1827        assert_eq!(ast.pattern_origins.get(&access).unwrap().slot_arity, 2);
1828    }
1829
1830    #[test]
1831    fn lowers_class_method_parameter_defaults() {
1832        let ast = pipeline("class C { method({ x = 1 }: T): number { return x; } }");
1833        let StmtKind::ClassDecl { ref members, .. } = ast.try_stmt(ast.top_level[0]).unwrap().kind
1834        else {
1835            panic!("expected class")
1836        };
1837        let crate::ClassMember::Method { params, body, .. } = &members[0] else {
1838            panic!("expected method")
1839        };
1840        assert!(params[0].pattern.is_none());
1841        let StmtKind::Block(ref statements) = ast.try_stmt(*body).unwrap().kind else {
1842            panic!("expected block")
1843        };
1844        assert_eq!(statements.len(), 1);
1845        assert_eq!(
1846            ast.parameter_bindings
1847                .get(&params[0].name.span)
1848                .unwrap()
1849                .len(),
1850            2
1851        );
1852    }
1853
1854    fn tokens_of(source: &str) -> Vec<Token> {
1855        let mut asi = Asi::new(source, crate::FileId(0));
1856        let mut tokens = Vec::new();
1857        loop {
1858            let t = asi.next_token();
1859            let is_eof = matches!(t.kind, TokenKind::Eof);
1860            tokens.push(t);
1861            if is_eof {
1862                break;
1863            }
1864        }
1865        let _ = asi.into_diagnostics();
1866        tokens
1867    }
1868}