1use 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 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
31const FRESH_MARKER: &str = "#pattern_";
33
34const PATTERN_PARAM: &str = "p";
36
37pub(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
48const PATTERN_VALUE: &str = "dst";
50
51pub(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#[derive(Clone, Copy)]
65struct PatternRoot {
66 is_const: bool,
67 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 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 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()), );
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 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 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 None,
323 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 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 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()), );
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 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 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 let literal = match (¶m.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, 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 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 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 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 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 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 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
935enum LiteralSource {
937 Tuple(usize),
939 Object(BTreeSet<String>),
941}
942
943fn 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
1001fn 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
1018fn 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
1047fn 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 ¶m.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
1088pub(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
1104fn 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 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
1127fn 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
1148fn 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
1180fn 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
1203fn 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
1231fn 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
1276fn 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
1325fn 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
1354fn 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 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 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(¶ms[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(¶ms[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(¶ms[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 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 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 let ast = pipeline("let { a } = obj;");
1680 no_patterns_left(&ast);
1681 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 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(¶ms[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}