Skip to main content

xpile_ruchy_codegen/
lib.rs

1//! Ruchy backend.
2//!
3//! Lowers meta-HIR to Ruchy source. v0.1.0 emits the same arithmetic
4//! subset as `xpile-rust-codegen` — the surface difference is
5//! `fun ... -> T { ... }` instead of Rust's `pub fn ... -> T { ... }`,
6//! and floor-div / modulo still go through Euclidean semantics
7//! (`div_euclid` / `rem_euclid`).
8//!
9//! Future scope (tracked by `Profile::RuchyOut`): reconstruct the
10//! pipeline operator `|>` and DataFrame-flavored sugar from meta-HIR
11//! patterns. See `docs/specifications/sub/bidirectional-ruchy.md`.
12
13use std::fmt::Write;
14use xpile_backend::{Artifact, Backend, BackendConfig, BackendError, QuorumStatus, Target};
15use xpile_meta_hir::{BinOp, Block, Expr, Function, Item, Module, Param, Stmt, Type, UnOp};
16
17#[derive(Debug, thiserror::Error)]
18pub enum RuchyCodegenError {
19    #[error("unsupported item: {0}")]
20    Unsupported(String),
21    #[error("formatting error: {0}")]
22    Format(#[from] std::fmt::Error),
23}
24
25pub fn emit_module(module: &Module) -> Result<String, RuchyCodegenError> {
26    let mut out = String::new();
27    writeln!(
28        out,
29        "// xpile-generated from {:?} module {}",
30        module.source_lang, module.name
31    )?;
32    writeln!(out)?;
33    for item in &module.items {
34        match item {
35            Item::Function(f) => emit_function(&mut out, f)?,
36        }
37    }
38    Ok(out)
39}
40
41fn emit_function(out: &mut String, f: &Function) -> Result<(), RuchyCodegenError> {
42    emit_contract_citations(out, f)?;
43    // Ruchy: `fun name(params) -> ret { body }`. No `pub`.
44    write!(out, "fun {}(", f.name)?;
45    for (i, p) in f.params.iter().enumerate() {
46        if i > 0 {
47            write!(out, ", ")?;
48        }
49        emit_param(out, p)?;
50    }
51    write!(out, ") -> ")?;
52    emit_type(out, f.return_type)?;
53    writeln!(out, " {{")?;
54    let mode = function_bigint_mode(f);
55    emit_block(out, &f.body, mode)?;
56    writeln!(out, "}}")?;
57    Ok(())
58}
59
60/// PMAT-012-FOLLOWUP / PMAT-025: a function is in BigInt mode if any
61/// param is BigInt, the return type is BigInt, OR any pre-bound Let
62/// is BigInt. In BigInt mode, the Ruchy backend emits the same shape
63/// as the Rust backend (since Ruchy compiles to Rust):
64/// `xpile_bigint::BigInt::from(<n>i64)` literals + plain infix
65/// arithmetic + `.clone()` on Ident references (BigInt isn't `Copy`).
66fn function_bigint_mode(f: &Function) -> bool {
67    if f.return_type == Type::BigInt {
68        return true;
69    }
70    if f.params.iter().any(|p| p.ty == Type::BigInt) {
71        return true;
72    }
73    fn stmt_has_bigint(s: &Stmt) -> bool {
74        match s {
75            Stmt::Let { ty, .. } => *ty == Type::BigInt,
76            Stmt::Assign { .. } | Stmt::Assert { .. } => false,
77            Stmt::While { body, .. } => body.iter().any(stmt_has_bigint),
78            // PMAT-039: see rust-codegen's twin arm — shell commands
79            // carry no BigInt operands.
80            Stmt::Cmd { .. } => false,
81            // PMAT-041: see rust-codegen's twin arm.
82            Stmt::Pipeline { .. } => false,
83            // PMAT-048: see rust-codegen's twin arm.
84            Stmt::ShellLoop { .. } => false,
85            // PMAT-051: see rust-codegen's twin arm.
86            Stmt::ShellAssign { .. } => false,
87        }
88    }
89    f.body.stmts.iter().any(stmt_has_bigint)
90}
91
92/// PMAT-011: same `// xpile-contract: <ID>` form as the Rust backend.
93/// Ruchy compiles to Rust, so it shares the comment-citation convention.
94fn emit_contract_citations(out: &mut String, f: &Function) -> Result<(), RuchyCodegenError> {
95    for id in f.applicable_contracts() {
96        writeln!(out, "// xpile-contract: {id}")?;
97    }
98    Ok(())
99}
100
101fn emit_block(out: &mut String, block: &Block, mode: bool) -> Result<(), RuchyCodegenError> {
102    for stmt in &block.stmts {
103        emit_stmt(out, stmt, mode)?;
104    }
105    write!(out, "    ")?;
106    emit_expr(out, &block.trailing_return, mode)?;
107    writeln!(out)?;
108    Ok(())
109}
110
111fn emit_stmt(out: &mut String, stmt: &Stmt, mode: bool) -> Result<(), RuchyCodegenError> {
112    emit_stmt_indented(out, stmt, "    ", mode)
113}
114
115fn emit_stmt_indented(
116    out: &mut String,
117    stmt: &Stmt,
118    indent: &str,
119    mode: bool,
120) -> Result<(), RuchyCodegenError> {
121    match stmt {
122        Stmt::Let {
123            name,
124            ty,
125            value,
126            mutable,
127        } => {
128            let kw = if *mutable { "let mut" } else { "let" };
129            write!(out, "{indent}{kw} {name}: ")?;
130            emit_type(out, *ty)?;
131            write!(out, " = ")?;
132            emit_expr(out, value, mode)?;
133            writeln!(out, ";")?;
134            Ok(())
135        }
136        Stmt::Assign { name, value } => {
137            write!(out, "{indent}{name} = ")?;
138            emit_expr(out, value, mode)?;
139            writeln!(out, ";")?;
140            Ok(())
141        }
142        Stmt::While { cond, body } => {
143            write!(out, "{indent}while ")?;
144            emit_expr(out, cond, mode)?;
145            writeln!(out, " {{")?;
146            let inner = format!("{indent}    ");
147            for s in body {
148                emit_stmt_indented(out, s, &inner, mode)?;
149            }
150            writeln!(out, "{indent}}}")?;
151            Ok(())
152        }
153        Stmt::Assert { cond } => {
154            write!(out, "{indent}assert!(")?;
155            emit_expr(out, cond, mode)?;
156            writeln!(out, ");")?;
157            Ok(())
158        }
159        // PMAT-039 / XPILE-BASHRS-MERGER-001 Layer B: see rust-codegen's
160        // matching arm. Ruchy compiles to Rust and inherits Rust's
161        // disposition — no Ruchy-level translation of `Stmt::Cmd`
162        // exists.
163        Stmt::Cmd { program, args } => Err(RuchyCodegenError::Unsupported(format!(
164            "Ruchy backend does not lower Stmt::Cmd (`{program}` with {} arg(s)) — \
165             contract C-BASHRS-POSIX-IDEMPOTENCE governs this construct; \
166             use `--target shell` to emit POSIX sh via bashrs-backend",
167            args.len()
168        ))),
169        // PMAT-041: same disposition as Cmd.
170        Stmt::Pipeline { stages } => Err(RuchyCodegenError::Unsupported(format!(
171            "Ruchy backend does not lower Stmt::Pipeline ({} stages) — \
172             contract C-BASHRS-POSIX-IDEMPOTENCE governs shell pipelines; \
173             use `--target shell`",
174            stages.len()
175        ))),
176        // PMAT-048: same disposition.
177        Stmt::ShellLoop { .. } => Err(RuchyCodegenError::Unsupported(
178            "Ruchy backend does not lower Stmt::ShellLoop — \
179             contract C-BASHRS-POSIX-IDEMPOTENCE governs shell loops; \
180             use `--target shell`"
181                .into(),
182        )),
183        // PMAT-051: same disposition.
184        Stmt::ShellAssign { name, .. } => Err(RuchyCodegenError::Unsupported(format!(
185            "Ruchy backend does not lower Stmt::ShellAssign (`{name}=…`) — \
186             contract C-BASHRS-POSIX-IDEMPOTENCE governs shell variable assignment; \
187             use `--target shell`"
188        ))),
189    }
190}
191
192fn emit_param(out: &mut String, p: &Param) -> Result<(), RuchyCodegenError> {
193    write!(out, "{}: ", p.name)?;
194    emit_type(out, p.ty)?;
195    Ok(())
196}
197
198fn emit_type(out: &mut String, t: Type) -> Result<(), RuchyCodegenError> {
199    out.push_str(match t {
200        Type::I64 => "i64",
201        Type::Bool => "bool",
202        // Ruchy compiles to Rust → same BigInt re-export. PMAT-012.
203        Type::BigInt => "xpile_bigint::BigInt",
204        // PMAT-046: same disposition as the Rust backend.
205        Type::ShellString | Type::ExitCode => {
206            return Err(RuchyCodegenError::Unsupported(format!(
207                "Ruchy backend does not lower {t:?} — \
208                 contract C-BASHRS-POSIX-IDEMPOTENCE governs the bashrs type domain; \
209                 use `--target shell`"
210            )));
211        }
212    });
213    Ok(())
214}
215
216fn emit_expr(out: &mut String, e: &Expr, mode: bool) -> Result<(), RuchyCodegenError> {
217    match e {
218        Expr::Ident(name) => {
219            // PMAT-025: in BigInt mode, append `.clone()` to every
220            // Ident reference. BigInt isn't `Copy` (it's
221            // heap-allocated), so a name referenced in cond +
222            // branches + recursive call would move-on-first-use.
223            // Mirrors the Rust backend's PMAT-013 emission.
224            if mode {
225                write!(out, "{}.clone()", name)?;
226            } else {
227                write!(out, "{}", name)?;
228            }
229        }
230        Expr::LitInt(v) => {
231            if mode {
232                write!(out, "xpile_bigint::BigInt::from({}i64)", v)?;
233            } else {
234                write!(out, "{}i64", v)?;
235            }
236        }
237        Expr::BinOp { op, lhs, rhs } => emit_binop(out, *op, lhs, rhs, mode)?,
238        Expr::IfExpr {
239            cond,
240            then_expr,
241            else_expr,
242        } => emit_if_expr(out, cond, then_expr, else_expr, mode)?,
243        Expr::Call { callee, args } => emit_call(out, callee, args, mode)?,
244        Expr::UnOp { op, operand } => emit_unop(out, *op, operand, mode)?,
245        // PMAT-042: see rust-codegen's matching arm.
246        Expr::LitStr(_) | Expr::QuotedString { .. } => {
247            return Err(RuchyCodegenError::Unsupported(
248                "Ruchy backend does not lower Expr::LitStr / Expr::QuotedString — \
249                 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell string literals; \
250                 use `--target shell`"
251                    .into(),
252            ));
253        }
254        // PMAT-045: see rust-codegen's matching arm.
255        Expr::ShellVar(name) => {
256            return Err(RuchyCodegenError::Unsupported(format!(
257                "Ruchy backend does not lower Expr::ShellVar (${name}) — \
258                 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell variable refs; \
259                 use `--target shell`"
260            )));
261        }
262        // PMAT-047: see rust-codegen.
263        Expr::CommandSubstitution(_) => {
264            return Err(RuchyCodegenError::Unsupported(
265                "Ruchy backend does not lower Expr::CommandSubstitution — \
266                 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell substitution; \
267                 use `--target shell`"
268                    .into(),
269            ));
270        }
271        // PMAT-055: see rust-codegen.
272        Expr::ShellSpecial(name) => {
273            return Err(RuchyCodegenError::Unsupported(format!(
274                "Ruchy backend does not lower Expr::ShellSpecial (${name}) — \
275                 contract C-BASHRS-POSIX-IDEMPOTENCE governs shell special params; \
276                 use `--target shell`"
277            )));
278        }
279    }
280    Ok(())
281}
282
283fn emit_unop(
284    out: &mut String,
285    op: UnOp,
286    operand: &Expr,
287    mode: bool,
288) -> Result<(), RuchyCodegenError> {
289    match op {
290        UnOp::Neg => {
291            if mode {
292                // BigInt::neg is total — no overflow.
293                write!(out, "(-")?;
294                emit_expr(out, operand, mode)?;
295                write!(out, ")")?;
296            } else {
297                // Python: `-x` on int never overflows mathematically.
298                // Rust i64::MIN.checked_neg() == None — use checked_neg
299                // + panic pointing at C-PY-INT-ARITH slow path.
300                write!(out, "(")?;
301                emit_expr(out, operand, mode)?;
302                write!(
303                    out,
304                    ").checked_neg().expect(\"xpile: i64 negation overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented\")"
305                )?;
306            }
307        }
308        UnOp::Not => {
309            write!(out, "(!")?;
310            emit_expr(out, operand, mode)?;
311            write!(out, ")")?;
312        }
313    }
314    Ok(())
315}
316
317fn emit_call(
318    out: &mut String,
319    callee: &str,
320    args: &[Expr],
321    mode: bool,
322) -> Result<(), RuchyCodegenError> {
323    write!(out, "{}(", callee)?;
324    for (i, a) in args.iter().enumerate() {
325        if i > 0 {
326            write!(out, ", ")?;
327        }
328        emit_expr(out, a, mode)?;
329    }
330    write!(out, ")")?;
331    Ok(())
332}
333
334/// Ruchy uses Rust-like `if cond { then } else { else_ }` as an expression.
335/// Flattens nested `else if` for readability (same pattern as the Rust backend).
336fn emit_if_expr(
337    out: &mut String,
338    cond: &Expr,
339    then_expr: &Expr,
340    else_expr: &Expr,
341    mode: bool,
342) -> Result<(), RuchyCodegenError> {
343    write!(out, "if ")?;
344    emit_expr(out, cond, mode)?;
345    write!(out, " {{ ")?;
346    emit_expr(out, then_expr, mode)?;
347    write!(out, " }} else ")?;
348    match else_expr {
349        Expr::IfExpr {
350            cond: c2,
351            then_expr: t2,
352            else_expr: e2,
353        } => emit_if_expr(out, c2, t2, e2, mode),
354        _ => {
355            write!(out, "{{ ")?;
356            emit_expr(out, else_expr, mode)?;
357            write!(out, " }}")?;
358            Ok(())
359        }
360    }
361}
362
363/// Arithmetic emits two shapes per the C-PY-INT-ARITH contract:
364///
365/// * i64 fast path: `.checked_*().expect("...")` with the slow-path
366///   panic message (no overflow → no panic).
367/// * BigInt slow path (mode=true): plain infix on BigInt operands
368///   (BigInt overloads `+ - * <= ...`); FloorDiv / Mod use
369///   `xpile_bigint::div_floor / mod_floor`; bitwise / shift / pow
370///   deferred (same scope as the Rust backend).
371///
372/// Mirrors the Rust backend's emission shape — Ruchy compiles to Rust
373/// so they share semantics. PMAT-025.
374fn emit_binop(
375    out: &mut String,
376    op: BinOp,
377    lhs: &Expr,
378    rhs: &Expr,
379    mode: bool,
380) -> Result<(), RuchyCodegenError> {
381    match op {
382        BinOp::Add if mode => emit_infix(out, lhs, " + ", rhs, mode),
383        BinOp::Sub if mode => emit_infix(out, lhs, " - ", rhs, mode),
384        BinOp::Mul if mode => emit_infix(out, lhs, " * ", rhs, mode),
385        BinOp::FloorDiv if mode => emit_bigint_floor_call(out, "div_floor", lhs, rhs, mode),
386        BinOp::Mod if mode => emit_bigint_floor_call(out, "mod_floor", lhs, rhs, mode),
387        // PMAT-026 / PMAT-013-FOLLOWUP — mirror of the Rust backend.
388        // See `xpile-rust-codegen/src/lib.rs` for the design rationale.
389        BinOp::BitAnd if mode => emit_infix(out, lhs, " & ", rhs, mode),
390        BinOp::BitOr if mode => emit_infix(out, lhs, " | ", rhs, mode),
391        BinOp::BitXor if mode => emit_infix(out, lhs, " ^ ", rhs, mode),
392        BinOp::Shl if mode => emit_bigint_floor_call(out, "shl", lhs, rhs, mode),
393        BinOp::Shr if mode => emit_bigint_floor_call(out, "shr", lhs, rhs, mode),
394        BinOp::Pow if mode => emit_bigint_floor_call(out, "pow", lhs, rhs, mode),
395        BinOp::Add => emit_checked(out, lhs, "checked_add", rhs, "addition", mode),
396        BinOp::Sub => emit_checked(out, lhs, "checked_sub", rhs, "subtraction", mode),
397        BinOp::Mul => emit_checked(out, lhs, "checked_mul", rhs, "multiplication", mode),
398        BinOp::FloorDiv => emit_checked(out, lhs, "checked_div_euclid", rhs, "floor-div", mode),
399        BinOp::Mod => emit_checked(out, lhs, "checked_rem_euclid", rhs, "modulo", mode),
400        BinOp::Eq => emit_infix(out, lhs, " == ", rhs, mode),
401        BinOp::NotEq => emit_infix(out, lhs, " != ", rhs, mode),
402        BinOp::Lt => emit_infix(out, lhs, " < ", rhs, mode),
403        BinOp::LtEq => emit_infix(out, lhs, " <= ", rhs, mode),
404        BinOp::Gt => emit_infix(out, lhs, " > ", rhs, mode),
405        BinOp::GtEq => emit_infix(out, lhs, " >= ", rhs, mode),
406        BinOp::And => emit_infix(out, lhs, " && ", rhs, mode),
407        BinOp::Or => emit_infix(out, lhs, " || ", rhs, mode),
408        BinOp::BitAnd => emit_infix(out, lhs, " & ", rhs, mode),
409        BinOp::BitOr => emit_infix(out, lhs, " | ", rhs, mode),
410        BinOp::BitXor => emit_infix(out, lhs, " ^ ", rhs, mode),
411        BinOp::Shl => emit_checked_shift(out, lhs, "checked_shl", rhs, "left-shift", mode),
412        BinOp::Shr => emit_checked_shift(out, lhs, "checked_shr", rhs, "right-shift", mode),
413        BinOp::Pow => emit_checked_pow(out, lhs, rhs, mode),
414    }
415}
416
417/// BigInt-mode floor-div / mod via the helpers in xpile-bigint
418/// (num-bigint requires `Integer` trait + reference operands).
419/// PMAT-025; mirrors Rust backend.
420fn emit_bigint_floor_call(
421    out: &mut String,
422    method: &str,
423    lhs: &Expr,
424    rhs: &Expr,
425    mode: bool,
426) -> Result<(), RuchyCodegenError> {
427    write!(out, "xpile_bigint::{method}(&")?;
428    emit_expr(out, lhs, mode)?;
429    write!(out, ", &")?;
430    emit_expr(out, rhs, mode)?;
431    write!(out, ")")?;
432    Ok(())
433}
434
435fn emit_checked_pow(
436    out: &mut String,
437    lhs: &Expr,
438    rhs: &Expr,
439    mode: bool,
440) -> Result<(), RuchyCodegenError> {
441    write!(out, "(")?;
442    emit_expr(out, lhs, mode)?;
443    write!(out, ").checked_pow(u32::try_from(")?;
444    emit_expr(out, rhs, mode)?;
445    write!(
446        out,
447        ").expect(\"xpile: exponent out of range for u32 — Python returns Float for negative exponents which v0.1.0 cannot represent (contract C-PY-INT-ARITH)\")).expect(\"xpile: i64 power overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented\")"
448    )?;
449    Ok(())
450}
451
452fn emit_checked_shift(
453    out: &mut String,
454    lhs: &Expr,
455    method: &str,
456    rhs: &Expr,
457    op_name: &str,
458    mode: bool,
459) -> Result<(), RuchyCodegenError> {
460    write!(out, "(")?;
461    emit_expr(out, lhs, mode)?;
462    write!(out, ").{method}(u32::try_from(")?;
463    emit_expr(out, rhs, mode)?;
464    write!(
465        out,
466        ").expect(\"xpile: shift amount out of range for u32 (contract C-PY-INT-ARITH)\")).expect(\"xpile: i64 {op_name} overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented\")"
467    )?;
468    Ok(())
469}
470
471fn emit_checked(
472    out: &mut String,
473    lhs: &Expr,
474    method: &str,
475    rhs: &Expr,
476    op_name: &str,
477    mode: bool,
478) -> Result<(), RuchyCodegenError> {
479    write!(out, "(")?;
480    emit_expr(out, lhs, mode)?;
481    write!(out, ").{method}(")?;
482    emit_expr(out, rhs, mode)?;
483    write!(
484        out,
485        ").expect(\"xpile: i64 {op_name} overflow; bigint promotion (contract C-PY-INT-ARITH slow path) not yet implemented\")"
486    )?;
487    Ok(())
488}
489
490fn emit_infix(
491    out: &mut String,
492    lhs: &Expr,
493    op: &str,
494    rhs: &Expr,
495    mode: bool,
496) -> Result<(), RuchyCodegenError> {
497    write!(out, "(")?;
498    emit_expr(out, lhs, mode)?;
499    out.push_str(op);
500    emit_expr(out, rhs, mode)?;
501    write!(out, ")")?;
502    Ok(())
503}
504
505pub struct RuchyBackend;
506
507impl Backend for RuchyBackend {
508    fn name(&self) -> &'static str {
509        "ruchy"
510    }
511
512    fn targets(&self) -> &[Target] {
513        &[Target::Ruchy]
514    }
515
516    fn lower(&self, module: &Module, _config: &BackendConfig) -> Result<Artifact, BackendError> {
517        let primary = emit_module(module).map_err(|e| BackendError::Lower(e.to_string()))?;
518        Ok(Artifact {
519            primary,
520            sidecars: Vec::new(),
521            citations: Vec::new(),
522            quorum_status: QuorumStatus::Single {
523                emitter: "xpile-ruchy-codegen".to_string(),
524            },
525        })
526    }
527}
528
529#[cfg(test)]
530mod tests {
531    use super::*;
532    use xpile_meta_hir::{Module, SourceLang};
533
534    fn module_with(name: &str, items: Vec<Item>) -> Module {
535        Module {
536            name: name.into(),
537            source_lang: SourceLang::Python,
538            items,
539            ffi_boundaries: Vec::new(),
540        }
541    }
542
543    fn add_fn() -> Function {
544        Function {
545            name: "add".into(),
546            params: vec![
547                Param {
548                    name: "a".into(),
549                    ty: Type::I64,
550                },
551                Param {
552                    name: "b".into(),
553                    ty: Type::I64,
554                },
555            ],
556            return_type: Type::I64,
557            body: Block {
558                stmts: vec![],
559                trailing_return: Expr::BinOp {
560                    op: BinOp::Add,
561                    lhs: Box::new(Expr::Ident("a".into())),
562                    rhs: Box::new(Expr::Ident("b".into())),
563                },
564            },
565        }
566    }
567
568    #[test]
569    fn emits_fun_keyword_not_pub_fn() {
570        let m = module_with("fixture", vec![Item::Function(add_fn())]);
571        let ruchy = emit_module(&m).expect("emit ok");
572        assert!(
573            ruchy.contains("fun add("),
574            "Ruchy uses `fun`, not `fn` or `pub fn`: got\n{}",
575            ruchy
576        );
577        assert!(
578            !ruchy.contains("pub fn"),
579            "Ruchy emission must not produce `pub fn` (that's Rust)"
580        );
581        // Post PMAT-002: addition lowers to checked_add (Ruchy compiles
582        // to Rust, so it shares Rust's overflow semantics + contract
583        // C-PY-INT-ARITH).
584        assert!(
585            ruchy.contains("checked_add"),
586            "expected checked_add: {ruchy}"
587        );
588        assert!(ruchy.contains("C-PY-INT-ARITH"));
589    }
590
591    #[test]
592    fn ruchy_floordiv_also_uses_div_euclid() {
593        let f = Function {
594            name: "fdiv".into(),
595            params: vec![
596                Param {
597                    name: "a".into(),
598                    ty: Type::I64,
599                },
600                Param {
601                    name: "b".into(),
602                    ty: Type::I64,
603                },
604            ],
605            return_type: Type::I64,
606            body: Block {
607                stmts: vec![],
608                trailing_return: Expr::BinOp {
609                    op: BinOp::FloorDiv,
610                    lhs: Box::new(Expr::Ident("a".into())),
611                    rhs: Box::new(Expr::Ident("b".into())),
612                },
613            },
614        };
615        let m = module_with("fixture", vec![Item::Function(f)]);
616        let ruchy = emit_module(&m).expect("emit ok");
617        assert!(ruchy.contains("div_euclid"));
618        assert!(!ruchy.contains(" / "));
619    }
620}