1use std::collections::{HashMap, HashSet};
2
3use crate::analysis::registry::TypeRegistry;
4use crate::analysis::types::RustNames;
5use crate::ast::logic::{LogicExpr, NumberKind, Term};
6use crate::ast::stmt::{BinaryOpKind, Expr, Literal, Stmt, TypeExpr};
7use crate::formatter::RustFormatter;
8use crate::intern::{Interner, Symbol};
9use crate::registry::SymbolRegistry;
10
11use super::context::RefinementContext;
12use super::detection::{collect_mutable_vars, expr_debug_prefix, parse_aos_tag};
13use super::i64_map::is_logos_map_type;
14use super::types::{codegen_type_expr, infer_logos_type, infer_numeric_type};
15use super::{
16 codegen_stmt, get_root_identifier, has_copy_element_type, has_copy_value_type, is_copy_type,
17};
18
19use std::sync::LazyLock;
20
21static EMPTY_SYMS: LazyLock<HashSet<Symbol>> = LazyLock::new(HashSet::new);
24static EMPTY_TYPES: LazyLock<HashMap<Symbol, String>> = LazyLock::new(HashMap::new);
25static EMPTY_BOXED_FIELDS: LazyLock<HashSet<(String, String, String)>> =
26 LazyLock::new(HashSet::new);
27static EMPTY_REGISTRY: LazyLock<TypeRegistry> = LazyLock::new(TypeRegistry::new);
28
29#[derive(Clone, Copy)]
38pub(crate) struct ExprCtx<'a> {
39 pub interner: &'a Interner,
40 pub synced_vars: &'a HashSet<Symbol>,
41 pub boxed_fields: &'a HashSet<(String, String, String)>,
42 pub registry: &'a TypeRegistry,
43 pub async_functions: &'a HashSet<Symbol>,
44 pub boxed_bindings: &'a HashSet<Symbol>,
45 pub string_vars: &'a HashSet<Symbol>,
46 pub variable_types: &'a HashMap<Symbol, String>,
47 pub fast_div: &'a HashMap<Symbol, String>,
50 pub oracle: Option<&'a crate::optimize::OracleFacts>,
56 pub int_exact_tolerant: bool,
62 pub int_index_context: bool,
66 pub int_wrapping: bool,
73}
74
75impl<'a> ExprCtx<'a> {
76 pub(crate) fn bare(interner: &'a Interner, synced_vars: &'a HashSet<Symbol>) -> Self {
79 ExprCtx {
80 interner,
81 synced_vars,
82 boxed_fields: &EMPTY_BOXED_FIELDS,
83 registry: &EMPTY_REGISTRY,
84 async_functions: &EMPTY_SYMS,
85 boxed_bindings: &EMPTY_SYMS,
86 string_vars: &EMPTY_SYMS,
87 variable_types: &EMPTY_TYPES,
88 fast_div: &EMPTY_TYPES,
89 oracle: None,
90 int_exact_tolerant: false,
91 int_index_context: false,
92 int_wrapping: false,
93 }
94 }
95}
96
97thread_local! {
105 static BIGINT_RETURNING_FNS: std::cell::RefCell<HashSet<Symbol>> =
114 std::cell::RefCell::new(HashSet::new());
115}
116
117pub(crate) fn set_bigint_returning_fns(fns: &HashSet<Symbol>) {
120 BIGINT_RETURNING_FNS.with(|c| *c.borrow_mut() = fns.clone());
121}
122
123fn is_bigint_returning_call(function: Symbol) -> bool {
124 BIGINT_RETURNING_FNS.with(|c| c.borrow().contains(&function))
125}
126
127fn mentions_bigint_var(e: &Expr, variable_types: &HashMap<Symbol, String>) -> bool {
132 match e {
133 Expr::Identifier(s) => variable_types.get(s).map_or(false, |t| t.contains("__bigint")),
134 Expr::Call { function, .. } => is_bigint_returning_call(*function),
135 Expr::BinaryOp { left, right, .. } => {
136 mentions_bigint_var(left, variable_types) || mentions_bigint_var(right, variable_types)
137 }
138 Expr::Not { operand } => mentions_bigint_var(operand, variable_types),
139 _ => false,
140 }
141}
142
143enum ConstExact {
144 InRange,
146 Promoted(String),
148}
149
150fn const_exact_int(op: BinaryOpKind, a: i64, b: i64) -> Option<ConstExact> {
154 let exact: i128 = match op {
155 BinaryOpKind::Add => a as i128 + b as i128,
156 BinaryOpKind::Subtract => a as i128 - b as i128,
157 BinaryOpKind::Multiply => a as i128 * b as i128,
158 BinaryOpKind::Divide => {
159 if b == 0 {
160 return None;
161 }
162 a as i128 / b as i128
163 }
164 BinaryOpKind::Modulo => {
165 if b == 0 {
166 return None;
167 }
168 a as i128 % b as i128
169 }
170 _ => return None,
171 };
172 Some(if i64::try_from(exact).is_ok() {
173 ConstExact::InRange
174 } else {
175 ConstExact::Promoted(format!("LogosInt::from_literal(\"{}\")", exact))
176 })
177}
178
179fn oracle_proves_int_op_in_range(
185 ecx: &ExprCtx,
186 whole: &Expr,
187 op: BinaryOpKind,
188 left: &Expr,
189 right: &Expr,
190) -> bool {
191 if crate::optimize::expr_proven_raw_int_op(whole) {
195 return true;
196 }
197 let Some(o) = ecx.oracle else { return false };
198 if let Some((lo, hi)) = o.expr_int_range(whole) {
202 let _ = (lo, hi); return true;
204 }
205 let (Some((la, lb)), Some((ra, rb))) = (o.expr_int_range(left), o.expr_int_range(right))
206 else {
207 return false;
208 };
209 let (la, lb, ra, rb) = (la as i128, lb as i128, ra as i128, rb as i128);
210 let (lo, hi) = match op {
211 BinaryOpKind::Add => (la + ra, lb + rb),
212 BinaryOpKind::Subtract => (la - rb, lb - ra),
213 BinaryOpKind::Multiply => {
214 let c = [la * ra, la * rb, lb * ra, lb * rb];
215 (*c.iter().min().unwrap(), *c.iter().max().unwrap())
216 }
217 _ => return false,
218 };
219 lo >= i64::MIN as i128 && hi <= i64::MAX as i128
220}
221
222fn is_exact_arith_node(e: &Expr) -> bool {
225 matches!(
226 e,
227 Expr::BinaryOp {
228 op: BinaryOpKind::Add
229 | BinaryOpKind::Subtract
230 | BinaryOpKind::Multiply
231 | BinaryOpKind::Divide
232 | BinaryOpKind::Modulo,
233 ..
234 }
235 )
236}
237
238fn exact_chain_max_magnitude(e: &Expr, ecx: &ExprCtx) -> Option<u128> {
248 if mentions_bigint_var(e, ecx.variable_types) {
249 return None;
250 }
251 match e {
252 Expr::Literal(Literal::Number(n)) => Some(n.unsigned_abs() as u128),
253 Expr::BinaryOp { op, left, right } if is_exact_arith_node(e) => {
254 let l = exact_chain_max_magnitude(left, ecx)?;
255 let r = exact_chain_max_magnitude(right, ecx)?;
256 match op {
257 BinaryOpKind::Add | BinaryOpKind::Subtract => l.checked_add(r),
258 BinaryOpKind::Multiply => l.checked_mul(r),
259 BinaryOpKind::Divide => Some(l),
260 BinaryOpKind::Modulo => Some(r.min(l)),
261 _ => unreachable!("is_exact_arith_node covers exactly these five"),
262 }
263 }
264 _ => (infer_numeric_type(e, ecx.interner, ecx.variable_types) == "i64")
265 .then_some(1u128 << 63),
266 }
267}
268
269fn emit_exact_chain_i128(e: &Expr, ecx: &ExprCtx) -> String {
273 match e {
274 Expr::Literal(Literal::Number(n)) => format!("{}i128", n),
275 Expr::BinaryOp { op, left, right } if is_exact_arith_node(e) => {
276 let l = emit_exact_chain_i128(left, ecx);
277 let r = emit_exact_chain_i128(right, ecx);
278 let nonzero_lit = matches!(&**right, Expr::Literal(Literal::Number(d)) if *d != 0);
279 match op {
280 BinaryOpKind::Add => format!("({} + {})", l, r),
281 BinaryOpKind::Subtract => format!("({} - {})", l, r),
282 BinaryOpKind::Multiply => format!("({} * {})", l, r),
283 BinaryOpKind::Divide if nonzero_lit => format!("({} / {})", l, r),
284 BinaryOpKind::Divide => format!("logos_div_i128({}, {})", l, r),
285 BinaryOpKind::Modulo if nonzero_lit => format!("({} % {})", l, r),
286 BinaryOpKind::Modulo => format!("logos_rem_i128({}, {})", l, r),
287 _ => unreachable!("is_exact_arith_node covers exactly these five"),
288 }
289 }
290 _ => {
291 let plain = ExprCtx { int_exact_tolerant: false, ..*ecx };
292 format!("(({}) as i128)", codegen_expr_ctx(e, &plain))
293 }
294 }
295}
296
297fn try_fuse_narrowed_int_op(
311 op: BinaryOpKind,
312 left: &Expr,
313 right: &Expr,
314 ecx: &ExprCtx,
315) -> Option<String> {
316 let plain = ExprCtx { int_exact_tolerant: false, ..*ecx };
317 if !is_exact_arith_node(left) && !is_exact_arith_node(right) {
318 let helper = match op {
319 BinaryOpKind::Add => "logos_add_i64",
320 BinaryOpKind::Subtract => "logos_sub_i64",
321 BinaryOpKind::Multiply => "logos_mul_i64",
322 BinaryOpKind::Divide => "logos_div_i64",
323 BinaryOpKind::Modulo => "logos_rem_i64",
324 _ => return None,
325 };
326 let l = codegen_expr_ctx(left, &plain);
327 let r = codegen_expr_ctx(right, &plain);
328 return Some(format!("{}({}, {})", helper, l, r));
329 }
330 None
331}
332
333fn try_i128_chain(
338 op: BinaryOpKind,
339 left: &Expr,
340 right: &Expr,
341 ecx: &ExprCtx,
342) -> Option<String> {
343 let l = exact_chain_max_magnitude(left, ecx)?;
344 let r = exact_chain_max_magnitude(right, ecx)?;
345 let root_magnitude = match op {
346 BinaryOpKind::Add | BinaryOpKind::Subtract => l.checked_add(r)?,
347 BinaryOpKind::Multiply => l.checked_mul(r)?,
348 BinaryOpKind::Divide => l,
349 BinaryOpKind::Modulo => r.min(l),
350 _ => return None,
351 };
352 if root_magnitude > (i128::MAX as u128) {
353 return None;
354 }
355 let le = emit_exact_chain_i128(left, ecx);
356 let re = emit_exact_chain_i128(right, ecx);
357 let nonzero_lit = matches!(right, Expr::Literal(Literal::Number(d)) if *d != 0);
358 let chain = match op {
359 BinaryOpKind::Add => format!("({} + {})", le, re),
360 BinaryOpKind::Subtract => format!("({} - {})", le, re),
361 BinaryOpKind::Multiply => format!("({} * {})", le, re),
362 BinaryOpKind::Divide if nonzero_lit => format!("({} / {})", le, re),
363 BinaryOpKind::Divide => format!("logos_div_i128({}, {})", le, re),
364 BinaryOpKind::Modulo if nonzero_lit => format!("({} % {})", le, re),
365 BinaryOpKind::Modulo => format!("logos_rem_i128({}, {})", le, re),
366 _ => unreachable!("root op matched above"),
367 };
368 Some(format!("logos_narrow_i128({})", chain))
369}
370
371fn chain_magnitude_with_leaf_bound(e: &Expr, bound: u128) -> Option<u128> {
374 match e {
375 Expr::Literal(Literal::Number(n)) => Some(n.unsigned_abs() as u128),
376 Expr::BinaryOp { op, left, right } if is_exact_arith_node(e) => {
377 let l = chain_magnitude_with_leaf_bound(left, bound)?;
378 let r = chain_magnitude_with_leaf_bound(right, bound)?;
379 match op {
380 BinaryOpKind::Add | BinaryOpKind::Subtract => l.checked_add(r),
381 BinaryOpKind::Multiply => l.checked_mul(r),
382 BinaryOpKind::Divide => Some(l),
383 BinaryOpKind::Modulo => Some(r.min(l)),
384 _ => unreachable!("is_exact_arith_node covers exactly these five"),
385 }
386 }
387 _ => Some(bound),
388 }
389}
390
391fn emit_chain_with_bound_leaves(
396 e: &Expr,
397 names: &HashMap<usize, String>,
398 raw: bool,
399) -> String {
400 match e {
401 Expr::Literal(Literal::Number(n)) => format!("{}i64", n),
402 Expr::BinaryOp { op, left, right } if is_exact_arith_node(e) => {
403 let l = emit_chain_with_bound_leaves(left, names, raw);
404 let r = emit_chain_with_bound_leaves(right, names, raw);
405 let nonzero_lit = matches!(&**right, Expr::Literal(Literal::Number(d)) if *d != 0);
406 if raw {
407 match op {
408 BinaryOpKind::Add => format!("({} + {})", l, r),
409 BinaryOpKind::Subtract => format!("({} - {})", l, r),
410 BinaryOpKind::Multiply => format!("({} * {})", l, r),
411 BinaryOpKind::Divide if nonzero_lit => format!("({} / {})", l, r),
412 BinaryOpKind::Divide => format!("logos_div_i64({}, {})", l, r),
413 BinaryOpKind::Modulo if nonzero_lit => format!("({} % {})", l, r),
414 BinaryOpKind::Modulo => format!("logos_rem_i64({}, {})", l, r),
415 _ => unreachable!("is_exact_arith_node covers exactly these five"),
416 }
417 } else {
418 let helper = match op {
419 BinaryOpKind::Add => "logos_add_exact",
420 BinaryOpKind::Subtract => "logos_sub_exact",
421 BinaryOpKind::Multiply => "logos_mul_exact",
422 BinaryOpKind::Divide => "logos_div_exact",
423 BinaryOpKind::Modulo => "logos_rem_exact",
424 _ => unreachable!("is_exact_arith_node covers exactly these five"),
425 };
426 format!("{}({}, {})", helper, l, r)
427 }
428 }
429 Expr::Identifier(s) => names[&(usize::MAX - s.index())].clone(),
430 _ => names[&(e as *const Expr as usize)].clone(),
431 }
432}
433
434fn try_guarded_dual_chain<'e>(
442 op: BinaryOpKind,
443 left: &'e Expr<'e>,
444 right: &'e Expr<'e>,
445 whole_ecx: &ExprCtx,
446) -> Option<String> {
447 fn walk<'e>(
448 e: &'e Expr<'e>,
449 ecx: &ExprCtx,
450 addrs: &mut Vec<usize>,
451 exprs: &mut Vec<&'e Expr<'e>>,
452 seen: &mut std::collections::HashSet<usize>,
453 ) -> Option<()> {
454 if mentions_bigint_var(e, ecx.variable_types) {
455 return None;
456 }
457 match e {
458 Expr::Literal(Literal::Number(_)) => Some(()),
459 Expr::BinaryOp { left, right, .. } if is_exact_arith_node(e) => {
460 walk(left, ecx, addrs, exprs, seen)?;
461 walk(right, ecx, addrs, exprs, seen)
462 }
463 _ => {
464 if infer_numeric_type(e, ecx.interner, ecx.variable_types) != "i64" {
465 return None;
466 }
467 let addr = match e {
471 Expr::Identifier(s) => usize::MAX - s.index(),
472 _ => e as *const Expr as usize,
473 };
474 if seen.insert(addr) {
475 addrs.push(addr);
476 exprs.push(e);
477 }
478 Some(())
479 }
480 }
481 }
482 let mut leaves: Vec<usize> = Vec::new();
485 let mut seen = std::collections::HashSet::new();
486 let mut leaf_exprs: Vec<&Expr> = Vec::new();
487 walk(left, whole_ecx, &mut leaves, &mut leaf_exprs, &mut seen)?;
488 walk(right, whole_ecx, &mut leaves, &mut leaf_exprs, &mut seen)?;
489 if leaves.is_empty() || leaves.len() > 6 {
490 return None;
491 }
492 let root_mag = |b: u128| -> Option<u128> {
495 let l = chain_magnitude_with_leaf_bound(left, b)?;
496 let r = chain_magnitude_with_leaf_bound(right, b)?;
497 match op {
498 BinaryOpKind::Add | BinaryOpKind::Subtract => l.checked_add(r),
499 BinaryOpKind::Multiply => l.checked_mul(r),
500 BinaryOpKind::Divide => Some(l),
501 BinaryOpKind::Modulo => Some(r.min(l)),
502 _ => None,
503 }
504 };
505 let mut bound: u128 = 0;
506 for exp in (16..=62).rev() {
507 let b = 1u128 << exp;
508 if root_mag(b).is_some_and(|m| m <= i64::MAX as u128) {
509 bound = b;
510 break;
511 }
512 }
513 if bound == 0 {
514 return None;
515 }
516 let plain = ExprCtx { int_exact_tolerant: false, ..*whole_ecx };
517 let mut names: HashMap<usize, String> = HashMap::new();
518 let mut binds = String::new();
519 for (k, (addr, le)) in leaves.iter().zip(leaf_exprs.iter()).enumerate() {
520 if matches!(le, Expr::Identifier(_)) {
523 names.insert(*addr, codegen_expr_ctx(le, &plain));
524 continue;
525 }
526 let name = format!("__chain_l{}", k);
527 binds.push_str(&format!("let {} = {}; ", name, codegen_expr_ctx(le, &plain)));
528 names.insert(*addr, name);
529 }
530 let guard = leaves
537 .iter()
538 .zip(leaf_exprs.iter())
539 .map(|(addr, le)| {
540 let n = &names[addr];
541 let b = bound as i64;
542 let nonneg = whole_ecx
543 .oracle
544 .and_then(|o| o.expr_int_range(le))
545 .is_some_and(|(lo, _)| lo >= 0);
546 if nonneg {
547 format!("({n} <= {b}i64)")
548 } else {
549 format!("({n} >= -{b}i64 && {n} <= {b}i64)")
550 }
551 })
552 .collect::<Vec<_>>()
553 .join(" && ");
554 let root = |raw: bool| -> String {
555 let l = emit_chain_with_bound_leaves(left, &names, raw);
556 let r = emit_chain_with_bound_leaves(right, &names, raw);
557 let nonzero_lit = matches!(right, Expr::Literal(Literal::Number(d)) if *d != 0);
558 if raw {
559 match op {
560 BinaryOpKind::Add => format!("({} + {})", l, r),
561 BinaryOpKind::Subtract => format!("({} - {})", l, r),
562 BinaryOpKind::Multiply => format!("({} * {})", l, r),
563 BinaryOpKind::Divide if nonzero_lit => format!("({} / {})", l, r),
564 BinaryOpKind::Divide => format!("logos_div_i64({}, {})", l, r),
565 BinaryOpKind::Modulo if nonzero_lit => format!("({} % {})", l, r),
566 BinaryOpKind::Modulo => format!("logos_rem_i64({}, {})", l, r),
567 _ => unreachable!(),
568 }
569 } else {
570 let helper = match op {
571 BinaryOpKind::Add => "logos_add_exact",
572 BinaryOpKind::Subtract => "logos_sub_exact",
573 BinaryOpKind::Multiply => "logos_mul_exact",
574 BinaryOpKind::Divide => "logos_div_exact",
575 BinaryOpKind::Modulo => "logos_rem_exact",
576 _ => unreachable!(),
577 };
578 format!("{}({}, {}).expect_i64(\"Int\")", helper, l, r)
579 }
580 };
581 Some(format!(
582 "{{ {binds}if {guard} {{ {fast} }} else {{ {slow} }} }}",
583 fast = root(true),
584 slow = root(false),
585 ))
586}
587
588fn oracle_proves_index(ecx: &ExprCtx, collection: &Expr, index: &Expr) -> bool {
589 if !crate::optimize::active_config().is_on(crate::optimization::Opt::Unchecked) {
590 return false;
591 }
592 let proven = ecx.oracle.map_or(false, |o| o.index_provably_in_bounds(collection, index));
593 if proven {
594 crate::optimize::mark_fired(crate::optimization::Opt::Unchecked);
595 }
596 proven
597}
598
599pub(crate) fn codegen_expr_with_async_oracle<'a>(
603 expr: &Expr,
604 interner: &'a Interner,
605 synced_vars: &'a HashSet<Symbol>,
606 async_functions: &'a HashSet<Symbol>,
607 variable_types: &'a HashMap<Symbol, String>,
608 oracle: Option<&'a crate::optimize::OracleFacts>,
609) -> String {
610 codegen_expr_ctx(
611 expr,
612 &ExprCtx { async_functions, variable_types, oracle, ..ExprCtx::bare(interner, synced_vars) },
613 )
614}
615
616#[allow(clippy::too_many_arguments)]
620pub(crate) fn codegen_expr_boxed_with_types_oracle<'a>(
621 expr: &Expr,
622 interner: &'a Interner,
623 synced_vars: &'a HashSet<Symbol>,
624 boxed_fields: &'a HashSet<(String, String, String)>,
625 registry: &'a TypeRegistry,
626 async_functions: &'a HashSet<Symbol>,
627 string_vars: &'a HashSet<Symbol>,
628 variable_types: &'a HashMap<Symbol, String>,
629 fast_div: &'a HashMap<Symbol, String>,
630 oracle: Option<&'a crate::optimize::OracleFacts>,
631) -> String {
632 codegen_expr_ctx(
633 expr,
634 &ExprCtx {
635 boxed_fields,
636 registry,
637 async_functions,
638 string_vars,
639 variable_types,
640 fast_div,
641 oracle,
642 ..ExprCtx::bare(interner, synced_vars)
643 },
644 )
645}
646
647#[allow(clippy::too_many_arguments)]
651pub(crate) fn codegen_expr_boxed_with_types_oracle_tolerant<'a>(
652 expr: &Expr,
653 interner: &'a Interner,
654 synced_vars: &'a HashSet<Symbol>,
655 boxed_fields: &'a HashSet<(String, String, String)>,
656 registry: &'a TypeRegistry,
657 async_functions: &'a HashSet<Symbol>,
658 string_vars: &'a HashSet<Symbol>,
659 variable_types: &'a HashMap<Symbol, String>,
660 fast_div: &'a HashMap<Symbol, String>,
661 oracle: Option<&'a crate::optimize::OracleFacts>,
662) -> String {
663 codegen_expr_ctx(
664 expr,
665 &ExprCtx {
666 boxed_fields,
667 registry,
668 async_functions,
669 string_vars,
670 variable_types,
671 fast_div,
672 oracle,
673 int_exact_tolerant: true,
674 ..ExprCtx::bare(interner, synced_vars)
675 },
676 )
677}
678
679pub fn codegen_expr(expr: &Expr, interner: &Interner, synced_vars: &HashSet<Symbol>) -> String {
680 codegen_expr_ctx(expr, &ExprCtx::bare(interner, synced_vars))
681}
682
683pub(crate) fn codegen_expr_with_async(
686 expr: &Expr,
687 interner: &Interner,
688 synced_vars: &HashSet<Symbol>,
689 async_functions: &HashSet<Symbol>,
690 variable_types: &HashMap<Symbol, String>,
691) -> String {
692 codegen_expr_ctx(
693 expr,
694 &ExprCtx { async_functions, variable_types, ..ExprCtx::bare(interner, synced_vars) },
695 )
696}
697
698pub(crate) fn codegen_expr_with_async_and_strings(
701 expr: &Expr,
702 interner: &Interner,
703 synced_vars: &HashSet<Symbol>,
704 async_functions: &HashSet<Symbol>,
705 string_vars: &HashSet<Symbol>,
706 variable_types: &HashMap<Symbol, String>,
707 fast_div: &HashMap<Symbol, String>,
708) -> String {
709 codegen_expr_ctx(
710 expr,
711 &ExprCtx {
712 async_functions,
713 string_vars,
714 variable_types,
715 fast_div,
716 int_exact_tolerant: true,
720 ..ExprCtx::bare(interner, synced_vars)
721 },
722 )
723}
724
725pub(crate) fn is_definitely_numeric_expr(expr: &Expr) -> bool {
729 match expr {
730 Expr::Literal(Literal::Number(_)) => true,
731 Expr::Literal(Literal::Float(_)) => true,
732 Expr::Literal(Literal::Duration(_)) => true,
733 Expr::Identifier(_) => true,
736 Expr::BinaryOp { op: BinaryOpKind::Subtract, .. } => true,
738 Expr::BinaryOp { op: BinaryOpKind::Multiply, .. } => true,
739 Expr::BinaryOp { op: BinaryOpKind::Divide, .. } => true,
740 Expr::BinaryOp { op: BinaryOpKind::Modulo, .. } => true,
741 Expr::Length { .. } => true,
743 Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => {
745 is_definitely_numeric_expr(left) && is_definitely_numeric_expr(right)
746 }
747 Expr::Call { .. } => true,
749 Expr::Index { .. } => true,
751 _ => true,
752 }
753}
754
755pub(crate) fn is_definitely_string_expr_with_vars(expr: &Expr, string_vars: &HashSet<Symbol>) -> bool {
758 match expr {
759 Expr::Literal(Literal::Text(_)) => true,
761 Expr::Identifier(sym) => string_vars.contains(sym),
763 Expr::BinaryOp { op: BinaryOpKind::Concat, .. } => true,
765 Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => {
767 is_definitely_string_expr_with_vars(left, string_vars)
768 || is_definitely_string_expr_with_vars(right, string_vars)
769 }
770 Expr::WithCapacity { value, .. } => is_definitely_string_expr_with_vars(value, string_vars),
772 Expr::InterpolatedString(_) => true,
774 _ => false,
775 }
776}
777
778pub(crate) fn is_definitely_string_expr(expr: &Expr) -> bool {
781 let empty = HashSet::new();
782 is_definitely_string_expr_with_vars(expr, &empty)
783}
784
785fn is_definitely_seq_expr(
790 expr: &Expr,
791 variable_types: &HashMap<Symbol, String>,
792 interner: &Interner,
793) -> bool {
794 match expr {
795 Expr::List(_) | Expr::Slice { .. } => true,
796 Expr::Identifier(sym) => variable_types
797 .get(sym)
798 .map(|t| {
799 let t = t.split("|__hl:").next().unwrap_or(t.as_str());
800 t.starts_with("LogosSeq") || t.starts_with("Vec<") || t.starts_with("&[")
801 })
802 .unwrap_or(false),
803 Expr::BinaryOp { op: BinaryOpKind::SeqConcat, .. } => true,
804 Expr::BinaryOp { op: BinaryOpKind::Add | BinaryOpKind::Multiply, left, right } => {
805 is_definitely_seq_expr(left, variable_types, interner)
806 || is_definitely_seq_expr(right, variable_types, interner)
807 }
808 Expr::Call { function, .. } => interner.resolve(*function) == "repeatSeq",
809 Expr::New { type_name, .. } => interner.resolve(*type_name) == "Seq",
810 _ => false,
811 }
812}
813
814pub(crate) fn collect_string_concat_operands<'a, 'b>(
821 expr: &'b Expr<'a>,
822 string_vars: &HashSet<Symbol>,
823 operands: &mut Vec<&'b Expr<'a>>,
824) {
825 match expr {
826 Expr::BinaryOp { op: BinaryOpKind::Concat, left, right } => {
827 collect_string_concat_operands(left, string_vars, operands);
828 collect_string_concat_operands(right, string_vars, operands);
829 }
830 Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => {
831 let has_string = is_definitely_string_expr_with_vars(left, string_vars)
832 || is_definitely_string_expr_with_vars(right, string_vars);
833 if has_string {
834 collect_string_concat_operands(left, string_vars, operands);
835 collect_string_concat_operands(right, string_vars, operands);
836 } else {
837 operands.push(expr);
838 }
839 }
840 _ => {
841 operands.push(expr);
842 }
843 }
844}
845
846pub(crate) fn codegen_expr_boxed(
850 expr: &Expr,
851 interner: &Interner,
852 synced_vars: &HashSet<Symbol>,
853 boxed_fields: &HashSet<(String, String, String)>, registry: &TypeRegistry, async_functions: &HashSet<Symbol>, ) -> String {
857 codegen_expr_ctx(
858 expr,
859 &ExprCtx {
860 boxed_fields,
861 registry,
862 async_functions,
863 ..ExprCtx::bare(interner, synced_vars)
864 },
865 )
866}
867
868pub(crate) fn codegen_expr_boxed_with_strings(
870 expr: &Expr,
871 interner: &Interner,
872 synced_vars: &HashSet<Symbol>,
873 boxed_fields: &HashSet<(String, String, String)>,
874 registry: &TypeRegistry,
875 async_functions: &HashSet<Symbol>,
876 string_vars: &HashSet<Symbol>,
877) -> String {
878 codegen_expr_ctx(
879 expr,
880 &ExprCtx {
881 boxed_fields,
882 registry,
883 async_functions,
884 string_vars,
885 ..ExprCtx::bare(interner, synced_vars)
886 },
887 )
888}
889
890pub(crate) fn codegen_expr_boxed_with_types(
894 expr: &Expr,
895 interner: &Interner,
896 synced_vars: &HashSet<Symbol>,
897 boxed_fields: &HashSet<(String, String, String)>,
898 registry: &TypeRegistry,
899 async_functions: &HashSet<Symbol>,
900 string_vars: &HashSet<Symbol>,
901 variable_types: &HashMap<Symbol, String>,
902 fast_div: &HashMap<Symbol, String>,
903) -> String {
904 codegen_expr_ctx(
905 expr,
906 &ExprCtx {
907 boxed_fields,
908 registry,
909 async_functions,
910 string_vars,
911 variable_types,
912 fast_div,
913 ..ExprCtx::bare(interner, synced_vars)
914 },
915 )
916}
917
918fn affine_array_coeff_offset(ty: Option<&String>) -> Option<(i64, i64)> {
924 let rest = ty?.strip_prefix("__affine_array:")?;
925 let mut parts = rest.splitn(3, ':');
926 let coeff: i64 = parts.next()?.parse().ok()?;
927 let offset: i64 = parts.next()?.parse().ok()?;
928 Some((coeff, offset))
929}
930
931fn affine_array_trip(ty: Option<&String>) -> Option<String> {
933 let rest = ty?.strip_prefix("__affine_array:")?;
934 let mut parts = rest.splitn(3, ':');
935 parts.next()?; parts.next()?; Some(parts.next()?.to_string())
938}
939
940pub(super) fn is_rational_expr(expr: &Expr, variable_types: &HashMap<Symbol, String>) -> bool {
945 match expr {
946 Expr::BinaryOp { op: BinaryOpKind::ExactDivide, .. } => true,
947 Expr::BinaryOp {
948 op: BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply,
949 left,
950 right,
951 } => is_rational_expr(left, variable_types) || is_rational_expr(right, variable_types),
952 Expr::Identifier(sym) => {
953 variable_types.get(sym).map_or(false, |t| t == "LogosRational")
954 }
955 _ => false,
956 }
957}
958
959fn rational_operand(expr: &Expr, code: &str, variable_types: &HashMap<Symbol, String>) -> String {
963 if is_rational_expr(expr, variable_types) {
964 code.to_string()
965 } else {
966 format!("LogosRational::from_i64(({}) as i64)", code)
967 }
968}
969
970pub(super) fn is_decimal_expr(
974 expr: &Expr,
975 variable_types: &HashMap<Symbol, String>,
976 interner: &Interner,
977) -> bool {
978 match expr {
979 Expr::Call { function, .. } => interner.resolve(*function) == "decimal",
980 Expr::BinaryOp {
981 op: BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply,
982 left,
983 right,
984 } => {
985 is_decimal_expr(left, variable_types, interner)
986 || is_decimal_expr(right, variable_types, interner)
987 }
988 Expr::Identifier(sym) => variable_types.get(sym).map_or(false, |t| t == "LogosDecimal"),
989 _ => false,
990 }
991}
992
993fn decimal_operand(
997 expr: &Expr,
998 code: &str,
999 variable_types: &HashMap<Symbol, String>,
1000 interner: &Interner,
1001) -> String {
1002 if is_decimal_expr(expr, variable_types, interner) {
1003 code.to_string()
1004 } else {
1005 format!("LogosDecimal::from_i64(({}) as i64)", code)
1006 }
1007}
1008
1009pub(super) fn is_complex_expr(
1012 expr: &Expr,
1013 variable_types: &HashMap<Symbol, String>,
1014 interner: &Interner,
1015) -> bool {
1016 match expr {
1017 Expr::Call { function, .. } => interner.resolve(*function) == "complex",
1018 Expr::BinaryOp {
1019 op: BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply | BinaryOpKind::Divide,
1020 left,
1021 right,
1022 } => {
1023 is_complex_expr(left, variable_types, interner)
1024 || is_complex_expr(right, variable_types, interner)
1025 }
1026 Expr::Identifier(sym) => variable_types.get(sym).map_or(false, |t| t == "LogosComplex"),
1027 _ => false,
1028 }
1029}
1030
1031fn complex_operand(
1034 expr: &Expr,
1035 code: &str,
1036 variable_types: &HashMap<Symbol, String>,
1037 interner: &Interner,
1038) -> String {
1039 if is_complex_expr(expr, variable_types, interner) {
1040 code.to_string()
1041 } else {
1042 format!("LogosComplex::from_i64(({}) as i64)", code)
1043 }
1044}
1045
1046pub(super) fn is_modular_expr(
1050 expr: &Expr,
1051 variable_types: &HashMap<Symbol, String>,
1052 interner: &Interner,
1053) -> bool {
1054 match expr {
1055 Expr::Call { function, .. } => interner.resolve(*function) == "modular",
1056 Expr::BinaryOp {
1057 op: BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply | BinaryOpKind::Divide,
1058 left,
1059 right,
1060 } => {
1061 is_modular_expr(left, variable_types, interner)
1062 && is_modular_expr(right, variable_types, interner)
1063 }
1064 Expr::Identifier(sym) => variable_types.get(sym).map_or(false, |t| t == "LogosModular"),
1065 _ => false,
1066 }
1067}
1068
1069pub(super) fn is_quantity_expr(
1073 expr: &Expr,
1074 variable_types: &HashMap<Symbol, String>,
1075 interner: &Interner,
1076) -> bool {
1077 match expr {
1078 Expr::Call { function, .. } => {
1079 matches!(interner.resolve(*function), "quantity" | "convert")
1080 }
1081 Expr::BinaryOp { op: BinaryOpKind::Add | BinaryOpKind::Subtract, left, right } => {
1082 is_quantity_expr(left, variable_types, interner)
1083 && is_quantity_expr(right, variable_types, interner)
1084 }
1085 Expr::BinaryOp { op: BinaryOpKind::Multiply | BinaryOpKind::Divide, left, right } => {
1086 is_quantity_expr(left, variable_types, interner)
1087 || is_quantity_expr(right, variable_types, interner)
1088 }
1089 Expr::Identifier(sym) => variable_types.get(sym).map_or(false, |t| t == "LogosQuantity"),
1090 _ => false,
1091 }
1092}
1093
1094pub(super) fn is_money_expr(
1098 expr: &Expr,
1099 variable_types: &HashMap<Symbol, String>,
1100 interner: &Interner,
1101) -> bool {
1102 match expr {
1103 Expr::Call { function, .. } => interner.resolve(*function) == "money",
1104 Expr::BinaryOp { op: BinaryOpKind::Add | BinaryOpKind::Subtract, left, right } => {
1105 is_money_expr(left, variable_types, interner)
1106 && is_money_expr(right, variable_types, interner)
1107 }
1108 Expr::BinaryOp { op: BinaryOpKind::Multiply | BinaryOpKind::Divide, left, right } => {
1109 is_money_expr(left, variable_types, interner)
1110 || is_money_expr(right, variable_types, interner)
1111 }
1112 Expr::Identifier(sym) => variable_types.get(sym).map_or(false, |t| t == "LogosMoney"),
1113 _ => false,
1114 }
1115}
1116
1117fn codegen_expr_ctx(expr: &Expr, ecx: &ExprCtx) -> String {
1118 let ExprCtx {
1121 interner,
1122 synced_vars,
1123 boxed_fields,
1124 async_functions,
1125 boxed_bindings,
1126 string_vars,
1127 variable_types,
1128 fast_div,
1129 ..
1130 } = *ecx;
1131 let names = RustNames::new(interner);
1132 macro_rules! recurse {
1134 ($e:expr) => {
1135 codegen_expr_ctx($e, ecx)
1136 };
1137 }
1138 macro_rules! irecurse {
1142 ($e:expr) => {
1143 codegen_expr_ctx($e, &ExprCtx { int_index_context: true, ..*ecx })
1144 };
1145 }
1146
1147 match expr {
1148 Expr::Literal(lit) => codegen_literal(lit, interner),
1149
1150 Expr::Identifier(sym) => {
1151 let name = names.ident(*sym);
1152 let base = if boxed_bindings.contains(sym) {
1154 format!("(*{})", name)
1155 } else {
1156 name
1157 };
1158 if variable_types.get(sym).map_or(false, |t| t.contains("__bigint")) {
1162 if ecx.int_index_context {
1163 format!("{}.expect_i64(\"Int\")", base)
1164 } else {
1165 format!("{}.clone()", base)
1166 }
1167 } else {
1168 base
1169 }
1170 }
1171
1172 Expr::BinaryOp { op, left, right } => {
1173 if matches!(op, BinaryOpKind::Modulo | BinaryOpKind::Divide)
1183 && !mentions_bigint_var(left, variable_types)
1184 {
1185 if let Expr::Identifier(n) = right {
1186 if let Some(helper) = fast_div.get(n) {
1187 let dividend = recurse!(left);
1188 let method = if matches!(op, BinaryOpKind::Modulo) { "rem" } else { "div" };
1189 return format!("({}.{}(({}) as u64) as i64)", helper, method, dividend);
1190 }
1191 }
1192 }
1193
1194 let is_string_concat = matches!(op, BinaryOpKind::Concat)
1197 || (matches!(op, BinaryOpKind::Add)
1198 && (is_definitely_string_expr_with_vars(left, string_vars)
1199 || is_definitely_string_expr_with_vars(right, string_vars)));
1200
1201 if is_string_concat {
1202 let mut operands = Vec::new();
1203 collect_string_concat_operands(expr, string_vars, &mut operands);
1204 let placeholders: String = operands.iter().map(|_| "{}").collect::<Vec<_>>().join("");
1205 let values: Vec<String> = operands.iter().map(|e| {
1206 if let Expr::Literal(Literal::Text(sym)) = e {
1208 format!("\"{}\"", interner.resolve(*sym))
1209 } else {
1210 recurse!(e)
1211 }
1212 }).collect();
1213 return format!("format!(\"{}\", {})", placeholders, values.join(", "));
1214 }
1215
1216 if matches!(op, BinaryOpKind::SeqConcat) {
1221 let l = recurse!(left);
1222 let r = recurse!(right);
1223 return format!(
1224 "LogosSeq::from_vec({{ let mut __sc = ({}).to_vec(); __sc.extend(({}).to_vec()); __sc }})",
1225 l, r
1226 );
1227 }
1228
1229 if matches!(op, BinaryOpKind::ApproxEq) {
1233 let l = recurse!(left);
1234 let r = recurse!(right);
1235 return format!("logos_approx_eq(({}) as f64, ({}) as f64)", l, r);
1236 }
1237
1238 if matches!(op, BinaryOpKind::BitAnd | BinaryOpKind::BitOr | BinaryOpKind::BitXor | BinaryOpKind::Subtract) {
1242 let set_typed = |e: &Expr| -> bool {
1243 matches!(e, Expr::Identifier(sym)
1244 if variable_types.get(sym).is_some_and(|t| t.starts_with("Set<") || t.starts_with("FxHashSet<")))
1245 || matches!(e, Expr::Call { function, .. } if names.raw(*function) == "setOf")
1246 };
1247 if set_typed(left) || set_typed(right) {
1248 let l = recurse!(left);
1249 let r = recurse!(right);
1250 let method = match op {
1251 BinaryOpKind::BitAnd => "intersection",
1252 BinaryOpKind::BitOr => "union",
1253 BinaryOpKind::BitXor => "symmetric_difference",
1254 _ => "difference",
1255 };
1256 return format!("({}).{}(&({})).cloned().collect::<Set<_>>()", l, method, r);
1257 }
1258 }
1259
1260 if matches!(op, BinaryOpKind::Add)
1262 && (is_definitely_seq_expr(left, variable_types, interner)
1263 || is_definitely_seq_expr(right, variable_types, interner))
1264 {
1265 let l = recurse!(left);
1266 let r = recurse!(right);
1267 return format!(
1268 "LogosSeq::from_vec({{ let mut __sc = ({}).to_vec(); __sc.extend(({}).to_vec()); __sc }})",
1269 l, r
1270 );
1271 }
1272
1273 if matches!(op, BinaryOpKind::Multiply) {
1277 let seq_first = is_definitely_seq_expr(left, variable_types, interner);
1278 let seq_second = is_definitely_seq_expr(right, variable_types, interner);
1279 if seq_first || seq_second {
1280 let (seq_e, n_e) = if seq_first { (left, right) } else { (right, left) };
1281 let s = recurse!(seq_e);
1282 let n = recurse!(n_e);
1283 return format!(
1284 "{{ let __rp_src = ({}).to_vec(); let __rp_n = (({}) as i64).max(0) as usize; \
1285 let mut __rp = Vec::with_capacity(__rp_src.len() * __rp_n); \
1286 for _ in 0..__rp_n {{ __rp.extend(__rp_src.iter().map(|__e| __e.fill_clone())); }} \
1287 LogosSeq::from_vec(__rp) }}",
1288 s, n
1289 );
1290 }
1291 }
1292
1293 if matches!(op, BinaryOpKind::Eq | BinaryOpKind::NotEq) {
1295 let neg = matches!(op, BinaryOpKind::NotEq);
1296 if let Expr::Index { collection, index } = left {
1298 if let Expr::Identifier(sym) = collection {
1299 if let Some(t) = variable_types.get(sym) {
1300 if is_logos_map_type(t) {
1301 let coll_str = recurse!(collection);
1302 let key_str = irecurse!(index);
1303 let val_str = recurse!(right);
1304 let cmp = if neg { "!=" } else { "==" };
1305 return format!("({}.get(&({})) {} Some({}))", coll_str, key_str, cmp, val_str);
1306 } else if t.starts_with("std::collections::HashMap") || t.starts_with("HashMap") || t.starts_with("rustc_hash::FxHashMap") || t.starts_with("FxHashMap") {
1307 let coll_str = recurse!(collection);
1308 let key_str = irecurse!(index);
1309 let val_str = recurse!(right);
1310 let cmp = if neg { "!=" } else { "==" };
1311 if has_copy_value_type(t) {
1312 return format!("({}.get(&({})).copied() {} Some({}))", coll_str, key_str, cmp, val_str);
1313 } else {
1314 return format!("({}.get(&({})) {} Some(&({})))", coll_str, key_str, cmp, val_str);
1315 }
1316 }
1317 }
1318 }
1319 }
1320 if let Expr::Index { collection, index } = right {
1322 if let Expr::Identifier(sym) = collection {
1323 if let Some(t) = variable_types.get(sym) {
1324 if is_logos_map_type(t) {
1325 let coll_str = recurse!(collection);
1326 let key_str = irecurse!(index);
1327 let val_str = recurse!(left);
1328 let cmp = if neg { "!=" } else { "==" };
1329 return format!("(Some({}) {} {}.get(&({})))", val_str, cmp, coll_str, key_str);
1330 } else if t.starts_with("std::collections::HashMap") || t.starts_with("HashMap") || t.starts_with("rustc_hash::FxHashMap") || t.starts_with("FxHashMap") {
1331 let coll_str = recurse!(collection);
1332 let key_str = irecurse!(index);
1333 let val_str = recurse!(left);
1334 let cmp = if neg { "!=" } else { "==" };
1335 if has_copy_value_type(t) {
1336 return format!("(Some({}) {} {}.get(&({})).copied())", val_str, cmp, coll_str, key_str);
1337 } else {
1338 return format!("(Some(&({})) {} {}.get(&({})))", val_str, cmp, coll_str, key_str);
1339 }
1340 }
1341 }
1342 }
1343 }
1344
1345 if let (Expr::Index { collection: left_coll, index: left_idx },
1351 Expr::Index { collection: right_coll, index: right_idx }) = (left, right) {
1352 let left_is_string = if let Expr::Identifier(sym) = left_coll {
1353 string_vars.contains(sym) || variable_types.get(sym).map_or(false, |t| t == "String")
1354 } else { false };
1355 let right_is_string = if let Expr::Identifier(sym) = right_coll {
1356 string_vars.contains(sym) || variable_types.get(sym).map_or(false, |t| t == "String")
1357 } else { false };
1358 if left_is_string && right_is_string {
1359 let cmp = if neg { "!=" } else { "==" };
1360 let left_coll_str = recurse!(left_coll);
1361 let right_coll_str = recurse!(right_coll);
1362 let left_idx_simplified = super::peephole::simplify_1based_index(left_idx, interner, true, variable_types);
1363 let right_idx_simplified = super::peephole::simplify_1based_index(right_idx, interner, true, variable_types);
1364 return format!("({}.as_bytes()[{}] {} {}.as_bytes()[{}])",
1365 left_coll_str, left_idx_simplified, cmp, right_coll_str, right_idx_simplified);
1366 }
1367 }
1368
1369 let is_string_index = |expr: &Expr| -> bool {
1373 if let Expr::Index { collection, .. } = expr {
1374 if let Expr::Identifier(sym) = collection {
1375 return string_vars.contains(sym) || variable_types.get(sym).map_or(false, |t| t == "String");
1376 }
1377 }
1378 false
1379 };
1380 let single_char_literal = |expr: &Expr| -> Option<char> {
1381 if let Expr::Literal(Literal::Text(sym)) = expr {
1382 let s = interner.resolve(*sym);
1383 let mut chars = s.chars();
1384 if let Some(c) = chars.next() {
1385 if chars.next().is_none() {
1386 return Some(c);
1387 }
1388 }
1389 }
1390 None
1391 };
1392
1393 if is_string_index(left) {
1395 if let Some(ch) = single_char_literal(right) {
1396 if let Expr::Index { collection, index } = left {
1397 let coll_str = recurse!(collection);
1398 let idx_str = irecurse!(index);
1399 let cmp = if neg { "!=" } else { "==" };
1400 let ch_escaped = match ch {
1401 '\'' => "\\'".to_string(),
1402 '\\' => "\\\\".to_string(),
1403 '\n' => "\\n".to_string(),
1404 '\t' => "\\t".to_string(),
1405 '\r' => "\\r".to_string(),
1406 _ => ch.to_string(),
1407 };
1408 return format!("({}.logos_get_char({}) {} '{}')",
1409 coll_str, idx_str, cmp, ch_escaped);
1410 }
1411 }
1412 }
1413 if is_string_index(right) {
1415 if let Some(ch) = single_char_literal(left) {
1416 if let Expr::Index { collection, index } = right {
1417 let coll_str = recurse!(collection);
1418 let idx_str = irecurse!(index);
1419 let cmp = if neg { "!=" } else { "==" };
1420 let ch_escaped = match ch {
1421 '\'' => "\\'".to_string(),
1422 '\\' => "\\\\".to_string(),
1423 '\n' => "\\n".to_string(),
1424 '\t' => "\\t".to_string(),
1425 '\r' => "\\r".to_string(),
1426 _ => ch.to_string(),
1427 };
1428 return format!("('{}' {} {}.logos_get_char({}))",
1429 ch_escaped, cmp, coll_str, idx_str);
1430 }
1431 }
1432 }
1433 }
1434
1435 if matches!(op, BinaryOpKind::Lt | BinaryOpKind::LtEq | BinaryOpKind::Gt
1440 | BinaryOpKind::GtEq | BinaryOpKind::Eq | BinaryOpKind::NotEq)
1441 {
1442 let left_zb = if let Expr::Identifier(sym) = left {
1443 variable_types.get(sym).map_or(false, |t| t == "__zero_based_i64")
1444 } else { false };
1445 let right_zb = if let Expr::Identifier(sym) = right {
1446 variable_types.get(sym).map_or(false, |t| t == "__zero_based_i64")
1447 } else { false };
1448 if left_zb || right_zb {
1449 let left_str = if left_zb {
1450 format!("({} + 1)", recurse!(left))
1451 } else { recurse!(left) };
1452 let right_str = if right_zb {
1453 format!("({} + 1)", recurse!(right))
1454 } else { recurse!(right) };
1455 let op_str = match op {
1456 BinaryOpKind::Lt => "<", BinaryOpKind::LtEq => "<=",
1457 BinaryOpKind::Gt => ">", BinaryOpKind::GtEq => ">=",
1458 BinaryOpKind::Eq => "==", BinaryOpKind::NotEq => "!=",
1459 _ => unreachable!(),
1460 };
1461 return format!("({} {} {})", left_str, op_str, right_str);
1462 }
1463 }
1464
1465 let left_str = recurse!(left);
1466 let right_str = recurse!(right);
1467
1468 if matches!(op, BinaryOpKind::And | BinaryOpKind::Or) {
1471 let op_str = match op {
1472 BinaryOpKind::And => "&&",
1473 BinaryOpKind::Or => "||",
1474 _ => unreachable!(),
1475 };
1476 let bools = matches!(
1477 infer_logos_type(left, interner, variable_types),
1478 crate::analysis::types::LogosType::Bool
1479 ) && matches!(
1480 infer_logos_type(right, interner, variable_types),
1481 crate::analysis::types::LogosType::Bool
1482 );
1483 return if bools {
1484 format!("({} {} {})", left_str, op_str, right_str)
1485 } else {
1486 format!(
1487 "(logos_truthy(&({})) {} logos_truthy(&({})))",
1488 left_str, op_str, right_str
1489 )
1490 };
1491 }
1492
1493 if matches!(
1498 op,
1499 BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply | BinaryOpKind::Divide
1500 | BinaryOpKind::Lt | BinaryOpKind::Gt | BinaryOpKind::LtEq | BinaryOpKind::GtEq
1501 | BinaryOpKind::Eq | BinaryOpKind::NotEq
1502 ) && (is_quantity_expr(left, variable_types, interner)
1503 || is_quantity_expr(right, variable_types, interner))
1504 {
1505 let l_q = is_quantity_expr(left, variable_types, interner);
1506 let r_q = is_quantity_expr(right, variable_types, interner);
1507 let code: Option<std::string::String> = match (op, l_q, r_q) {
1508 (BinaryOpKind::Add, true, true) => Some(format!("{}.add(&{})", left_str, right_str)),
1509 (BinaryOpKind::Subtract, true, true) => Some(format!("{}.sub(&{})", left_str, right_str)),
1510 (BinaryOpKind::Multiply, true, true) => Some(format!("{}.mul(&{})", left_str, right_str)),
1511 (BinaryOpKind::Divide, true, true) => Some(format!("{}.div_exact(&{})", left_str, right_str)),
1512 (BinaryOpKind::Multiply, true, false) => Some(format!("{}.scale_int({})", left_str, right_str)),
1514 (BinaryOpKind::Multiply, false, true) => Some(format!("{}.scale_int({})", right_str, left_str)),
1515 (BinaryOpKind::Divide, true, false) => Some(format!("{}.div_int({})", left_str, right_str)),
1516 (BinaryOpKind::Lt, true, true) => Some(format!("({} < {})", left_str, right_str)),
1518 (BinaryOpKind::Gt, true, true) => Some(format!("({} > {})", left_str, right_str)),
1519 (BinaryOpKind::LtEq, true, true) => Some(format!("({} <= {})", left_str, right_str)),
1520 (BinaryOpKind::GtEq, true, true) => Some(format!("({} >= {})", left_str, right_str)),
1521 (BinaryOpKind::Eq, true, true) => Some(format!("({} == {})", left_str, right_str)),
1522 (BinaryOpKind::NotEq, true, true) => Some(format!("({} != {})", left_str, right_str)),
1523 _ => None,
1524 };
1525 if let Some(c) = code {
1526 return c;
1527 }
1528 }
1529
1530 if matches!(
1533 op,
1534 BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply | BinaryOpKind::Divide
1535 | BinaryOpKind::Lt | BinaryOpKind::Gt | BinaryOpKind::LtEq | BinaryOpKind::GtEq
1536 | BinaryOpKind::Eq | BinaryOpKind::NotEq
1537 ) && (is_money_expr(left, variable_types, interner)
1538 || is_money_expr(right, variable_types, interner))
1539 {
1540 let l_m = is_money_expr(left, variable_types, interner);
1541 let r_m = is_money_expr(right, variable_types, interner);
1542 let code: Option<std::string::String> = match (op, l_m, r_m) {
1543 (BinaryOpKind::Add, true, true) => Some(format!("{}.add(&{})", left_str, right_str)),
1544 (BinaryOpKind::Subtract, true, true) => Some(format!("{}.sub(&{})", left_str, right_str)),
1545 (BinaryOpKind::Divide, true, true) => Some(format!("{}.ratio(&{})", left_str, right_str)),
1547 (BinaryOpKind::Multiply, true, false) => Some(format!("{}.scale_int({})", left_str, right_str)),
1549 (BinaryOpKind::Multiply, false, true) => Some(format!("{}.scale_int({})", right_str, left_str)),
1550 (BinaryOpKind::Divide, true, false) => Some(format!("{}.div_int({})", left_str, right_str)),
1551 (BinaryOpKind::Lt, true, true) => Some(format!("({} < {})", left_str, right_str)),
1552 (BinaryOpKind::Gt, true, true) => Some(format!("({} > {})", left_str, right_str)),
1553 (BinaryOpKind::LtEq, true, true) => Some(format!("({} <= {})", left_str, right_str)),
1554 (BinaryOpKind::GtEq, true, true) => Some(format!("({} >= {})", left_str, right_str)),
1555 (BinaryOpKind::Eq, true, true) => Some(format!("({} == {})", left_str, right_str)),
1556 (BinaryOpKind::NotEq, true, true) => Some(format!("({} != {})", left_str, right_str)),
1557 _ => None,
1558 };
1559 if let Some(c) = code {
1560 return c;
1561 }
1562 }
1563
1564 if matches!(
1567 op,
1568 BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply | BinaryOpKind::Divide
1569 ) && is_modular_expr(left, variable_types, interner)
1570 && is_modular_expr(right, variable_types, interner)
1571 {
1572 let method = match op {
1573 BinaryOpKind::Add => "add",
1574 BinaryOpKind::Subtract => "sub",
1575 BinaryOpKind::Multiply => "mul",
1576 BinaryOpKind::Divide => "div_exact",
1577 _ => unreachable!(),
1578 };
1579 return format!("{}.{}(&{})", left_str, method, right_str);
1580 }
1581
1582 if matches!(
1585 op,
1586 BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply | BinaryOpKind::Divide
1587 ) && (is_complex_expr(left, variable_types, interner)
1588 || is_complex_expr(right, variable_types, interner))
1589 {
1590 let l = complex_operand(left, &left_str, variable_types, interner);
1591 let r = complex_operand(right, &right_str, variable_types, interner);
1592 let method = match op {
1593 BinaryOpKind::Add => "add",
1594 BinaryOpKind::Subtract => "sub",
1595 BinaryOpKind::Multiply => "mul",
1596 BinaryOpKind::Divide => "div_exact",
1597 _ => unreachable!(),
1598 };
1599 return format!("{}.{}(&{})", l, method, r);
1600 }
1601
1602 if matches!(op, BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply)
1606 && (is_decimal_expr(left, variable_types, interner)
1607 || is_decimal_expr(right, variable_types, interner))
1608 {
1609 let l = decimal_operand(left, &left_str, variable_types, interner);
1610 let r = decimal_operand(right, &right_str, variable_types, interner);
1611 let method = match op {
1612 BinaryOpKind::Add => "add",
1613 BinaryOpKind::Subtract => "sub",
1614 BinaryOpKind::Multiply => "mul",
1615 _ => unreachable!(),
1616 };
1617 return format!("{}.{}(&{})", l, method, r);
1618 }
1619
1620 if matches!(op, BinaryOpKind::Divide)
1623 && (is_decimal_expr(left, variable_types, interner)
1624 || is_decimal_expr(right, variable_types, interner))
1625 {
1626 let l = decimal_operand(left, &left_str, variable_types, interner);
1627 let r = decimal_operand(right, &right_str, variable_types, interner);
1628 return format!("{}.to_rational().div_exact(&{}.to_rational())", l, r);
1629 }
1630
1631 if matches!(
1634 op,
1635 BinaryOpKind::Lt | BinaryOpKind::Gt | BinaryOpKind::LtEq | BinaryOpKind::GtEq
1636 | BinaryOpKind::Eq | BinaryOpKind::NotEq
1637 ) && (is_decimal_expr(left, variable_types, interner)
1638 || is_decimal_expr(right, variable_types, interner))
1639 {
1640 let l = decimal_operand(left, &left_str, variable_types, interner);
1641 let r = decimal_operand(right, &right_str, variable_types, interner);
1642 let op_str = match op {
1643 BinaryOpKind::Lt => "<",
1644 BinaryOpKind::Gt => ">",
1645 BinaryOpKind::LtEq => "<=",
1646 BinaryOpKind::GtEq => ">=",
1647 BinaryOpKind::Eq => "==",
1648 BinaryOpKind::NotEq => "!=",
1649 _ => unreachable!(),
1650 };
1651 return format!("({} {} {})", l, op_str, r);
1652 }
1653
1654 if matches!(op, BinaryOpKind::Eq | BinaryOpKind::NotEq)
1657 && (is_complex_expr(left, variable_types, interner)
1658 || is_complex_expr(right, variable_types, interner))
1659 {
1660 let l = complex_operand(left, &left_str, variable_types, interner);
1661 let r = complex_operand(right, &right_str, variable_types, interner);
1662 let op_str = if matches!(op, BinaryOpKind::Eq) { "==" } else { "!=" };
1663 return format!("({} {} {})", l, op_str, r);
1664 }
1665
1666 if matches!(op, BinaryOpKind::ExactDivide)
1671 || (matches!(
1672 op,
1673 BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply
1674 ) && (is_rational_expr(left, variable_types)
1675 || is_rational_expr(right, variable_types)))
1676 {
1677 let l = rational_operand(left, &left_str, variable_types);
1678 let r = rational_operand(right, &right_str, variable_types);
1679 let method = match op {
1680 BinaryOpKind::Add => "add",
1681 BinaryOpKind::Subtract => "sub",
1682 BinaryOpKind::Multiply => "mul",
1683 BinaryOpKind::ExactDivide => "div_exact",
1684 _ => unreachable!(),
1685 };
1686 return format!("{}.{}(&{})", l, method, r);
1687 }
1688
1689 if matches!(op, BinaryOpKind::Pow) {
1696 let lt = infer_numeric_type(left, interner, variable_types);
1697 let rt = infer_numeric_type(right, interner, variable_types);
1698 if lt == "f64" || rt == "f64" {
1699 let l = if lt == "f64" { left_str.clone() } else { format!("(({}) as f64)", left_str) };
1700 let r = if rt == "f64" { right_str.clone() } else { format!("(({}) as f64)", right_str) };
1701 return format!("({}).powf({})", l, r);
1702 }
1703 if let (Expr::Literal(Literal::Number(a)), Expr::Literal(Literal::Number(b))) = (left, right) {
1708 if let Ok(e) = u32::try_from(*b) {
1709 if let Some(v) = a.checked_pow(e) {
1710 return format!("{}i64", v);
1711 }
1712 }
1713 }
1714 let tol = ExprCtx { int_exact_tolerant: true, ..*ecx };
1715 let l = codegen_expr_ctx(left, &tol);
1716 let r = codegen_expr_ctx(right, &tol);
1717 let l = if matches!(left, Expr::Literal(Literal::Number(_))) { format!("{}i64", l) } else { l };
1720 let r = if matches!(right, Expr::Literal(Literal::Number(_))) { format!("{}i64", r) } else { r };
1721 let inner = format!("logos_pow_exact({}, {})", l, r);
1722 return if ecx.int_exact_tolerant {
1723 inner
1724 } else {
1725 format!("{}.expect_i64(\"Int\")", inner)
1726 };
1727 }
1728
1729 if matches!(op, BinaryOpKind::FloorDivide) {
1736 let lt = infer_numeric_type(left, interner, variable_types);
1737 let rt = infer_numeric_type(right, interner, variable_types);
1738 if lt == "f64" || rt == "f64" {
1739 let l = if lt == "f64" { left_str.clone() } else { format!("(({}) as f64)", left_str) };
1740 let r = if rt == "f64" { right_str.clone() } else { format!("(({}) as f64)", right_str) };
1741 return format!("({} / {}).floor()", l, r);
1742 }
1743 if let (Expr::Literal(Literal::Number(a)), Expr::Literal(Literal::Number(b))) = (left, right) {
1747 if *b != 0 {
1748 if let (Some(q), Some(r)) = (a.checked_div(*b), a.checked_rem(*b)) {
1749 let floored = if r != 0 && (r < 0) != (*b < 0) { q - 1 } else { q };
1750 return format!("{}i64", floored);
1751 }
1752 }
1753 }
1754 let tol = ExprCtx { int_exact_tolerant: true, ..*ecx };
1755 let l = codegen_expr_ctx(left, &tol);
1756 let r = codegen_expr_ctx(right, &tol);
1757 let l = if matches!(left, Expr::Literal(Literal::Number(_))) { format!("{}i64", l) } else { l };
1758 let r = if matches!(right, Expr::Literal(Literal::Number(_))) { format!("{}i64", r) } else { r };
1759 let inner = format!("logos_floordiv_exact({}, {})", l, r);
1760 return if ecx.int_exact_tolerant {
1761 inner
1762 } else {
1763 format!("{}.expect_i64(\"Int\")", inner)
1764 };
1765 }
1766
1767 let op_str = match op {
1768 BinaryOpKind::Add => "+",
1769 BinaryOpKind::Subtract => "-",
1770 BinaryOpKind::Multiply => "*",
1771 BinaryOpKind::Divide | BinaryOpKind::ExactDivide => "/",
1773 BinaryOpKind::Modulo => "%",
1774 BinaryOpKind::Eq => "==",
1775 BinaryOpKind::NotEq => "!=",
1776 BinaryOpKind::Lt => "<",
1777 BinaryOpKind::Gt => ">",
1778 BinaryOpKind::LtEq => "<=",
1779 BinaryOpKind::GtEq => ">=",
1780 BinaryOpKind::And | BinaryOpKind::Or => unreachable!(), BinaryOpKind::Concat => unreachable!(), BinaryOpKind::SeqConcat => unreachable!(), BinaryOpKind::ApproxEq => unreachable!(), BinaryOpKind::Pow => unreachable!(), BinaryOpKind::FloorDivide => unreachable!(), BinaryOpKind::BitXor => "^",
1787 BinaryOpKind::BitAnd => "&",
1788 BinaryOpKind::BitOr => "|",
1789 BinaryOpKind::Shl => "<<",
1790 BinaryOpKind::Shr => ">>",
1791 };
1792
1793 if !matches!(op, BinaryOpKind::And | BinaryOpKind::Or) {
1800 let left_type = infer_numeric_type(left, interner, variable_types);
1801 let right_type = infer_numeric_type(right, interner, variable_types);
1802 let is_cmp = matches!(
1803 op,
1804 BinaryOpKind::Eq
1805 | BinaryOpKind::NotEq
1806 | BinaryOpKind::Lt
1807 | BinaryOpKind::Gt
1808 | BinaryOpKind::LtEq
1809 | BinaryOpKind::GtEq
1810 );
1811 let mixed_cmp = if is_cmp && left_type == "f64" && right_type == "i64" {
1815 Some((right_str.clone(), left_str.clone(), true))
1816 } else if is_cmp && left_type == "i64" && right_type == "f64" {
1817 Some((left_str.clone(), right_str.clone(), false))
1818 } else {
1819 None
1820 };
1821 if let Some((int_e, float_e, flip)) = mixed_cmp {
1822 let pat = match (op, flip) {
1823 (BinaryOpKind::Lt, false) | (BinaryOpKind::Gt, true) => "Some(core::cmp::Ordering::Less)",
1824 (BinaryOpKind::Gt, false) | (BinaryOpKind::Lt, true) => "Some(core::cmp::Ordering::Greater)",
1825 (BinaryOpKind::LtEq, false) | (BinaryOpKind::GtEq, true) => {
1826 "Some(core::cmp::Ordering::Less | core::cmp::Ordering::Equal)"
1827 }
1828 (BinaryOpKind::GtEq, false) | (BinaryOpKind::LtEq, true) => {
1829 "Some(core::cmp::Ordering::Greater | core::cmp::Ordering::Equal)"
1830 }
1831 (BinaryOpKind::Eq, _) => {
1832 return format!("logos_i64_eq_f64({}, {})", int_e, float_e);
1833 }
1834 (BinaryOpKind::NotEq, _) => {
1835 return format!("(!logos_i64_eq_f64({}, {}))", int_e, float_e);
1836 }
1837 _ => unreachable!("is_cmp covers exactly the six comparison ops"),
1838 };
1839 return format!("matches!(logos_cmp_i64_f64({}, {}), {})", int_e, float_e, pat);
1840 }
1841 if left_type == "f64" && right_type != "f64" {
1842 return format!("({} {} (({}) as f64))", left_str, op_str, right_str);
1843 } else if right_type == "f64" && left_type != "f64" {
1844 return format!("((({}) as f64) {} {})", left_str, op_str, right_str);
1845 }
1846
1847 if ecx.int_wrapping
1852 && left_type == "i64"
1853 && right_type == "i64"
1854 && !ecx.int_index_context
1855 && matches!(op, BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply)
1856 {
1857 let method = match op {
1858 BinaryOpKind::Add => "wrapping_add",
1859 BinaryOpKind::Subtract => "wrapping_sub",
1860 BinaryOpKind::Multiply => "wrapping_mul",
1861 _ => unreachable!("matches! guards exactly these three"),
1862 };
1863 let l = codegen_expr_ctx(left, ecx);
1864 let r = codegen_expr_ctx(right, ecx);
1865 return format!("(({}) as i64).{}({})", l, method, r);
1871 }
1872
1873 let is_exactable = matches!(
1880 op,
1881 BinaryOpKind::Add
1882 | BinaryOpKind::Subtract
1883 | BinaryOpKind::Multiply
1884 | BinaryOpKind::Divide
1885 | BinaryOpKind::Modulo
1886 );
1887 let has_bigint_operand = mentions_bigint_var(left, variable_types)
1892 || mentions_bigint_var(right, variable_types);
1893 if is_exactable
1894 && (left_type == "i64" && right_type == "i64" || has_bigint_operand)
1895 && !ecx.int_index_context
1896 {
1897 let narrow = |e: String| {
1898 if ecx.int_exact_tolerant {
1899 e
1900 } else {
1901 format!("{}.expect_i64(\"Int\")", e)
1902 }
1903 };
1904 if let Expr::Literal(Literal::Number(d)) = right {
1912 let safe_div = matches!(op, BinaryOpKind::Divide) && *d != 0 && *d != -1;
1913 let safe_mod = matches!(op, BinaryOpKind::Modulo) && *d != 0;
1914 if (safe_div || safe_mod) && !has_bigint_operand {
1917 let l = if ecx.int_exact_tolerant {
1922 codegen_expr_ctx(left, &ExprCtx { int_exact_tolerant: false, ..*ecx })
1923 } else {
1924 left_str.clone()
1925 };
1926 return format!("({} {} {})", l, op_str, right_str);
1927 }
1928 }
1929 if let (Expr::Literal(Literal::Number(a)), Expr::Literal(Literal::Number(b))) =
1930 (left, right)
1931 {
1932 match const_exact_int(*op, *a, *b) {
1933 Some(ConstExact::InRange) => {
1934 return format!("({} {} {})", left_str, op_str, right_str);
1935 }
1936 Some(ConstExact::Promoted(lit)) => return narrow(lit),
1937 None => {}
1940 }
1941 } else if !has_bigint_operand && oracle_proves_int_op_in_range(ecx, expr, *op, left, right) {
1942 return format!("({} {} {})", left_str, op_str, right_str);
1946 }
1947 if !ecx.int_exact_tolerant && !has_bigint_operand {
1953 if let Some(fused) = try_fuse_narrowed_int_op(*op, left, right, ecx) {
1954 return fused;
1955 }
1956 if let Some(dual) = try_guarded_dual_chain(*op, left, right, ecx) {
1957 return dual;
1958 }
1959 if let Some(chain) = try_i128_chain(*op, left, right, ecx) {
1960 return chain;
1961 }
1962 }
1963 let helper = match op {
1964 BinaryOpKind::Add => "logos_add_exact",
1965 BinaryOpKind::Subtract => "logos_sub_exact",
1966 BinaryOpKind::Multiply => "logos_mul_exact",
1967 BinaryOpKind::Divide => "logos_div_exact",
1968 BinaryOpKind::Modulo => "logos_rem_exact",
1969 _ => unreachable!("is_exactable covers exactly these five"),
1970 };
1971 let tol = ExprCtx { int_exact_tolerant: true, ..*ecx };
1974 let l = codegen_expr_ctx(left, &tol);
1975 let r = codegen_expr_ctx(right, &tol);
1976 return narrow(format!("{}({}, {})", helper, l, r));
1977 }
1978 }
1979
1980 format!("({} {} {})", left_str, op_str, right_str)
1981 }
1982
1983 Expr::Call { function, args } => {
1984 let func_name = names.ident(*function);
1985 let raw_name = names.raw(*function);
1986 let callee_slot = variable_types.get(function);
1991 let callee_borrow_indices: HashSet<usize> =
1992 super::fn_role_indices(callee_slot, super::FnRole::Borrow);
1993 let callee_value_mutable: HashSet<usize> =
1997 super::fn_role_indices(callee_slot, super::FnRole::ValueMutable);
1998 let arg_wrapping = matches!(raw_name, "word8" | "word16" | "word32" | "word64");
2005 let args_str: Vec<String> = args.iter()
2006 .enumerate()
2007 .map(|(i, a)| {
2008 let s = codegen_expr_ctx(a, &ExprCtx { int_wrapping: arg_wrapping, ..*ecx });
2009 if callee_value_mutable.contains(&i) {
2010 if let Expr::Identifier(sym) = a {
2015 if variable_types.get(sym).is_some_and(|t| t.starts_with('&')) {
2016 return s;
2017 }
2018 }
2019 return format!("&{}", s);
2020 }
2021 if callee_borrow_indices.contains(&i) {
2022 if let Expr::Identifier(sym) = a {
2024 if let Some(ty) = variable_types.get(sym) {
2025 let ty = ty.split("|__hl:").next().unwrap_or(ty.as_str());
2026 if ty.starts_with("&[") || ty.starts_with("&mut [") {
2027 return s; }
2029 if ty.starts_with("Vec<") {
2030 return format!("&{}", s); }
2032 if ty.starts_with('[') {
2033 return format!("&{}", s); }
2035 if ty.starts_with("LogosSeq") {
2036 return format!("&*{}.borrow()", s);
2037 }
2038 }
2039 return format!("&*{}.borrow()", s);
2043 }
2044 format!("&*{}.borrow()", s)
2047 } else {
2048 if let Expr::Identifier(sym) = a {
2050 if variable_types.get(sym).map_or(false, |t| t.contains("__bigint")) {
2054 return format!("{}.expect_i64(\"Int\")", names.ident(*sym));
2055 }
2056 if let Some(ty) = variable_types.get(sym) {
2057 if !is_copy_type(ty) {
2058 return format!("{}.clone()", s);
2059 }
2060 } else {
2061 return format!("{}.clone()", s);
2064 }
2065 }
2066 s
2067 }
2068 })
2069 .collect();
2070 match raw_name {
2072 "sqrt" if args_str.len() == 1 => {
2073 format!("(({}) as f64).sqrt()", args_str[0])
2074 }
2075 "mapOf" if !args_str.is_empty() && args_str.len() % 2 == 0 => {
2078 let mut s = String::from("{ let __map_lit = LogosMap::new(); ");
2079 for pair in args_str.chunks(2) {
2080 s.push_str(&format!("__map_lit.insert({}, {}); ", pair[0], pair[1]));
2081 }
2082 s.push_str("__map_lit }");
2083 s
2084 }
2085 "repeatSeq" if args_str.len() == 2 => {
2088 format!(
2089 "{{ let __rp_x = ({}); let __rp_n = (({}) as i64).max(0) as usize; \
2090 let mut __rp = Vec::with_capacity(__rp_n); \
2091 for _ in 0..__rp_n {{ __rp.push(__rp_x.fill_clone()); }} \
2092 LogosSeq::from_vec(__rp) }}",
2093 args_str[0], args_str[1]
2094 )
2095 }
2096 "setOf" if !args_str.is_empty() => {
2099 let mut s = String::from("{ let mut __set_lit = Set::default(); ");
2100 for a in &args_str {
2101 s.push_str(&format!("__set_lit.insert({}); ", a));
2102 }
2103 s.push_str("__set_lit }");
2104 s
2105 }
2106 "decimal" if args_str.len() == 1 => {
2109 format!("LogosDecimal::parse(&({}).to_string())", args_str[0])
2110 }
2111 "complex" if args_str.len() == 2 => {
2113 let re = rational_operand(&args[0], &args_str[0], variable_types);
2114 let im = rational_operand(&args[1], &args_str[1], variable_types);
2115 format!("LogosComplex::new({}, {})", re, im)
2116 }
2117 "modular" if args_str.len() == 2 => {
2119 format!("LogosModular::new(({}) as i64, ({}) as i64)", args_str[0], args_str[1])
2120 }
2121 "quantity" if args_str.len() == 2 => {
2124 let mag = rational_operand(&args[0], &args_str[0], variable_types);
2125 format!("LogosQuantity::from_rational({}, &({}).to_string())", mag, args_str[1])
2126 }
2127 "convert" if args_str.len() == 2 => {
2129 format!("({}).convert(&({}).to_string())", args_str[0], args_str[1])
2130 }
2131 "money" if args_str.len() == 2 => {
2133 if is_decimal_expr(&args[0], variable_types, interner) {
2134 let amt = decimal_operand(&args[0], &args_str[0], variable_types, interner);
2135 format!("LogosMoney::of({}, &({}).to_string())", amt, args_str[1])
2136 } else {
2137 format!("LogosMoney::from_i64({}, &({}).to_string())", args_str[0], args_str[1])
2138 }
2139 }
2140 "uuid" if args_str.len() == 1 => {
2143 format!("LogosUuid::parse(&({}).to_string())", args_str[0])
2144 }
2145 "uuid_nil" if args_str.is_empty() => "LogosUuid::nil()".to_string(),
2146 "uuid_max" if args_str.is_empty() => "LogosUuid::max()".to_string(),
2147 "uuid_dns" if args_str.is_empty() => "LogosUuid::namespace_dns()".to_string(),
2148 "uuid_url" if args_str.is_empty() => "LogosUuid::namespace_url()".to_string(),
2149 "uuid_oid" if args_str.is_empty() => "LogosUuid::namespace_oid()".to_string(),
2150 "uuid_x500" if args_str.is_empty() => "LogosUuid::namespace_x500()".to_string(),
2151 "uuid_version" if args_str.len() == 1 => format!("({}).version()", args_str[0]),
2152 "text_bytes" if args_str.len() == 1 => format!("text_bytes(&({}))", args_str[0]),
2155 "text_from_bytes" if args_str.len() == 1 => format!("text_from_bytes(&({}))", args_str[0]),
2156 "readWireProgram" if args_str.is_empty() => {
2157 "{ use std::io::Read as _; let mut __len = [0u8; 4]; if std::io::stdin().read_exact(&mut __len).is_err() { std::process::exit(0); } let __n = u32::from_le_bytes(__len) as usize; let mut __wb = vec![0u8; __n]; std::io::stdin().read_exact(&mut __wb).expect(\"readWireProgram: frame\"); <CProgram as logicaffeine_data::wire::WireDecode>::wire_decode(&__wb, &mut 0usize).expect(\"readWireProgram: decode\") }".to_string()
2158 }
2159 "writeWireResidual" if args_str.len() == 1 => {
2160 format!("{{ use std::io::Write as _; let __s: String = ({}).into(); let __b = __s.as_bytes(); let __o = std::io::stdout(); let mut __h = __o.lock(); __h.write_all(&(__b.len() as u32).to_le_bytes()).unwrap(); __h.write_all(__b).unwrap(); __h.flush().unwrap(); __b.len() as i64 }}", args_str[0])
2161 }
2162 "uuid_bytes" if args_str.len() == 1 => format!("({}).byte_seq()", args_str[0]),
2163 "uuid_from_bytes" if args_str.len() == 1 => {
2164 format!("LogosUuid::from_byte_seq(&({}))", args_str[0])
2165 }
2166 "chr" if args_str.len() == 1 => {
2169 format!("logicaffeine_system::text::chr(({}) as i64)", args_str[0])
2170 }
2171 "parse_timestamp" if args_str.len() == 1 => {
2173 format!("LogosMoment::parse_rfc3339(&({}).to_string())", args_str[0])
2174 }
2175 "format_timestamp" if args_str.len() == 1 => {
2177 format!("({}).format_rfc3339()", args_str[0])
2178 }
2179 "year_of" if args_str.len() == 1 => format!("({}).year()", args_str[0]),
2181 "month_of" if args_str.len() == 1 => format!("({}).month()", args_str[0]),
2182 "day_of" if args_str.len() == 1 => format!("({}).day()", args_str[0]),
2183 "weekday_of" if args_str.len() == 1 => format!("({}).weekday()", args_str[0]),
2184 "hour_of" if args_str.len() == 1 => format!("({}).hour()", args_str[0]),
2185 "minute_of" if args_str.len() == 1 => format!("({}).minute()", args_str[0]),
2186 "second_of" if args_str.len() == 1 => format!("({}).second()", args_str[0]),
2187 "week_of" if args_str.len() == 1 => format!("({}).iso_week()", args_str[0]),
2189 "quarter_of" if args_str.len() == 1 => format!("({}).quarter()", args_str[0]),
2191 "date_of" if args_str.len() == 1 => format!("({}).date()", args_str[0]),
2193 "time_of" if args_str.len() == 1 => format!("({}).time_of_day()", args_str[0]),
2195 "seconds_between" if args_str.len() == 2 => {
2197 format!("({}).seconds_until(&({}))", args_str[0], args_str[1])
2198 }
2199 "months_between" if args_str.len() == 2 => {
2200 format!("({}).months_until(&({}))", args_str[0], args_str[1])
2201 }
2202 "years_between" if args_str.len() == 2 => {
2203 format!("({}).years_until(&({}))", args_str[0], args_str[1])
2204 }
2205 "add_seconds" if args_str.len() == 2 => {
2206 format!("({}).add_seconds({})", args_str[0], args_str[1])
2207 }
2208 "in_zone" if args_str.len() == 2 => {
2209 format!("({}).in_zone(&({}).to_string())", args_str[0], args_str[1])
2210 }
2211 "local_instant" if args_str.len() == 2 => {
2212 format!("({}).local_instant(&({}).to_string())", args_str[0], args_str[1])
2213 }
2214 "lanes4Word32" if args_str.len() == 1 => {
2217 format!("lanes4_word32(&({}))", args_str[0])
2218 }
2219 "lanes4Of" if args_str.len() == 4 => {
2220 format!("lanes4_of({}, {}, {}, {})", args_str[0], args_str[1], args_str[2], args_str[3])
2221 }
2222 "lanes16Word8" if args_str.len() == 1 => format!("lanes16_word8(&({}))", args_str[0]),
2225 "seqOfLanes16W8" if args_str.len() == 1 => format!("seq_of_lanes16w8({})", args_str[0]),
2226 "splat16Word8" if args_str.len() == 1 => format!("splat16_word8({})", args_str[0]),
2227 "shuffle16" if args_str.len() == 2 => {
2228 format!("shuffle16({}, {})", args_str[0], args_str[1])
2229 }
2230 "shrBytes16" if args_str.len() == 2 => {
2231 format!("shr_bytes16({}, {})", args_str[0], args_str[1])
2232 }
2233 "interleaveLo16" if args_str.len() == 2 => {
2234 format!("interleave_lo16({}, {})", args_str[0], args_str[1])
2235 }
2236 "interleaveHi16" if args_str.len() == 2 => {
2237 format!("interleave_hi16({}, {})", args_str[0], args_str[1])
2238 }
2239 "byteAdd16" if args_str.len() == 2 => {
2240 format!("byte_add16({}, {})", args_str[0], args_str[1])
2241 }
2242 "maddubs16" if args_str.len() == 2 => {
2243 format!("maddubs16({}, {})", args_str[0], args_str[1])
2244 }
2245 "packus16" if args_str.len() == 2 => {
2246 format!("packus16({}, {})", args_str[0], args_str[1])
2247 }
2248 "seqOfLanes4W32" if args_str.len() == 1 => {
2249 format!("seq_of_lanes4w32({})", args_str[0])
2250 }
2251 "sha1rnds4" if args_str.len() == 3 => {
2252 format!("sha1rnds4({}, {}, {})", args_str[0], args_str[1], args_str[2])
2253 }
2254 "sha1msg1" if args_str.len() == 2 => {
2255 format!("sha1msg1({}, {})", args_str[0], args_str[1])
2256 }
2257 "sha1msg2" if args_str.len() == 2 => {
2258 format!("sha1msg2({}, {})", args_str[0], args_str[1])
2259 }
2260 "sha1nexte" if args_str.len() == 2 => {
2261 format!("sha1nexte({}, {})", args_str[0], args_str[1])
2262 }
2263 "lanes8Word32" if args_str.len() == 1 => {
2265 format!("lanes8_word32(&({}))", args_str[0])
2266 }
2267 "seqOfLanes8" if args_str.len() == 1 => {
2268 format!("seq_of_lanes8({})", args_str[0])
2269 }
2270 "splat8Word32" if args_str.len() == 1 => {
2271 format!("splat8_word32({})", args_str[0])
2272 }
2273 "intOfWord32" if args_str.len() == 1 => {
2274 format!("int_of_word32({})", args_str[0])
2275 }
2276 "intOfWord64" if args_str.len() == 1 => {
2277 format!("int_of_word64({})", args_str[0])
2278 }
2279 "word64Shl" if args_str.len() == 2 => {
2280 format!("word64_shl({}, {})", args_str[0], args_str[1])
2281 }
2282 "word64Shr" if args_str.len() == 2 => {
2283 format!("word64_shr({}, {})", args_str[0], args_str[1])
2284 }
2285 "word32Shr" if args_str.len() == 2 => {
2286 format!("word32_shr({}, {})", args_str[0], args_str[1])
2287 }
2288 "word64And" if args_str.len() == 2 => {
2289 format!("word64_and({}, {})", args_str[0], args_str[1])
2290 }
2291 "word16" if args_str.len() == 1 => {
2292 format!("word16({})", args_str[0])
2293 }
2294 "intOfWord16" if args_str.len() == 1 => {
2295 format!("int_of_word16({})", args_str[0])
2296 }
2297 "lanes4Word64" if args_str.len() == 1 => {
2298 format!("lanes4_word64(&({}))", args_str[0])
2299 }
2300 "seqOfLanes4" if args_str.len() == 1 => {
2301 format!("seq_of_lanes4({})", args_str[0])
2302 }
2303 "mul32x32to64" if args_str.len() == 2 => {
2304 format!("mul32x32to64({}, {})", args_str[0], args_str[1])
2305 }
2306 "hsumLanes4" if args_str.len() == 1 => {
2307 format!("hsum_lanes4({})", args_str[0])
2308 }
2309 "splat4Word64" if args_str.len() == 1 => {
2310 format!("splat4_word64({})", args_str[0])
2311 }
2312 "andNot4" if args_str.len() == 2 => {
2313 format!("and_not4({}, {})", args_str[0], args_str[1])
2314 }
2315 "lanes16Word16" if args_str.len() == 1 => {
2316 format!("lanes16_word16(&({}))", args_str[0])
2317 }
2318 "seqOfLanes16" if args_str.len() == 1 => {
2319 format!("seq_of_lanes16({})", args_str[0])
2320 }
2321 "splat16Word16" if args_str.len() == 1 => {
2322 format!("splat16_word16({})", args_str[0])
2323 }
2324 "mulhi16" if args_str.len() == 2 => {
2325 format!("mulhi16({}, {})", args_str[0], args_str[1])
2326 }
2327 "montmul32" if args_str.len() == 4 => {
2328 format!("montmul32({}, {}, {}, {})", args_str[0], args_str[1], args_str[2], args_str[3])
2329 }
2330 "nttBcastLo" if args_str.len() == 2 => {
2333 format!("({}).ntt_bcast_lo(({}) as usize)", args_str[0], args_str[1])
2334 }
2335 "nttBcastHi" if args_str.len() == 2 => {
2336 format!("({}).ntt_bcast_hi(({}) as usize)", args_str[0], args_str[1])
2337 }
2338 "nttBlend" if args_str.len() == 3 => {
2339 format!("({}).ntt_blend({}, ({}) as usize)", args_str[0], args_str[1], args_str[2])
2340 }
2341 "pow" if args_str.len() == 2 && is_modular_expr(&args[0], variable_types, interner) => {
2343 format!("({}).pow(({}) as u64)", args_str[0], args_str[1])
2344 }
2345 "abs" if args_str.len() == 1 && is_rational_expr(&args[0], variable_types) => {
2348 format!("({}).abs()", args_str[0])
2349 }
2350 "floor" if args_str.len() == 1 && is_rational_expr(&args[0], variable_types) => {
2351 format!("({}).floor()", args_str[0])
2352 }
2353 "ceil" if args_str.len() == 1 && is_rational_expr(&args[0], variable_types) => {
2354 format!("({}).ceil()", args_str[0])
2355 }
2356 "round" if args_str.len() == 1 && is_rational_expr(&args[0], variable_types) => {
2357 format!("({}).round()", args_str[0])
2358 }
2359 "abs" if args_str.len() == 1 => {
2360 let arg_type = infer_numeric_type(&args[0], interner, variable_types);
2361 if arg_type == "f64" {
2362 format!("(({}) as f64).abs()", args_str[0])
2363 } else {
2364 format!("(({}) as i64).abs()", args_str[0])
2365 }
2366 }
2367 "floor" if args_str.len() == 1 => {
2368 format!("((({}) as f64).floor() as i64)", args_str[0])
2369 }
2370 "ceil" if args_str.len() == 1 => {
2371 format!("((({}) as f64).ceil() as i64)", args_str[0])
2372 }
2373 "round" if args_str.len() == 1 => {
2374 format!("((({}) as f64).round() as i64)", args_str[0])
2375 }
2376 "pow" if args_str.len() == 2 => {
2377 format!("((({}) as f64).powf(({}) as f64))", args_str[0], args_str[1])
2378 }
2379 "min" if args_str.len() == 2 => {
2380 format!("({}).min({})", args_str[0], args_str[1])
2381 }
2382 "max" if args_str.len() == 2 => {
2383 format!("({}).max({})", args_str[0], args_str[1])
2384 }
2385 _ => {
2386 if async_functions.contains(function) {
2388 format!("{}({}).await", func_name, args_str.join(", "))
2389 } else {
2390 format!("{}({})", func_name, args_str.join(", "))
2391 }
2392 }
2393 }
2394 }
2395
2396 Expr::Index { collection: Expr::Identifier(sym), index }
2401 if affine_array_coeff_offset(variable_types.get(sym)).is_some() =>
2402 {
2403 let (coeff, offset) = affine_array_coeff_offset(variable_types.get(sym)).unwrap();
2404 enum Pos {
2405 Const(i64),
2406 Expr(String),
2407 }
2408 let pos = match index {
2409 Expr::Literal(Literal::Number(n)) => Pos::Const(n - 1),
2410 Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => match (left, right) {
2411 (_, Expr::Literal(Literal::Number(1))) => Pos::Expr(recurse!(left)),
2412 (Expr::Literal(Literal::Number(1)), _) => Pos::Expr(recurse!(right)),
2413 (_, Expr::Literal(Literal::Number(k))) if *k > 1 => {
2414 Pos::Expr(format!("({} + {})", recurse!(left), k - 1))
2415 }
2416 (Expr::Literal(Literal::Number(k)), _) if *k > 1 => {
2417 Pos::Expr(format!("({} + {})", recurse!(right), k - 1))
2418 }
2419 _ => Pos::Expr(format!("(({}) - 1)", irecurse!(index))),
2420 },
2421 _ => Pos::Expr(format!("(({}) - 1)", irecurse!(index))),
2422 };
2423 match pos {
2424 Pos::Const(p) => format!("{}i64", coeff.wrapping_mul(p).wrapping_add(offset)),
2425 Pos::Expr(_) if coeff == 0 => format!("{}i64", offset),
2426 Pos::Expr(p) => {
2427 let base = if coeff == 1 { p } else { format!("(({}) * {})", p, coeff) };
2428 if offset == 0 {
2429 base
2430 } else {
2431 format!("({} + {})", base, offset)
2432 }
2433 }
2434 }
2435 }
2436
2437 Expr::Index { collection: Expr::Identifier(sym), index }
2441 if parse_aos_tag(variable_types.get(sym)).is_some() =>
2442 {
2443 let tag = parse_aos_tag(variable_types.get(sym)).unwrap();
2444 let row = match index {
2445 Expr::Literal(Literal::Number(1)) => "0".to_string(),
2446 Expr::Literal(Literal::Number(n)) => format!("{}", n - 1),
2447 Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => match (left, right) {
2448 (_, Expr::Literal(Literal::Number(1))) => format!("({}) as usize", recurse!(left)),
2449 (Expr::Literal(Literal::Number(1)), _) => format!("({}) as usize", recurse!(right)),
2450 _ => format!("(({}) - 1) as usize", irecurse!(index)),
2451 },
2452 _ => format!("(({}) - 1) as usize", irecurse!(index)),
2453 };
2454 format!("{}[{}][{}]", tag.backing, row, tag.col)
2455 }
2456
2457 Expr::Index { collection, index } => {
2458 let coll_str = recurse!(collection);
2459 let known_type = if let Expr::Identifier(sym) = collection {
2462 variable_types.get(sym).map(|s| s.split("|__hl:").next().unwrap_or(s.as_str()))
2463 } else {
2464 None
2465 };
2466 if let Expr::Literal(Literal::Number(n)) = index {
2473 if *n < 0 {
2474 if let Some(t) = known_type {
2475 let idx_capable = t.starts_with("LogosSeq")
2476 || t.starts_with("Vec")
2477 || t.starts_with("&[")
2478 || t.starts_with("&mut [")
2479 || t.starts_with('[')
2480 || t == "String";
2481 if idx_capable {
2482 let read = format!(
2483 "logicaffeine_data::LogosIndex::logos_get(&{}, {}i64)",
2484 coll_str, n
2485 );
2486 return if t == "Vec<i32>" {
2487 format!("(({}) as i64)", read)
2488 } else {
2489 read
2490 };
2491 }
2492 }
2493 }
2494 }
2495 match known_type {
2496 Some(t) if t.starts_with("LogosSeq") || t.starts_with("Vec") => {
2497 let is_logos_seq = t.starts_with("LogosSeq");
2498 let suffix = if has_copy_element_type(t) { "" } else { ".clone()" };
2499 let is_zero_based_counter = if let Expr::Identifier(idx_sym) = index {
2501 variable_types.get(idx_sym).map_or(false, |t| t == "__zero_based_i64")
2502 } else {
2503 false
2504 };
2505 let index_part = if is_zero_based_counter {
2506 let idx_name = irecurse!(index);
2507 format!("{} as usize", idx_name)
2508 } else { match index {
2509 Expr::Literal(Literal::Number(1)) => "0".to_string(),
2511 Expr::Literal(Literal::Number(n)) => format!("{}", n - 1),
2513 Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => {
2515 match (left, right) {
2516 (_, Expr::Literal(Literal::Number(1))) => {
2517 let left_str = irecurse!(left);
2518 if matches!(left, Expr::Identifier(_)) {
2519 format!("{} as usize", left_str)
2520 } else {
2521 format!("({}) as usize", left_str)
2522 }
2523 }
2524 (Expr::Literal(Literal::Number(1)), _) => {
2525 let right_str = irecurse!(right);
2526 if matches!(right, Expr::Identifier(_)) {
2527 format!("{} as usize", right_str)
2528 } else {
2529 format!("({}) as usize", right_str)
2530 }
2531 }
2532 (_, Expr::Literal(Literal::Number(k))) if *k > 1 => {
2533 format!("({} + {}) as usize", irecurse!(left), k - 1)
2534 }
2535 (Expr::Literal(Literal::Number(k)), _) if *k > 1 => {
2536 format!("({} + {}) as usize", irecurse!(right), k - 1)
2537 }
2538 _ => {
2539 format!("({} - 1) as usize", irecurse!(index))
2540 }
2541 }
2542 }
2543 _ => {
2544 format!("({} - 1) as usize", irecurse!(index))
2545 }
2546 } };
2547 let read = if oracle_proves_index(ecx, collection, index) {
2551 match (is_logos_seq, suffix.is_empty()) {
2552 (true, true) => format!("unsafe {{ *{}.borrow().get_unchecked({}) }}", coll_str, index_part),
2553 (true, false) => format!("unsafe {{ {}.borrow().get_unchecked({}){} }}", coll_str, index_part, suffix),
2554 (false, true) => format!("unsafe {{ *{}.get_unchecked({}) }}", coll_str, index_part),
2555 (false, false) => format!("unsafe {{ {}.get_unchecked({}){} }}", coll_str, index_part, suffix),
2556 }
2557 } else if is_logos_seq {
2558 format!("{}.borrow()[{}]{}", coll_str, index_part, suffix)
2559 } else {
2560 format!("{}[{}]{}", coll_str, index_part, suffix)
2561 };
2562 if t == "Vec<i32>" {
2565 format!("(({}) as i64)", read)
2566 } else {
2567 read
2568 }
2569 }
2570 Some(t) if t.starts_with("&[") || t.starts_with("&mut [") || t.starts_with('[') => {
2571 let elem = if t.starts_with('[') {
2574 t.trim_start_matches('[').split("; ").next().unwrap_or("_")
2575 } else {
2576 t.strip_prefix("&mut [")
2577 .or_else(|| t.strip_prefix("&["))
2578 .and_then(|s| s.strip_suffix(']'))
2579 .unwrap_or("_")
2580 };
2581 let suffix = if is_copy_type(elem) { "" } else { ".clone()" };
2582 let is_zero_based_counter = if let Expr::Identifier(idx_sym) = index {
2584 variable_types.get(idx_sym).map_or(false, |t| t == "__zero_based_i64")
2585 } else {
2586 false
2587 };
2588 let index_part = if is_zero_based_counter {
2589 let idx_name = irecurse!(index);
2590 format!("{} as usize", idx_name)
2591 } else { match index {
2592 Expr::Literal(Literal::Number(1)) => "0".to_string(),
2593 Expr::Literal(Literal::Number(n)) => format!("{}", n - 1),
2594 Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => {
2595 match (left, right) {
2596 (_, Expr::Literal(Literal::Number(1))) => {
2597 let left_str = irecurse!(left);
2598 if matches!(left, Expr::Identifier(_)) {
2599 format!("{} as usize", left_str)
2600 } else {
2601 format!("({}) as usize", left_str)
2602 }
2603 }
2604 (Expr::Literal(Literal::Number(1)), _) => {
2605 let right_str = irecurse!(right);
2606 if matches!(right, Expr::Identifier(_)) {
2607 format!("{} as usize", right_str)
2608 } else {
2609 format!("({}) as usize", right_str)
2610 }
2611 }
2612 (_, Expr::Literal(Literal::Number(k))) if *k > 1 => {
2613 format!("({} + {}) as usize", irecurse!(left), k - 1)
2614 }
2615 (Expr::Literal(Literal::Number(k)), _) if *k > 1 => {
2616 format!("({} + {}) as usize", irecurse!(right), k - 1)
2617 }
2618 _ => {
2619 format!("({} - 1) as usize", irecurse!(index))
2620 }
2621 }
2622 }
2623 _ => {
2624 format!("({} - 1) as usize", irecurse!(index))
2625 }
2626 } };
2627 let read = if oracle_proves_index(ecx, collection, index) {
2629 if suffix.is_empty() {
2630 format!("unsafe {{ *{}.get_unchecked({}) }}", coll_str, index_part)
2631 } else {
2632 format!("unsafe {{ {}.get_unchecked({}){} }}", coll_str, index_part, suffix)
2633 }
2634 } else {
2635 format!("{}[{}]{}", coll_str, index_part, suffix)
2636 };
2637 if elem == "i32" {
2640 format!("(({}) as i64)", read)
2641 } else {
2642 read
2643 }
2644 }
2645 Some(t) if is_logos_map_type(t) => {
2646 let index_str = irecurse!(index);
2647 format!("{}.get(&({})).expect(\"Key not found in map\")", coll_str, index_str)
2649 }
2650 Some(t) if t.starts_with("std::collections::HashMap") || t.starts_with("HashMap") || t.starts_with("rustc_hash::FxHashMap") || t.starts_with("FxHashMap") => {
2651 let index_str = irecurse!(index);
2652 let suffix = if has_copy_value_type(t) { "" } else { ".clone()" };
2653 format!("{}[&({})]{}", coll_str, index_str, suffix)
2654 }
2655 Some("String") => {
2656 let index_str = irecurse!(index);
2657 format!("LogosIndex::logos_get(&{}, {})", coll_str, index_str)
2658 }
2659 _ => {
2660 let index_str = irecurse!(index);
2661 format!("LogosIndex::logos_get(&{}, {})", coll_str, index_str)
2662 }
2663 }
2664 }
2665
2666 Expr::Slice { collection, start, end } => {
2667 let coll_str = recurse!(collection);
2668 let start_str = recurse!(start);
2669 let end_str = recurse!(end);
2670 let known_type = if let Expr::Identifier(sym) = collection {
2672 variable_types.get(sym).map(|s| s.split("|__hl:").next().unwrap_or(s.as_str()))
2673 } else {
2674 None
2675 };
2676 if matches!(known_type, Some(t) if t.starts_with("LogosSeq")) {
2677 format!("&{}.borrow()[({} - 1) as usize..{} as usize]", coll_str, start_str, end_str)
2678 } else {
2679 format!("&{}[({} - 1) as usize..{} as usize]", coll_str, start_str, end_str)
2680 }
2681 }
2682
2683 Expr::Copy { expr: inner } => {
2684 if let Expr::Slice { collection, start, end } = inner {
2686 let coll_str = recurse!(collection);
2687 let start_str = recurse!(start);
2688 let end_str = recurse!(end);
2689 let known_type = if let Expr::Identifier(sym) = collection {
2690 variable_types.get(sym).map(|s| s.split("|__hl:").next().unwrap_or(s.as_str()))
2691 } else {
2692 None
2693 };
2694 if matches!(known_type, Some(t) if t.starts_with("LogosSeq")) {
2695 format!("LogosSeq::from_vec({}.borrow()[({} - 1) as usize..{} as usize].to_vec())", coll_str, start_str, end_str)
2696 } else if matches!(known_type, Some(t) if t.starts_with("&[") || t.starts_with("Vec<")) {
2697 format!("LogosSeq::from_vec({}[({} - 1) as usize..{} as usize].to_vec())", coll_str, start_str, end_str)
2698 } else {
2699 format!("{}[({} - 1) as usize..{} as usize].to_vec()", coll_str, start_str, end_str)
2700 }
2701 } else {
2702 let known_type = if let Expr::Identifier(sym) = inner {
2704 variable_types.get(sym).map(|s| s.split("|__hl:").next().unwrap_or(s.as_str()))
2705 } else {
2706 None
2707 };
2708 let expr_str = recurse!(inner);
2709 if matches!(known_type, Some(t) if t.starts_with("Vec<")) {
2710 format!("LogosSeq::from_vec({}.clone())", expr_str)
2713 } else if matches!(known_type, Some(t) if t.starts_with("&[")) {
2714 format!("LogosSeq::from_vec({}.to_vec())", expr_str)
2715 } else if matches!(known_type, Some(t) if t.starts_with("LogosSeq") || t.starts_with("LogosMap")) {
2716 format!("{}.deep_clone()", expr_str)
2717 } else {
2718 format!("{}.to_owned()", expr_str)
2719 }
2720 }
2721 }
2722
2723 Expr::Give { value } => {
2724 recurse!(value)
2727 }
2728
2729 Expr::Length { collection: Expr::Identifier(sym) }
2732 if affine_array_trip(variable_types.get(sym)).is_some() =>
2733 {
2734 format!("(({}) as i64)", affine_array_trip(variable_types.get(sym)).unwrap())
2735 }
2736
2737 Expr::Length { collection: Expr::Identifier(sym) }
2739 if parse_aos_tag(variable_types.get(sym)).is_some() =>
2740 {
2741 format!("{}i64", parse_aos_tag(variable_types.get(sym)).unwrap().len)
2742 }
2743
2744 Expr::Length { collection } => {
2745 if let Expr::Identifier(sym) = collection {
2746 if let Some(t) = variable_types.get(sym) {
2747 if let Some(pos) = t.find("|__hl:") {
2748 return t[pos + "|__hl:".len()..].to_string();
2749 }
2750 }
2751 }
2752 let coll_str = recurse!(collection);
2753 format!("({}.len() as i64)", coll_str)
2755 }
2756
2757 Expr::Contains { collection, value } => {
2758 let coll_str = recurse!(collection);
2759 let val_str = recurse!(value);
2760 if let crate::analysis::types::LogosType::Map(k, _) =
2764 super::types::infer_logos_type(collection, interner, variable_types)
2765 {
2766 if matches!(*k, crate::analysis::types::LogosType::Int)
2767 && super::types::infer_numeric_type(value, interner, variable_types) == "f64"
2768 {
2769 return format!(
2770 "logicaffeine_data::logos_i64_key_of_f64({}).map_or(false, |__k| {}.logos_contains(&__k))",
2771 val_str, coll_str
2772 );
2773 }
2774 }
2775 format!("{}.logos_contains(&{})", coll_str, val_str)
2777 }
2778
2779 Expr::Union { left, right } => {
2780 let left_str = recurse!(left);
2781 let right_str = recurse!(right);
2782 format!("{}.union(&{}).cloned().collect::<Set<_>>()", left_str, right_str)
2783 }
2784
2785 Expr::Intersection { left, right } => {
2786 let left_str = recurse!(left);
2787 let right_str = recurse!(right);
2788 format!("{}.intersection(&{}).cloned().collect::<Set<_>>()", left_str, right_str)
2789 }
2790
2791 Expr::ManifestOf { zone } => {
2793 let zone_str = recurse!(zone);
2794 format!("logicaffeine_system::network::FileSipper::from_zone(&{}).manifest()", zone_str)
2795 }
2796
2797 Expr::ChunkAt { index, zone } => {
2798 let zone_str = recurse!(zone);
2799 let index_str = irecurse!(index);
2800 format!("logicaffeine_system::network::FileSipper::from_zone(&{}).get_chunk(({} - 1) as usize)", zone_str, index_str)
2802 }
2803
2804 Expr::List(ref items) => {
2805 let item_strs: Vec<String> = items.iter()
2806 .map(|i| recurse!(i))
2807 .collect();
2808 format!("LogosSeq::from_vec(vec![{}])", item_strs.join(", "))
2809 }
2810
2811 Expr::Tuple(ref items) => {
2812 let item_strs: Vec<String> = items.iter()
2813 .map(|i| format!("Value::from({})", recurse!(i)))
2814 .collect();
2815 format!("vec![{}]", item_strs.join(", "))
2817 }
2818
2819 Expr::Range { start, end } => {
2820 let start_str = recurse!(start);
2821 let end_str = recurse!(end);
2822 format!("({}..={})", start_str, end_str)
2823 }
2824
2825 Expr::FieldAccess { object, field } => {
2826 let field_name = interner.resolve(*field);
2827
2828 let root_sym = get_root_identifier(object);
2830 if let Some(sym) = root_sym {
2831 if synced_vars.contains(&sym) {
2832 let obj_name = interner.resolve(sym);
2833 return format!("{}.get().await.{}", obj_name, field_name);
2834 }
2835 }
2836
2837 let obj_str = recurse!(object);
2838 format!("{}.{}", obj_str, field_name)
2839 }
2840
2841 Expr::New { type_name, type_args, init_fields } => {
2842 let type_str = interner.resolve(*type_name);
2843 if !init_fields.is_empty() {
2844 let fields_str = init_fields.iter()
2847 .map(|(name, value)| {
2848 let field_name = interner.resolve(*name);
2849 let value_str = recurse!(value);
2850 format!("{}: {}", field_name, value_str)
2851 })
2852 .collect::<Vec<_>>()
2853 .join(", ");
2854 format!("{} {{ {}, ..Default::default() }}", type_str, fields_str)
2855 } else if type_args.is_empty() {
2856 format!("{}::default()", type_str)
2857 } else {
2858 let args_str = type_args.iter()
2861 .map(|t| codegen_type_expr(t, interner))
2862 .collect::<Vec<_>>()
2863 .join(", ");
2864 format!("{}::<{}>::default()", type_str, args_str)
2865 }
2866 }
2867
2868 Expr::NewVariant { enum_name, variant, fields } => {
2869 let enum_str = interner.resolve(*enum_name);
2870 let variant_str = interner.resolve(*variant);
2871 if fields.is_empty() {
2872 format!("{}::{}", enum_str, variant_str)
2874 } else {
2875 let mut identifier_counts: HashMap<Symbol, usize> = HashMap::new();
2878 for (_, value) in fields.iter() {
2879 if let Expr::Identifier(sym) = value {
2880 *identifier_counts.entry(*sym).or_insert(0) += 1;
2881 }
2882 }
2883
2884 let mut remaining_uses: HashMap<Symbol, usize> = identifier_counts.clone();
2886
2887 let fields_str: Vec<String> = fields.iter()
2890 .map(|(field_name, value)| {
2891 let name = interner.resolve(*field_name);
2892
2893 let val = if let Expr::Identifier(sym) = value {
2896 let total = identifier_counts.get(sym).copied().unwrap_or(0);
2897 let remaining = remaining_uses.get_mut(sym);
2898 let base_name = if boxed_bindings.contains(sym) {
2899 format!("(*{})", interner.resolve(*sym))
2900 } else {
2901 interner.resolve(*sym).to_string()
2902 };
2903 if total > 1 {
2904 if let Some(r) = remaining {
2905 *r -= 1;
2906 if *r > 0 {
2907 format!("{}.clone()", base_name)
2909 } else {
2910 base_name
2912 }
2913 } else {
2914 base_name
2915 }
2916 } else {
2917 base_name
2918 }
2919 } else {
2920 recurse!(value)
2921 };
2922
2923 let key = (enum_str.to_string(), variant_str.to_string(), name.to_string());
2925 if boxed_fields.contains(&key) {
2926 format!("{}: Box::new({})", name, val)
2927 } else {
2928 format!("{}: {}", name, val)
2929 }
2930 })
2931 .collect();
2932 format!("{}::{} {{ {} }}", enum_str, variant_str, fields_str.join(", "))
2933 }
2934 }
2935
2936 Expr::OptionSome { value } => {
2937 format!("Some({})", recurse!(value))
2938 }
2939
2940 Expr::OptionNone => {
2941 "None".to_string()
2942 }
2943
2944 Expr::Escape { code, .. } => {
2945 let raw_code = interner.resolve(*code);
2946 let mut block = String::from("{\n");
2947 for line in raw_code.lines() {
2948 block.push_str(" ");
2949 block.push_str(line);
2950 block.push('\n');
2951 }
2952 block.push('}');
2953 block
2954 }
2955
2956 Expr::WithCapacity { value, capacity } => {
2957 let cap_str = recurse!(capacity);
2958 match value {
2959 Expr::Literal(Literal::Text(sym)) if interner.resolve(*sym).is_empty() => {
2961 format!("String::with_capacity(({}) as usize)", cap_str)
2962 }
2963 Expr::Literal(Literal::Text(sym)) => {
2965 let text = interner.resolve(*sym);
2966 format!("{{ let mut __s = String::with_capacity(({}) as usize); __s.push_str(\"{}\"); __s }}", cap_str, text)
2967 }
2968 Expr::New { type_name, type_args, .. } => {
2970 let type_str = interner.resolve(*type_name);
2971 match type_str {
2972 "Seq" | "List" | "Vec" => {
2973 let elem = if !type_args.is_empty() {
2974 codegen_type_expr(&type_args[0], interner)
2975 } else { "()".to_string() };
2976 format!("LogosSeq::<{}>::with_capacity(({}) as usize)", elem, cap_str)
2977 }
2978 "Map" | "HashMap" => {
2979 let (k, v) = if type_args.len() >= 2 {
2980 (codegen_type_expr(&type_args[0], interner),
2981 codegen_type_expr(&type_args[1], interner))
2982 } else { ("String".to_string(), "String".to_string()) };
2983 format!("LogosMap::<{}, {}>::with_capacity(({}) as usize)", k, v, cap_str)
2984 }
2985 "Set" | "HashSet" => {
2986 let elem = if !type_args.is_empty() {
2987 codegen_type_expr(&type_args[0], interner)
2988 } else { "()".to_string() };
2989 format!("{{ let __s: Set<{}> = Set::with_capacity_and_hasher(({}) as usize, Default::default()); __s }}", elem, cap_str)
2990 }
2991 _ => recurse!(value) }
2993 }
2994 _ => recurse!(value)
2996 }
2997 }
2998
2999 Expr::Closure { params, body, .. } => {
3000 use crate::ast::stmt::ClosureBody;
3001 let params_str: Vec<String> = params.iter()
3002 .map(|(name, ty)| {
3003 let param_name = names.ident(*name);
3004 let param_type = codegen_type_expr(ty, interner);
3005 format!("{}: {}", param_name, param_type)
3006 })
3007 .collect();
3008
3009 match body {
3010 ClosureBody::Expression(expr) => {
3011 let body_str = recurse!(expr);
3012 format!("move |{}| {{ {} }}", params_str.join(", "), body_str)
3013 }
3014 ClosureBody::Block(stmts) => {
3015 let mut body_str = String::new();
3016 let mut ctx = RefinementContext::new();
3017 let empty_mutable = collect_mutable_vars(stmts);
3018 let empty_lww = HashSet::new();
3019 let empty_mv = HashSet::new();
3020 let mut empty_synced = HashSet::new();
3021 let empty_caps = HashMap::new();
3022 let empty_pipes = HashSet::new();
3023 let empty_boxed = HashSet::new();
3024 let empty_registry = TypeRegistry::new();
3025 let type_env = crate::analysis::types::TypeEnv::new();
3026 for stmt in stmts.iter() {
3027 body_str.push_str(&codegen_stmt(
3028 stmt, interner, 2, &empty_mutable, &mut ctx,
3029 &empty_lww, &empty_mv, &mut empty_synced, &empty_caps,
3030 async_functions, &empty_pipes, &empty_boxed, &empty_registry,
3031 &type_env,
3032 ));
3033 }
3034 format!("move |{}| {{\n{}{}}}", params_str.join(", "), body_str, " ")
3035 }
3036 }
3037 }
3038
3039 Expr::CallExpr { callee, args } => {
3040 let callee_str = recurse!(callee);
3041 let args_str: Vec<String> = args.iter().map(|a| recurse!(a)).collect();
3042 format!("({})({})", callee_str, args_str.join(", "))
3043 }
3044
3045 Expr::InterpolatedString(parts) => {
3046 codegen_interpolated_string(parts, ecx)
3047 }
3048
3049 Expr::Not { operand } => {
3050 let operand_str = recurse!(operand);
3052 if matches!(
3053 infer_logos_type(operand, interner, variable_types),
3054 crate::analysis::types::LogosType::Bool
3055 ) {
3056 format!("!({})", operand_str)
3057 } else {
3058 format!("(!logos_truthy(&({})))", operand_str)
3059 }
3060 }
3061 }
3062}
3063
3064pub(crate) fn codegen_interpolated_string(
3065 parts: &[crate::ast::stmt::StringPart],
3066 ecx: &ExprCtx,
3067) -> String {
3068 use crate::ast::stmt::StringPart;
3069 let interner = ecx.interner;
3070
3071 let mut fmt_str = String::new();
3072 let mut args = Vec::new();
3073
3074 for part in parts {
3075 match part {
3076 StringPart::Literal(sym) => {
3077 let text = interner.resolve(*sym);
3078 for ch in text.chars() {
3080 match ch {
3081 '{' => fmt_str.push_str("{{"),
3082 '}' => fmt_str.push_str("}}"),
3083 '\n' => fmt_str.push_str("\\n"),
3084 '\t' => fmt_str.push_str("\\t"),
3085 '\r' => fmt_str.push_str("\\r"),
3086 _ => fmt_str.push(ch),
3087 }
3088 }
3089 }
3090 StringPart::Expr { value, format_spec, debug } => {
3091 if *debug {
3092 let debug_prefix = expr_debug_prefix(value, interner);
3093 for ch in debug_prefix.chars() {
3094 match ch {
3095 '{' => fmt_str.push_str("{{"),
3096 '}' => fmt_str.push_str("}}"),
3097 _ => fmt_str.push(ch),
3098 }
3099 }
3100 fmt_str.push('=');
3101 }
3102 let needs_float_cast = if let Some(spec) = format_spec {
3103 let spec_str = interner.resolve(*spec);
3104 if spec_str == "$" {
3105 fmt_str.push('$');
3106 fmt_str.push_str("{:.2}");
3107 true
3108 } else if spec_str.starts_with('.') {
3109 fmt_str.push_str(&format!("{{:{}}}", spec_str));
3110 true
3111 } else {
3112 fmt_str.push_str(&format!("{{:{}}}", spec_str));
3113 false
3114 }
3115 } else {
3116 fmt_str.push_str("{}");
3117 false
3118 };
3119 let arg_str = codegen_expr_ctx(value, ecx);
3120 if needs_float_cast {
3121 args.push(format!("{} as f64", arg_str));
3122 } else {
3123 args.push(arg_str);
3124 }
3125 }
3126 }
3127 }
3128
3129 if args.is_empty() {
3130 let mut raw = String::new();
3133 for part in parts {
3134 if let StringPart::Literal(sym) = part {
3135 let text = interner.resolve(*sym);
3136 for ch in text.chars() {
3137 match ch {
3138 '\n' => raw.push_str("\\n"),
3139 '\t' => raw.push_str("\\t"),
3140 '\r' => raw.push_str("\\r"),
3141 '"' => raw.push_str("\\\""),
3142 '\\' => raw.push_str("\\\\"),
3143 _ => raw.push(ch),
3144 }
3145 }
3146 }
3147 }
3148 format!("String::from(\"{}\")", raw)
3149 } else {
3150 format!("format!(\"{}\"{})", fmt_str, args.iter().map(|a| format!(", {}", a)).collect::<String>())
3151 }
3152}
3153
3154pub(crate) fn codegen_literal(lit: &Literal, interner: &Interner) -> String {
3155 match lit {
3156 Literal::Number(n) => {
3157 if *n > i32::MAX as i64 || *n < i32::MIN as i64 {
3158 format!("{}_i64", n)
3159 } else {
3160 n.to_string()
3161 }
3162 }
3163 Literal::Float(f) if f.is_nan() => "f64::NAN".to_string(),
3166 Literal::Float(f) if f.is_infinite() && *f > 0.0 => "f64::INFINITY".to_string(),
3167 Literal::Float(f) if f.is_infinite() => "f64::NEG_INFINITY".to_string(),
3168 Literal::Float(f) => format!("{}f64", f),
3169 Literal::Text(sym) => {
3171 let raw = interner.resolve(*sym);
3172 let escaped: String = raw.chars().map(|c| match c {
3173 '\n' => "\\n".to_string(),
3174 '\r' => "\\r".to_string(),
3175 '\t' => "\\t".to_string(),
3176 '\\' => "\\\\".to_string(),
3177 '"' => "\\\"".to_string(),
3178 other => other.to_string(),
3179 }).collect();
3180 format!("String::from(\"{}\")", escaped)
3181 }
3182 Literal::Boolean(b) => b.to_string(),
3183 Literal::Nothing => "()".to_string(),
3184 Literal::Char(c) => {
3186 match c {
3188 '\n' => "'\\n'".to_string(),
3189 '\t' => "'\\t'".to_string(),
3190 '\r' => "'\\r'".to_string(),
3191 '\\' => "'\\\\'".to_string(),
3192 '\'' => "'\\''".to_string(),
3193 '\0' => "'\\0'".to_string(),
3194 c => format!("'{}'", c),
3195 }
3196 }
3197 Literal::Duration(nanos) => format!("std::time::Duration::from_nanos({}u64)", nanos),
3199 Literal::Date(days) => format!("LogosDate({})", days),
3201 Literal::Moment(nanos) => format!("LogosMoment({})", nanos),
3203 Literal::Span { months, days } => format!("LogosSpan::new({}, {})", months, days),
3205 Literal::Time(nanos) => format!("LogosTime({})", nanos),
3207 }
3208}
3209
3210pub fn codegen_assertion(expr: &LogicExpr, interner: &Interner) -> String {
3213 let mut registry = SymbolRegistry::new();
3214 let formatter = RustFormatter;
3215 let mut buf = String::new();
3216
3217 match expr.write_logic(&mut buf, &mut registry, interner, &formatter) {
3218 Ok(_) => buf,
3219 Err(_) => "/* error generating assertion */ false".to_string(),
3220 }
3221}
3222
3223pub fn codegen_term(term: &Term, interner: &Interner) -> String {
3224 match term {
3225 Term::Constant(sym) => interner.resolve(*sym).to_string(),
3226 Term::Variable(sym) => interner.resolve(*sym).to_string(),
3227 Term::Value { kind, .. } => match kind {
3228 NumberKind::Integer(n) => n.to_string(),
3229 NumberKind::Real(f) => f.to_string(),
3230 NumberKind::Symbolic(sym) => interner.resolve(*sym).to_string(),
3231 },
3232 Term::Function(name, args) => {
3233 let args_str: Vec<String> = args.iter()
3234 .map(|a| codegen_term(a, interner))
3235 .collect();
3236 format!("{}({})", interner.resolve(*name), args_str.join(", "))
3237 }
3238 Term::Possessed { possessor, possessed } => {
3239 let poss_str = codegen_term(possessor, interner);
3240 format!("{}.{}", poss_str, interner.resolve(*possessed))
3241 }
3242 Term::Group(members) => {
3243 let members_str: Vec<String> = members.iter()
3244 .map(|m| codegen_term(m, interner))
3245 .collect();
3246 format!("({})", members_str.join(", "))
3247 }
3248 _ => "/* unsupported Term */".to_string(),
3249 }
3250}
3251
3252#[cfg(test)]
3253mod tests {
3254 use super::*;
3255
3256 #[test]
3257 fn test_literal_number() {
3258 let interner = Interner::new();
3259 let synced_vars = HashSet::new();
3260 let expr = Expr::Literal(Literal::Number(42));
3261 assert_eq!(codegen_expr(&expr, &interner, &synced_vars), "42");
3262 }
3263
3264 #[test]
3265 fn test_literal_boolean() {
3266 let interner = Interner::new();
3267 let synced_vars = HashSet::new();
3268 assert_eq!(codegen_expr(&Expr::Literal(Literal::Boolean(true)), &interner, &synced_vars), "true");
3269 assert_eq!(codegen_expr(&Expr::Literal(Literal::Boolean(false)), &interner, &synced_vars), "false");
3270 }
3271
3272 #[test]
3273 fn test_literal_nothing() {
3274 let interner = Interner::new();
3275 let synced_vars = HashSet::new();
3276 assert_eq!(codegen_expr(&Expr::Literal(Literal::Nothing), &interner, &synced_vars), "()");
3277 }
3278}