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