1use std::collections::{HashMap, HashSet};
2
3use crate::analysis::registry::{FieldType, TypeDef, TypeRegistry};
4use crate::ast::stmt::{Expr, Literal, ReadSource, Stmt, TypeExpr};
5use crate::intern::{Interner, Symbol};
6
7use super::is_recursive_field;
8use super::types::codegen_type_expr;
9
10pub(super) fn is_result_type(ty: &TypeExpr, interner: &Interner) -> bool {
11 if let TypeExpr::Generic { base, .. } = ty {
12 interner.resolve(*base) == "Result"
13 } else {
14 false
15 }
16}
17
18pub(super) fn requires_async(stmts: &[Stmt]) -> bool {
21 stmts.iter().any(|s| requires_async_stmt(s))
22}
23
24pub(super) fn requires_async_stmt(stmt: &Stmt) -> bool {
25 match stmt {
26 Stmt::Concurrent { tasks } => true,
28 Stmt::Listen { .. } => true,
30 Stmt::ConnectTo { .. } => true,
31 Stmt::Sleep { .. } => true,
32 Stmt::Sync { .. } => true,
34 Stmt::Mount { .. } => true,
36 Stmt::ReadFrom { source: ReadSource::File(_), .. } => true,
38 Stmt::WriteFile { .. } => true,
39 Stmt::LaunchTask { .. } => true,
41 Stmt::LaunchTaskWithHandle { .. } => true,
42 Stmt::SendPipe { .. } => true,
43 Stmt::ReceivePipe { .. } => true,
44 Stmt::Select { .. } => true,
45 Stmt::If { then_block, else_block, .. } => {
49 then_block.iter().any(|s| requires_async_stmt(s))
50 || else_block.map_or(false, |b| b.iter().any(|s| requires_async_stmt(s)))
51 }
52 Stmt::While { body, .. } => body.iter().any(|s| requires_async_stmt(s)),
53 Stmt::Repeat { body, .. } => body.iter().any(|s| requires_async_stmt(s)),
54 Stmt::Zone { body, .. } => body.iter().any(|s| requires_async_stmt(s)),
55 Stmt::Parallel { tasks } => tasks.iter().any(|s| requires_async_stmt(s)),
56 Stmt::FunctionDef { body, .. } => body.iter().any(|s| requires_async_stmt(s)),
57 Stmt::Inspect { arms, .. } => {
59 arms.iter().any(|arm| arm.body.iter().any(|s| requires_async_stmt(s)))
60 }
61 _ => false,
62 }
63}
64
65pub(super) fn requires_vfs(stmts: &[Stmt]) -> bool {
68 stmts.iter().any(|s| requires_vfs_stmt(s))
69}
70
71pub(super) fn requires_vfs_stmt(stmt: &Stmt) -> bool {
72 match stmt {
73 Stmt::Mount { .. } => true,
75 Stmt::ReadFrom { source: ReadSource::File(_), .. } => true,
77 Stmt::WriteFile { .. } => true,
78 Stmt::If { then_block, else_block, .. } => {
80 then_block.iter().any(|s| requires_vfs_stmt(s))
81 || else_block.map_or(false, |b| b.iter().any(|s| requires_vfs_stmt(s)))
82 }
83 Stmt::While { body, .. } => body.iter().any(|s| requires_vfs_stmt(s)),
84 Stmt::Repeat { body, .. } => body.iter().any(|s| requires_vfs_stmt(s)),
85 Stmt::Zone { body, .. } => body.iter().any(|s| requires_vfs_stmt(s)),
86 Stmt::Concurrent { tasks } => tasks.iter().any(|s| requires_vfs_stmt(s)),
87 Stmt::Parallel { tasks } => tasks.iter().any(|s| requires_vfs_stmt(s)),
88 Stmt::FunctionDef { body, .. } => body.iter().any(|s| requires_vfs_stmt(s)),
89 _ => false,
90 }
91}
92
93pub(super) fn get_root_identifier_for_mutability(expr: &Expr) -> Option<Symbol> {
96 match expr {
97 Expr::Identifier(sym) => Some(*sym),
98 Expr::FieldAccess { object, .. } => get_root_identifier_for_mutability(object),
99 _ => None,
100 }
101}
102
103pub(super) fn collect_mutable_vars(stmts: &[Stmt]) -> HashSet<Symbol> {
109 let mut targets = HashSet::new();
110 for stmt in stmts {
111 collect_mutable_vars_stmt(stmt, &mut targets);
112 }
113 targets
114}
115
116pub(super) fn collect_mutable_vars_stmt(stmt: &Stmt, targets: &mut HashSet<Symbol>) {
117 match stmt {
118 Stmt::Set { target, .. } => {
119 targets.insert(*target);
120 }
121 Stmt::Push { collection, .. } => {
122 if let Some(sym) = get_root_identifier_for_mutability(collection) {
124 targets.insert(sym);
125 }
126 }
127 Stmt::Pop { collection, .. } => {
128 if let Some(sym) = get_root_identifier_for_mutability(collection) {
130 targets.insert(sym);
131 }
132 }
133 Stmt::Add { collection, .. } => {
134 if let Some(sym) = get_root_identifier_for_mutability(collection) {
136 targets.insert(sym);
137 }
138 }
139 Stmt::Remove { collection, .. } => {
140 if let Some(sym) = get_root_identifier_for_mutability(collection) {
142 targets.insert(sym);
143 }
144 }
145 Stmt::SetIndex { collection, .. } => {
146 if let Some(sym) = get_root_identifier_for_mutability(collection) {
148 targets.insert(sym);
149 }
150 }
151 Stmt::If { then_block, else_block, .. } => {
152 for s in *then_block {
153 collect_mutable_vars_stmt(s, targets);
154 }
155 if let Some(else_stmts) = else_block {
156 for s in *else_stmts {
157 collect_mutable_vars_stmt(s, targets);
158 }
159 }
160 }
161 Stmt::While { body, .. } => {
162 for s in *body {
163 collect_mutable_vars_stmt(s, targets);
164 }
165 }
166 Stmt::Repeat { body, .. } => {
167 for s in *body {
168 collect_mutable_vars_stmt(s, targets);
169 }
170 }
171 Stmt::Zone { body, .. } => {
172 for s in *body {
173 collect_mutable_vars_stmt(s, targets);
174 }
175 }
176 Stmt::Inspect { arms, .. } => {
178 for arm in arms.iter() {
179 for s in arm.body.iter() {
180 collect_mutable_vars_stmt(s, targets);
181 }
182 }
183 }
184 Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
186 for s in *tasks {
187 collect_mutable_vars_stmt(s, targets);
188 }
189 }
190 Stmt::IncreaseCrdt { object, .. } | Stmt::DecreaseCrdt { object, .. } => {
192 if let Some(sym) = get_root_identifier_for_mutability(object) {
194 targets.insert(sym);
195 }
196 }
197 Stmt::AppendToSequence { sequence, .. } => {
198 if let Some(sym) = get_root_identifier_for_mutability(sequence) {
199 targets.insert(sym);
200 }
201 }
202 Stmt::ResolveConflict { object, .. } => {
203 if let Some(sym) = get_root_identifier_for_mutability(object) {
204 targets.insert(sym);
205 }
206 }
207 Stmt::SetField { object, .. } => {
209 if let Some(sym) = get_root_identifier_for_mutability(object) {
210 targets.insert(sym);
211 }
212 }
213 _ => {}
214 }
215}
216
217pub(super) fn collect_single_char_text_vars(stmts: &[Stmt], interner: &Interner) -> HashSet<Symbol> {
229 let mut candidates: HashSet<Symbol> = HashSet::new();
230 let mut disqualified: HashSet<Symbol> = HashSet::new();
231
232 scan_single_char_candidates(stmts, interner, &mut candidates, &mut disqualified);
235
236 for sym in &disqualified {
238 candidates.remove(sym);
239 }
240
241 let mut usage_disqualified: HashSet<Symbol> = HashSet::new();
243 check_single_char_usage(stmts, &candidates, &mut usage_disqualified);
244
245 for sym in &usage_disqualified {
246 candidates.remove(sym);
247 }
248
249 candidates
250}
251
252fn is_single_char_text_literal<'a>(expr: &Expr<'a>, interner: &Interner) -> bool {
253 if let Expr::Literal(crate::ast::stmt::Literal::Text(sym)) = expr {
254 let text = interner.resolve(*sym);
255 text.len() == 1 && text.is_ascii()
256 } else {
257 false
258 }
259}
260
261fn scan_single_char_candidates(
262 stmts: &[Stmt],
263 interner: &Interner,
264 candidates: &mut HashSet<Symbol>,
265 disqualified: &mut HashSet<Symbol>,
266) {
267 for stmt in stmts {
268 match stmt {
269 Stmt::Let { var, value, mutable, .. } => {
270 if *mutable && is_single_char_text_literal(value, interner) {
271 candidates.insert(*var);
272 }
273 }
275 Stmt::Set { target, value } => {
276 if candidates.contains(target) || !disqualified.contains(target) {
277 if !is_single_char_text_literal(value, interner) {
278 disqualified.insert(*target);
280 }
281 }
282 }
283 Stmt::If { then_block, else_block, .. } => {
284 scan_single_char_candidates(then_block, interner, candidates, disqualified);
285 if let Some(eb) = else_block {
286 scan_single_char_candidates(eb, interner, candidates, disqualified);
287 }
288 }
289 Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Zone { body, .. } => {
290 scan_single_char_candidates(body, interner, candidates, disqualified);
291 }
292 Stmt::FunctionDef { body, .. } => {
293 scan_single_char_candidates(body, interner, candidates, disqualified);
294 }
295 Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
296 scan_single_char_candidates(tasks, interner, candidates, disqualified);
297 }
298 _ => {}
299 }
300 }
301}
302
303fn check_single_char_usage(
314 stmts: &[Stmt],
315 candidates: &HashSet<Symbol>,
316 disqualified: &mut HashSet<Symbol>,
317) {
318 for stmt in stmts {
319 match stmt {
320 Stmt::Set { target, value } => {
321 if !candidates.contains(target) {
325 check_expr_usage(value, candidates, disqualified, true);
328 }
329 }
330 Stmt::Show { object, .. } => {
331 }
334 Stmt::Let { value, .. } => {
335 check_expr_usage_strict(value, candidates, disqualified);
338 }
339 Stmt::Return { value: Some(v) } => {
340 check_expr_usage_strict(v, candidates, disqualified);
341 }
342 Stmt::Call { args, .. } => {
343 for a in args.iter() {
344 check_expr_usage_strict(a, candidates, disqualified);
345 }
346 }
347 Stmt::Push { value, .. } => {
348 check_expr_usage_strict(value, candidates, disqualified);
350 }
351 Stmt::If { cond, then_block, else_block } => {
352 check_single_char_usage(then_block, candidates, disqualified);
354 if let Some(eb) = else_block {
355 check_single_char_usage(eb, candidates, disqualified);
356 }
357 }
358 Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Zone { body, .. } => {
359 check_single_char_usage(body, candidates, disqualified);
360 }
361 Stmt::FunctionDef { body, .. } => {
362 check_single_char_usage(body, candidates, disqualified);
363 }
364 _ => {}
365 }
366 }
367}
368
369fn check_expr_usage(
373 expr: &Expr,
374 candidates: &HashSet<Symbol>,
375 disqualified: &mut HashSet<Symbol>,
376 is_self_append: bool,
377) {
378 match expr {
379 Expr::BinaryOp { op: crate::ast::stmt::BinaryOpKind::Add, left, right } if is_self_append => {
380 check_expr_usage(left, candidates, disqualified, true);
382 if !matches!(right, Expr::Identifier(sym) if candidates.contains(sym)) {
384 check_expr_usage(right, candidates, disqualified, false);
385 }
386 }
387 Expr::Identifier(sym) if !is_self_append => {
388 if candidates.contains(sym) {
390 disqualified.insert(*sym);
391 }
392 }
393 _ => {}
394 }
395}
396
397fn check_expr_usage_strict(expr: &Expr, candidates: &HashSet<Symbol>, disqualified: &mut HashSet<Symbol>) {
399 match expr {
400 Expr::Identifier(sym) => {
401 if candidates.contains(sym) {
402 disqualified.insert(*sym);
403 }
404 }
405 Expr::BinaryOp { left, right, .. } => {
406 check_expr_usage_strict(left, candidates, disqualified);
407 check_expr_usage_strict(right, candidates, disqualified);
408 }
409 Expr::Call { args, .. } => {
410 for a in args.iter() {
411 check_expr_usage_strict(a, candidates, disqualified);
412 }
413 }
414 Expr::Not { operand } => check_expr_usage_strict(operand, candidates, disqualified),
415 Expr::Index { collection, index } => {
416 check_expr_usage_strict(collection, candidates, disqualified);
417 check_expr_usage_strict(index, candidates, disqualified);
418 }
419 Expr::Length { collection } => check_expr_usage_strict(collection, candidates, disqualified),
420 Expr::List(items) => {
421 for item in items.iter() {
422 check_expr_usage_strict(item, candidates, disqualified);
423 }
424 }
425 _ => {}
426 }
427}
428
429pub(super) fn collect_crdt_register_fields(registry: &TypeRegistry, interner: &Interner) -> (HashSet<(String, String)>, HashSet<(String, String)>) {
430 let mut lww_fields = HashSet::new();
431 let mut mv_fields = HashSet::new();
432 for (type_sym, def) in registry.iter_types() {
433 if let TypeDef::Struct { fields, .. } = def {
434 let type_name = interner.resolve(*type_sym).to_string();
435 for field in fields {
436 if let FieldType::Generic { base, .. } = &field.ty {
437 let base_name = interner.resolve(*base);
438 let field_name = interner.resolve(field.name).to_string();
439 if base_name == "LastWriteWins" {
440 lww_fields.insert((type_name.clone(), field_name));
441 } else if base_name == "Divergent" || base_name == "MVRegister" {
442 mv_fields.insert((type_name.clone(), field_name));
443 }
444 }
445 }
446 }
447 }
448 (lww_fields, mv_fields)
449}
450
451pub(super) fn collect_boxed_fields(registry: &TypeRegistry, interner: &Interner) -> HashSet<(String, String, String)> {
454 let mut boxed_fields = HashSet::new();
455 for (type_sym, def) in registry.iter_types() {
456 if let TypeDef::Enum { variants, .. } = def {
457 let enum_name = interner.resolve(*type_sym);
458 for variant in variants {
459 let variant_name = interner.resolve(variant.name);
460 for field in &variant.fields {
461 if is_recursive_field(&field.ty, enum_name, interner) {
462 let field_name = interner.resolve(field.name).to_string();
463 boxed_fields.insert((
464 enum_name.to_string(),
465 variant_name.to_string(),
466 field_name,
467 ));
468 }
469 }
470 }
471 }
472 }
473 boxed_fields
474}
475
476pub fn collect_async_functions(stmts: &[Stmt]) -> HashSet<Symbol> {
483 let mut func_bodies: HashMap<Symbol, &[Stmt]> = HashMap::new();
485 for stmt in stmts {
486 if let Stmt::FunctionDef { name, body, .. } = stmt {
487 func_bodies.insert(*name, *body);
488 }
489 }
490
491 let mut async_fns = HashSet::new();
493 for stmt in stmts {
494 if let Stmt::FunctionDef { name, body, .. } = stmt {
495 if body.iter().any(|s| requires_async_stmt(s)) {
496 async_fns.insert(*name);
497 }
498 }
499 }
500
501 loop {
503 let mut changed = false;
504 for (func_name, body) in &func_bodies {
505 if async_fns.contains(func_name) {
506 continue; }
508 if body.iter().any(|s| calls_async_function(s, &async_fns)) {
510 async_fns.insert(*func_name);
511 changed = true;
512 }
513 }
514 if !changed {
515 break;
516 }
517 }
518
519 async_fns
520}
521
522pub(super) fn calls_async_function(stmt: &Stmt, async_fns: &HashSet<Symbol>) -> bool {
524 match stmt {
525 Stmt::Call { function, args } => {
526 async_fns.contains(function)
528 || args.iter().any(|a| calls_async_function_in_expr(a, async_fns))
529 }
530 Stmt::If { cond, then_block, else_block } => {
531 calls_async_function_in_expr(cond, async_fns)
532 || then_block.iter().any(|s| calls_async_function(s, async_fns))
533 || else_block.map_or(false, |b| b.iter().any(|s| calls_async_function(s, async_fns)))
534 }
535 Stmt::While { cond, body, .. } => {
536 calls_async_function_in_expr(cond, async_fns)
537 || body.iter().any(|s| calls_async_function(s, async_fns))
538 }
539 Stmt::Repeat { iterable, body, .. } => {
540 calls_async_function_in_expr(iterable, async_fns)
541 || body.iter().any(|s| calls_async_function(s, async_fns))
542 }
543 Stmt::Zone { body, .. } => {
544 body.iter().any(|s| calls_async_function(s, async_fns))
545 }
546 Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
547 tasks.iter().any(|s| calls_async_function(s, async_fns))
548 }
549 Stmt::FunctionDef { body, .. } => {
550 body.iter().any(|s| calls_async_function(s, async_fns))
551 }
552 Stmt::Let { value, .. } => calls_async_function_in_expr(value, async_fns),
554 Stmt::Set { value, .. } => calls_async_function_in_expr(value, async_fns),
556 Stmt::Return { value } => {
558 value.as_ref().map_or(false, |v| calls_async_function_in_expr(v, async_fns))
559 }
560 Stmt::RuntimeAssert { condition, .. } => calls_async_function_in_expr(condition, async_fns),
562 Stmt::Show { object, .. } => calls_async_function_in_expr(object, async_fns),
564 Stmt::Push { collection, value } => {
566 calls_async_function_in_expr(collection, async_fns)
567 || calls_async_function_in_expr(value, async_fns)
568 }
569 Stmt::SetIndex { collection, index, value } => {
571 calls_async_function_in_expr(collection, async_fns)
572 || calls_async_function_in_expr(index, async_fns)
573 || calls_async_function_in_expr(value, async_fns)
574 }
575 Stmt::SendPipe { value, pipe } | Stmt::TrySendPipe { value, pipe, .. } => {
577 calls_async_function_in_expr(value, async_fns)
578 || calls_async_function_in_expr(pipe, async_fns)
579 }
580 Stmt::Inspect { target, arms, .. } => {
582 calls_async_function_in_expr(target, async_fns)
583 || arms.iter().any(|arm| arm.body.iter().any(|s| calls_async_function(s, async_fns)))
584 }
585 _ => false,
586 }
587}
588
589fn calls_async_function_in_expr(expr: &Expr, async_fns: &HashSet<Symbol>) -> bool {
591 match expr {
592 Expr::Call { function, args } => {
593 async_fns.contains(function)
594 || args.iter().any(|a| calls_async_function_in_expr(a, async_fns))
595 }
596 Expr::BinaryOp { left, right, .. } => {
597 calls_async_function_in_expr(left, async_fns)
598 || calls_async_function_in_expr(right, async_fns)
599 }
600 Expr::Index { collection, index } => {
601 calls_async_function_in_expr(collection, async_fns)
602 || calls_async_function_in_expr(index, async_fns)
603 }
604 Expr::FieldAccess { object, .. } => calls_async_function_in_expr(object, async_fns),
605 Expr::List(items) | Expr::Tuple(items) => {
606 items.iter().any(|i| calls_async_function_in_expr(i, async_fns))
607 }
608 Expr::Closure { body, .. } => {
609 match body {
610 crate::ast::stmt::ClosureBody::Expression(expr) => calls_async_function_in_expr(expr, async_fns),
611 crate::ast::stmt::ClosureBody::Block(_) => false,
612 }
613 }
614 Expr::CallExpr { callee, args } => {
615 calls_async_function_in_expr(callee, async_fns)
616 || args.iter().any(|a| calls_async_function_in_expr(a, async_fns))
617 }
618 Expr::InterpolatedString(parts) => {
619 parts.iter().any(|p| {
620 if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
621 calls_async_function_in_expr(value, async_fns)
622 } else { false }
623 })
624 }
625 _ => false,
626 }
627}
628
629pub(super) fn collect_pure_functions(stmts: &[Stmt]) -> HashSet<Symbol> {
634 let mut func_bodies: HashMap<Symbol, &[Stmt]> = HashMap::new();
635 for stmt in stmts {
636 if let Stmt::FunctionDef { name, body, .. } = stmt {
637 func_bodies.insert(*name, *body);
638 }
639 }
640
641 let mut impure_fns = HashSet::new();
643 for (func_name, body) in &func_bodies {
644 if body.iter().any(|s| is_directly_impure_stmt(s)) {
645 impure_fns.insert(*func_name);
646 }
647 }
648
649 loop {
651 let mut changed = false;
652 for (func_name, body) in &func_bodies {
653 if impure_fns.contains(func_name) {
654 continue;
655 }
656 if body.iter().any(|s| calls_impure_function(s, &impure_fns)) {
657 impure_fns.insert(*func_name);
658 changed = true;
659 }
660 }
661 if !changed {
662 break;
663 }
664 }
665
666 let mut pure_fns = HashSet::new();
668 for func_name in func_bodies.keys() {
669 if !impure_fns.contains(func_name) {
670 pure_fns.insert(*func_name);
671 }
672 }
673 pure_fns
674}
675
676fn is_directly_impure_stmt(stmt: &Stmt) -> bool {
677 match stmt {
678 Stmt::Show { .. }
679 | Stmt::Give { .. }
680 | Stmt::WriteFile { .. }
681 | Stmt::ReadFrom { .. }
682 | Stmt::Listen { .. }
683 | Stmt::ConnectTo { .. }
684 | Stmt::SendMessage { .. }
685 | Stmt::StreamMessage { .. }
686 | Stmt::AwaitMessage { .. }
687 | Stmt::Sleep { .. }
688 | Stmt::Sync { .. }
689 | Stmt::Mount { .. }
690 | Stmt::MergeCrdt { .. }
691 | Stmt::IncreaseCrdt { .. }
692 | Stmt::DecreaseCrdt { .. }
693 | Stmt::AppendToSequence { .. }
694 | Stmt::ResolveConflict { .. }
695 | Stmt::CreatePipe { .. }
696 | Stmt::SendPipe { .. }
697 | Stmt::ReceivePipe { .. }
698 | Stmt::TrySendPipe { .. }
699 | Stmt::TryReceivePipe { .. }
700 | Stmt::LaunchTask { .. }
701 | Stmt::LaunchTaskWithHandle { .. }
702 | Stmt::StopTask { .. }
703 | Stmt::Concurrent { .. }
704 | Stmt::Parallel { .. } => true,
705 Stmt::If { then_block, else_block, .. } => {
706 then_block.iter().any(|s| is_directly_impure_stmt(s))
707 || else_block.map_or(false, |b| b.iter().any(|s| is_directly_impure_stmt(s)))
708 }
709 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
710 body.iter().any(|s| is_directly_impure_stmt(s))
711 }
712 Stmt::Zone { body, .. } => {
713 body.iter().any(|s| is_directly_impure_stmt(s))
714 }
715 Stmt::Inspect { arms, .. } => {
716 arms.iter().any(|arm| arm.body.iter().any(|s| is_directly_impure_stmt(s)))
717 }
718 _ => false,
719 }
720}
721
722fn calls_impure_function(stmt: &Stmt, impure_fns: &HashSet<Symbol>) -> bool {
723 match stmt {
724 Stmt::Call { function, args } => {
725 impure_fns.contains(function)
726 || args.iter().any(|a| expr_calls_impure(a, impure_fns))
727 }
728 Stmt::Let { value, .. } => expr_calls_impure(value, impure_fns),
729 Stmt::Set { value, .. } => expr_calls_impure(value, impure_fns),
730 Stmt::Return { value } => value.as_ref().map_or(false, |v| expr_calls_impure(v, impure_fns)),
731 Stmt::If { cond, then_block, else_block } => {
732 expr_calls_impure(cond, impure_fns)
733 || then_block.iter().any(|s| calls_impure_function(s, impure_fns))
734 || else_block.map_or(false, |b| b.iter().any(|s| calls_impure_function(s, impure_fns)))
735 }
736 Stmt::While { cond, body, .. } => {
737 expr_calls_impure(cond, impure_fns)
738 || body.iter().any(|s| calls_impure_function(s, impure_fns))
739 }
740 Stmt::Repeat { body, .. } => body.iter().any(|s| calls_impure_function(s, impure_fns)),
741 Stmt::Zone { body, .. } => body.iter().any(|s| calls_impure_function(s, impure_fns)),
742 Stmt::Inspect { arms, .. } => {
743 arms.iter().any(|arm| arm.body.iter().any(|s| calls_impure_function(s, impure_fns)))
744 }
745 Stmt::Show { object, .. } => expr_calls_impure(object, impure_fns),
746 Stmt::Push { value, collection } | Stmt::Add { value, collection } | Stmt::Remove { value, collection } => {
747 expr_calls_impure(value, impure_fns) || expr_calls_impure(collection, impure_fns)
748 }
749 _ => false,
750 }
751}
752
753fn expr_calls_impure(expr: &Expr, impure_fns: &HashSet<Symbol>) -> bool {
754 match expr {
755 Expr::Call { function, args } => {
756 impure_fns.contains(function)
757 || args.iter().any(|a| expr_calls_impure(a, impure_fns))
758 }
759 Expr::BinaryOp { left, right, .. } => {
760 expr_calls_impure(left, impure_fns) || expr_calls_impure(right, impure_fns)
761 }
762 Expr::Index { collection, index } => {
763 expr_calls_impure(collection, impure_fns) || expr_calls_impure(index, impure_fns)
764 }
765 Expr::FieldAccess { object, .. } => expr_calls_impure(object, impure_fns),
766 Expr::List(items) | Expr::Tuple(items) => items.iter().any(|i| expr_calls_impure(i, impure_fns)),
767 Expr::CallExpr { callee, args } => {
768 expr_calls_impure(callee, impure_fns)
769 || args.iter().any(|a| expr_calls_impure(a, impure_fns))
770 }
771 Expr::InterpolatedString(parts) => {
772 parts.iter().any(|p| {
773 if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
774 expr_calls_impure(value, impure_fns)
775 } else { false }
776 })
777 }
778 _ => false,
779 }
780}
781
782pub(super) fn count_self_calls(func_name: Symbol, body: &[Stmt]) -> usize {
787 let mut count = 0;
788 for stmt in body {
789 count += count_self_calls_in_stmt(func_name, stmt);
790 }
791 count
792}
793
794fn count_self_calls_in_stmt(func_name: Symbol, stmt: &Stmt) -> usize {
795 match stmt {
796 Stmt::Return { value: Some(expr) } => count_self_calls_in_expr(func_name, expr),
797 Stmt::Let { value, .. } => count_self_calls_in_expr(func_name, value),
798 Stmt::Set { value, .. } => count_self_calls_in_expr(func_name, value),
799 Stmt::Call { function, args } => {
800 let mut c = if *function == func_name { 1 } else { 0 };
801 c += args.iter().map(|a| count_self_calls_in_expr(func_name, a)).sum::<usize>();
802 c
803 }
804 Stmt::If { cond, then_block, else_block } => {
805 let mut c = count_self_calls_in_expr(func_name, cond);
806 c += count_self_calls(func_name, then_block);
807 if let Some(else_stmts) = else_block {
808 c += count_self_calls(func_name, else_stmts);
809 }
810 c
811 }
812 Stmt::While { cond, body, .. } => {
813 count_self_calls_in_expr(func_name, cond) + count_self_calls(func_name, body)
814 }
815 Stmt::Repeat { body, .. } => count_self_calls(func_name, body),
816 Stmt::Show { object, .. } => count_self_calls_in_expr(func_name, object),
817 _ => 0,
818 }
819}
820
821fn count_self_calls_in_expr(func_name: Symbol, expr: &Expr) -> usize {
822 match expr {
823 Expr::Call { function, args } => {
824 let mut c = if *function == func_name { 1 } else { 0 };
825 c += args.iter().map(|a| count_self_calls_in_expr(func_name, a)).sum::<usize>();
826 c
827 }
828 Expr::BinaryOp { left, right, .. } => {
829 count_self_calls_in_expr(func_name, left) + count_self_calls_in_expr(func_name, right)
830 }
831 Expr::Index { collection, index } => {
832 count_self_calls_in_expr(func_name, collection) + count_self_calls_in_expr(func_name, index)
833 }
834 Expr::FieldAccess { object, .. } => count_self_calls_in_expr(func_name, object),
835 Expr::List(items) | Expr::Tuple(items) => {
836 items.iter().map(|i| count_self_calls_in_expr(func_name, i)).sum()
837 }
838 Expr::InterpolatedString(parts) => {
839 parts.iter().map(|p| {
840 if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
841 count_self_calls_in_expr(func_name, value)
842 } else { 0 }
843 }).sum()
844 }
845 _ => 0,
846 }
847}
848
849pub(super) fn is_hashable_type(ty: &TypeExpr, interner: &Interner) -> bool {
850 match ty {
851 TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
852 let name = interner.resolve(*sym);
853 matches!(name, "Int" | "Nat" | "Bool" | "Char" | "Byte" | "Text"
854 | "i64" | "u64" | "bool" | "char" | "u8" | "String")
855 }
856 TypeExpr::Refinement { base, .. } => is_hashable_type(base, interner),
857 _ => false,
858 }
859}
860
861pub(super) fn is_copy_type_expr(ty: &TypeExpr, interner: &Interner) -> bool {
862 match ty {
863 TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
864 let name = interner.resolve(*sym);
865 matches!(name, "Int" | "Nat" | "Bool" | "Char" | "Byte"
866 | "i64" | "u64" | "bool" | "char" | "u8")
867 }
868 TypeExpr::Refinement { base, .. } => is_copy_type_expr(base, interner),
869 _ => false,
870 }
871}
872
873pub(super) fn should_memoize(
874 name: Symbol,
875 body: &[Stmt],
876 params: &[(Symbol, &TypeExpr)],
877 return_type: Option<&TypeExpr>,
878 is_pure: bool,
879 interner: &Interner,
880) -> bool {
881 if !is_pure {
882 return false;
883 }
884 if !body_contains_self_call(name, body) {
885 return false;
886 }
887 if count_self_calls(name, body) < 2 {
888 return false;
889 }
890 if params.is_empty() {
891 return false;
892 }
893 if !params.iter().all(|(_, ty)| is_hashable_type(ty, interner)) {
894 return false;
895 }
896 if return_type.is_none() {
897 return false;
898 }
899 true
900}
901
902pub(super) fn body_contains_self_call(func_name: Symbol, body: &[Stmt]) -> bool {
903 body.iter().any(|s| stmt_contains_self_call(func_name, s))
904}
905
906fn stmt_contains_self_call(func_name: Symbol, stmt: &Stmt) -> bool {
907 match stmt {
908 Stmt::Return { value: Some(expr) } => expr_contains_self_call(func_name, expr),
909 Stmt::Return { value: None } => false,
910 Stmt::Let { value, .. } => expr_contains_self_call(func_name, value),
911 Stmt::Set { value, .. } => expr_contains_self_call(func_name, value),
912 Stmt::Call { function, args } => {
913 *function == func_name || args.iter().any(|a| expr_contains_self_call(func_name, a))
914 }
915 Stmt::If { cond, then_block, else_block } => {
916 expr_contains_self_call(func_name, cond)
917 || then_block.iter().any(|s| stmt_contains_self_call(func_name, s))
918 || else_block.map_or(false, |b| b.iter().any(|s| stmt_contains_self_call(func_name, s)))
919 }
920 Stmt::While { cond, body, .. } => {
921 expr_contains_self_call(func_name, cond)
922 || body.iter().any(|s| stmt_contains_self_call(func_name, s))
923 }
924 Stmt::Repeat { body, .. } => {
925 body.iter().any(|s| stmt_contains_self_call(func_name, s))
926 }
927 Stmt::Show { object, .. } => expr_contains_self_call(func_name, object),
928 _ => false,
929 }
930}
931
932pub(super) fn expr_contains_self_call(func_name: Symbol, expr: &Expr) -> bool {
933 match expr {
934 Expr::Call { function, args } => {
935 *function == func_name || args.iter().any(|a| expr_contains_self_call(func_name, a))
936 }
937 Expr::BinaryOp { left, right, .. } => {
938 expr_contains_self_call(func_name, left) || expr_contains_self_call(func_name, right)
939 }
940 Expr::Index { collection, index } => {
941 expr_contains_self_call(func_name, collection) || expr_contains_self_call(func_name, index)
942 }
943 Expr::FieldAccess { object, .. } => expr_contains_self_call(func_name, object),
944 Expr::List(items) | Expr::Tuple(items) => {
945 items.iter().any(|i| expr_contains_self_call(func_name, i))
946 }
947 Expr::InterpolatedString(parts) => {
948 parts.iter().any(|p| {
949 if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
950 expr_contains_self_call(func_name, value)
951 } else { false }
952 })
953 }
954 _ => false,
955 }
956}
957
958pub(super) fn expr_indexes_collection(expr: &Expr, coll_sym: Symbol) -> bool {
962 match expr {
963 Expr::Index { collection, index } => {
964 let indexes_this = matches!(collection, Expr::Identifier(sym) if *sym == coll_sym);
965 indexes_this
966 || expr_indexes_collection(collection, coll_sym)
967 || expr_indexes_collection(index, coll_sym)
968 }
969 Expr::BinaryOp { left, right, .. } => {
970 expr_indexes_collection(left, coll_sym) || expr_indexes_collection(right, coll_sym)
971 }
972 Expr::Call { args, .. } => {
973 args.iter().any(|a| expr_indexes_collection(a, coll_sym))
974 }
975 Expr::FieldAccess { object, .. } => expr_indexes_collection(object, coll_sym),
976 Expr::Length { collection } => expr_indexes_collection(collection, coll_sym),
977 Expr::Not { operand } => expr_indexes_collection(operand, coll_sym),
978 Expr::List(items) | Expr::Tuple(items) => {
979 items.iter().any(|i| expr_indexes_collection(i, coll_sym))
980 }
981 _ => false,
982 }
983}
984
985pub(super) fn expr_reads_any_collection(expr: &Expr) -> bool {
994 match expr {
995 Expr::Index { .. } | Expr::Slice { .. } | Expr::Length { .. } => true,
996 Expr::BinaryOp { left, right, .. } => {
997 expr_reads_any_collection(left) || expr_reads_any_collection(right)
998 }
999 Expr::Call { args, .. } => args.iter().any(|a| expr_reads_any_collection(a)),
1000 Expr::FieldAccess { object, .. } => expr_reads_any_collection(object),
1001 Expr::Not { operand } => expr_reads_any_collection(operand),
1002 Expr::List(items) | Expr::Tuple(items) => items.iter().any(|i| expr_reads_any_collection(i)),
1003 _ => false,
1004 }
1005}
1006
1007pub(super) fn should_inline(name: Symbol, body: &[Stmt], is_native: bool, is_exported: bool, is_async: bool) -> bool {
1012 !is_native && !is_exported && !is_async
1013 && body.len() <= 10
1014 && !body_contains_self_call(name, body)
1015}
1016
1017pub fn collect_pipe_sender_params(body: &[Stmt]) -> HashSet<Symbol> {
1022 let mut senders = HashSet::new();
1023 for stmt in body {
1024 collect_pipe_sender_params_stmt(stmt, &mut senders);
1025 }
1026 senders
1027}
1028
1029fn collect_pipe_sender_params_stmt(stmt: &Stmt, senders: &mut HashSet<Symbol>) {
1030 match stmt {
1031 Stmt::SendPipe { pipe, .. } | Stmt::TrySendPipe { pipe, .. } => {
1032 if let Expr::Identifier(sym) = pipe {
1033 senders.insert(*sym);
1034 }
1035 }
1036 Stmt::If { then_block, else_block, .. } => {
1037 for s in *then_block {
1038 collect_pipe_sender_params_stmt(s, senders);
1039 }
1040 if let Some(else_stmts) = else_block {
1041 for s in *else_stmts {
1042 collect_pipe_sender_params_stmt(s, senders);
1043 }
1044 }
1045 }
1046 Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Zone { body, .. } => {
1047 for s in *body {
1048 collect_pipe_sender_params_stmt(s, senders);
1049 }
1050 }
1051 Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
1052 for s in *tasks {
1053 collect_pipe_sender_params_stmt(s, senders);
1054 }
1055 }
1056 _ => {}
1057 }
1058}
1059
1060pub fn collect_pipe_vars(stmts: &[Stmt]) -> HashSet<Symbol> {
1063 let mut pipe_vars = HashSet::new();
1064 for stmt in stmts {
1065 collect_pipe_vars_stmt(stmt, &mut pipe_vars);
1066 }
1067 pipe_vars
1068}
1069
1070fn collect_pipe_vars_stmt(stmt: &Stmt, pipe_vars: &mut HashSet<Symbol>) {
1071 match stmt {
1072 Stmt::CreatePipe { var, .. } => {
1073 pipe_vars.insert(*var);
1074 }
1075 Stmt::If { then_block, else_block, .. } => {
1076 for s in *then_block {
1077 collect_pipe_vars_stmt(s, pipe_vars);
1078 }
1079 if let Some(else_stmts) = else_block {
1080 for s in *else_stmts {
1081 collect_pipe_vars_stmt(s, pipe_vars);
1082 }
1083 }
1084 }
1085 Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Zone { body, .. } => {
1086 for s in *body {
1087 collect_pipe_vars_stmt(s, pipe_vars);
1088 }
1089 }
1090 Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
1091 for s in *tasks {
1092 collect_pipe_vars_stmt(s, pipe_vars);
1093 }
1094 }
1095 _ => {}
1096 }
1097}
1098
1099pub(super) fn collect_expr_identifiers(expr: &Expr, identifiers: &mut HashSet<Symbol>) {
1102 match expr {
1103 Expr::Identifier(sym) => {
1104 identifiers.insert(*sym);
1105 }
1106 Expr::BinaryOp { left, right, .. } => {
1107 collect_expr_identifiers(left, identifiers);
1108 collect_expr_identifiers(right, identifiers);
1109 }
1110 Expr::Call { args, .. } => {
1111 for arg in args {
1112 collect_expr_identifiers(arg, identifiers);
1113 }
1114 }
1115 Expr::Index { collection, index } => {
1116 collect_expr_identifiers(collection, identifiers);
1117 collect_expr_identifiers(index, identifiers);
1118 }
1119 Expr::Slice { collection, start, end } => {
1120 collect_expr_identifiers(collection, identifiers);
1121 collect_expr_identifiers(start, identifiers);
1122 collect_expr_identifiers(end, identifiers);
1123 }
1124 Expr::Copy { expr: inner } | Expr::Give { value: inner } | Expr::Length { collection: inner }
1125 | Expr::Not { operand: inner } => {
1126 collect_expr_identifiers(inner, identifiers);
1127 }
1128 Expr::Contains { collection, value } | Expr::Union { left: collection, right: value } | Expr::Intersection { left: collection, right: value } => {
1129 collect_expr_identifiers(collection, identifiers);
1130 collect_expr_identifiers(value, identifiers);
1131 }
1132 Expr::ManifestOf { zone } | Expr::ChunkAt { zone, .. } => {
1133 collect_expr_identifiers(zone, identifiers);
1134 }
1135 Expr::List(items) | Expr::Tuple(items) => {
1136 for item in items {
1137 collect_expr_identifiers(item, identifiers);
1138 }
1139 }
1140 Expr::Range { start, end } => {
1141 collect_expr_identifiers(start, identifiers);
1142 collect_expr_identifiers(end, identifiers);
1143 }
1144 Expr::FieldAccess { object, .. } => {
1145 collect_expr_identifiers(object, identifiers);
1146 }
1147 Expr::New { init_fields, .. } => {
1148 for (_, value) in init_fields {
1149 collect_expr_identifiers(value, identifiers);
1150 }
1151 }
1152 Expr::NewVariant { fields, .. } => {
1153 for (_, value) in fields {
1154 collect_expr_identifiers(value, identifiers);
1155 }
1156 }
1157 Expr::OptionSome { value } => {
1158 collect_expr_identifiers(value, identifiers);
1159 }
1160 Expr::WithCapacity { value, capacity } => {
1161 collect_expr_identifiers(value, identifiers);
1162 collect_expr_identifiers(capacity, identifiers);
1163 }
1164 Expr::Closure { body, .. } => {
1165 match body {
1166 crate::ast::stmt::ClosureBody::Expression(expr) => collect_expr_identifiers(expr, identifiers),
1167 crate::ast::stmt::ClosureBody::Block(_) => {}
1168 }
1169 }
1170 Expr::CallExpr { callee, args } => {
1171 collect_expr_identifiers(callee, identifiers);
1172 for arg in args {
1173 collect_expr_identifiers(arg, identifiers);
1174 }
1175 }
1176 Expr::InterpolatedString(parts) => {
1177 for part in parts {
1178 if let crate::ast::stmt::StringPart::Expr { value, .. } = part {
1179 collect_expr_identifiers(value, identifiers);
1180 }
1181 }
1182 }
1183 Expr::OptionNone => {}
1184 Expr::Escape { .. } => {}
1185 Expr::Literal(_) => {}
1186 }
1187}
1188
1189pub(super) fn symbol_appears_in_stmts(sym: Symbol, stmts: &[&Stmt]) -> bool {
1192 stmts.iter().any(|s| symbol_appears_in_stmt(sym, s))
1193}
1194
1195pub(super) fn symbol_appears_in_stmt(sym: Symbol, stmt: &Stmt) -> bool {
1196 match stmt {
1197 Stmt::Let { value, .. } => symbol_appears_in_expr(sym, value),
1198 Stmt::Set { target, value, .. } => *target == sym || symbol_appears_in_expr(sym, value),
1199 Stmt::Show { object, .. } => symbol_appears_in_expr(sym, object),
1200 Stmt::Return { value } => value.as_ref().map_or(false, |v| symbol_appears_in_expr(sym, v)),
1201 Stmt::Call { function, args } => *function == sym || args.iter().any(|a| symbol_appears_in_expr(sym, a)),
1202 Stmt::If { cond, then_block, else_block } => {
1203 symbol_appears_in_expr(sym, cond)
1204 || then_block.iter().any(|s| symbol_appears_in_stmt(sym, s))
1205 || else_block.map_or(false, |b| b.iter().any(|s| symbol_appears_in_stmt(sym, s)))
1206 }
1207 Stmt::While { cond, body, .. } => {
1208 symbol_appears_in_expr(sym, cond)
1209 || body.iter().any(|s| symbol_appears_in_stmt(sym, s))
1210 }
1211 Stmt::Repeat { iterable, body, .. } => {
1212 symbol_appears_in_expr(sym, iterable)
1213 || body.iter().any(|s| symbol_appears_in_stmt(sym, s))
1214 }
1215 Stmt::Zone { body, .. } => body.iter().any(|s| symbol_appears_in_stmt(sym, s)),
1216 Stmt::Push { collection, value } => {
1217 symbol_appears_in_expr(sym, collection) || symbol_appears_in_expr(sym, value)
1218 }
1219 Stmt::Pop { collection, .. } => symbol_appears_in_expr(sym, collection),
1220 Stmt::Add { collection, value } | Stmt::Remove { collection, value } => {
1221 symbol_appears_in_expr(sym, collection) || symbol_appears_in_expr(sym, value)
1222 }
1223 Stmt::SetIndex { collection, index, value } => {
1224 symbol_appears_in_expr(sym, collection) || symbol_appears_in_expr(sym, index) || symbol_appears_in_expr(sym, value)
1225 }
1226 Stmt::Inspect { target, arms, .. } => {
1227 symbol_appears_in_expr(sym, target)
1228 || arms.iter().any(|arm| arm.body.iter().any(|s| symbol_appears_in_stmt(sym, s)))
1229 }
1230 Stmt::RuntimeAssert { condition, .. } => symbol_appears_in_expr(sym, condition),
1231 Stmt::FunctionDef { body, .. } => body.iter().any(|s| symbol_appears_in_stmt(sym, s)),
1232 Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
1233 tasks.iter().any(|s| symbol_appears_in_stmt(sym, s))
1234 }
1235 _ => false,
1236 }
1237}
1238
1239pub(super) fn symbol_appears_in_expr(sym: Symbol, expr: &Expr) -> bool {
1240 match expr {
1241 Expr::Identifier(s) => *s == sym,
1242 Expr::BinaryOp { left, right, .. } => {
1243 symbol_appears_in_expr(sym, left) || symbol_appears_in_expr(sym, right)
1244 }
1245 Expr::Call { function, args } => {
1246 *function == sym || args.iter().any(|a| symbol_appears_in_expr(sym, a))
1247 }
1248 Expr::Index { collection, index } => {
1249 symbol_appears_in_expr(sym, collection) || symbol_appears_in_expr(sym, index)
1250 }
1251 Expr::Slice { collection, start, end } => {
1252 symbol_appears_in_expr(sym, collection)
1253 || symbol_appears_in_expr(sym, start)
1254 || symbol_appears_in_expr(sym, end)
1255 }
1256 Expr::Length { collection } | Expr::Copy { expr: collection } | Expr::Give { value: collection } => {
1257 symbol_appears_in_expr(sym, collection)
1258 }
1259 Expr::Contains { collection, value } | Expr::Union { left: collection, right: value } | Expr::Intersection { left: collection, right: value } => {
1260 symbol_appears_in_expr(sym, collection) || symbol_appears_in_expr(sym, value)
1261 }
1262 Expr::FieldAccess { object, .. } => symbol_appears_in_expr(sym, object),
1263 Expr::List(items) | Expr::Tuple(items) => {
1264 items.iter().any(|i| symbol_appears_in_expr(sym, i))
1265 }
1266 Expr::Range { start, end } => {
1267 symbol_appears_in_expr(sym, start) || symbol_appears_in_expr(sym, end)
1268 }
1269 Expr::New { init_fields, .. } => {
1270 init_fields.iter().any(|(_, v)| symbol_appears_in_expr(sym, v))
1271 }
1272 Expr::NewVariant { fields, .. } => {
1273 fields.iter().any(|(_, v)| symbol_appears_in_expr(sym, v))
1274 }
1275 Expr::OptionSome { value } => symbol_appears_in_expr(sym, value),
1276 Expr::WithCapacity { value, capacity } => {
1277 symbol_appears_in_expr(sym, value) || symbol_appears_in_expr(sym, capacity)
1278 }
1279 Expr::CallExpr { callee, args } => {
1280 symbol_appears_in_expr(sym, callee)
1281 || args.iter().any(|a| symbol_appears_in_expr(sym, a))
1282 }
1283 Expr::InterpolatedString(parts) => {
1284 parts.iter().any(|p| {
1285 if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
1286 symbol_appears_in_expr(sym, value)
1287 } else { false }
1288 })
1289 }
1290 Expr::Closure { body, .. } => {
1291 match body {
1292 crate::ast::stmt::ClosureBody::Expression(e) => symbol_appears_in_expr(sym, e),
1293 crate::ast::stmt::ClosureBody::Block(stmts) => stmts.iter().any(|s| symbol_appears_in_stmt(sym, s)),
1294 }
1295 }
1296 _ => false,
1297 }
1298}
1299
1300pub(crate) fn is_vec_type_expr(ty: &TypeExpr, interner: &Interner) -> bool {
1302 match ty {
1303 TypeExpr::Generic { base, .. } => {
1304 let name = interner.resolve(*base);
1305 matches!(name, "Seq" | "List" | "Vec")
1306 }
1307 _ => false,
1308 }
1309}
1310
1311pub(super) fn collect_readonly_vec_param_indices(
1315 params: &[(Symbol, &TypeExpr)],
1316 body: &[Stmt],
1317 interner: &Interner,
1318) -> HashSet<usize> {
1319 let mutable_vars = collect_mutable_vars(body);
1320 let mut readonly_indices = HashSet::new();
1321 for (i, (param_sym, param_ty)) in params.iter().enumerate() {
1322 if is_vec_type_expr(param_ty, interner) && !mutable_vars.contains(param_sym) {
1323 readonly_indices.insert(i);
1324 }
1325 }
1326 readonly_indices
1327}
1328
1329pub(super) fn vec_to_slice_type(vec_type: &str) -> String {
1332 if let Some(inner) = vec_type.strip_prefix("Vec<").and_then(|s| s.strip_suffix('>')) {
1333 format!("&[{}]", inner)
1334 } else if let Some(inner) = vec_type.strip_prefix("LogosSeq<").and_then(|s| s.strip_suffix('>')) {
1335 format!("&[{}]", inner)
1336 } else {
1337 vec_type.to_string()
1338 }
1339}
1340
1341pub(super) fn vec_to_mut_slice_type(vec_type: &str) -> String {
1343 if let Some(inner) = vec_type.strip_prefix("Vec<").and_then(|s| s.strip_suffix('>')) {
1344 format!("&mut [{}]", inner)
1345 } else if let Some(inner) = vec_type.strip_prefix("LogosSeq<").and_then(|s| s.strip_suffix('>')) {
1346 format!("&mut [{}]", inner)
1347 } else {
1348 vec_type.to_string()
1349 }
1350}
1351
1352pub(crate) fn collect_de_rc_seqs(
1366 stmts: &[Stmt],
1367 interner: &Interner,
1368 borrow_params: &HashMap<Symbol, HashSet<usize>>,
1369 mut_borrow_params: &HashMap<Symbol, HashSet<usize>>,
1370 vec_return_fns: &HashSet<Symbol>,
1371 returns_vec: bool,
1372) -> HashSet<Symbol> {
1373 if !crate::optimize::active_config().is_on(crate::optimization::Opt::Unbox) {
1375 return HashSet::new();
1376 }
1377 let borrow_slots: HashMap<Symbol, HashSet<usize>> = if mut_borrow_params.is_empty() {
1383 borrow_params.clone()
1384 } else {
1385 let mut m = borrow_params.clone();
1386 for (f, idx) in mut_borrow_params {
1387 m.entry(*f).or_default().extend(idx.iter().copied());
1388 }
1389 m
1390 };
1391 let borrow_params = &borrow_slots;
1392 let mut candidates = HashSet::new();
1393 collect_de_rc_candidates_block(stmts, interner, vec_return_fns, &mut candidates);
1394 if candidates.is_empty() {
1395 return candidates;
1396 }
1397 let mut swaps = HashSet::new();
1410 collect_buffer_swap_pairs(stmts, interner, &mut swaps);
1411
1412 let mut disqualified = HashSet::new();
1413 derc_scan_uses(stmts, &candidates, &swaps, borrow_params, mut_borrow_params, vec_return_fns, returns_vec, interner, &mut disqualified);
1414 candidates.retain(|c| !disqualified.contains(c));
1415
1416 loop {
1420 let mut changed = false;
1421 for (outer, inner) in &swaps {
1422 if candidates.contains(outer) != candidates.contains(inner) {
1423 changed |= candidates.remove(outer);
1424 changed |= candidates.remove(inner);
1425 }
1426 }
1427 if !changed {
1428 break;
1429 }
1430 }
1431 if !candidates.is_empty() {
1432 crate::optimize::mark_fired(crate::optimization::Opt::Unbox);
1433 }
1434 candidates
1435}
1436
1437pub(super) fn collect_vec_return_fns(
1448 stmts: &[Stmt],
1449 interner: &Interner,
1450 borrow_params: &HashMap<Symbol, HashSet<usize>>,
1451 mut_borrow_params: &HashMap<Symbol, HashSet<usize>>,
1452) -> HashSet<Symbol> {
1453 if !crate::optimize::active_config().is_on(crate::optimization::Opt::Unbox) {
1454 return HashSet::new();
1455 }
1456 let mut seq_fns: Vec<(Symbol, &[Stmt], &[(Symbol, &TypeExpr)])> = Vec::new();
1459 for s in stmts {
1460 if let Stmt::FunctionDef {
1461 name, body, params, return_type: Some(rt), is_native: false, ..
1462 } = s
1463 {
1464 if is_vec_type_expr(rt, interner) {
1465 seq_fns.push((*name, body, params));
1466 }
1467 }
1468 }
1469 if seq_fns.is_empty() {
1470 return HashSet::new();
1471 }
1472
1473 let mut vec_fns: HashSet<Symbol> = seq_fns.iter().map(|(n, _, _)| *n).collect();
1493 loop {
1494 let mut remove = HashSet::new();
1495
1496 flag_inline_vec_calls(stmts, &vec_fns, &mut remove);
1498
1499 let main_de_rc = collect_de_rc_seqs(stmts, interner, borrow_params, mut_borrow_params, &vec_fns, false);
1504 collect_unsound_vec_returns(stmts, &vec_fns, &main_de_rc, &mut remove);
1505 for (name, body, params) in &seq_fns {
1506 let rv = vec_fns.contains(name);
1507 let de_rc = collect_de_rc_seqs(body, interner, borrow_params, mut_borrow_params, &vec_fns, rv);
1508 collect_unsound_vec_returns(body, &vec_fns, &de_rc, &mut remove);
1509 if rv && !all_returns_vec_ownable(body, params, borrow_params.get(name), &de_rc) {
1510 remove.insert(*name);
1511 }
1512 }
1513
1514 if remove.is_empty() {
1515 break;
1516 }
1517 vec_fns.retain(|f| !remove.contains(f));
1518 }
1519 if !vec_fns.is_empty() {
1520 crate::optimize::mark_fired(crate::optimization::Opt::Unbox);
1521 }
1522 vec_fns
1523}
1524
1525fn collect_unsound_vec_returns(
1529 stmts: &[Stmt],
1530 vec_fns: &HashSet<Symbol>,
1531 de_rc: &HashSet<Symbol>,
1532 remove: &mut HashSet<Symbol>,
1533) {
1534 for stmt in stmts {
1535 let binding = match stmt {
1536 Stmt::Let { var, value, .. } => Some((*var, value)),
1537 Stmt::Set { target, value } => Some((*target, value)),
1538 _ => None,
1539 };
1540 if let Some((x, Expr::Call { function, .. })) = binding {
1541 if vec_fns.contains(function) && !de_rc.contains(&x) {
1542 remove.insert(*function);
1543 }
1544 }
1545 match stmt {
1546 Stmt::If { then_block, else_block, .. } => {
1547 collect_unsound_vec_returns(then_block, vec_fns, de_rc, remove);
1548 if let Some(eb) = else_block {
1549 collect_unsound_vec_returns(eb, vec_fns, de_rc, remove);
1550 }
1551 }
1552 Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Zone { body, .. } => {
1553 collect_unsound_vec_returns(body, vec_fns, de_rc, remove)
1554 }
1555 _ => {}
1556 }
1557 }
1558}
1559
1560fn all_returns_vec_ownable(
1570 body: &[Stmt],
1571 params: &[(Symbol, &TypeExpr)],
1572 borrow_idx: Option<&HashSet<usize>>,
1573 de_rc: &HashSet<Symbol>,
1574) -> bool {
1575 fn walk(
1576 stmts: &[Stmt],
1577 params: &[(Symbol, &TypeExpr)],
1578 borrow_idx: Option<&HashSet<usize>>,
1579 de_rc: &HashSet<Symbol>,
1580 ok: &mut bool,
1581 ) {
1582 for s in stmts {
1583 match s {
1584 Stmt::Return { value: Some(e) } => {
1585 if !is_vec_ownable_return(e, params, borrow_idx, de_rc) {
1586 *ok = false;
1587 }
1588 }
1589 Stmt::Return { value: None } => *ok = false,
1590 Stmt::If { then_block, else_block, .. } => {
1591 walk(then_block, params, borrow_idx, de_rc, ok);
1592 if let Some(eb) = else_block {
1593 walk(eb, params, borrow_idx, de_rc, ok);
1594 }
1595 }
1596 Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Zone { body, .. } => {
1597 walk(body, params, borrow_idx, de_rc, ok)
1598 }
1599 _ => {}
1600 }
1601 }
1602 }
1603 let mut ok = true;
1604 walk(body, params, borrow_idx, de_rc, &mut ok);
1605 ok
1606}
1607
1608fn is_vec_ownable_return(
1609 e: &Expr,
1610 params: &[(Symbol, &TypeExpr)],
1611 borrow_idx: Option<&HashSet<usize>>,
1612 de_rc: &HashSet<Symbol>,
1613) -> bool {
1614 match e {
1615 Expr::Identifier(x) => match params.iter().position(|(p, _)| p == x) {
1616 Some(idx) => borrow_idx.map_or(false, |b| b.contains(&idx)),
1618 None => de_rc.contains(x),
1620 },
1621 _ => false,
1622 }
1623}
1624
1625fn flag_inline_vec_calls(
1633 stmts: &[Stmt],
1634 vec_fns: &HashSet<Symbol>,
1635 remove: &mut HashSet<Symbol>,
1636) {
1637 let flag_in_expr = |e: &Expr, remove: &mut HashSet<Symbol>| {
1638 for &f in vec_fns {
1639 if symbol_appears_in_expr(f, e) {
1640 remove.insert(f);
1641 }
1642 }
1643 };
1644 for stmt in stmts {
1645 match stmt {
1646 Stmt::Let { value, .. } | Stmt::Set { value, .. } => {
1647 if let Expr::Call { args, .. } = value {
1652 for a in args.iter() {
1653 flag_in_expr(a, remove);
1654 }
1655 } else {
1656 flag_in_expr(value, remove);
1657 }
1658 }
1659 Stmt::If { cond, then_block, else_block } => {
1660 flag_in_expr(cond, remove);
1661 flag_inline_vec_calls(then_block, vec_fns, remove);
1662 if let Some(eb) = else_block {
1663 flag_inline_vec_calls(eb, vec_fns, remove);
1664 }
1665 }
1666 Stmt::While { cond, body, .. } => {
1667 flag_in_expr(cond, remove);
1668 flag_inline_vec_calls(body, vec_fns, remove);
1669 }
1670 Stmt::Repeat { iterable, body, .. } => {
1671 flag_in_expr(iterable, remove);
1672 flag_inline_vec_calls(body, vec_fns, remove);
1673 }
1674 Stmt::Zone { body, .. } | Stmt::FunctionDef { body, .. } => {
1675 flag_inline_vec_calls(body, vec_fns, remove)
1676 }
1677 other => {
1681 for &f in vec_fns {
1682 if symbol_appears_in_stmt(f, other) {
1683 remove.insert(f);
1684 }
1685 }
1686 }
1687 }
1688 }
1689}
1690
1691fn is_vec_return_call(value: &Expr, vec_return_fns: &HashSet<Symbol>) -> bool {
1695 matches!(value, Expr::Call { function, .. } if vec_return_fns.contains(function))
1696}
1697
1698fn is_mut_borrow_inplace_call(
1703 target: Symbol,
1704 value: &Expr,
1705 mut_borrow_params: &HashMap<Symbol, HashSet<usize>>,
1706) -> bool {
1707 if let Expr::Call { function, args } = value {
1708 if let Some(slots) = mut_borrow_params.get(function) {
1709 return args.iter().enumerate().any(|(i, a)| {
1710 slots.contains(&i) && matches!(a, Expr::Identifier(s) if *s == target)
1711 });
1712 }
1713 }
1714 false
1715}
1716
1717fn collect_buffer_swap_pairs(
1723 stmts: &[Stmt],
1724 interner: &Interner,
1725 out: &mut HashSet<(Symbol, Symbol)>,
1726) {
1727 for stmt in stmts {
1728 match stmt {
1729 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
1730 detect_swap_in_body(body, interner, out);
1731 collect_buffer_swap_pairs(body, interner, out);
1732 }
1733 Stmt::If { then_block, else_block, .. } => {
1734 collect_buffer_swap_pairs(then_block, interner, out);
1735 if let Some(eb) = else_block {
1736 collect_buffer_swap_pairs(eb, interner, out);
1737 }
1738 }
1739 Stmt::Zone { body, .. } => collect_buffer_swap_pairs(body, interner, out),
1740 _ => {}
1741 }
1742 }
1743}
1744
1745fn detect_swap_in_body(body: &[Stmt], interner: &Interner, out: &mut HashSet<(Symbol, Symbol)>) {
1746 let mut fresh_inner: HashSet<Symbol> = HashSet::new();
1747 for stmt in body {
1748 if let Stmt::Let { var, value, mutable: true, .. } = stmt {
1749 if is_fresh_seq_value(value, interner) {
1750 fresh_inner.insert(*var);
1751 }
1752 }
1753 }
1754 if fresh_inner.is_empty() {
1755 return;
1756 }
1757 for (idx, stmt) in body.iter().enumerate() {
1758 if let Stmt::Set { target, value: Expr::Identifier(src) } = stmt {
1759 if *target != *src && fresh_inner.contains(src) {
1760 let used_after = body[idx + 1..]
1761 .iter()
1762 .any(|s| symbol_appears_in_stmt(*src, s));
1763 if !used_after {
1764 out.insert((*target, *src));
1765 }
1766 }
1767 }
1768 }
1769}
1770
1771fn derc_scan_uses(
1774 stmts: &[Stmt],
1775 cands: &HashSet<Symbol>,
1776 swaps: &HashSet<(Symbol, Symbol)>,
1777 borrow_params: &HashMap<Symbol, HashSet<usize>>,
1778 mut_borrow_params: &HashMap<Symbol, HashSet<usize>>,
1779 vec_return_fns: &HashSet<Symbol>,
1780 returns_vec: bool,
1781 interner: &Interner,
1782 dq: &mut HashSet<Symbol>,
1783) {
1784 for stmt in stmts {
1785 derc_scan_stmt(stmt, cands, swaps, borrow_params, mut_borrow_params, vec_return_fns, returns_vec, interner, dq);
1786 }
1787}
1788
1789fn derc_scan_call_args(
1794 function: Symbol,
1795 args: &[&Expr],
1796 cands: &HashSet<Symbol>,
1797 borrow_params: &HashMap<Symbol, HashSet<usize>>,
1798 dq: &mut HashSet<Symbol>,
1799) {
1800 let slots = borrow_params.get(&function);
1801 for (i, a) in args.iter().enumerate() {
1802 match a {
1803 Expr::Identifier(s) if cands.contains(s) => {
1804 let is_borrow_slot = slots.map_or(false, |set| set.contains(&i));
1805 if !is_borrow_slot {
1806 dq.insert(*s);
1807 }
1808 }
1809 _ => derc_scan_expr(a, cands, borrow_params, dq),
1810 }
1811 }
1812}
1813
1814fn is_fresh_seq_value(value: &Expr, interner: &Interner) -> bool {
1816 match value {
1817 Expr::New { type_name, init_fields, .. } if init_fields.is_empty() => {
1818 matches!(interner.resolve(*type_name), "Seq" | "List" | "Vec")
1819 }
1820 Expr::WithCapacity { value: inner, .. } => is_fresh_seq_value(inner, interner),
1821 Expr::List(_) => true,
1822 _ => false,
1823 }
1824}
1825
1826fn derc_scan_stmt(
1827 stmt: &Stmt,
1828 cands: &HashSet<Symbol>,
1829 swaps: &HashSet<(Symbol, Symbol)>,
1830 borrow_params: &HashMap<Symbol, HashSet<usize>>,
1831 mut_borrow_params: &HashMap<Symbol, HashSet<usize>>,
1832 vec_return_fns: &HashSet<Symbol>,
1833 returns_vec: bool,
1834 interner: &Interner,
1835 dq: &mut HashSet<Symbol>,
1836) {
1837 match stmt {
1838 Stmt::Let { value, .. } => {
1841 if is_vec_return_call(value, vec_return_fns) {
1842 if let Expr::Call { function, args } = value {
1846 derc_scan_call_args(*function, args, cands, borrow_params, dq);
1847 }
1848 } else if !is_fresh_seq_value(value, interner) {
1849 derc_scan_expr(value, cands, borrow_params, dq);
1850 }
1851 }
1852 Stmt::Set { target, value } => {
1853 if let Expr::Identifier(src) = value {
1854 if swaps.contains(&(*target, *src)) {
1855 } else if target != src {
1858 dq.insert(*src);
1861 dq.insert(*target);
1862 }
1863 } else if is_vec_return_call(value, vec_return_fns) {
1864 if let Expr::Call { function, args } = value {
1869 derc_scan_call_args(*function, args, cands, borrow_params, dq);
1870 }
1871 } else if is_mut_borrow_inplace_call(*target, value, mut_borrow_params) {
1872 if let Expr::Call { function, args } = value {
1878 derc_scan_call_args(*function, args, cands, borrow_params, dq);
1879 }
1880 } else if !is_fresh_seq_value(value, interner) {
1881 dq.insert(*target);
1886 derc_scan_expr(value, cands, borrow_params, dq);
1887 }
1888 }
1889 Stmt::Push { value, collection } | Stmt::Add { value, collection } => {
1892 derc_scan_collection_slot(collection, cands, borrow_params, dq);
1893 derc_scan_expr(value, cands, borrow_params, dq);
1894 }
1895 Stmt::Pop { collection, .. } => derc_scan_collection_slot(collection, cands, borrow_params, dq),
1896 Stmt::Remove { value, collection } => {
1897 derc_scan_collection_slot(collection, cands, borrow_params, dq);
1898 derc_scan_expr(value, cands, borrow_params, dq);
1899 }
1900 Stmt::SetIndex { collection, index, value } => {
1901 derc_scan_collection_slot(collection, cands, borrow_params, dq);
1902 derc_scan_expr(index, cands, borrow_params, dq);
1903 derc_scan_expr(value, cands, borrow_params, dq);
1904 }
1905 Stmt::If { cond, then_block, else_block } => {
1906 derc_scan_expr(cond, cands, borrow_params, dq);
1907 derc_scan_uses(then_block, cands, swaps, borrow_params, mut_borrow_params, vec_return_fns, returns_vec, interner, dq);
1908 if let Some(eb) = else_block {
1909 derc_scan_uses(eb, cands, swaps, borrow_params, mut_borrow_params, vec_return_fns, returns_vec, interner, dq);
1910 }
1911 }
1912 Stmt::While { cond, body, .. } => {
1913 derc_scan_expr(cond, cands, borrow_params, dq);
1914 derc_scan_uses(body, cands, swaps, borrow_params, mut_borrow_params, vec_return_fns, returns_vec, interner, dq);
1915 }
1916 Stmt::Repeat { iterable, body, .. } => {
1917 derc_scan_expr(iterable, cands, borrow_params, dq);
1919 derc_scan_uses(body, cands, swaps, borrow_params, mut_borrow_params, vec_return_fns, returns_vec, interner, dq);
1920 }
1921 Stmt::Zone { body, .. } => derc_scan_uses(body, cands, swaps, borrow_params, mut_borrow_params, vec_return_fns, returns_vec, interner, dq),
1922 Stmt::Call { function, args } => {
1925 derc_scan_call_args(*function, args, cands, borrow_params, dq);
1926 }
1927 Stmt::Return { value: Some(e) } => {
1930 if !(returns_vec && matches!(e, Expr::Identifier(x) if cands.contains(x))) {
1931 derc_scan_expr(e, cands, borrow_params, dq);
1932 }
1933 }
1934 Stmt::Return { value: None } => {}
1935 Stmt::Show { object, recipient } | Stmt::Give { object, recipient } => {
1936 derc_scan_expr(object, cands, borrow_params, dq);
1937 derc_scan_expr(recipient, cands, borrow_params, dq);
1938 }
1939 Stmt::FunctionDef { .. } => {}
1947 other => {
1951 for &c in cands {
1952 if symbol_appears_in_stmt(c, other) {
1953 dq.insert(c);
1954 }
1955 }
1956 }
1957 }
1958}
1959
1960fn derc_scan_collection_slot(
1963 collection: &Expr,
1964 cands: &HashSet<Symbol>,
1965 borrow_params: &HashMap<Symbol, HashSet<usize>>,
1966 dq: &mut HashSet<Symbol>,
1967) {
1968 match collection {
1969 Expr::Identifier(_) => {}
1970 other => derc_scan_expr(other, cands, borrow_params, dq),
1971 }
1972}
1973
1974fn derc_scan_expr(
1979 expr: &Expr,
1980 cands: &HashSet<Symbol>,
1981 borrow_params: &HashMap<Symbol, HashSet<usize>>,
1982 dq: &mut HashSet<Symbol>,
1983) {
1984 match expr {
1985 Expr::Identifier(s) => {
1986 if cands.contains(s) {
1987 dq.insert(*s);
1988 }
1989 }
1990 Expr::Index { collection, index } => {
1991 derc_scan_collection_slot(collection, cands, borrow_params, dq);
1992 derc_scan_expr(index, cands, borrow_params, dq);
1993 }
1994 Expr::Length { collection } => derc_scan_collection_slot(collection, cands, borrow_params, dq),
1995 Expr::BinaryOp { left, right, .. } => {
1996 derc_scan_expr(left, cands, borrow_params, dq);
1997 derc_scan_expr(right, cands, borrow_params, dq);
1998 }
1999 Expr::Not { operand } => derc_scan_expr(operand, cands, borrow_params, dq),
2000 Expr::Call { function, args } => {
2001 derc_scan_call_args(*function, args, cands, borrow_params, dq);
2002 }
2003 other => {
2007 for &c in cands {
2008 if symbol_appears_in_expr(c, other) {
2009 dq.insert(c);
2010 }
2011 }
2012 }
2013 }
2014}
2015
2016pub(crate) fn homogeneous_scalar_literal_elem(items: &[&Expr]) -> Option<String> {
2023 fn elem_ty(e: &Expr) -> Option<&'static str> {
2024 match e {
2025 Expr::Literal(Literal::Number(_)) => Some("i64"),
2026 Expr::Literal(Literal::Float(_)) => Some("f64"),
2027 Expr::Literal(Literal::Boolean(_)) => Some("bool"),
2028 Expr::Literal(Literal::Char(_)) => Some("char"),
2029 _ => None,
2030 }
2031 }
2032 let first = elem_ty(items.first()?)?;
2033 if items.iter().all(|i| elem_ty(i) == Some(first)) {
2034 Some(first.to_string())
2035 } else {
2036 None
2037 }
2038}
2039
2040fn fresh_scalar_seq_elem(value: &Expr, interner: &Interner) -> Option<String> {
2042 match value {
2043 Expr::New { type_name, type_args, init_fields } if init_fields.is_empty() => {
2044 match interner.resolve(*type_name) {
2045 "Seq" | "List" | "Vec" => {
2046 let elem = type_args.first()?;
2047 let ty = codegen_type_expr(elem, interner);
2048 if is_scalar_elem_type(&ty) { Some(ty) } else { None }
2049 }
2050 _ => None,
2051 }
2052 }
2053 Expr::WithCapacity { value: inner, .. } => fresh_scalar_seq_elem(inner, interner),
2054 Expr::List(items) => homogeneous_scalar_literal_elem(items),
2059 _ => None,
2060 }
2061}
2062
2063fn is_scalar_elem_type(ty: &str) -> bool {
2065 matches!(
2066 ty,
2067 "i64" | "i32" | "i16" | "i8" | "u64" | "u32" | "u16" | "u8" | "usize" | "isize"
2068 | "f64" | "f32" | "bool" | "char" | "String"
2069 | "Word8" | "Word16" | "Word32" | "Word64"
2071 )
2072}
2073
2074fn collect_de_rc_candidates_block(
2075 stmts: &[Stmt],
2076 interner: &Interner,
2077 vec_return_fns: &HashSet<Symbol>,
2078 out: &mut HashSet<Symbol>,
2079) {
2080 for stmt in stmts {
2081 match stmt {
2082 Stmt::Let { var, value, .. } => {
2083 if fresh_scalar_seq_elem(value, interner).is_some()
2086 || is_vec_return_call(value, vec_return_fns)
2087 {
2088 out.insert(*var);
2089 }
2090 }
2091 Stmt::If { then_block, else_block, .. } => {
2092 collect_de_rc_candidates_block(then_block, interner, vec_return_fns, out);
2093 if let Some(eb) = else_block {
2094 collect_de_rc_candidates_block(eb, interner, vec_return_fns, out);
2095 }
2096 }
2097 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
2098 collect_de_rc_candidates_block(body, interner, vec_return_fns, out);
2099 }
2100 Stmt::Zone { body, .. } => {
2101 collect_de_rc_candidates_block(body, interner, vec_return_fns, out);
2102 }
2103 _ => {}
2104 }
2105 }
2106}
2107
2108pub(super) fn collect_escaping_collection_vars(stmts: &[Stmt], interner: &Interner) -> HashSet<Symbol> {
2114 let mut escaped = HashSet::new();
2115 collect_escaping_vars_block(stmts, &mut escaped);
2116 escaped
2117}
2118
2119fn collect_escaping_vars_block(stmts: &[Stmt], escaped: &mut HashSet<Symbol>) {
2120 for stmt in stmts {
2121 collect_escaping_vars_stmt(stmt, escaped);
2122 }
2123}
2124
2125fn collect_escaping_vars_stmt(stmt: &Stmt, escaped: &mut HashSet<Symbol>) {
2126 match stmt {
2127 Stmt::Call { args, .. } => {
2128 for arg in args.iter() {
2129 collect_escaping_vars_from_call_arg(arg, escaped);
2130 }
2131 }
2132 Stmt::Return { value: Some(expr) } => {
2133 if let Expr::Identifier(sym) = expr {
2134 escaped.insert(*sym);
2135 }
2136 }
2137 Stmt::If { then_block, else_block, .. } => {
2138 collect_escaping_vars_block(then_block, escaped);
2139 if let Some(else_stmts) = else_block {
2140 collect_escaping_vars_block(else_stmts, escaped);
2141 }
2142 }
2143 Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
2144 collect_escaping_vars_block(body, escaped);
2145 }
2146 Stmt::Let { value, .. } => {
2147 collect_escaping_vars_from_expr_calls(value, escaped);
2148 }
2149 Stmt::Set { value, .. } => {
2150 collect_escaping_vars_from_expr_calls(value, escaped);
2151 }
2152 _ => {}
2153 }
2154}
2155
2156fn collect_escaping_vars_from_call_arg(expr: &Expr, escaped: &mut HashSet<Symbol>) {
2158 match expr {
2159 Expr::Identifier(sym) => { escaped.insert(*sym); }
2160 Expr::Copy { .. } => { }
2161 _ => {
2162 collect_escaping_vars_from_expr_ids(expr, escaped);
2164 }
2165 }
2166}
2167
2168fn collect_escaping_vars_from_expr_calls(expr: &Expr, escaped: &mut HashSet<Symbol>) {
2170 match expr {
2171 Expr::Call { args, .. } => {
2172 for arg in args.iter() {
2173 collect_escaping_vars_from_call_arg(arg, escaped);
2174 }
2175 }
2176 Expr::BinaryOp { left, right, .. } => {
2177 collect_escaping_vars_from_expr_calls(left, escaped);
2178 collect_escaping_vars_from_expr_calls(right, escaped);
2179 }
2180 _ => {}
2181 }
2182}
2183
2184fn collect_escaping_vars_from_expr_ids(expr: &Expr, escaped: &mut HashSet<Symbol>) {
2186 match expr {
2187 Expr::Identifier(sym) => { escaped.insert(*sym); }
2188 Expr::BinaryOp { left, right, .. } => {
2189 collect_escaping_vars_from_expr_ids(left, escaped);
2190 collect_escaping_vars_from_expr_ids(right, escaped);
2191 }
2192 _ => {}
2193 }
2194}
2195
2196pub(super) fn expr_debug_prefix(expr: &Expr, interner: &Interner) -> String {
2198 match expr {
2199 Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
2200 _ => "expr".to_string(),
2201 }
2202}
2203
2204pub(super) fn collect_stmt_identifiers(stmt: &Stmt, identifiers: &mut HashSet<Symbol>) {
2206 match stmt {
2207 Stmt::Let { value, .. } => {
2208 collect_expr_identifiers(value, identifiers);
2209 }
2210 Stmt::Call { args, .. } => {
2211 for arg in args {
2212 collect_expr_identifiers(arg, identifiers);
2213 }
2214 }
2215 _ => {}
2216 }
2217}
2218
2219pub(super) fn collect_give_arg_indices(fn_sym: Symbol, stmts: &[Stmt]) -> HashSet<usize> {
2222 let mut result = HashSet::new();
2223 for stmt in stmts {
2224 scan_give_args_stmt(fn_sym, stmt, &mut result);
2225 }
2226 result
2227}
2228
2229fn scan_give_args_stmt(fn_sym: Symbol, stmt: &Stmt, result: &mut HashSet<usize>) {
2230 match stmt {
2231 Stmt::Call { function, args } => {
2232 if *function == fn_sym {
2233 for (i, arg) in args.iter().enumerate() {
2234 if matches!(arg, Expr::Give { .. }) {
2235 result.insert(i);
2236 }
2237 }
2238 }
2239 for arg in args {
2240 scan_give_args_expr(fn_sym, arg, result);
2241 }
2242 }
2243 Stmt::Let { value, .. } | Stmt::Set { value, .. } => {
2244 scan_give_args_expr(fn_sym, value, result);
2245 }
2246 Stmt::Return { value: Some(v) } => scan_give_args_expr(fn_sym, v, result),
2247 Stmt::FunctionDef { body, .. } => {
2248 for s in *body {
2249 scan_give_args_stmt(fn_sym, s, result);
2250 }
2251 }
2252 Stmt::If { then_block, else_block, .. } => {
2253 for s in *then_block {
2254 scan_give_args_stmt(fn_sym, s, result);
2255 }
2256 if let Some(b) = else_block {
2257 for s in *b {
2258 scan_give_args_stmt(fn_sym, s, result);
2259 }
2260 }
2261 }
2262 Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Zone { body, .. } => {
2263 for s in *body {
2264 scan_give_args_stmt(fn_sym, s, result);
2265 }
2266 }
2267 Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
2268 for s in *tasks {
2269 scan_give_args_stmt(fn_sym, s, result);
2270 }
2271 }
2272 Stmt::Inspect { arms, .. } => {
2273 for arm in arms.iter() {
2274 for s in arm.body.iter() {
2275 scan_give_args_stmt(fn_sym, s, result);
2276 }
2277 }
2278 }
2279 _ => {}
2280 }
2281}
2282
2283fn scan_give_args_expr(fn_sym: Symbol, expr: &Expr, result: &mut HashSet<usize>) {
2284 match expr {
2285 Expr::Call { function, args } => {
2286 if *function == fn_sym {
2287 for (i, arg) in args.iter().enumerate() {
2288 if matches!(arg, Expr::Give { .. }) {
2289 result.insert(i);
2290 }
2291 }
2292 }
2293 for arg in args {
2294 scan_give_args_expr(fn_sym, arg, result);
2295 }
2296 }
2297 Expr::BinaryOp { left, right, .. } => {
2298 scan_give_args_expr(fn_sym, left, result);
2299 scan_give_args_expr(fn_sym, right, result);
2300 }
2301 Expr::FieldAccess { object, .. }
2302 | Expr::Give { value: object }
2303 | Expr::Copy { expr: object }
2304 | Expr::Length { collection: object } => {
2305 scan_give_args_expr(fn_sym, object, result);
2306 }
2307 Expr::Index { collection, index } => {
2308 scan_give_args_expr(fn_sym, collection, result);
2309 scan_give_args_expr(fn_sym, index, result);
2310 }
2311 Expr::List(items) | Expr::Tuple(items) => {
2312 for item in items {
2313 scan_give_args_expr(fn_sym, item, result);
2314 }
2315 }
2316 _ => {}
2317 }
2318}
2319
2320pub(super) struct ClosedFormInfo {
2328 pub base: i64,
2329 pub k: i64,
2330}
2331
2332pub(super) fn detect_double_recursion_closed_form<'a>(
2340 func_name: Symbol,
2341 params: &[(Symbol, &'a TypeExpr<'a>)],
2342 body: &'a [Stmt<'a>],
2343 interner: &Interner,
2344) -> Option<ClosedFormInfo> {
2345 use crate::ast::stmt::BinaryOpKind;
2346
2347 if params.len() != 1 {
2348 return None;
2349 }
2350 let param_sym = params[0].0;
2351
2352 let is_integer_param = matches!(
2358 params[0].1,
2359 TypeExpr::Primitive(sym) | TypeExpr::Named(sym)
2360 if matches!(interner.resolve(*sym), "Int" | "Nat" | "Byte")
2361 );
2362 if !is_integer_param {
2363 return None;
2364 }
2365
2366 if body.len() != 2 {
2368 return None;
2369 }
2370
2371 let base_value = match &body[0] {
2373 Stmt::If { cond, then_block, else_block } => {
2374 if else_block.is_some() {
2375 return None;
2376 }
2377 if then_block.len() != 1 {
2378 return None;
2379 }
2380 let base = match &then_block[0] {
2381 Stmt::Return { value: Some(expr) } => cf_extract_literal_int(expr)?,
2382 _ => return None,
2383 };
2384 match cond {
2385 Expr::BinaryOp { op: BinaryOpKind::Eq, left, right } => {
2386 let ok = (cf_is_identifier(left, param_sym) && cf_is_literal_int(right, 0))
2387 || (cf_is_literal_int(left, 0) && cf_is_identifier(right, param_sym));
2388 if !ok { return None; }
2389 }
2390 _ => return None,
2391 }
2392 base
2393 }
2394 _ => return None,
2395 };
2396
2397 let recursive_expr = match &body[1] {
2399 Stmt::Return { value: Some(expr) } => *expr,
2400 _ => return None,
2401 };
2402
2403 let mut self_call_count = 0usize;
2406 let mut constant_sum = 0i64;
2407 if !cf_analyze_add_tree(recursive_expr, func_name, param_sym, &mut self_call_count, &mut constant_sum) {
2408 return None;
2409 }
2410
2411 if self_call_count != 2 {
2412 return None;
2413 }
2414
2415 Some(ClosedFormInfo { base: base_value, k: constant_sum })
2416}
2417
2418fn cf_extract_literal_int(expr: &Expr) -> Option<i64> {
2419 if let Expr::Literal(crate::ast::stmt::Literal::Number(n)) = expr {
2420 Some(*n)
2421 } else {
2422 None
2423 }
2424}
2425
2426fn cf_is_identifier(expr: &Expr, sym: Symbol) -> bool {
2427 matches!(expr, Expr::Identifier(s) if *s == sym)
2428}
2429
2430fn cf_is_literal_int(expr: &Expr, val: i64) -> bool {
2431 matches!(expr, Expr::Literal(crate::ast::stmt::Literal::Number(n)) if *n == val)
2432}
2433
2434fn cf_is_self_call_with_decrement(expr: &Expr, func_name: Symbol, param_sym: Symbol) -> bool {
2435 use crate::ast::stmt::BinaryOpKind;
2436 if let Expr::Call { function, args } = expr {
2437 if *function != func_name || args.len() != 1 {
2438 return false;
2439 }
2440 if let Expr::BinaryOp { op: BinaryOpKind::Subtract, left, right } = args[0] {
2441 cf_is_identifier(left, param_sym) && cf_is_literal_int(right, 1)
2442 } else {
2443 false
2444 }
2445 } else {
2446 false
2447 }
2448}
2449
2450fn cf_analyze_add_tree(
2453 expr: &Expr,
2454 func_name: Symbol,
2455 param_sym: Symbol,
2456 self_calls: &mut usize,
2457 constant_sum: &mut i64,
2458) -> bool {
2459 use crate::ast::stmt::BinaryOpKind;
2460 match expr {
2461 Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => {
2462 cf_analyze_add_tree(left, func_name, param_sym, self_calls, constant_sum)
2463 && cf_analyze_add_tree(right, func_name, param_sym, self_calls, constant_sum)
2464 }
2465 _ if cf_is_self_call_with_decrement(expr, func_name, param_sym) => {
2466 *self_calls += 1;
2467 true
2468 }
2469 _ => {
2470 if let Some(n) = cf_extract_literal_int(expr) {
2471 *constant_sum += n;
2472 true
2473 } else {
2474 false
2475 }
2476 }
2477 }
2478}
2479
2480pub(crate) struct ScalarizableSeq {
2489 pub elem_ty: String,
2490 pub len: usize,
2491}
2492
2493struct ScalarCand {
2494 elem_ty: String,
2495 len: usize,
2496 seen_use: bool,
2498 disqualified: bool,
2499}
2500
2501fn scalar_seq_elem_ty(value: &Expr, interner: &Interner) -> Option<String> {
2503 if let Expr::New { type_name, type_args, .. } = value {
2504 if matches!(interner.resolve(*type_name), "Seq" | "List") && type_args.len() == 1 {
2505 if let TypeExpr::Primitive(t) | TypeExpr::Named(t) = &type_args[0] {
2506 return match interner.resolve(*t) {
2507 "Int" | "Nat" => Some("i64".to_string()),
2508 "Float" => Some("f64".to_string()),
2509 "Bool" => Some("bool".to_string()),
2510 _ => None,
2511 };
2512 }
2513 }
2514 }
2515 None
2516}
2517
2518const MAX_SCALARIZE_LEN: usize = 64;
2520
2521pub(crate) fn collect_scalarizable_seqs(
2525 stmts: &[Stmt],
2526 interner: &Interner,
2527) -> HashMap<Symbol, ScalarizableSeq> {
2528 if !crate::optimize::active_config().is_on(crate::optimization::Opt::Scalarize) {
2530 return HashMap::new();
2531 }
2532 let mut cand: HashMap<Symbol, ScalarCand> = HashMap::new();
2533 scalar_walk_block(stmts, true, &mut cand, interner);
2534 let result: HashMap<Symbol, ScalarizableSeq> = cand
2535 .into_iter()
2536 .filter_map(|(sym, c)| {
2537 if c.disqualified || c.len == 0 || c.len > MAX_SCALARIZE_LEN {
2538 None
2539 } else {
2540 Some((sym, ScalarizableSeq { elem_ty: c.elem_ty, len: c.len }))
2541 }
2542 })
2543 .collect();
2544 if !result.is_empty() {
2545 crate::optimize::mark_fired(crate::optimization::Opt::Scalarize);
2546 }
2547 result
2548}
2549
2550pub(crate) fn scalarizable_seq_symbols(
2559 stmts: &[Stmt],
2560 interner: &Interner,
2561) -> std::collections::HashSet<Symbol> {
2562 collect_scalarizable_seqs(stmts, interner)
2563 .into_keys()
2564 .collect()
2565}
2566
2567fn scalar_disq(cand: &mut HashMap<Symbol, ScalarCand>, sym: Symbol) {
2568 if let Some(c) = cand.get_mut(&sym) {
2569 c.disqualified = true;
2570 }
2571}
2572
2573pub(super) struct InterleaveGroup {
2583 pub members: Vec<Symbol>,
2585 pub len: usize,
2587 pub elem_ty: String,
2589}
2590
2591pub(super) fn collect_interleaved_groups(
2597 stmts: &[Stmt],
2598 scalarizable: &HashMap<Symbol, ScalarizableSeq>,
2599 _interner: &Interner,
2600) -> Vec<InterleaveGroup> {
2601 if !crate::optimize::active_config().is_on(crate::optimization::Opt::Interleave) {
2603 return Vec::new();
2604 }
2605 if scalarizable.len() < 2 {
2606 return Vec::new();
2607 }
2608 let mut push_seq: Vec<Symbol> = Vec::new();
2611 for s in stmts {
2612 if let Stmt::Push { collection, .. } = s {
2613 if let Expr::Identifier(sym) = collection {
2614 if scalarizable.contains_key(sym) {
2615 push_seq.push(*sym);
2616 }
2617 }
2618 }
2619 }
2620 if push_seq.len() < 2 {
2621 return Vec::new();
2622 }
2623 let first = push_seq[0];
2625 let w = match push_seq.iter().skip(1).position(|&s| s == first) {
2626 Some(p) => p + 1,
2627 None => return Vec::new(),
2628 };
2629 if w < 2 || push_seq.len() % w != 0 {
2630 return Vec::new();
2631 }
2632 let group: Vec<Symbol> = push_seq[..w].to_vec();
2633 let mut seen = std::collections::HashSet::new();
2635 if !group.iter().all(|s| seen.insert(*s)) {
2636 return Vec::new();
2637 }
2638 let rounds = push_seq.len() / w;
2640 for r in 0..rounds {
2641 for c in 0..w {
2642 if push_seq[r * w + c] != group[c] {
2643 return Vec::new();
2644 }
2645 }
2646 }
2647 if group.len() != scalarizable.len() {
2650 return Vec::new();
2651 }
2652 let elem_ty = scalarizable[&group[0]].elem_ty.clone();
2653 for m in &group {
2654 let info = &scalarizable[m];
2655 if info.elem_ty != elem_ty || info.len != rounds {
2656 return Vec::new();
2657 }
2658 }
2659 let force = std::env::var("LOGOS_AOS_FORCE").map(|v| v == "1").unwrap_or(false);
2668 let members: std::collections::HashSet<Symbol> = group.iter().copied().collect();
2669 if !force && block_has_const_member_index(stmts, &members) {
2670 crate::optimize::mark_preempted(
2673 crate::optimization::Opt::Scalarize,
2674 crate::optimization::Opt::Interleave,
2675 );
2676 crate::optimize::mark_preempted(
2677 crate::optimization::Opt::Unroll,
2678 crate::optimization::Opt::Interleave,
2679 );
2680 return Vec::new();
2681 }
2682 crate::optimize::mark_fired(crate::optimization::Opt::Interleave);
2683 vec![InterleaveGroup { members: group, len: rounds, elem_ty }]
2684}
2685
2686fn block_has_const_member_index(stmts: &[Stmt], members: &std::collections::HashSet<Symbol>) -> bool {
2689 stmts.iter().any(|s| stmt_has_const_member_index(s, members))
2690}
2691
2692fn is_member_const_index(collection: &Expr, index: &Expr, members: &std::collections::HashSet<Symbol>) -> bool {
2693 matches!(collection, Expr::Identifier(s) if members.contains(s))
2694 && matches!(index, Expr::Literal(Literal::Number(_)))
2695}
2696
2697fn stmt_has_const_member_index(s: &Stmt, members: &std::collections::HashSet<Symbol>) -> bool {
2698 match s {
2699 Stmt::SetIndex { collection, index, value } => {
2700 is_member_const_index(collection, index, members)
2701 || expr_has_const_member_index(collection, members)
2702 || expr_has_const_member_index(index, members)
2703 || expr_has_const_member_index(value, members)
2704 }
2705 Stmt::Let { value, .. } | Stmt::Set { value, .. } => expr_has_const_member_index(value, members),
2706 Stmt::Push { value, collection } => {
2707 expr_has_const_member_index(value, members) || expr_has_const_member_index(collection, members)
2708 }
2709 Stmt::Show { object, .. } => expr_has_const_member_index(object, members),
2710 Stmt::SetField { object, value, .. } => {
2711 expr_has_const_member_index(object, members) || expr_has_const_member_index(value, members)
2712 }
2713 Stmt::Give { object, recipient } => {
2714 expr_has_const_member_index(object, members) || expr_has_const_member_index(recipient, members)
2715 }
2716 Stmt::Add { value, collection } | Stmt::Remove { value, collection } => {
2717 expr_has_const_member_index(value, members) || expr_has_const_member_index(collection, members)
2718 }
2719 Stmt::RuntimeAssert { condition, .. } => expr_has_const_member_index(condition, members),
2720 Stmt::Return { value } => matches!(value, Some(v) if expr_has_const_member_index(v, members)),
2721 Stmt::Call { args, .. } => args.iter().any(|a| expr_has_const_member_index(a, members)),
2722 Stmt::If { cond, then_block, else_block } => {
2723 expr_has_const_member_index(cond, members)
2724 || block_has_const_member_index(then_block, members)
2725 || matches!(else_block, Some(eb) if block_has_const_member_index(eb, members))
2726 }
2727 Stmt::While { cond, body, .. } => {
2728 expr_has_const_member_index(cond, members) || block_has_const_member_index(body, members)
2729 }
2730 Stmt::Repeat { iterable, body, .. } => {
2731 expr_has_const_member_index(iterable, members) || block_has_const_member_index(body, members)
2732 }
2733 Stmt::Inspect { target, arms, .. } => {
2734 expr_has_const_member_index(target, members)
2735 || arms.iter().any(|a| block_has_const_member_index(a.body, members))
2736 }
2737 _ => false,
2738 }
2739}
2740
2741fn expr_has_const_member_index(e: &Expr, members: &std::collections::HashSet<Symbol>) -> bool {
2742 match e {
2743 Expr::Index { collection, index } => {
2744 is_member_const_index(collection, index, members)
2745 || expr_has_const_member_index(collection, members)
2746 || expr_has_const_member_index(index, members)
2747 }
2748 Expr::Slice { collection, start, end } => {
2749 expr_has_const_member_index(collection, members)
2750 || expr_has_const_member_index(start, members)
2751 || expr_has_const_member_index(end, members)
2752 }
2753 Expr::BinaryOp { left, right, .. } => {
2754 expr_has_const_member_index(left, members) || expr_has_const_member_index(right, members)
2755 }
2756 Expr::Not { operand } => expr_has_const_member_index(operand, members),
2757 Expr::Length { collection } => expr_has_const_member_index(collection, members),
2758 Expr::Contains { collection, value } => {
2759 expr_has_const_member_index(collection, members) || expr_has_const_member_index(value, members)
2760 }
2761 Expr::Union { left, right } | Expr::Intersection { left, right } => {
2762 expr_has_const_member_index(left, members) || expr_has_const_member_index(right, members)
2763 }
2764 Expr::Call { args, .. } | Expr::CallExpr { args, .. } => {
2765 args.iter().any(|a| expr_has_const_member_index(a, members))
2766 }
2767 Expr::FieldAccess { object, .. } => expr_has_const_member_index(object, members),
2768 Expr::Copy { expr } => expr_has_const_member_index(expr, members),
2769 Expr::Give { value } | Expr::OptionSome { value } => expr_has_const_member_index(value, members),
2770 Expr::WithCapacity { value, capacity } => {
2771 expr_has_const_member_index(value, members) || expr_has_const_member_index(capacity, members)
2772 }
2773 Expr::Range { start, end } => {
2774 expr_has_const_member_index(start, members) || expr_has_const_member_index(end, members)
2775 }
2776 Expr::List(items) | Expr::Tuple(items) => {
2777 items.iter().any(|i| expr_has_const_member_index(i, members))
2778 }
2779 Expr::InterpolatedString(parts) => parts.iter().any(|p| match p {
2780 crate::ast::stmt::StringPart::Expr { value, .. } => expr_has_const_member_index(value, members),
2781 _ => false,
2782 }),
2783 _ => false,
2784 }
2785}
2786
2787pub(super) struct AosTag {
2791 pub backing: String,
2792 pub col: usize,
2793 pub width: usize,
2794 pub len: usize,
2795 pub elem: String,
2796}
2797
2798pub(super) fn parse_aos_tag(ty: Option<&String>) -> Option<AosTag> {
2803 let rest = ty?.strip_prefix("__aos:")?;
2804 let mut parts = rest.split(':');
2805 let backing = parts.next()?.to_string();
2806 let col = parts.next()?.parse().ok()?;
2807 let width = parts.next()?.parse().ok()?;
2808 let len = parts.next()?.parse().ok()?;
2809 let elem = parts.next()?.to_string();
2810 Some(AosTag { backing, col, width, len, elem })
2811}
2812
2813fn scalar_mark_use(cand: &mut HashMap<Symbol, ScalarCand>, sym: Symbol) {
2814 if let Some(c) = cand.get_mut(&sym) {
2815 c.seen_use = true;
2816 }
2817}
2818
2819fn scalar_note_access(e: &Expr, cand: &mut HashMap<Symbol, ScalarCand>) {
2822 if let Expr::Identifier(s) = e {
2823 scalar_mark_use(cand, *s);
2824 } else {
2825 scalar_note_value(e, cand);
2826 }
2827}
2828
2829fn scalar_note_value(e: &Expr, cand: &mut HashMap<Symbol, ScalarCand>) {
2833 match e {
2834 Expr::Identifier(s) => scalar_disq(cand, *s),
2835 Expr::Literal(_) | Expr::OptionNone => {}
2836 Expr::Index { collection, index } => {
2837 scalar_note_access(collection, cand);
2838 scalar_note_value(index, cand);
2839 }
2840 Expr::Length { collection } => scalar_note_access(collection, cand),
2841 Expr::Slice { collection, start, end } => {
2842 if let Expr::Identifier(s) = collection {
2844 scalar_disq(cand, *s);
2845 } else {
2846 scalar_note_value(collection, cand);
2847 }
2848 scalar_note_value(start, cand);
2849 scalar_note_value(end, cand);
2850 }
2851 Expr::Contains { collection, value } => {
2852 if let Expr::Identifier(s) = collection {
2853 scalar_disq(cand, *s);
2854 } else {
2855 scalar_note_value(collection, cand);
2856 }
2857 scalar_note_value(value, cand);
2858 }
2859 Expr::Copy { expr } => {
2860 if let Expr::Identifier(s) = expr {
2861 scalar_disq(cand, *s);
2862 } else {
2863 scalar_note_value(expr, cand);
2864 }
2865 }
2866 Expr::BinaryOp { left, right, .. }
2867 | Expr::Union { left, right }
2868 | Expr::Intersection { left, right }
2869 | Expr::Range { start: left, end: right } => {
2870 scalar_note_value(left, cand);
2871 scalar_note_value(right, cand);
2872 }
2873 Expr::Not { operand } => scalar_note_value(operand, cand),
2874 Expr::FieldAccess { object, .. } => scalar_note_value(object, cand),
2875 Expr::OptionSome { value } | Expr::Give { value } => scalar_note_value(value, cand),
2876 Expr::WithCapacity { value, capacity } => {
2877 scalar_note_value(value, cand);
2878 scalar_note_value(capacity, cand);
2879 }
2880 Expr::List(items) | Expr::Tuple(items) => {
2881 for it in items {
2882 scalar_note_value(it, cand);
2883 }
2884 }
2885 Expr::New { init_fields, .. } => {
2886 for (_, v) in init_fields {
2887 scalar_note_value(v, cand);
2888 }
2889 }
2890 Expr::NewVariant { fields, .. } => {
2891 for (_, v) in fields {
2892 scalar_note_value(v, cand);
2893 }
2894 }
2895 Expr::Call { args, .. } => {
2896 for a in args {
2897 scalar_note_value(a, cand);
2898 }
2899 }
2900 Expr::CallExpr { callee, args } => {
2901 scalar_note_value(callee, cand);
2902 for a in args {
2903 scalar_note_value(a, cand);
2904 }
2905 }
2906 Expr::InterpolatedString(parts) => {
2907 for p in parts {
2908 if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
2909 scalar_note_value(value, cand);
2910 }
2911 }
2912 }
2913 Expr::ManifestOf { zone } => scalar_note_value(zone, cand),
2914 Expr::ChunkAt { index, zone } => {
2915 scalar_note_value(index, cand);
2916 scalar_note_value(zone, cand);
2917 }
2918 Expr::Closure { .. } | Expr::Escape { .. } => {
2922 for c in cand.values_mut() {
2923 c.disqualified = true;
2924 }
2925 }
2926 _ => {}
2927 }
2928}
2929
2930fn scalar_walk_block(
2931 stmts: &[Stmt],
2932 top_level: bool,
2933 cand: &mut HashMap<Symbol, ScalarCand>,
2934 interner: &Interner,
2935) {
2936 for stmt in stmts {
2937 match stmt {
2938 Stmt::Let { var, value, mutable, .. } => {
2939 if top_level && *mutable {
2940 if let Some(elem) = scalar_seq_elem_ty(value, interner) {
2941 cand.insert(
2942 *var,
2943 ScalarCand { elem_ty: elem, len: 0, seen_use: false, disqualified: false },
2944 );
2945 continue;
2946 }
2947 }
2948 if let Expr::Identifier(s) = value {
2950 scalar_disq(cand, *s);
2951 } else {
2952 scalar_note_value(value, cand);
2953 }
2954 }
2955 Stmt::Push { value, collection } => {
2956 if let Expr::Identifier(s) = value {
2958 scalar_disq(cand, *s);
2959 } else {
2960 scalar_note_value(value, cand);
2961 }
2962 if let Expr::Identifier(x) = collection {
2963 let nested_or_used = {
2964 match cand.get(x) {
2965 Some(c) => !top_level || c.seen_use || c.disqualified,
2966 None => false,
2967 }
2968 };
2969 if cand.contains_key(x) {
2970 if nested_or_used {
2971 scalar_disq(cand, *x);
2972 } else if let Some(c) = cand.get_mut(x) {
2973 c.len += 1;
2974 }
2975 }
2976 } else {
2977 scalar_note_value(collection, cand);
2978 }
2979 }
2980 Stmt::SetIndex { collection, index, value } => {
2981 if let Expr::Identifier(x) = collection {
2982 scalar_mark_use(cand, *x);
2983 } else {
2984 scalar_note_value(collection, cand);
2985 }
2986 scalar_note_value(index, cand);
2987 scalar_note_value(value, cand);
2988 }
2989 Stmt::Set { target, value } => {
2990 scalar_disq(cand, *target);
2992 if let Expr::Identifier(s) = value {
2993 scalar_disq(cand, *s);
2994 } else {
2995 scalar_note_value(value, cand);
2996 }
2997 }
2998 Stmt::Pop { collection, .. } => {
2999 if let Expr::Identifier(x) = collection {
3000 scalar_disq(cand, *x);
3001 } else {
3002 scalar_note_value(collection, cand);
3003 }
3004 }
3005 Stmt::Add { collection, value } | Stmt::Remove { collection, value } => {
3006 if let Expr::Identifier(x) = collection {
3007 scalar_disq(cand, *x);
3008 } else {
3009 scalar_note_value(collection, cand);
3010 }
3011 scalar_note_value(value, cand);
3012 }
3013 Stmt::Show { object, recipient } => {
3014 scalar_note_value(object, cand);
3015 scalar_note_value(recipient, cand);
3016 }
3017 Stmt::Give { object, recipient } => {
3018 if let Expr::Identifier(s) = object {
3019 scalar_disq(cand, *s);
3020 } else {
3021 scalar_note_value(object, cand);
3022 }
3023 scalar_note_value(recipient, cand);
3024 }
3025 Stmt::Return { value: Some(v) } => {
3026 if let Expr::Identifier(s) = v {
3027 scalar_disq(cand, *s);
3028 } else {
3029 scalar_note_value(v, cand);
3030 }
3031 }
3032 Stmt::SetField { object, value, .. } => {
3033 scalar_note_value(object, cand);
3034 scalar_note_value(value, cand);
3035 }
3036 Stmt::If { cond, then_block, else_block } => {
3037 scalar_note_value(cond, cand);
3038 scalar_walk_block(then_block, false, cand, interner);
3039 if let Some(eb) = else_block {
3040 scalar_walk_block(eb, false, cand, interner);
3041 }
3042 }
3043 Stmt::While { cond, body, .. } => {
3044 scalar_note_value(cond, cand);
3045 scalar_walk_block(body, false, cand, interner);
3046 }
3047 Stmt::Repeat { iterable, body, .. } => {
3048 if let Expr::Identifier(x) = iterable {
3049 scalar_disq(cand, *x);
3050 } else {
3051 scalar_note_value(iterable, cand);
3052 }
3053 scalar_walk_block(body, false, cand, interner);
3054 }
3055 Stmt::Inspect { target, arms, .. } => {
3056 scalar_note_value(target, cand);
3057 for arm in arms {
3058 scalar_walk_block(&arm.body, false, cand, interner);
3059 }
3060 }
3061 Stmt::RuntimeAssert { condition, .. } => scalar_note_value(condition, cand),
3062 Stmt::Call { args, .. } => {
3063 for a in args {
3064 if let Expr::Identifier(s) = a {
3065 scalar_disq(cand, *s);
3066 } else {
3067 scalar_note_value(a, cand);
3068 }
3069 }
3070 }
3071 Stmt::Zone { body, .. } => scalar_walk_block(body, false, cand, interner),
3072 Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
3073 scalar_walk_block(tasks, false, cand, interner)
3074 }
3075 _ => {}
3078 }
3079 }
3080}