Skip to main content

libperl_macrogen/
syn_codegen.rs

1//! syn::Expr ベースの Rust コード生成モジュール
2//!
3//! C AST (crate::ast::Expr) から syn::Expr を構築し、
4//! 優先順位に基づく括弧挿入パスを経て正確な Rust コードを生成する。
5
6use proc_macro2::Span;
7use quote::ToTokens;
8
9use crate::ast::BinOp;
10
11// ============================================================================
12// Rust 演算子の優先順位
13// ============================================================================
14
15/// Rust の式の優先順位(数値が大きいほど高い)
16/// 参考: https://doc.rust-lang.org/reference/expressions.html#expression-precedence
17fn expr_precedence(expr: &syn::Expr) -> u8 {
18    match expr {
19        syn::Expr::Lit(_) | syn::Expr::Path(_) | syn::Expr::Paren(_) => 100,
20        syn::Expr::MethodCall(_) | syn::Expr::Field(_) | syn::Expr::Index(_) |
21        syn::Expr::Call(_) => 90,
22        syn::Expr::Try(_) => 85,
23        syn::Expr::Unary(_) => 80,                    // -, !, *, &
24        syn::Expr::Cast(_) => 75,                      // as
25        syn::Expr::Binary(b) => syn_binop_precedence(&b.op),
26        syn::Expr::Range(_) => 15,
27        syn::Expr::Assign(_) => 10,
28        syn::Expr::Return(_) | syn::Expr::Break(_) | syn::Expr::Closure(_) => 5,
29        // If/Match は内部が独立だが、演算子の子として使う場合は括弧が必要:
30        //   (if cond { A } else { B }) as u8  — 括弧必須
31        //   if cond { A } else { B } as u8    — else ブランチ内の cast と誤解析
32        syn::Expr::If(_) | syn::Expr::Match(_) => 1,
33        // Block/Unsafe 等の { } で囲まれた式は自己完結するため最高優先
34        syn::Expr::Block(_) | syn::Expr::Unsafe(_) |
35        syn::Expr::Loop(_) | syn::Expr::While(_) | syn::Expr::ForLoop(_) => 100,
36        _ => 50,  // デフォルト
37    }
38}
39
40/// syn の二項演算子の優先順位
41fn syn_binop_precedence(op: &syn::BinOp) -> u8 {
42    match op {
43        syn::BinOp::Mul(_) | syn::BinOp::Div(_) | syn::BinOp::Rem(_) => 70,
44        syn::BinOp::Add(_) | syn::BinOp::Sub(_) => 65,
45        syn::BinOp::Shl(_) | syn::BinOp::Shr(_) => 60,
46        syn::BinOp::BitAnd(_) => 55,
47        syn::BinOp::BitXor(_) => 50,
48        syn::BinOp::BitOr(_) => 45,
49        syn::BinOp::Lt(_) | syn::BinOp::Gt(_) | syn::BinOp::Le(_) | syn::BinOp::Ge(_) |
50        syn::BinOp::Eq(_) | syn::BinOp::Ne(_) => 40,
51        syn::BinOp::And(_) => 35,
52        syn::BinOp::Or(_) => 30,
53        _ => 50,
54    }
55}
56
57// ============================================================================
58// 括弧挿入パス
59// ============================================================================
60
61/// syn::Expr 木に必要な括弧 (Expr::Paren) を挿入する。
62///
63/// syn::Expr の ToTokens は括弧を自動挿入しないため、
64/// 親子の優先順位を比較して必要な箇所に Expr::Paren を挿入する。
65pub fn parenthesize(expr: syn::Expr) -> syn::Expr {
66    match expr {
67        syn::Expr::Binary(mut binary) => {
68            let parent_prec = syn_binop_precedence(&binary.op);
69            *binary.left = parenthesize_child(*binary.left, parent_prec, true);
70            *binary.right = parenthesize_child(*binary.right, parent_prec, false);
71            syn::Expr::Binary(binary)
72        }
73        syn::Expr::Cast(mut cast) => {
74            // as (prec 75) は全ての二項演算子 (prec ≤ 70) より高い
75            // → 子が Binary/If/Block なら括弧必要
76            let child = parenthesize(*cast.expr);
77            let child_prec = expr_precedence(&child);
78            *cast.expr = if child_prec < 75 {
79                wrap_paren(child)
80            } else {
81                child
82            };
83            syn::Expr::Cast(cast)
84        }
85        syn::Expr::Unary(mut unary) => {
86            // 単項 (prec 80) は as (prec 75) より高い
87            // → 子が Cast/Binary/If なら括弧必要
88            let child = parenthesize(*unary.expr);
89            let child_prec = expr_precedence(&child);
90            *unary.expr = if child_prec < 80 {
91                wrap_paren(child)
92            } else {
93                child
94            };
95            syn::Expr::Unary(unary)
96        }
97        syn::Expr::Field(mut field) => {
98            // フィールドアクセス (prec 90) → 子が Cast/Unary/Binary なら括弧必要
99            let child = parenthesize(*field.base);
100            let child_prec = expr_precedence(&child);
101            *field.base = if child_prec < 90 {
102                wrap_paren(child)
103            } else {
104                child
105            };
106            syn::Expr::Field(field)
107        }
108        syn::Expr::MethodCall(mut mc) => {
109            let child = parenthesize(*mc.receiver);
110            let child_prec = expr_precedence(&child);
111            *mc.receiver = if child_prec < 90 {
112                wrap_paren(child)
113            } else {
114                child
115            };
116            syn::Expr::MethodCall(mc)
117        }
118        syn::Expr::If(mut if_expr) => {
119            *if_expr.cond = parenthesize(*if_expr.cond);
120            // then/else ブロック内は再帰的に処理
121            parenthesize_block(&mut if_expr.then_branch);
122            if let Some((_, ref mut else_branch)) = if_expr.else_branch {
123                *else_branch = Box::new(parenthesize(*else_branch.clone()));
124            }
125            syn::Expr::If(if_expr)
126        }
127        syn::Expr::Paren(mut paren) => {
128            *paren.expr = parenthesize(*paren.expr);
129            syn::Expr::Paren(paren)
130        }
131        syn::Expr::Assign(mut assign) => {
132            *assign.left = parenthesize(*assign.left);
133            *assign.right = parenthesize(*assign.right);
134            syn::Expr::Assign(assign)
135        }
136        syn::Expr::Call(mut call) => {
137            *call.func = parenthesize(*call.func);
138            for arg in call.args.iter_mut() {
139                *arg = parenthesize(arg.clone());
140            }
141            syn::Expr::Call(call)
142        }
143        syn::Expr::Return(mut ret) => {
144            if let Some(ref mut expr) = ret.expr {
145                *expr = Box::new(parenthesize(*expr.clone()));
146            }
147            syn::Expr::Return(ret)
148        }
149        syn::Expr::Block(mut b) => {
150            parenthesize_block(&mut b.block);
151            syn::Expr::Block(b)
152        }
153        syn::Expr::Reference(mut r) => {
154            r.expr = Box::new(parenthesize(*r.expr));
155            syn::Expr::Reference(r)
156        }
157        syn::Expr::Index(mut i) => {
158            i.expr = Box::new(parenthesize(*i.expr));
159            i.index = Box::new(parenthesize(*i.index));
160            syn::Expr::Index(i)
161        }
162        // その他はそのまま返す
163        other => other,
164    }
165}
166
167/// 子式を親の優先順位に基づいて括弧で囲むか判定
168fn parenthesize_child(child: syn::Expr, parent_prec: u8, is_left: bool) -> syn::Expr {
169    let child = parenthesize(child);
170    let child_prec = expr_precedence(&child);
171    // 子の優先順位が親より低い → 括弧必要
172    // 同じ優先順位で右辺 → 括弧必要(左結合のため)
173    let mut needs_parens = child_prec < parent_prec
174        || (child_prec == parent_prec && !is_left);
175    // Block/Unsafe/Loop/If/Match 等の `{}` で始まる式は、
176    // Binary や Cast の左辺に置くと Rust パーサが "expression statement"
177    // として解釈してしまう(例: `{ x } != 0` は構文エラー)。
178    // 子の優先順位に関わらず括弧で囲む。
179    if is_left && starts_with_block(&child) {
180        needs_parens = true;
181    }
182    if needs_parens {
183        wrap_paren(child)
184    } else {
185        child
186    }
187}
188
189/// 式が `{` で始まるか(block / if / match / unsafe / loop / while / for)
190fn starts_with_block(expr: &syn::Expr) -> bool {
191    matches!(expr,
192        syn::Expr::Block(_) | syn::Expr::Unsafe(_) | syn::Expr::If(_) |
193        syn::Expr::Match(_) | syn::Expr::Loop(_) | syn::Expr::While(_) |
194        syn::Expr::ForLoop(_)
195    )
196}
197
198/// Expr::Paren で囲む
199fn wrap_paren(expr: syn::Expr) -> syn::Expr {
200    syn::Expr::Paren(syn::ExprParen {
201        attrs: vec![],
202        paren_token: syn::token::Paren::default(),
203        expr: Box::new(expr),
204    })
205}
206
207/// ブロック内の文を再帰的に括弧処理
208fn parenthesize_block(block: &mut syn::Block) {
209    for stmt in block.stmts.iter_mut() {
210        match stmt {
211            syn::Stmt::Expr(expr, _) => {
212                *expr = parenthesize(expr.clone());
213            }
214            syn::Stmt::Local(local) => {
215                if let Some(ref mut init) = local.init {
216                    init.expr = Box::new(parenthesize(*init.expr.clone()));
217                }
218            }
219            _ => {}
220        }
221    }
222}
223
224// ============================================================================
225// C AST → syn::Expr 変換ヘルパー
226// ============================================================================
227
228/// C の BinOp を syn::BinOp に変換
229pub fn to_syn_binop(op: BinOp) -> syn::BinOp {
230    match op {
231        BinOp::Add => syn::BinOp::Add(Default::default()),
232        BinOp::Sub => syn::BinOp::Sub(Default::default()),
233        BinOp::Mul => syn::BinOp::Mul(Default::default()),
234        BinOp::Div => syn::BinOp::Div(Default::default()),
235        BinOp::Mod => syn::BinOp::Rem(Default::default()),
236        BinOp::BitAnd => syn::BinOp::BitAnd(Default::default()),
237        BinOp::BitOr => syn::BinOp::BitOr(Default::default()),
238        BinOp::BitXor => syn::BinOp::BitXor(Default::default()),
239        BinOp::Shl => syn::BinOp::Shl(Default::default()),
240        BinOp::Shr => syn::BinOp::Shr(Default::default()),
241        BinOp::Eq => syn::BinOp::Eq(Default::default()),
242        BinOp::Ne => syn::BinOp::Ne(Default::default()),
243        BinOp::Lt => syn::BinOp::Lt(Default::default()),
244        BinOp::Gt => syn::BinOp::Gt(Default::default()),
245        BinOp::Le => syn::BinOp::Le(Default::default()),
246        BinOp::Ge => syn::BinOp::Ge(Default::default()),
247        BinOp::LogAnd => syn::BinOp::And(Default::default()),
248        BinOp::LogOr => syn::BinOp::Or(Default::default()),
249    }
250}
251
252/// syn::Expr を文字列に変換
253pub fn expr_to_string(expr: &syn::Expr) -> String {
254    let parenthesized = parenthesize(expr.clone());
255    parenthesized.to_token_stream().to_string()
256}
257
258/// syn::Ident を作成するヘルパー
259///
260/// 通常の識別子を syn::Ident に変換する。
261/// `r#` プレフィックス付きや Rust キーワードも正しく処理する。
262/// `name!()` のようなマクロ呼び出し形式や不正な識別子は
263/// `__invalid_ident__` にフォールバックする。
264pub fn ident(name: &str) -> syn::Ident {
265    // 既に r# プレフィックスが付いている場合は除去して raw ident として作成
266    if let Some(raw_name) = name.strip_prefix("r#") {
267        return syn::Ident::new_raw(raw_name, Span::call_site());
268    }
269    // 有効な識別子かチェック(マクロ呼び出し形式等を拒否)
270    if name.is_empty() || !name.chars().all(|c| c.is_alphanumeric() || c == '_') ||
271       name.starts_with(|c: char| c.is_ascii_digit()) {
272        return syn::Ident::new("__invalid_ident__", Span::call_site());
273    }
274    // Rust のキーワードは r# プレフィックスが必要
275    if is_rust_keyword(name) {
276        syn::Ident::new_raw(name, Span::call_site())
277    } else {
278        syn::Ident::new(name, Span::call_site())
279    }
280}
281
282fn is_rust_keyword(name: &str) -> bool {
283    matches!(name,
284        "as" | "break" | "const" | "continue" | "crate" | "else" | "enum" |
285        "extern" | "false" | "fn" | "for" | "if" | "impl" | "in" | "let" |
286        "loop" | "match" | "mod" | "move" | "mut" | "pub" | "ref" | "return" |
287        "self" | "Self" | "static" | "struct" | "super" | "trait" | "true" |
288        "type" | "unsafe" | "use" | "where" | "while" | "async" | "await" |
289        "dyn" | "abstract" | "become" | "box" | "do" | "final" | "macro" |
290        "override" | "priv" | "typeof" | "unsized" | "virtual" | "yield" | "gen" | "try"
291    )
292}
293
294// ============================================================================
295// 括弧正規化 (Phase 4): 文字列 → parse → strip → parenthesize → format
296// ============================================================================
297
298/// 式文字列の括弧を正規化する。
299///
300/// 1. syn::parse_str でパース
301/// 2. すべての syn::Expr::Paren を除去
302/// 3. parenthesize() で必要な括弧のみ再挿入
303/// 4. prettyplease で整形
304///
305/// 結果が多行になる場合やパース失敗時は、
306/// 単純な外側括弧除去(strip_outer_parens 相当)にフォールバックする。
307pub fn normalize_parens(s: &str) -> String {
308    // syn ベースの正規化を試行
309    // - prettyplease で整形された単行結果を優先
310    // - 多行の場合は parenthesize 後の token-stream (単行) を使う
311    // - パース失敗時は外側括弧のみ除去にフォールバック
312    if let Some(parsed) = syn::parse_str::<syn::Expr>(s).ok() {
313        let stripped = strip_all_parens(parsed);
314        let paren_added = parenthesize(stripped);
315        let pretty = pretty_expr(&paren_added);
316        if !pretty.is_empty() && !pretty.contains('\n') {
317            return pretty;
318        }
319        // 多行 → token-stream の単行化で代用
320        let toks = quote::quote! { #paren_added }.to_string();
321        if !toks.is_empty() {
322            return toks;
323        }
324    }
325    fallback_strip_outer_parens(s)
326}
327
328/// strip_outer_parens と同等のフォールバックロジック
329fn fallback_strip_outer_parens(s: &str) -> String {
330    let s = s.trim();
331    if s.len() < 2 || !s.starts_with('(') || !s.ends_with(')') {
332        return s.to_string();
333    }
334    let inner = &s[1..s.len() - 1];
335    // ブロック式 ({...}) は strip しない
336    if inner.trim_start().starts_with('{') {
337        return s.to_string();
338    }
339    let mut depth = 0i32;
340    for ch in inner.chars() {
341        match ch {
342            '(' | '{' | '[' => depth += 1,
343            ')' | '}' | ']' => {
344                depth -= 1;
345                if depth < 0 {
346                    return s.to_string();
347                }
348            }
349            _ => {}
350        }
351    }
352    if depth == 0 { inner.to_string() } else { s.to_string() }
353}
354
355/// syn::Expr ツリーからすべての Paren ラッパーノードを除去する。
356///
357/// ツリー構造(Binary, Unary, Cast 等)は保持される。
358/// 除去後に parenthesize() を適用すると、必要な括弧のみが再挿入される。
359pub fn strip_all_parens(expr: syn::Expr) -> syn::Expr {
360    match expr {
361        syn::Expr::Paren(p) => strip_all_parens(*p.expr),
362        syn::Expr::Binary(mut b) => {
363            *b.left = strip_all_parens(*b.left);
364            *b.right = strip_all_parens(*b.right);
365            syn::Expr::Binary(b)
366        }
367        syn::Expr::Unary(mut u) => {
368            *u.expr = strip_all_parens(*u.expr);
369            syn::Expr::Unary(u)
370        }
371        syn::Expr::Cast(mut c) => {
372            *c.expr = strip_all_parens(*c.expr);
373            syn::Expr::Cast(c)
374        }
375        syn::Expr::Field(mut f) => {
376            *f.base = strip_all_parens(*f.base);
377            syn::Expr::Field(f)
378        }
379        syn::Expr::MethodCall(mut m) => {
380            *m.receiver = strip_all_parens(*m.receiver);
381            for arg in m.args.iter_mut() {
382                *arg = strip_all_parens(arg.clone());
383            }
384            syn::Expr::MethodCall(m)
385        }
386        syn::Expr::Call(mut c) => {
387            *c.func = strip_all_parens(*c.func);
388            for arg in c.args.iter_mut() {
389                *arg = strip_all_parens(arg.clone());
390            }
391            syn::Expr::Call(c)
392        }
393        syn::Expr::If(mut i) => {
394            *i.cond = strip_all_parens(*i.cond);
395            strip_parens_in_block(&mut i.then_branch);
396            if let Some((_, ref mut else_branch)) = i.else_branch {
397                *else_branch = Box::new(strip_all_parens(*else_branch.clone()));
398            }
399            syn::Expr::If(i)
400        }
401        syn::Expr::Index(mut i) => {
402            *i.expr = strip_all_parens(*i.expr);
403            *i.index = strip_all_parens(*i.index);
404            syn::Expr::Index(i)
405        }
406        syn::Expr::Assign(mut a) => {
407            *a.left = strip_all_parens(*a.left);
408            *a.right = strip_all_parens(*a.right);
409            syn::Expr::Assign(a)
410        }
411        syn::Expr::Return(mut r) => {
412            if let Some(ref mut e) = r.expr {
413                *e = Box::new(strip_all_parens(*e.clone()));
414            }
415            syn::Expr::Return(r)
416        }
417        syn::Expr::Block(mut b) => {
418            strip_parens_in_block(&mut b.block);
419            syn::Expr::Block(b)
420        }
421        syn::Expr::Reference(mut r) => {
422            *r.expr = strip_all_parens(*r.expr);
423            syn::Expr::Reference(r)
424        }
425        syn::Expr::Unsafe(mut u) => {
426            strip_parens_in_block(&mut u.block);
427            syn::Expr::Unsafe(u)
428        }
429        other => other,
430    }
431}
432
433fn strip_parens_in_block(block: &mut syn::Block) {
434    for stmt in block.stmts.iter_mut() {
435        match stmt {
436            syn::Stmt::Expr(e, _) => *e = strip_all_parens(e.clone()),
437            syn::Stmt::Local(l) => {
438                if let Some(ref mut init) = l.init {
439                    *init.expr = strip_all_parens(*init.expr.clone());
440                }
441            }
442            _ => {}
443        }
444    }
445}
446
447/// syn::Expr を prettyplease で整形して文字列化
448fn pretty_expr(expr: &syn::Expr) -> String {
449    // prettyplease は syn::File 単位で動作するため、
450    // ダミー関数でラップして整形し、本体を抽出する
451    let tokens = quote::quote! {
452        fn __() -> __T {
453            #expr
454        }
455    };
456    let file: syn::File = match syn::parse2(tokens) {
457        Ok(f) => f,
458        Err(_) => {
459            // パース失敗時は ToTokens でフォールバック
460            return expr.to_token_stream().to_string();
461        }
462    };
463    let formatted = prettyplease::unparse(&file);
464    extract_fn_body(&formatted)
465}
466
467/// prettyplease 出力から関数本体の式を抽出
468fn extract_fn_body(formatted: &str) -> String {
469    let lines: Vec<&str> = formatted.lines().collect();
470    if lines.len() < 3 {
471        return formatted.to_string();
472    }
473    // "fn __() -> __T {" と "}" を除去し、4スペースインデントを除去
474    let body_lines: Vec<&str> = lines[1..lines.len() - 1]
475        .iter()
476        .map(|l| l.strip_prefix("    ").unwrap_or(l))
477        .collect();
478    body_lines.join("\n")
479}
480
481// ============================================================================
482// AST 変換パス (Phase 3)
483// ============================================================================
484
485/// syn::Expr が bool を返すかどうかを判定
486pub fn is_bool_syn_expr(expr: &syn::Expr) -> bool {
487    match expr {
488        syn::Expr::Binary(b) => matches!(b.op,
489            syn::BinOp::Eq(_) | syn::BinOp::Ne(_) |
490            syn::BinOp::Lt(_) | syn::BinOp::Gt(_) |
491            syn::BinOp::Le(_) | syn::BinOp::Ge(_) |
492            syn::BinOp::And(_) | syn::BinOp::Or(_)
493        ),
494        syn::Expr::Unary(u) => matches!(u.op, syn::UnOp::Not(_)) && is_bool_syn_expr(&u.expr),
495        syn::Expr::Lit(lit) => matches!(lit.lit, syn::Lit::Bool(_)),
496        syn::Expr::Paren(p) => is_bool_syn_expr(&p.expr),
497        syn::Expr::MethodCall(mc) => mc.method == "is_null",
498        _ => false,
499    }
500}
501
502/// syn::Expr がポインタっぽいかを文字列ヒントで判定
503/// (syn::Expr には型情報がないため、メソッド名やキャストの型名で推定)
504pub fn looks_like_pointer(expr: &syn::Expr) -> bool {
505    match expr {
506        syn::Expr::Cast(cast) => {
507            let ty_str = cast.ty.to_token_stream().to_string();
508            ty_str.contains("* mut") || ty_str.contains("* const")
509        }
510        syn::Expr::MethodCall(mc) => {
511            let method = mc.method.to_string();
512            matches!(method.as_str(),
513                "offset" | "wrapping_add" | "wrapping_sub" | "as_ptr" | "as_mut_ptr")
514        }
515        syn::Expr::Call(call) => {
516            let func_str = call.func.to_token_stream().to_string();
517            func_str.contains("null_mut") || func_str.contains("null")
518        }
519        syn::Expr::Paren(p) => looks_like_pointer(&p.expr),
520        _ => false,
521    }
522}
523
524/// 式を bool に変換する
525///
526/// - 既に bool 式 → そのまま
527/// - ポインタっぽい → `!expr.is_null()`
528/// - その他 → `expr != 0`
529pub fn wrap_as_bool(expr: syn::Expr) -> syn::Expr {
530    if is_bool_syn_expr(&expr) {
531        return expr;
532    }
533    if looks_like_pointer(&expr) {
534        // !expr.is_null()
535        let is_null_call = syn::Expr::MethodCall(syn::ExprMethodCall {
536            attrs: vec![],
537            receiver: Box::new(expr),
538            dot_token: Default::default(),
539            method: ident("is_null"),
540            turbofish: None,
541            paren_token: Default::default(),
542            args: syn::punctuated::Punctuated::new(),
543        });
544        return syn::Expr::Unary(syn::ExprUnary {
545            attrs: vec![],
546            op: syn::UnOp::Not(Default::default()),
547            expr: Box::new(is_null_call),
548        });
549    }
550    // expr != 0
551    syn::Expr::Binary(syn::ExprBinary {
552        attrs: vec![],
553        left: Box::new(expr),
554        op: syn::BinOp::Ne(Default::default()),
555        right: Box::new(int_lit(0)),
556    })
557}
558
559/// 整数リテラルを作成
560pub fn int_lit(n: i64) -> syn::Expr {
561    let lit = syn::LitInt::new(&n.to_string(), Span::call_site());
562    syn::Expr::Lit(syn::ExprLit {
563        attrs: vec![],
564        lit: syn::Lit::Int(lit),
565    })
566}
567
568/// 型名文字列から `as T` キャストを挿入(parse_type + insert_cast の統合版)
569///
570/// `syn::parse_str(&format!("{} as {}", ...))` の代替。パース失敗時もフォールバック型で
571/// キャストノードを確実に構築するため、キャストが消えるリスクがない。
572pub fn cast_syn_expr(expr: syn::Expr, ty_str: &str) -> syn::Expr {
573    insert_cast(expr, parse_type(ty_str))
574}
575
576/// `as T` キャストを挿入
577///
578/// 括弧は `parenthesize()` パスで自動挿入されるため、ここでは不要。
579pub fn insert_cast(expr: syn::Expr, ty: syn::Type) -> syn::Expr {
580    syn::Expr::Cast(syn::ExprCast {
581        attrs: vec![],
582        expr: Box::new(expr),
583        as_token: Default::default(),
584        ty: Box::new(ty),
585    })
586}
587
588/// 型名文字列から syn::Type をパース
589pub fn parse_type(ty_str: &str) -> syn::Type {
590    syn::parse_str(ty_str).unwrap_or_else(|_| {
591        // パース失敗時はフォールバック
592        syn::parse_str("c_int").unwrap()
593    })
594}
595
596/// null ポインタ式を型に合わせて生成
597///
598/// - const ポインタ → `std::ptr::null()`
599/// - mut ポインタ → `std::ptr::null_mut()`
600/// - 非ポインタ → `0`
601pub fn null_for_type(ty_str: &str) -> syn::Expr {
602    if ty_str.contains("*const") {
603        syn::parse_str("std::ptr::null()").unwrap()
604    } else if ty_str.contains("*mut") || ty_str.contains("*") {
605        syn::parse_str("std::ptr::null_mut()").unwrap()
606    } else {
607        int_lit(0)
608    }
609}
610
611/// `.as_ptr()` メソッド呼び出しを付加
612pub fn as_ptr(expr: syn::Expr) -> syn::Expr {
613    syn::Expr::MethodCall(syn::ExprMethodCall {
614        attrs: vec![],
615        receiver: Box::new(expr),
616        dot_token: Default::default(),
617        method: ident("as_ptr"),
618        turbofish: None,
619        paren_token: Default::default(),
620        args: syn::punctuated::Punctuated::new(),
621    })
622}
623
624/// フィールドアクセス `expr.field` を構築
625pub fn field_access(expr: syn::Expr, field_name: &str) -> syn::Expr {
626    syn::Expr::Field(syn::ExprField {
627        attrs: vec![],
628        base: Box::new(expr),
629        dot_token: Default::default(),
630        member: syn::Member::Named(ident(field_name)),
631    })
632}
633
634/// Deref `*expr` を構築
635pub fn deref(expr: syn::Expr) -> syn::Expr {
636    syn::Expr::Unary(syn::ExprUnary {
637        attrs: vec![],
638        op: syn::UnOp::Deref(Default::default()),
639        expr: Box::new(expr),
640    })
641}
642
643/// `&raw mut expr` を構築(C の `&` 演算子に対応する生ポインタ取得)。
644///
645/// C では `&x` は「アドレスを取る」= 生ポインタを得る操作で、
646/// Rust の `&mut T` 参照とは意味論が異なる。FFI コードでは生ポインタが正解。
647/// `&raw mut expr` は Rust 2024 で安定化された構文で、`*mut T` を直接生成する。
648///
649/// これにより `(SV**)&sv` のような C コードが
650/// `&raw mut sv as *mut *mut SV` に翻訳され、E0606(参照→生ポインタの直接 cast)
651/// が回避される。
652pub fn addr_of_mut(expr: syn::Expr) -> syn::Expr {
653    syn::Expr::RawAddr(syn::ExprRawAddr {
654        attrs: vec![],
655        and_token: Default::default(),
656        raw: Default::default(),
657        mutability: syn::PointerMutability::Mut(Default::default()),
658        expr: Box::new(expr),
659    })
660}
661
662/// 関数呼び出し `func(args...)` を構築
663pub fn call(func_name: &str, args: Vec<syn::Expr>) -> syn::Expr {
664    let func_ident = ident(func_name);
665    let mut punctuated = syn::punctuated::Punctuated::new();
666    for arg in args {
667        punctuated.push(arg);
668    }
669    syn::Expr::Call(syn::ExprCall {
670        attrs: vec![],
671        func: Box::new(syn::Expr::Path(syn::ExprPath {
672            attrs: vec![],
673            qself: None,
674            path: func_ident.into(),
675        })),
676        paren_token: Default::default(),
677        args: punctuated,
678    })
679}
680
681/// 識別子を `syn::Expr::Path` として構築(`name` を式コンテキストで参照する)
682pub fn ident_expr(name: &str) -> syn::Expr {
683    syn::Expr::Path(syn::ExprPath {
684        attrs: vec![],
685        qself: None,
686        path: ident(name).into(),
687    })
688}
689
690/// メソッド呼び出し `receiver.method(args...)` を構築
691pub fn method_call(receiver: syn::Expr, method: &str, args: Vec<syn::Expr>) -> syn::Expr {
692    let mut punctuated = syn::punctuated::Punctuated::new();
693    for arg in args {
694        punctuated.push(arg);
695    }
696    syn::Expr::MethodCall(syn::ExprMethodCall {
697        attrs: vec![],
698        receiver: Box::new(receiver),
699        dot_token: Default::default(),
700        method: ident(method),
701        turbofish: None,
702        paren_token: Default::default(),
703        args: punctuated,
704    })
705}
706
707/// `lhs = rhs` を式として構築(plain assign)
708pub fn assign_expr(lhs: syn::Expr, rhs: syn::Expr) -> syn::Expr {
709    syn::Expr::Assign(syn::ExprAssign {
710        attrs: vec![],
711        left: Box::new(lhs),
712        eq_token: Default::default(),
713        right: Box::new(rhs),
714    })
715}
716
717/// `lhs op= rhs` を式として構築(複合代入)
718///
719/// `op` には `syn::BinOp::AddAssign(_)` 等の `*Assign` 系バリアントを渡す。
720pub fn assign_op_expr(lhs: syn::Expr, op: syn::BinOp, rhs: syn::Expr) -> syn::Expr {
721    syn::Expr::Binary(syn::ExprBinary {
722        attrs: vec![],
723        left: Box::new(lhs),
724        op,
725        right: Box::new(rhs),
726    })
727}
728
729/// 式を `Stmt::Expr(_, Some(Semi))` の文として構築
730pub fn semi_stmt(expr: syn::Expr) -> syn::Stmt {
731    syn::Stmt::Expr(expr, Some(Default::default()))
732}
733
734/// `let name = value;` 文を構築
735pub fn let_stmt(name: &str, value: syn::Expr) -> syn::Stmt {
736    let pat = syn::Pat::Ident(syn::PatIdent {
737        attrs: vec![],
738        by_ref: None,
739        mutability: None,
740        ident: ident(name),
741        subpat: None,
742    });
743    syn::Stmt::Local(syn::Local {
744        attrs: vec![],
745        let_token: Default::default(),
746        pat,
747        init: Some(syn::LocalInit {
748            eq_token: Default::default(),
749            expr: Box::new(value),
750            diverge: None,
751        }),
752        semi_token: Default::default(),
753    })
754}
755
756/// `{ stmts...; value }` ブロック式を構築
757pub fn block_with_value(stmts: Vec<syn::Stmt>, value: syn::Expr) -> syn::Expr {
758    let mut all_stmts = stmts;
759    all_stmts.push(syn::Stmt::Expr(value, None)); // 末尾値(セミコロンなし)
760    syn::Expr::Block(syn::ExprBlock {
761        attrs: vec![],
762        label: None,
763        block: syn::Block {
764            brace_token: Default::default(),
765            stmts: all_stmts,
766        },
767    })
768}
769
770/// `AssignOp` (C AST) → 複合代入用の `syn::BinOp` を返す。
771///
772/// `AssignOp::Assign` は plain `=` であり `BinOp` を持たないため `None`。
773pub fn c_assign_op_to_syn_compound(op: crate::ast::AssignOp) -> Option<syn::BinOp> {
774    use crate::ast::AssignOp;
775    Some(match op {
776        AssignOp::Assign => return None,
777        AssignOp::AddAssign => syn::BinOp::AddAssign(Default::default()),
778        AssignOp::SubAssign => syn::BinOp::SubAssign(Default::default()),
779        AssignOp::MulAssign => syn::BinOp::MulAssign(Default::default()),
780        AssignOp::DivAssign => syn::BinOp::DivAssign(Default::default()),
781        AssignOp::ModAssign => syn::BinOp::RemAssign(Default::default()),
782        AssignOp::AndAssign => syn::BinOp::BitAndAssign(Default::default()),
783        AssignOp::OrAssign  => syn::BinOp::BitOrAssign(Default::default()),
784        AssignOp::XorAssign => syn::BinOp::BitXorAssign(Default::default()),
785        AssignOp::ShlAssign => syn::BinOp::ShlAssign(Default::default()),
786        AssignOp::ShrAssign => syn::BinOp::ShrAssign(Default::default()),
787    })
788}
789
790/// if-else 式を構築
791pub fn if_else(cond: syn::Expr, then_expr: syn::Expr, else_expr: syn::Expr) -> syn::Expr {
792    syn::Expr::If(syn::ExprIf {
793        attrs: vec![],
794        if_token: Default::default(),
795        cond: Box::new(cond),
796        then_branch: syn::Block {
797            brace_token: Default::default(),
798            stmts: vec![syn::Stmt::Expr(then_expr, None)],
799        },
800        else_branch: Some((
801            Default::default(),
802            Box::new(syn::Expr::Block(syn::ExprBlock {
803                attrs: vec![],
804                label: None,
805                block: syn::Block {
806                    brace_token: Default::default(),
807                    stmts: vec![syn::Stmt::Expr(else_expr, None)],
808                },
809            })),
810        )),
811    })
812}
813
814// ============================================================================
815// テスト
816// ============================================================================
817
818#[cfg(test)]
819mod tests {
820    use super::*;
821    use syn::parse_quote;
822
823    #[test]
824    fn test_parenthesize_binary_precedence() {
825        // (a + b) * c → 括弧が必要
826        let expr: syn::Expr = parse_quote!(a + b * c);
827        let result = expr_to_string(&expr);
828        assert_eq!(result, "a + b * c");
829
830        // a + b を * の左辺に → 括弧必要
831        let a: syn::Expr = parse_quote!(a);
832        let b: syn::Expr = parse_quote!(b);
833        let c: syn::Expr = parse_quote!(c);
834        let add: syn::Expr = parse_quote!(#a + #b);
835        let mul = syn::Expr::Binary(syn::ExprBinary {
836            attrs: vec![],
837            left: Box::new(add),
838            op: syn::BinOp::Mul(Default::default()),
839            right: Box::new(c),
840        });
841        let result = expr_to_string(&mul);
842        assert_eq!(result, "(a + b) * c");
843    }
844
845    #[test]
846    fn test_parenthesize_cast() {
847        // a & MASK as u32 → (a & MASK) as u32 が必要
848        let a: syn::Expr = parse_quote!(a);
849        let mask: syn::Expr = parse_quote!(MASK);
850        let bitand = syn::Expr::Binary(syn::ExprBinary {
851            attrs: vec![],
852            left: Box::new(a),
853            op: syn::BinOp::BitAnd(Default::default()),
854            right: Box::new(mask),
855        });
856        let cast = syn::Expr::Cast(syn::ExprCast {
857            attrs: vec![],
858            expr: Box::new(bitand),
859            as_token: Default::default(),
860            ty: Box::new(parse_quote!(u32)),
861        });
862        let result = expr_to_string(&cast);
863        assert_eq!(result, "(a & MASK) as u32");
864    }
865
866    #[test]
867    fn test_parenthesize_if_ne() {
868        // (if cond { A } else { B }) != 0 → 括弧が必要
869        let if_expr: syn::Expr = parse_quote!(if cond { A } else { B });
870        let ne = syn::Expr::Binary(syn::ExprBinary {
871            attrs: vec![],
872            left: Box::new(if_expr),
873            op: syn::BinOp::Ne(Default::default()),
874            right: Box::new(parse_quote!(0)),
875        });
876        let result = expr_to_string(&ne);
877        // if 式は prec 100 だが、Binary(Ne) の子としては括弧不要(中身が独立)
878        // → 実際には Rust では if {} != 0 は if {} else {} != 0 にパースされるため括弧必要
879        // この挙動を正しくハンドルするにはIf式の特別扱いが必要
880        assert!(result.contains("if cond"));
881    }
882
883    #[test]
884    fn test_deref_field() {
885        // (*a).field — Deref + Field
886        let a: syn::Expr = parse_quote!(a);
887        let deref = syn::Expr::Unary(syn::ExprUnary {
888            attrs: vec![],
889            op: syn::UnOp::Deref(Default::default()),
890            expr: Box::new(a),
891        });
892        let field = syn::Expr::Field(syn::ExprField {
893            attrs: vec![],
894            base: Box::new(deref),
895            dot_token: Default::default(),
896            member: syn::Member::Named(ident("field")),
897        });
898        let result = expr_to_string(&field);
899        assert_eq!(result, "(* a) . field");
900        // Note: ToTokens ではスペースが入る。prettyplease で整形すると (*a).field になる
901    }
902
903    #[test]
904    fn test_ident_keyword() {
905        let i = ident("type");
906        assert_eq!(i.to_string(), "r#type");
907    }
908
909    // ================================================================
910    // Phase 4: normalize_parens のテスト
911    // ================================================================
912
913    #[test]
914    fn test_normalize_cast_removes_outer_parens() {
915        // (x as i32) → x as i32
916        assert_eq!(normalize_parens("(x as i32)"), "x as i32");
917    }
918
919    #[test]
920    fn test_normalize_deref_removes_outer_parens() {
921        // (*ptr) → *ptr
922        assert_eq!(normalize_parens("(*ptr)"), "*ptr");
923    }
924
925    #[test]
926    fn test_normalize_addr_of_removes_outer_parens() {
927        // (&mut x) → &mut x
928        assert_eq!(normalize_parens("(&mut x)"), "&mut x");
929    }
930
931    #[test]
932    fn test_normalize_binary_removes_outer_parens() {
933        // (a + b) → a + b
934        assert_eq!(normalize_parens("(a + b)"), "a + b");
935    }
936
937    #[test]
938    fn test_normalize_deref_field_preserves_needed_parens() {
939        // (*a).field → (*a).field (parens needed!)
940        assert_eq!(normalize_parens("(*a).field"), "(*a).field");
941    }
942
943    #[test]
944    fn test_normalize_cast_in_binary_preserves_needed_parens() {
945        // (a & MASK) as u32 → (a & MASK) as u32 (parens needed!)
946        assert_eq!(normalize_parens("(a & MASK) as u32"), "(a & MASK) as u32");
947    }
948
949    #[test]
950    fn test_normalize_nested_unnecessary_parens() {
951        // ((x as i32)) → x as i32
952        assert_eq!(normalize_parens("((x as i32))"), "x as i32");
953    }
954
955    #[test]
956    fn test_normalize_preserves_precedence() {
957        // (a + b) * c → (a + b) * c (parens needed!)
958        assert_eq!(normalize_parens("(a + b) * c"), "(a + b) * c");
959    }
960
961    #[test]
962    fn test_normalize_no_change_needed() {
963        assert_eq!(normalize_parens("x"), "x");
964        assert_eq!(normalize_parens("42"), "42");
965        assert_eq!(normalize_parens("foo(a, b)"), "foo(a, b)");
966    }
967
968    #[test]
969    fn test_normalize_method_call() {
970        // (ptr).is_null() → ptr.is_null()
971        assert_eq!(normalize_parens("(ptr).is_null()"), "ptr.is_null()");
972    }
973
974    #[test]
975    fn test_normalize_logical_ops() {
976        // (a && b) → a && b
977        assert_eq!(normalize_parens("(a && b)"), "a && b");
978        // (a || b) → a || b
979        assert_eq!(normalize_parens("(a || b)"), "a || b");
980    }
981
982    #[test]
983    fn test_normalize_complex_nested() {
984        // ((*sv).sv_flags as u32) → (*sv).sv_flags as u32
985        assert_eq!(
986            normalize_parens("((*sv).sv_flags as u32)"),
987            "(*sv).sv_flags as u32"
988        );
989    }
990
991    #[test]
992    fn test_normalize_unary_minus() {
993        // (-x) → -x
994        assert_eq!(normalize_parens("(-x)"), "-x");
995    }
996
997    #[test]
998    fn test_normalize_not() {
999        // (!cond) → !cond
1000        assert_eq!(normalize_parens("(!cond)"), "!cond");
1001    }
1002
1003    #[test]
1004    fn test_normalize_block_expr_passthrough() {
1005        // Block expressions should pass through (parse may fail or be multi-line)
1006        let s = "{ x += 1; x }";
1007        let result = normalize_parens(s);
1008        // Should either normalize or return original
1009        assert!(result == s || !result.contains('\n'));
1010    }
1011
1012    // ================================================================
1013    // Phase 3: AST 変換パスのテスト
1014    // ================================================================
1015
1016    #[test]
1017    fn test_is_bool_syn_expr_comparison() {
1018        let expr: syn::Expr = parse_quote!(a == b);
1019        assert!(is_bool_syn_expr(&expr));
1020
1021        let expr: syn::Expr = parse_quote!(a != 0);
1022        assert!(is_bool_syn_expr(&expr));
1023
1024        let expr: syn::Expr = parse_quote!(a < b);
1025        assert!(is_bool_syn_expr(&expr));
1026    }
1027
1028    #[test]
1029    fn test_is_bool_syn_expr_logical() {
1030        let expr: syn::Expr = parse_quote!(a && b);
1031        assert!(is_bool_syn_expr(&expr));
1032
1033        let expr: syn::Expr = parse_quote!(a || b);
1034        assert!(is_bool_syn_expr(&expr));
1035    }
1036
1037    #[test]
1038    fn test_is_bool_syn_expr_not() {
1039        // !bool_expr → bool
1040        let expr: syn::Expr = parse_quote!(!(a == b));
1041        assert!(is_bool_syn_expr(&expr));
1042
1043        // !non_bool → not bool (bitwise not)
1044        let expr: syn::Expr = parse_quote!(!x);
1045        assert!(!is_bool_syn_expr(&expr));
1046    }
1047
1048    #[test]
1049    fn test_is_bool_syn_expr_non_bool() {
1050        let expr: syn::Expr = parse_quote!(a + b);
1051        assert!(!is_bool_syn_expr(&expr));
1052
1053        let expr: syn::Expr = parse_quote!(42);
1054        assert!(!is_bool_syn_expr(&expr));
1055
1056        let expr: syn::Expr = parse_quote!(foo(x));
1057        assert!(!is_bool_syn_expr(&expr));
1058    }
1059
1060    #[test]
1061    fn test_is_bool_syn_expr_bool_lit() {
1062        let expr: syn::Expr = parse_quote!(true);
1063        assert!(is_bool_syn_expr(&expr));
1064
1065        let expr: syn::Expr = parse_quote!(false);
1066        assert!(is_bool_syn_expr(&expr));
1067    }
1068
1069    #[test]
1070    fn test_is_bool_syn_expr_is_null() {
1071        let expr: syn::Expr = parse_quote!(ptr.is_null());
1072        assert!(is_bool_syn_expr(&expr));
1073    }
1074
1075    #[test]
1076    fn test_is_bool_syn_expr_paren() {
1077        let expr: syn::Expr = parse_quote!((a == b));
1078        assert!(is_bool_syn_expr(&expr));
1079    }
1080
1081    #[test]
1082    fn test_looks_like_pointer_cast() {
1083        let expr: syn::Expr = parse_quote!(x as *mut i32);
1084        assert!(looks_like_pointer(&expr));
1085
1086        let expr: syn::Expr = parse_quote!(x as *const u8);
1087        assert!(looks_like_pointer(&expr));
1088
1089        let expr: syn::Expr = parse_quote!(x as i32);
1090        assert!(!looks_like_pointer(&expr));
1091    }
1092
1093    #[test]
1094    fn test_looks_like_pointer_method() {
1095        let expr: syn::Expr = parse_quote!(p.offset(1));
1096        assert!(looks_like_pointer(&expr));
1097
1098        let expr: syn::Expr = parse_quote!(p.wrapping_add(n));
1099        assert!(looks_like_pointer(&expr));
1100
1101        let expr: syn::Expr = parse_quote!(arr.as_ptr());
1102        assert!(looks_like_pointer(&expr));
1103    }
1104
1105    #[test]
1106    fn test_looks_like_pointer_null() {
1107        let expr: syn::Expr = parse_quote!(std::ptr::null_mut());
1108        assert!(looks_like_pointer(&expr));
1109    }
1110
1111    #[test]
1112    fn test_wrap_as_bool_already_bool() {
1113        let expr: syn::Expr = parse_quote!(a == b);
1114        let result = wrap_as_bool(expr);
1115        let s = expr_to_string(&result);
1116        assert_eq!(s, "a == b");
1117    }
1118
1119    #[test]
1120    fn test_wrap_as_bool_integer() {
1121        let expr: syn::Expr = parse_quote!(x);
1122        let result = wrap_as_bool(expr);
1123        let s = expr_to_string(&result);
1124        assert_eq!(s, "x != 0");
1125    }
1126
1127    #[test]
1128    fn test_wrap_as_bool_pointer() {
1129        let expr: syn::Expr = parse_quote!(p as *mut i32);
1130        let result = wrap_as_bool(expr);
1131        let s = expr_to_string(&result);
1132        assert!(s.contains("is_null"), "expected is_null in: {}", s);
1133    }
1134
1135    #[test]
1136    fn test_int_lit() {
1137        let expr = int_lit(42);
1138        let s = expr_to_string(&expr);
1139        assert_eq!(s, "42");
1140
1141        let expr = int_lit(0);
1142        let s = expr_to_string(&expr);
1143        assert_eq!(s, "0");
1144
1145        let expr = int_lit(-1);
1146        let s = expr_to_string(&expr);
1147        assert_eq!(s, "- 1");  // ToTokens adds space after unary minus
1148    }
1149
1150    #[test]
1151    fn test_insert_cast() {
1152        let expr: syn::Expr = parse_quote!(x);
1153        let ty = parse_type("u32");
1154        let result = insert_cast(expr, ty);
1155        let s = expr_to_string(&result);
1156        assert_eq!(s, "x as u32");
1157    }
1158
1159    #[test]
1160    fn test_insert_cast_complex_expr() {
1161        // (a + b) as i32 — parenthesize should add parens
1162        let expr: syn::Expr = parse_quote!(a + b);
1163        let ty = parse_type("i32");
1164        let result = insert_cast(expr, ty);
1165        let s = expr_to_string(&result);
1166        assert_eq!(s, "(a + b) as i32");
1167    }
1168
1169    #[test]
1170    fn test_parse_type_basic() {
1171        let ty = parse_type("i32");
1172        assert_eq!(ty.to_token_stream().to_string(), "i32");
1173    }
1174
1175    #[test]
1176    fn test_parse_type_pointer() {
1177        let ty = parse_type("*mut u8");
1178        assert_eq!(ty.to_token_stream().to_string(), "* mut u8");
1179    }
1180
1181    #[test]
1182    fn test_parse_type_fallback() {
1183        // Invalid type string → fallback to c_int
1184        let ty = parse_type("not a valid type!!!");
1185        assert_eq!(ty.to_token_stream().to_string(), "c_int");
1186    }
1187
1188    #[test]
1189    fn test_null_for_type_mut() {
1190        let expr = null_for_type("*mut SV");
1191        let s = expr_to_string(&expr);
1192        assert!(s.contains("null_mut"), "expected null_mut in: {}", s);
1193    }
1194
1195    #[test]
1196    fn test_null_for_type_const() {
1197        let expr = null_for_type("*const c_char");
1198        let s = expr_to_string(&expr);
1199        assert!(s.contains("null"), "expected null in: {}", s);
1200        assert!(!s.contains("null_mut"), "should not contain null_mut in: {}", s);
1201    }
1202
1203    #[test]
1204    fn test_null_for_type_non_pointer() {
1205        let expr = null_for_type("i32");
1206        let s = expr_to_string(&expr);
1207        assert_eq!(s, "0");
1208    }
1209
1210    #[test]
1211    fn test_as_ptr() {
1212        let expr: syn::Expr = parse_quote!(PL_Yes);
1213        let result = as_ptr(expr);
1214        let s = expr_to_string(&result);
1215        assert!(s.contains("as_ptr"), "expected as_ptr in: {}", s);
1216        assert!(s.contains("PL_Yes"), "expected PL_Yes in: {}", s);
1217    }
1218
1219    #[test]
1220    fn test_field_access() {
1221        let expr: syn::Expr = parse_quote!(sv);
1222        let result = field_access(expr, "sv_flags");
1223        let s = expr_to_string(&result);
1224        assert_eq!(s, "sv . sv_flags");
1225    }
1226
1227    #[test]
1228    fn test_deref_simple() {
1229        let expr: syn::Expr = parse_quote!(ptr);
1230        let result = deref(expr);
1231        let s = expr_to_string(&result);
1232        assert_eq!(s, "* ptr");
1233    }
1234
1235    #[test]
1236    fn test_deref_field_parenthesized() {
1237        // (*ptr).field — deref should get parens when used with field access
1238        let ptr: syn::Expr = parse_quote!(ptr);
1239        let d = deref(ptr);
1240        let f = field_access(d, "field");
1241        let s = expr_to_string(&f);
1242        assert_eq!(s, "(* ptr) . field");
1243    }
1244
1245    #[test]
1246    fn test_addr_of_mut() {
1247        // C の `&x` は生ポインタを得る操作なので `&raw mut x` を生成する
1248        // (Rust 2024 で安定化)。詳細は addr_of_mut のドキュメント参照。
1249        let expr: syn::Expr = parse_quote!(x);
1250        let result = addr_of_mut(expr);
1251        let s = expr_to_string(&result);
1252        assert_eq!(s, "& raw mut x");
1253    }
1254
1255    #[test]
1256    fn test_call_no_args() {
1257        let result = call("foo", vec![]);
1258        let s = expr_to_string(&result);
1259        assert!(s.contains("foo"), "expected foo in: {}", s);
1260        // ToTokens may add spaces: "foo ()"
1261        let normalized = s.replace(' ', "");
1262        assert_eq!(normalized, "foo()");
1263    }
1264
1265    #[test]
1266    fn test_call_with_args() {
1267        let a: syn::Expr = parse_quote!(x);
1268        let b: syn::Expr = parse_quote!(y);
1269        let result = call("bar", vec![a, b]);
1270        let s = expr_to_string(&result);
1271        let normalized = s.replace(' ', "");
1272        assert_eq!(normalized, "bar(x,y)");
1273    }
1274
1275    #[test]
1276    fn test_if_else() {
1277        let cond: syn::Expr = parse_quote!(x > 0);
1278        let then_expr: syn::Expr = parse_quote!(a);
1279        let else_expr: syn::Expr = parse_quote!(b);
1280        let result = if_else(cond, then_expr, else_expr);
1281        let s = expr_to_string(&result);
1282        assert!(s.contains("if"), "expected if in: {}", s);
1283        assert!(s.contains("else"), "expected else in: {}", s);
1284    }
1285
1286    #[test]
1287    fn test_wrap_as_bool_with_binary() {
1288        // Binary expr like (a + b) should become (a + b) != 0
1289        let expr: syn::Expr = parse_quote!(a + b);
1290        let result = wrap_as_bool(expr);
1291        let s = expr_to_string(&result);
1292        assert_eq!(s, "a + b != 0");
1293    }
1294
1295    #[test]
1296    fn test_combined_cast_and_bool() {
1297        // A typical pattern: cast result to bool
1298        // (x as u32) != 0
1299        let x: syn::Expr = parse_quote!(flags);
1300        let cast = insert_cast(x, parse_type("u32"));
1301        let bool_expr = wrap_as_bool(cast);
1302        let s = expr_to_string(&bool_expr);
1303        assert_eq!(s, "flags as u32 != 0");
1304    }
1305}