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