use std::collections::{HashMap, HashSet};
use crate::analysis::registry::{FieldType, TypeDef, TypeRegistry};
use crate::ast::stmt::{Expr, ReadSource, Stmt, TypeExpr};
use crate::intern::{Interner, Symbol};
use super::is_recursive_field;
pub(super) fn is_result_type(ty: &TypeExpr, interner: &Interner) -> bool {
if let TypeExpr::Generic { base, .. } = ty {
interner.resolve(*base) == "Result"
} else {
false
}
}
pub(super) fn requires_async(stmts: &[Stmt]) -> bool {
stmts.iter().any(|s| requires_async_stmt(s))
}
pub(super) fn requires_async_stmt(stmt: &Stmt) -> bool {
match stmt {
Stmt::Concurrent { tasks } => true,
Stmt::Listen { .. } => true,
Stmt::ConnectTo { .. } => true,
Stmt::Sleep { .. } => true,
Stmt::Sync { .. } => true,
Stmt::Mount { .. } => true,
Stmt::ReadFrom { source: ReadSource::File(_), .. } => true,
Stmt::WriteFile { .. } => true,
Stmt::LaunchTask { .. } => true,
Stmt::LaunchTaskWithHandle { .. } => true,
Stmt::SendPipe { .. } => true,
Stmt::ReceivePipe { .. } => true,
Stmt::Select { .. } => true,
Stmt::If { then_block, else_block, .. } => {
then_block.iter().any(|s| requires_async_stmt(s))
|| else_block.map_or(false, |b| b.iter().any(|s| requires_async_stmt(s)))
}
Stmt::While { body, .. } => body.iter().any(|s| requires_async_stmt(s)),
Stmt::Repeat { body, .. } => body.iter().any(|s| requires_async_stmt(s)),
Stmt::Zone { body, .. } => body.iter().any(|s| requires_async_stmt(s)),
Stmt::Parallel { tasks } => tasks.iter().any(|s| requires_async_stmt(s)),
Stmt::FunctionDef { body, .. } => body.iter().any(|s| requires_async_stmt(s)),
Stmt::Inspect { arms, .. } => {
arms.iter().any(|arm| arm.body.iter().any(|s| requires_async_stmt(s)))
}
_ => false,
}
}
pub(super) fn requires_vfs(stmts: &[Stmt]) -> bool {
stmts.iter().any(|s| requires_vfs_stmt(s))
}
pub(super) fn requires_vfs_stmt(stmt: &Stmt) -> bool {
match stmt {
Stmt::Mount { .. } => true,
Stmt::ReadFrom { source: ReadSource::File(_), .. } => true,
Stmt::WriteFile { .. } => true,
Stmt::If { then_block, else_block, .. } => {
then_block.iter().any(|s| requires_vfs_stmt(s))
|| else_block.map_or(false, |b| b.iter().any(|s| requires_vfs_stmt(s)))
}
Stmt::While { body, .. } => body.iter().any(|s| requires_vfs_stmt(s)),
Stmt::Repeat { body, .. } => body.iter().any(|s| requires_vfs_stmt(s)),
Stmt::Zone { body, .. } => body.iter().any(|s| requires_vfs_stmt(s)),
Stmt::Concurrent { tasks } => tasks.iter().any(|s| requires_vfs_stmt(s)),
Stmt::Parallel { tasks } => tasks.iter().any(|s| requires_vfs_stmt(s)),
Stmt::FunctionDef { body, .. } => body.iter().any(|s| requires_vfs_stmt(s)),
_ => false,
}
}
pub(super) fn get_root_identifier_for_mutability(expr: &Expr) -> Option<Symbol> {
match expr {
Expr::Identifier(sym) => Some(*sym),
Expr::FieldAccess { object, .. } => get_root_identifier_for_mutability(object),
_ => None,
}
}
pub(super) fn collect_mutable_vars(stmts: &[Stmt]) -> HashSet<Symbol> {
let mut targets = HashSet::new();
for stmt in stmts {
collect_mutable_vars_stmt(stmt, &mut targets);
}
targets
}
pub(super) fn collect_mutable_vars_stmt(stmt: &Stmt, targets: &mut HashSet<Symbol>) {
match stmt {
Stmt::Set { target, .. } => {
targets.insert(*target);
}
Stmt::Push { collection, .. } => {
if let Some(sym) = get_root_identifier_for_mutability(collection) {
targets.insert(sym);
}
}
Stmt::Pop { collection, .. } => {
if let Some(sym) = get_root_identifier_for_mutability(collection) {
targets.insert(sym);
}
}
Stmt::Add { collection, .. } => {
if let Some(sym) = get_root_identifier_for_mutability(collection) {
targets.insert(sym);
}
}
Stmt::Remove { collection, .. } => {
if let Some(sym) = get_root_identifier_for_mutability(collection) {
targets.insert(sym);
}
}
Stmt::SetIndex { collection, .. } => {
if let Some(sym) = get_root_identifier_for_mutability(collection) {
targets.insert(sym);
}
}
Stmt::If { then_block, else_block, .. } => {
for s in *then_block {
collect_mutable_vars_stmt(s, targets);
}
if let Some(else_stmts) = else_block {
for s in *else_stmts {
collect_mutable_vars_stmt(s, targets);
}
}
}
Stmt::While { body, .. } => {
for s in *body {
collect_mutable_vars_stmt(s, targets);
}
}
Stmt::Repeat { body, .. } => {
for s in *body {
collect_mutable_vars_stmt(s, targets);
}
}
Stmt::Zone { body, .. } => {
for s in *body {
collect_mutable_vars_stmt(s, targets);
}
}
Stmt::Inspect { arms, .. } => {
for arm in arms.iter() {
for s in arm.body.iter() {
collect_mutable_vars_stmt(s, targets);
}
}
}
Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
for s in *tasks {
collect_mutable_vars_stmt(s, targets);
}
}
Stmt::IncreaseCrdt { object, .. } | Stmt::DecreaseCrdt { object, .. } => {
if let Some(sym) = get_root_identifier_for_mutability(object) {
targets.insert(sym);
}
}
Stmt::AppendToSequence { sequence, .. } => {
if let Some(sym) = get_root_identifier_for_mutability(sequence) {
targets.insert(sym);
}
}
Stmt::ResolveConflict { object, .. } => {
if let Some(sym) = get_root_identifier_for_mutability(object) {
targets.insert(sym);
}
}
Stmt::SetField { object, .. } => {
if let Some(sym) = get_root_identifier_for_mutability(object) {
targets.insert(sym);
}
}
_ => {}
}
}
pub(super) fn collect_single_char_text_vars(stmts: &[Stmt], interner: &Interner) -> HashSet<Symbol> {
let mut candidates: HashSet<Symbol> = HashSet::new();
let mut disqualified: HashSet<Symbol> = HashSet::new();
scan_single_char_candidates(stmts, interner, &mut candidates, &mut disqualified);
for sym in &disqualified {
candidates.remove(sym);
}
let mut usage_disqualified: HashSet<Symbol> = HashSet::new();
check_single_char_usage(stmts, &candidates, &mut usage_disqualified);
for sym in &usage_disqualified {
candidates.remove(sym);
}
candidates
}
fn is_single_char_text_literal<'a>(expr: &Expr<'a>, interner: &Interner) -> bool {
if let Expr::Literal(crate::ast::stmt::Literal::Text(sym)) = expr {
let text = interner.resolve(*sym);
text.len() == 1 && text.is_ascii()
} else {
false
}
}
fn scan_single_char_candidates(
stmts: &[Stmt],
interner: &Interner,
candidates: &mut HashSet<Symbol>,
disqualified: &mut HashSet<Symbol>,
) {
for stmt in stmts {
match stmt {
Stmt::Let { var, value, mutable, .. } => {
if *mutable && is_single_char_text_literal(value, interner) {
candidates.insert(*var);
}
}
Stmt::Set { target, value } => {
if candidates.contains(target) || !disqualified.contains(target) {
if !is_single_char_text_literal(value, interner) {
disqualified.insert(*target);
}
}
}
Stmt::If { then_block, else_block, .. } => {
scan_single_char_candidates(then_block, interner, candidates, disqualified);
if let Some(eb) = else_block {
scan_single_char_candidates(eb, interner, candidates, disqualified);
}
}
Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Zone { body, .. } => {
scan_single_char_candidates(body, interner, candidates, disqualified);
}
Stmt::FunctionDef { body, .. } => {
scan_single_char_candidates(body, interner, candidates, disqualified);
}
Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
scan_single_char_candidates(tasks, interner, candidates, disqualified);
}
_ => {}
}
}
}
fn check_single_char_usage(
stmts: &[Stmt],
candidates: &HashSet<Symbol>,
disqualified: &mut HashSet<Symbol>,
) {
for stmt in stmts {
match stmt {
Stmt::Set { target, value } => {
if !candidates.contains(target) {
check_expr_usage(value, candidates, disqualified, true);
}
}
Stmt::Show { object, .. } => {
}
Stmt::Let { value, .. } => {
check_expr_usage_strict(value, candidates, disqualified);
}
Stmt::Return { value: Some(v) } => {
check_expr_usage_strict(v, candidates, disqualified);
}
Stmt::Call { args, .. } => {
for a in args.iter() {
check_expr_usage_strict(a, candidates, disqualified);
}
}
Stmt::Push { value, .. } => {
check_expr_usage_strict(value, candidates, disqualified);
}
Stmt::If { cond, then_block, else_block } => {
check_single_char_usage(then_block, candidates, disqualified);
if let Some(eb) = else_block {
check_single_char_usage(eb, candidates, disqualified);
}
}
Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Zone { body, .. } => {
check_single_char_usage(body, candidates, disqualified);
}
Stmt::FunctionDef { body, .. } => {
check_single_char_usage(body, candidates, disqualified);
}
_ => {}
}
}
}
fn check_expr_usage(
expr: &Expr,
candidates: &HashSet<Symbol>,
disqualified: &mut HashSet<Symbol>,
is_self_append: bool,
) {
match expr {
Expr::BinaryOp { op: crate::ast::stmt::BinaryOpKind::Add, left, right } if is_self_append => {
check_expr_usage(left, candidates, disqualified, true);
if !matches!(right, Expr::Identifier(sym) if candidates.contains(sym)) {
check_expr_usage(right, candidates, disqualified, false);
}
}
Expr::Identifier(sym) if !is_self_append => {
if candidates.contains(sym) {
disqualified.insert(*sym);
}
}
_ => {}
}
}
fn check_expr_usage_strict(expr: &Expr, candidates: &HashSet<Symbol>, disqualified: &mut HashSet<Symbol>) {
match expr {
Expr::Identifier(sym) => {
if candidates.contains(sym) {
disqualified.insert(*sym);
}
}
Expr::BinaryOp { left, right, .. } => {
check_expr_usage_strict(left, candidates, disqualified);
check_expr_usage_strict(right, candidates, disqualified);
}
Expr::Call { args, .. } => {
for a in args.iter() {
check_expr_usage_strict(a, candidates, disqualified);
}
}
Expr::Not { operand } => check_expr_usage_strict(operand, candidates, disqualified),
Expr::Index { collection, index } => {
check_expr_usage_strict(collection, candidates, disqualified);
check_expr_usage_strict(index, candidates, disqualified);
}
Expr::Length { collection } => check_expr_usage_strict(collection, candidates, disqualified),
Expr::List(items) => {
for item in items.iter() {
check_expr_usage_strict(item, candidates, disqualified);
}
}
_ => {}
}
}
pub(super) fn collect_crdt_register_fields(registry: &TypeRegistry, interner: &Interner) -> (HashSet<(String, String)>, HashSet<(String, String)>) {
let mut lww_fields = HashSet::new();
let mut mv_fields = HashSet::new();
for (type_sym, def) in registry.iter_types() {
if let TypeDef::Struct { fields, .. } = def {
let type_name = interner.resolve(*type_sym).to_string();
for field in fields {
if let FieldType::Generic { base, .. } = &field.ty {
let base_name = interner.resolve(*base);
let field_name = interner.resolve(field.name).to_string();
if base_name == "LastWriteWins" {
lww_fields.insert((type_name.clone(), field_name));
} else if base_name == "Divergent" || base_name == "MVRegister" {
mv_fields.insert((type_name.clone(), field_name));
}
}
}
}
}
(lww_fields, mv_fields)
}
pub(super) fn collect_boxed_fields(registry: &TypeRegistry, interner: &Interner) -> HashSet<(String, String, String)> {
let mut boxed_fields = HashSet::new();
for (type_sym, def) in registry.iter_types() {
if let TypeDef::Enum { variants, .. } = def {
let enum_name = interner.resolve(*type_sym);
for variant in variants {
let variant_name = interner.resolve(variant.name);
for field in &variant.fields {
if is_recursive_field(&field.ty, enum_name, interner) {
let field_name = interner.resolve(field.name).to_string();
boxed_fields.insert((
enum_name.to_string(),
variant_name.to_string(),
field_name,
));
}
}
}
}
}
boxed_fields
}
pub fn collect_async_functions(stmts: &[Stmt]) -> HashSet<Symbol> {
let mut func_bodies: HashMap<Symbol, &[Stmt]> = HashMap::new();
for stmt in stmts {
if let Stmt::FunctionDef { name, body, .. } = stmt {
func_bodies.insert(*name, *body);
}
}
let mut async_fns = HashSet::new();
for stmt in stmts {
if let Stmt::FunctionDef { name, body, .. } = stmt {
if body.iter().any(|s| requires_async_stmt(s)) {
async_fns.insert(*name);
}
}
}
loop {
let mut changed = false;
for (func_name, body) in &func_bodies {
if async_fns.contains(func_name) {
continue; }
if body.iter().any(|s| calls_async_function(s, &async_fns)) {
async_fns.insert(*func_name);
changed = true;
}
}
if !changed {
break;
}
}
async_fns
}
pub(super) fn calls_async_function(stmt: &Stmt, async_fns: &HashSet<Symbol>) -> bool {
match stmt {
Stmt::Call { function, args } => {
async_fns.contains(function)
|| args.iter().any(|a| calls_async_function_in_expr(a, async_fns))
}
Stmt::If { cond, then_block, else_block } => {
calls_async_function_in_expr(cond, async_fns)
|| then_block.iter().any(|s| calls_async_function(s, async_fns))
|| else_block.map_or(false, |b| b.iter().any(|s| calls_async_function(s, async_fns)))
}
Stmt::While { cond, body, .. } => {
calls_async_function_in_expr(cond, async_fns)
|| body.iter().any(|s| calls_async_function(s, async_fns))
}
Stmt::Repeat { iterable, body, .. } => {
calls_async_function_in_expr(iterable, async_fns)
|| body.iter().any(|s| calls_async_function(s, async_fns))
}
Stmt::Zone { body, .. } => {
body.iter().any(|s| calls_async_function(s, async_fns))
}
Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
tasks.iter().any(|s| calls_async_function(s, async_fns))
}
Stmt::FunctionDef { body, .. } => {
body.iter().any(|s| calls_async_function(s, async_fns))
}
Stmt::Let { value, .. } => calls_async_function_in_expr(value, async_fns),
Stmt::Set { value, .. } => calls_async_function_in_expr(value, async_fns),
Stmt::Return { value } => {
value.as_ref().map_or(false, |v| calls_async_function_in_expr(v, async_fns))
}
Stmt::RuntimeAssert { condition } => calls_async_function_in_expr(condition, async_fns),
Stmt::Show { object, .. } => calls_async_function_in_expr(object, async_fns),
Stmt::Push { collection, value } => {
calls_async_function_in_expr(collection, async_fns)
|| calls_async_function_in_expr(value, async_fns)
}
Stmt::SetIndex { collection, index, value } => {
calls_async_function_in_expr(collection, async_fns)
|| calls_async_function_in_expr(index, async_fns)
|| calls_async_function_in_expr(value, async_fns)
}
Stmt::SendPipe { value, pipe } | Stmt::TrySendPipe { value, pipe, .. } => {
calls_async_function_in_expr(value, async_fns)
|| calls_async_function_in_expr(pipe, async_fns)
}
Stmt::Inspect { target, arms, .. } => {
calls_async_function_in_expr(target, async_fns)
|| arms.iter().any(|arm| arm.body.iter().any(|s| calls_async_function(s, async_fns)))
}
_ => false,
}
}
fn calls_async_function_in_expr(expr: &Expr, async_fns: &HashSet<Symbol>) -> bool {
match expr {
Expr::Call { function, args } => {
async_fns.contains(function)
|| args.iter().any(|a| calls_async_function_in_expr(a, async_fns))
}
Expr::BinaryOp { left, right, .. } => {
calls_async_function_in_expr(left, async_fns)
|| calls_async_function_in_expr(right, async_fns)
}
Expr::Index { collection, index } => {
calls_async_function_in_expr(collection, async_fns)
|| calls_async_function_in_expr(index, async_fns)
}
Expr::FieldAccess { object, .. } => calls_async_function_in_expr(object, async_fns),
Expr::List(items) | Expr::Tuple(items) => {
items.iter().any(|i| calls_async_function_in_expr(i, async_fns))
}
Expr::Closure { body, .. } => {
match body {
crate::ast::stmt::ClosureBody::Expression(expr) => calls_async_function_in_expr(expr, async_fns),
crate::ast::stmt::ClosureBody::Block(_) => false,
}
}
Expr::CallExpr { callee, args } => {
calls_async_function_in_expr(callee, async_fns)
|| args.iter().any(|a| calls_async_function_in_expr(a, async_fns))
}
Expr::InterpolatedString(parts) => {
parts.iter().any(|p| {
if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
calls_async_function_in_expr(value, async_fns)
} else { false }
})
}
_ => false,
}
}
pub(super) fn collect_pure_functions(stmts: &[Stmt]) -> HashSet<Symbol> {
let mut func_bodies: HashMap<Symbol, &[Stmt]> = HashMap::new();
for stmt in stmts {
if let Stmt::FunctionDef { name, body, .. } = stmt {
func_bodies.insert(*name, *body);
}
}
let mut impure_fns = HashSet::new();
for (func_name, body) in &func_bodies {
if body.iter().any(|s| is_directly_impure_stmt(s)) {
impure_fns.insert(*func_name);
}
}
loop {
let mut changed = false;
for (func_name, body) in &func_bodies {
if impure_fns.contains(func_name) {
continue;
}
if body.iter().any(|s| calls_impure_function(s, &impure_fns)) {
impure_fns.insert(*func_name);
changed = true;
}
}
if !changed {
break;
}
}
let mut pure_fns = HashSet::new();
for func_name in func_bodies.keys() {
if !impure_fns.contains(func_name) {
pure_fns.insert(*func_name);
}
}
pure_fns
}
fn is_directly_impure_stmt(stmt: &Stmt) -> bool {
match stmt {
Stmt::Show { .. }
| Stmt::Give { .. }
| Stmt::WriteFile { .. }
| Stmt::ReadFrom { .. }
| Stmt::Listen { .. }
| Stmt::ConnectTo { .. }
| Stmt::SendMessage { .. }
| Stmt::AwaitMessage { .. }
| Stmt::Sleep { .. }
| Stmt::Sync { .. }
| Stmt::Mount { .. }
| Stmt::MergeCrdt { .. }
| Stmt::IncreaseCrdt { .. }
| Stmt::DecreaseCrdt { .. }
| Stmt::AppendToSequence { .. }
| Stmt::ResolveConflict { .. }
| Stmt::CreatePipe { .. }
| Stmt::SendPipe { .. }
| Stmt::ReceivePipe { .. }
| Stmt::TrySendPipe { .. }
| Stmt::TryReceivePipe { .. }
| Stmt::LaunchTask { .. }
| Stmt::LaunchTaskWithHandle { .. }
| Stmt::StopTask { .. }
| Stmt::Concurrent { .. }
| Stmt::Parallel { .. } => true,
Stmt::If { then_block, else_block, .. } => {
then_block.iter().any(|s| is_directly_impure_stmt(s))
|| else_block.map_or(false, |b| b.iter().any(|s| is_directly_impure_stmt(s)))
}
Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
body.iter().any(|s| is_directly_impure_stmt(s))
}
Stmt::Zone { body, .. } => {
body.iter().any(|s| is_directly_impure_stmt(s))
}
Stmt::Inspect { arms, .. } => {
arms.iter().any(|arm| arm.body.iter().any(|s| is_directly_impure_stmt(s)))
}
_ => false,
}
}
fn calls_impure_function(stmt: &Stmt, impure_fns: &HashSet<Symbol>) -> bool {
match stmt {
Stmt::Call { function, args } => {
impure_fns.contains(function)
|| args.iter().any(|a| expr_calls_impure(a, impure_fns))
}
Stmt::Let { value, .. } => expr_calls_impure(value, impure_fns),
Stmt::Set { value, .. } => expr_calls_impure(value, impure_fns),
Stmt::Return { value } => value.as_ref().map_or(false, |v| expr_calls_impure(v, impure_fns)),
Stmt::If { cond, then_block, else_block } => {
expr_calls_impure(cond, impure_fns)
|| then_block.iter().any(|s| calls_impure_function(s, impure_fns))
|| else_block.map_or(false, |b| b.iter().any(|s| calls_impure_function(s, impure_fns)))
}
Stmt::While { cond, body, .. } => {
expr_calls_impure(cond, impure_fns)
|| body.iter().any(|s| calls_impure_function(s, impure_fns))
}
Stmt::Repeat { body, .. } => body.iter().any(|s| calls_impure_function(s, impure_fns)),
Stmt::Zone { body, .. } => body.iter().any(|s| calls_impure_function(s, impure_fns)),
Stmt::Inspect { arms, .. } => {
arms.iter().any(|arm| arm.body.iter().any(|s| calls_impure_function(s, impure_fns)))
}
Stmt::Show { object, .. } => expr_calls_impure(object, impure_fns),
Stmt::Push { value, collection } | Stmt::Add { value, collection } | Stmt::Remove { value, collection } => {
expr_calls_impure(value, impure_fns) || expr_calls_impure(collection, impure_fns)
}
_ => false,
}
}
fn expr_calls_impure(expr: &Expr, impure_fns: &HashSet<Symbol>) -> bool {
match expr {
Expr::Call { function, args } => {
impure_fns.contains(function)
|| args.iter().any(|a| expr_calls_impure(a, impure_fns))
}
Expr::BinaryOp { left, right, .. } => {
expr_calls_impure(left, impure_fns) || expr_calls_impure(right, impure_fns)
}
Expr::Index { collection, index } => {
expr_calls_impure(collection, impure_fns) || expr_calls_impure(index, impure_fns)
}
Expr::FieldAccess { object, .. } => expr_calls_impure(object, impure_fns),
Expr::List(items) | Expr::Tuple(items) => items.iter().any(|i| expr_calls_impure(i, impure_fns)),
Expr::CallExpr { callee, args } => {
expr_calls_impure(callee, impure_fns)
|| args.iter().any(|a| expr_calls_impure(a, impure_fns))
}
Expr::InterpolatedString(parts) => {
parts.iter().any(|p| {
if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
expr_calls_impure(value, impure_fns)
} else { false }
})
}
_ => false,
}
}
pub(super) fn count_self_calls(func_name: Symbol, body: &[Stmt]) -> usize {
let mut count = 0;
for stmt in body {
count += count_self_calls_in_stmt(func_name, stmt);
}
count
}
fn count_self_calls_in_stmt(func_name: Symbol, stmt: &Stmt) -> usize {
match stmt {
Stmt::Return { value: Some(expr) } => count_self_calls_in_expr(func_name, expr),
Stmt::Let { value, .. } => count_self_calls_in_expr(func_name, value),
Stmt::Set { value, .. } => count_self_calls_in_expr(func_name, value),
Stmt::Call { function, args } => {
let mut c = if *function == func_name { 1 } else { 0 };
c += args.iter().map(|a| count_self_calls_in_expr(func_name, a)).sum::<usize>();
c
}
Stmt::If { cond, then_block, else_block } => {
let mut c = count_self_calls_in_expr(func_name, cond);
c += count_self_calls(func_name, then_block);
if let Some(else_stmts) = else_block {
c += count_self_calls(func_name, else_stmts);
}
c
}
Stmt::While { cond, body, .. } => {
count_self_calls_in_expr(func_name, cond) + count_self_calls(func_name, body)
}
Stmt::Repeat { body, .. } => count_self_calls(func_name, body),
Stmt::Show { object, .. } => count_self_calls_in_expr(func_name, object),
_ => 0,
}
}
fn count_self_calls_in_expr(func_name: Symbol, expr: &Expr) -> usize {
match expr {
Expr::Call { function, args } => {
let mut c = if *function == func_name { 1 } else { 0 };
c += args.iter().map(|a| count_self_calls_in_expr(func_name, a)).sum::<usize>();
c
}
Expr::BinaryOp { left, right, .. } => {
count_self_calls_in_expr(func_name, left) + count_self_calls_in_expr(func_name, right)
}
Expr::Index { collection, index } => {
count_self_calls_in_expr(func_name, collection) + count_self_calls_in_expr(func_name, index)
}
Expr::FieldAccess { object, .. } => count_self_calls_in_expr(func_name, object),
Expr::List(items) | Expr::Tuple(items) => {
items.iter().map(|i| count_self_calls_in_expr(func_name, i)).sum()
}
Expr::InterpolatedString(parts) => {
parts.iter().map(|p| {
if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
count_self_calls_in_expr(func_name, value)
} else { 0 }
}).sum()
}
_ => 0,
}
}
pub(super) fn is_hashable_type(ty: &TypeExpr, interner: &Interner) -> bool {
match ty {
TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
let name = interner.resolve(*sym);
matches!(name, "Int" | "Nat" | "Bool" | "Char" | "Byte" | "Text"
| "i64" | "u64" | "bool" | "char" | "u8" | "String")
}
TypeExpr::Refinement { base, .. } => is_hashable_type(base, interner),
_ => false,
}
}
pub(super) fn is_copy_type_expr(ty: &TypeExpr, interner: &Interner) -> bool {
match ty {
TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
let name = interner.resolve(*sym);
matches!(name, "Int" | "Nat" | "Bool" | "Char" | "Byte"
| "i64" | "u64" | "bool" | "char" | "u8")
}
TypeExpr::Refinement { base, .. } => is_copy_type_expr(base, interner),
_ => false,
}
}
pub(super) fn should_memoize(
name: Symbol,
body: &[Stmt],
params: &[(Symbol, &TypeExpr)],
return_type: Option<&TypeExpr>,
is_pure: bool,
interner: &Interner,
) -> bool {
if !is_pure {
return false;
}
if !body_contains_self_call(name, body) {
return false;
}
if count_self_calls(name, body) < 2 {
return false;
}
if params.is_empty() {
return false;
}
if !params.iter().all(|(_, ty)| is_hashable_type(ty, interner)) {
return false;
}
if return_type.is_none() {
return false;
}
true
}
pub(super) fn body_contains_self_call(func_name: Symbol, body: &[Stmt]) -> bool {
body.iter().any(|s| stmt_contains_self_call(func_name, s))
}
fn stmt_contains_self_call(func_name: Symbol, stmt: &Stmt) -> bool {
match stmt {
Stmt::Return { value: Some(expr) } => expr_contains_self_call(func_name, expr),
Stmt::Return { value: None } => false,
Stmt::Let { value, .. } => expr_contains_self_call(func_name, value),
Stmt::Set { value, .. } => expr_contains_self_call(func_name, value),
Stmt::Call { function, args } => {
*function == func_name || args.iter().any(|a| expr_contains_self_call(func_name, a))
}
Stmt::If { cond, then_block, else_block } => {
expr_contains_self_call(func_name, cond)
|| then_block.iter().any(|s| stmt_contains_self_call(func_name, s))
|| else_block.map_or(false, |b| b.iter().any(|s| stmt_contains_self_call(func_name, s)))
}
Stmt::While { cond, body, .. } => {
expr_contains_self_call(func_name, cond)
|| body.iter().any(|s| stmt_contains_self_call(func_name, s))
}
Stmt::Repeat { body, .. } => {
body.iter().any(|s| stmt_contains_self_call(func_name, s))
}
Stmt::Show { object, .. } => expr_contains_self_call(func_name, object),
_ => false,
}
}
pub(super) fn expr_contains_self_call(func_name: Symbol, expr: &Expr) -> bool {
match expr {
Expr::Call { function, args } => {
*function == func_name || args.iter().any(|a| expr_contains_self_call(func_name, a))
}
Expr::BinaryOp { left, right, .. } => {
expr_contains_self_call(func_name, left) || expr_contains_self_call(func_name, right)
}
Expr::Index { collection, index } => {
expr_contains_self_call(func_name, collection) || expr_contains_self_call(func_name, index)
}
Expr::FieldAccess { object, .. } => expr_contains_self_call(func_name, object),
Expr::List(items) | Expr::Tuple(items) => {
items.iter().any(|i| expr_contains_self_call(func_name, i))
}
Expr::InterpolatedString(parts) => {
parts.iter().any(|p| {
if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
expr_contains_self_call(func_name, value)
} else { false }
})
}
_ => false,
}
}
pub(super) fn expr_indexes_collection(expr: &Expr, coll_sym: Symbol) -> bool {
match expr {
Expr::Index { collection, index } => {
let indexes_this = matches!(collection, Expr::Identifier(sym) if *sym == coll_sym);
indexes_this
|| expr_indexes_collection(collection, coll_sym)
|| expr_indexes_collection(index, coll_sym)
}
Expr::BinaryOp { left, right, .. } => {
expr_indexes_collection(left, coll_sym) || expr_indexes_collection(right, coll_sym)
}
Expr::Call { args, .. } => {
args.iter().any(|a| expr_indexes_collection(a, coll_sym))
}
Expr::FieldAccess { object, .. } => expr_indexes_collection(object, coll_sym),
Expr::Length { collection } => expr_indexes_collection(collection, coll_sym),
Expr::Not { operand } => expr_indexes_collection(operand, coll_sym),
Expr::List(items) | Expr::Tuple(items) => {
items.iter().any(|i| expr_indexes_collection(i, coll_sym))
}
_ => false,
}
}
pub(super) fn should_inline(name: Symbol, body: &[Stmt], is_native: bool, is_exported: bool, is_async: bool) -> bool {
!is_native && !is_exported && !is_async
&& body.len() <= 10
&& !body_contains_self_call(name, body)
}
pub fn collect_pipe_sender_params(body: &[Stmt]) -> HashSet<Symbol> {
let mut senders = HashSet::new();
for stmt in body {
collect_pipe_sender_params_stmt(stmt, &mut senders);
}
senders
}
fn collect_pipe_sender_params_stmt(stmt: &Stmt, senders: &mut HashSet<Symbol>) {
match stmt {
Stmt::SendPipe { pipe, .. } | Stmt::TrySendPipe { pipe, .. } => {
if let Expr::Identifier(sym) = pipe {
senders.insert(*sym);
}
}
Stmt::If { then_block, else_block, .. } => {
for s in *then_block {
collect_pipe_sender_params_stmt(s, senders);
}
if let Some(else_stmts) = else_block {
for s in *else_stmts {
collect_pipe_sender_params_stmt(s, senders);
}
}
}
Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Zone { body, .. } => {
for s in *body {
collect_pipe_sender_params_stmt(s, senders);
}
}
Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
for s in *tasks {
collect_pipe_sender_params_stmt(s, senders);
}
}
_ => {}
}
}
pub fn collect_pipe_vars(stmts: &[Stmt]) -> HashSet<Symbol> {
let mut pipe_vars = HashSet::new();
for stmt in stmts {
collect_pipe_vars_stmt(stmt, &mut pipe_vars);
}
pipe_vars
}
fn collect_pipe_vars_stmt(stmt: &Stmt, pipe_vars: &mut HashSet<Symbol>) {
match stmt {
Stmt::CreatePipe { var, .. } => {
pipe_vars.insert(*var);
}
Stmt::If { then_block, else_block, .. } => {
for s in *then_block {
collect_pipe_vars_stmt(s, pipe_vars);
}
if let Some(else_stmts) = else_block {
for s in *else_stmts {
collect_pipe_vars_stmt(s, pipe_vars);
}
}
}
Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Zone { body, .. } => {
for s in *body {
collect_pipe_vars_stmt(s, pipe_vars);
}
}
Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
for s in *tasks {
collect_pipe_vars_stmt(s, pipe_vars);
}
}
_ => {}
}
}
pub(super) fn collect_expr_identifiers(expr: &Expr, identifiers: &mut HashSet<Symbol>) {
match expr {
Expr::Identifier(sym) => {
identifiers.insert(*sym);
}
Expr::BinaryOp { left, right, .. } => {
collect_expr_identifiers(left, identifiers);
collect_expr_identifiers(right, identifiers);
}
Expr::Call { args, .. } => {
for arg in args {
collect_expr_identifiers(arg, identifiers);
}
}
Expr::Index { collection, index } => {
collect_expr_identifiers(collection, identifiers);
collect_expr_identifiers(index, identifiers);
}
Expr::Slice { collection, start, end } => {
collect_expr_identifiers(collection, identifiers);
collect_expr_identifiers(start, identifiers);
collect_expr_identifiers(end, identifiers);
}
Expr::Copy { expr: inner } | Expr::Give { value: inner } | Expr::Length { collection: inner }
| Expr::Not { operand: inner } => {
collect_expr_identifiers(inner, identifiers);
}
Expr::Contains { collection, value } | Expr::Union { left: collection, right: value } | Expr::Intersection { left: collection, right: value } => {
collect_expr_identifiers(collection, identifiers);
collect_expr_identifiers(value, identifiers);
}
Expr::ManifestOf { zone } | Expr::ChunkAt { zone, .. } => {
collect_expr_identifiers(zone, identifiers);
}
Expr::List(items) | Expr::Tuple(items) => {
for item in items {
collect_expr_identifiers(item, identifiers);
}
}
Expr::Range { start, end } => {
collect_expr_identifiers(start, identifiers);
collect_expr_identifiers(end, identifiers);
}
Expr::FieldAccess { object, .. } => {
collect_expr_identifiers(object, identifiers);
}
Expr::New { init_fields, .. } => {
for (_, value) in init_fields {
collect_expr_identifiers(value, identifiers);
}
}
Expr::NewVariant { fields, .. } => {
for (_, value) in fields {
collect_expr_identifiers(value, identifiers);
}
}
Expr::OptionSome { value } => {
collect_expr_identifiers(value, identifiers);
}
Expr::WithCapacity { value, capacity } => {
collect_expr_identifiers(value, identifiers);
collect_expr_identifiers(capacity, identifiers);
}
Expr::Closure { body, .. } => {
match body {
crate::ast::stmt::ClosureBody::Expression(expr) => collect_expr_identifiers(expr, identifiers),
crate::ast::stmt::ClosureBody::Block(_) => {}
}
}
Expr::CallExpr { callee, args } => {
collect_expr_identifiers(callee, identifiers);
for arg in args {
collect_expr_identifiers(arg, identifiers);
}
}
Expr::InterpolatedString(parts) => {
for part in parts {
if let crate::ast::stmt::StringPart::Expr { value, .. } = part {
collect_expr_identifiers(value, identifiers);
}
}
}
Expr::OptionNone => {}
Expr::Escape { .. } => {}
Expr::Literal(_) => {}
}
}
pub(super) fn symbol_appears_in_stmts(sym: Symbol, stmts: &[&Stmt]) -> bool {
stmts.iter().any(|s| symbol_appears_in_stmt(sym, s))
}
fn symbol_appears_in_stmt(sym: Symbol, stmt: &Stmt) -> bool {
match stmt {
Stmt::Let { value, .. } => symbol_appears_in_expr(sym, value),
Stmt::Set { target, value, .. } => *target == sym || symbol_appears_in_expr(sym, value),
Stmt::Show { object, .. } => symbol_appears_in_expr(sym, object),
Stmt::Return { value } => value.as_ref().map_or(false, |v| symbol_appears_in_expr(sym, v)),
Stmt::Call { function, args } => *function == sym || args.iter().any(|a| symbol_appears_in_expr(sym, a)),
Stmt::If { cond, then_block, else_block } => {
symbol_appears_in_expr(sym, cond)
|| then_block.iter().any(|s| symbol_appears_in_stmt(sym, s))
|| else_block.map_or(false, |b| b.iter().any(|s| symbol_appears_in_stmt(sym, s)))
}
Stmt::While { cond, body, .. } => {
symbol_appears_in_expr(sym, cond)
|| body.iter().any(|s| symbol_appears_in_stmt(sym, s))
}
Stmt::Repeat { iterable, body, .. } => {
symbol_appears_in_expr(sym, iterable)
|| body.iter().any(|s| symbol_appears_in_stmt(sym, s))
}
Stmt::Zone { body, .. } => body.iter().any(|s| symbol_appears_in_stmt(sym, s)),
Stmt::Push { collection, value } => {
symbol_appears_in_expr(sym, collection) || symbol_appears_in_expr(sym, value)
}
Stmt::Pop { collection, .. } => symbol_appears_in_expr(sym, collection),
Stmt::Add { collection, value } | Stmt::Remove { collection, value } => {
symbol_appears_in_expr(sym, collection) || symbol_appears_in_expr(sym, value)
}
Stmt::SetIndex { collection, index, value } => {
symbol_appears_in_expr(sym, collection) || symbol_appears_in_expr(sym, index) || symbol_appears_in_expr(sym, value)
}
Stmt::Inspect { target, arms, .. } => {
symbol_appears_in_expr(sym, target)
|| arms.iter().any(|arm| arm.body.iter().any(|s| symbol_appears_in_stmt(sym, s)))
}
Stmt::RuntimeAssert { condition } => symbol_appears_in_expr(sym, condition),
Stmt::FunctionDef { body, .. } => body.iter().any(|s| symbol_appears_in_stmt(sym, s)),
Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
tasks.iter().any(|s| symbol_appears_in_stmt(sym, s))
}
_ => false,
}
}
fn symbol_appears_in_expr(sym: Symbol, expr: &Expr) -> bool {
match expr {
Expr::Identifier(s) => *s == sym,
Expr::BinaryOp { left, right, .. } => {
symbol_appears_in_expr(sym, left) || symbol_appears_in_expr(sym, right)
}
Expr::Call { function, args } => {
*function == sym || args.iter().any(|a| symbol_appears_in_expr(sym, a))
}
Expr::Index { collection, index } => {
symbol_appears_in_expr(sym, collection) || symbol_appears_in_expr(sym, index)
}
Expr::Slice { collection, start, end } => {
symbol_appears_in_expr(sym, collection)
|| symbol_appears_in_expr(sym, start)
|| symbol_appears_in_expr(sym, end)
}
Expr::Length { collection } | Expr::Copy { expr: collection } | Expr::Give { value: collection } => {
symbol_appears_in_expr(sym, collection)
}
Expr::Contains { collection, value } | Expr::Union { left: collection, right: value } | Expr::Intersection { left: collection, right: value } => {
symbol_appears_in_expr(sym, collection) || symbol_appears_in_expr(sym, value)
}
Expr::FieldAccess { object, .. } => symbol_appears_in_expr(sym, object),
Expr::List(items) | Expr::Tuple(items) => {
items.iter().any(|i| symbol_appears_in_expr(sym, i))
}
Expr::Range { start, end } => {
symbol_appears_in_expr(sym, start) || symbol_appears_in_expr(sym, end)
}
Expr::New { init_fields, .. } => {
init_fields.iter().any(|(_, v)| symbol_appears_in_expr(sym, v))
}
Expr::NewVariant { fields, .. } => {
fields.iter().any(|(_, v)| symbol_appears_in_expr(sym, v))
}
Expr::OptionSome { value } => symbol_appears_in_expr(sym, value),
Expr::WithCapacity { value, capacity } => {
symbol_appears_in_expr(sym, value) || symbol_appears_in_expr(sym, capacity)
}
Expr::CallExpr { callee, args } => {
symbol_appears_in_expr(sym, callee)
|| args.iter().any(|a| symbol_appears_in_expr(sym, a))
}
Expr::InterpolatedString(parts) => {
parts.iter().any(|p| {
if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
symbol_appears_in_expr(sym, value)
} else { false }
})
}
Expr::Closure { body, .. } => {
match body {
crate::ast::stmt::ClosureBody::Expression(e) => symbol_appears_in_expr(sym, e),
crate::ast::stmt::ClosureBody::Block(stmts) => stmts.iter().any(|s| symbol_appears_in_stmt(sym, s)),
}
}
_ => false,
}
}
pub(super) fn is_vec_type_expr(ty: &TypeExpr, interner: &Interner) -> bool {
match ty {
TypeExpr::Generic { base, .. } => {
let name = interner.resolve(*base);
matches!(name, "Seq" | "List" | "Vec")
}
_ => false,
}
}
pub(super) fn collect_readonly_vec_param_indices(
params: &[(Symbol, &TypeExpr)],
body: &[Stmt],
interner: &Interner,
) -> HashSet<usize> {
let mutable_vars = collect_mutable_vars(body);
let mut readonly_indices = HashSet::new();
for (i, (param_sym, param_ty)) in params.iter().enumerate() {
if is_vec_type_expr(param_ty, interner) && !mutable_vars.contains(param_sym) {
readonly_indices.insert(i);
}
}
readonly_indices
}
pub(super) fn vec_to_slice_type(vec_type: &str) -> String {
if let Some(inner) = vec_type.strip_prefix("Vec<").and_then(|s| s.strip_suffix('>')) {
format!("&[{}]", inner)
} else {
vec_type.to_string()
}
}
pub(super) fn vec_to_mut_slice_type(vec_type: &str) -> String {
if let Some(inner) = vec_type.strip_prefix("Vec<").and_then(|s| s.strip_suffix('>')) {
format!("&mut [{}]", inner)
} else {
vec_type.to_string()
}
}
pub(super) fn expr_debug_prefix(expr: &Expr, interner: &Interner) -> String {
match expr {
Expr::Identifier(sym) => interner.resolve(*sym).to_string(),
_ => "expr".to_string(),
}
}
pub(super) fn collect_stmt_identifiers(stmt: &Stmt, identifiers: &mut HashSet<Symbol>) {
match stmt {
Stmt::Let { value, .. } => {
collect_expr_identifiers(value, identifiers);
}
Stmt::Call { args, .. } => {
for arg in args {
collect_expr_identifiers(arg, identifiers);
}
}
_ => {}
}
}
pub(super) fn collect_give_arg_indices(fn_sym: Symbol, stmts: &[Stmt]) -> HashSet<usize> {
let mut result = HashSet::new();
for stmt in stmts {
scan_give_args_stmt(fn_sym, stmt, &mut result);
}
result
}
fn scan_give_args_stmt(fn_sym: Symbol, stmt: &Stmt, result: &mut HashSet<usize>) {
match stmt {
Stmt::Call { function, args } => {
if *function == fn_sym {
for (i, arg) in args.iter().enumerate() {
if matches!(arg, Expr::Give { .. }) {
result.insert(i);
}
}
}
for arg in args {
scan_give_args_expr(fn_sym, arg, result);
}
}
Stmt::Let { value, .. } | Stmt::Set { value, .. } => {
scan_give_args_expr(fn_sym, value, result);
}
Stmt::Return { value: Some(v) } => scan_give_args_expr(fn_sym, v, result),
Stmt::FunctionDef { body, .. } => {
for s in *body {
scan_give_args_stmt(fn_sym, s, result);
}
}
Stmt::If { then_block, else_block, .. } => {
for s in *then_block {
scan_give_args_stmt(fn_sym, s, result);
}
if let Some(b) = else_block {
for s in *b {
scan_give_args_stmt(fn_sym, s, result);
}
}
}
Stmt::While { body, .. } | Stmt::Repeat { body, .. } | Stmt::Zone { body, .. } => {
for s in *body {
scan_give_args_stmt(fn_sym, s, result);
}
}
Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => {
for s in *tasks {
scan_give_args_stmt(fn_sym, s, result);
}
}
Stmt::Inspect { arms, .. } => {
for arm in arms.iter() {
for s in arm.body.iter() {
scan_give_args_stmt(fn_sym, s, result);
}
}
}
_ => {}
}
}
fn scan_give_args_expr(fn_sym: Symbol, expr: &Expr, result: &mut HashSet<usize>) {
match expr {
Expr::Call { function, args } => {
if *function == fn_sym {
for (i, arg) in args.iter().enumerate() {
if matches!(arg, Expr::Give { .. }) {
result.insert(i);
}
}
}
for arg in args {
scan_give_args_expr(fn_sym, arg, result);
}
}
Expr::BinaryOp { left, right, .. } => {
scan_give_args_expr(fn_sym, left, result);
scan_give_args_expr(fn_sym, right, result);
}
Expr::FieldAccess { object, .. }
| Expr::Give { value: object }
| Expr::Copy { expr: object }
| Expr::Length { collection: object } => {
scan_give_args_expr(fn_sym, object, result);
}
Expr::Index { collection, index } => {
scan_give_args_expr(fn_sym, collection, result);
scan_give_args_expr(fn_sym, index, result);
}
Expr::List(items) | Expr::Tuple(items) => {
for item in items {
scan_give_args_expr(fn_sym, item, result);
}
}
_ => {}
}
}
pub(super) struct ClosedFormInfo {
pub base: i64,
pub k: i64,
}
pub(super) fn detect_double_recursion_closed_form<'a>(
func_name: Symbol,
params: &[(Symbol, &'a TypeExpr<'a>)],
body: &'a [Stmt<'a>],
interner: &Interner,
) -> Option<ClosedFormInfo> {
use crate::ast::stmt::BinaryOpKind;
if params.len() != 1 {
return None;
}
let param_sym = params[0].0;
if body.len() != 2 {
return None;
}
let base_value = match &body[0] {
Stmt::If { cond, then_block, else_block } => {
if else_block.is_some() {
return None;
}
if then_block.len() != 1 {
return None;
}
let base = match &then_block[0] {
Stmt::Return { value: Some(expr) } => cf_extract_literal_int(expr)?,
_ => return None,
};
match cond {
Expr::BinaryOp { op: BinaryOpKind::Eq, left, right } => {
let ok = (cf_is_identifier(left, param_sym) && cf_is_literal_int(right, 0))
|| (cf_is_literal_int(left, 0) && cf_is_identifier(right, param_sym));
if !ok { return None; }
}
_ => return None,
}
base
}
_ => return None,
};
let recursive_expr = match &body[1] {
Stmt::Return { value: Some(expr) } => *expr,
_ => return None,
};
let mut self_call_count = 0usize;
let mut constant_sum = 0i64;
if !cf_analyze_add_tree(recursive_expr, func_name, param_sym, &mut self_call_count, &mut constant_sum) {
return None;
}
if self_call_count != 2 {
return None;
}
Some(ClosedFormInfo { base: base_value, k: constant_sum })
}
fn cf_extract_literal_int(expr: &Expr) -> Option<i64> {
if let Expr::Literal(crate::ast::stmt::Literal::Number(n)) = expr {
Some(*n)
} else {
None
}
}
fn cf_is_identifier(expr: &Expr, sym: Symbol) -> bool {
matches!(expr, Expr::Identifier(s) if *s == sym)
}
fn cf_is_literal_int(expr: &Expr, val: i64) -> bool {
matches!(expr, Expr::Literal(crate::ast::stmt::Literal::Number(n)) if *n == val)
}
fn cf_is_self_call_with_decrement(expr: &Expr, func_name: Symbol, param_sym: Symbol) -> bool {
use crate::ast::stmt::BinaryOpKind;
if let Expr::Call { function, args } = expr {
if *function != func_name || args.len() != 1 {
return false;
}
if let Expr::BinaryOp { op: BinaryOpKind::Subtract, left, right } = args[0] {
cf_is_identifier(left, param_sym) && cf_is_literal_int(right, 1)
} else {
false
}
} else {
false
}
}
fn cf_analyze_add_tree(
expr: &Expr,
func_name: Symbol,
param_sym: Symbol,
self_calls: &mut usize,
constant_sum: &mut i64,
) -> bool {
use crate::ast::stmt::BinaryOpKind;
match expr {
Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => {
cf_analyze_add_tree(left, func_name, param_sym, self_calls, constant_sum)
&& cf_analyze_add_tree(right, func_name, param_sym, self_calls, constant_sum)
}
_ if cf_is_self_call_with_decrement(expr, func_name, param_sym) => {
*self_calls += 1;
true
}
_ => {
if let Some(n) = cf_extract_literal_int(expr) {
*constant_sum += n;
true
} else {
false
}
}
}
}