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