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