Skip to content

Commit cc08650

Browse files
committed
fix: cast bare comparisons to value type in function macro translation
C comparison operators return int (0 or 1), but Rust's produce bool. Without the cast, macros like `#define GT(x, y) ((x) > (y))` generated `-> i64` functions with a bool body, and comparisons used inside arithmetic (`((x) > (y)) + 1`) failed to compile. Wrap bare comparison results with `as vt` in translate_expr. Remove the `expr_tokens_are_boolean` / `cond_is_raw_bool` short-circuits from the logical-op and ternary handlers — they now always convert operands via `!= 0`, which is slightly more verbose but correct for all nesting patterns.
1 parent 385a72f commit cc08650

File tree

6 files changed

+114
-38
lines changed

6 files changed

+114
-38
lines changed

bindgen-tests/tests/expectations/tests/translate-func-macro-comparison.rs

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindgen-tests/tests/expectations/tests/translate-func-macro-edge-cases.rs

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindgen-tests/tests/expectations/tests/translate-func-macro-ternary-bool.rs

Lines changed: 17 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindgen-tests/tests/expectations/tests/translate-function-macros.rs

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// bindgen-flags: --translate-function-macros
2+
3+
// Bare comparisons: C returns int (0 or 1), Rust returns bool.
4+
// Must cast to the value type so the result is integer-typed.
5+
#define GT(x, y) ((x) > (y))
6+
#define LT(x, y) ((x) < (y))
7+
#define EQ(x, y) ((x) == (y))
8+
#define NE(x, y) ((x) != (y))
9+
#define GTE(x, y) ((x) >= (y))
10+
#define LTE(x, y) ((x) <= (y))
11+
12+
// Without outer parens.
13+
#define GT_BARE(x, y) (x) > (y)
14+
15+
// Deeply nested parens around comparison.
16+
#define NESTED(x, y) (((((x) > (y)))))
17+
18+
// Comparison result used in arithmetic (bool + bool would fail).
19+
#define CMP_ADD(x, y) ((x) > (y)) + ((x) < (y))
20+
#define CMP_MUL(x, y) (((x) > (y)) * 2)
21+
22+
// Comparison with sizeof.
23+
#define IS_BIG(T) (sizeof(T) > 4)

bindgen/ir/func_macro.rs

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -512,49 +512,35 @@ fn translate_expr(
512512
ctx: &TranslateCtx,
513513
) -> Result<TokenStream, SkipReason> {
514514
if let Some((q_pos, c_pos)) = find_ternary(tokens) {
515-
let cond_tokens = &tokens[..q_pos];
516-
let cond = translate_expr(cond_tokens, ctx)?;
515+
let cond = translate_expr(&tokens[..q_pos], ctx)?;
517516
let then_ = translate_expr(&tokens[q_pos + 1..c_pos], ctx)?;
518517
let else_ = translate_expr(&tokens[c_pos + 1..], ctx)?;
519518

520-
// A condition is directly bool only if it has a comparison
521-
// AND no logical ops. Logical ops are translated with `as vt`,
522-
// making the result integer-typed even if sub-expressions are
523-
// comparisons (e.g., `(x) > 0 && (x) < 10` → `(bool && bool) as i64`).
524-
let cond_is_raw_bool = has_top_level_comparison(cond_tokens) &&
525-
find_top_level_logical_op(cond_tokens).is_none();
526-
527-
return if cond_is_raw_bool {
528-
Ok(quote! { if #cond { #then_ } else { #else_ } })
529-
} else if matches!(ctx.value_type, "f64" | "f32") {
519+
// Condition is always converted to bool via != 0.
520+
// Comparisons already return integer via `as vt` below,
521+
// so the != 0 check is redundant but harmless in that case.
522+
return if matches!(ctx.value_type, "f64" | "f32") {
530523
Ok(quote! { if (#cond) != 0.0 { #then_ } else { #else_ } })
531524
} else {
532525
Ok(quote! { if ((#cond) as i64) != 0 { #then_ } else { #else_ } })
533526
};
534527
}
535528

536529
// Logical operators: split on || (lowest precedence) or &&.
537-
// Each operand is wrapped with != 0 if it doesn't already produce
538-
// bool, and the result is cast back to the value type (C's && and
539-
// || return 0 or 1 as int).
530+
// Each operand is wrapped with != 0 to convert to bool, and
531+
// the result is cast back to the value type (C's && and ||
532+
// return 0 or 1 as int).
540533
if let Some((op_pos, is_or)) = find_top_level_logical_op(tokens) {
541-
let left_tokens = &tokens[..op_pos];
542-
let right_tokens = &tokens[op_pos + 1..];
543-
544-
let left = translate_expr(left_tokens, ctx)?;
545-
let right = translate_expr(right_tokens, ctx)?;
534+
let left = translate_expr(&tokens[..op_pos], ctx)?;
535+
let right = translate_expr(&tokens[op_pos + 1..], ctx)?;
546536

547537
let is_float = matches!(ctx.value_type, "f64" | "f32");
548-
let left_bool = if expr_tokens_are_boolean(left_tokens) {
549-
left
550-
} else if is_float {
538+
let left_bool = if is_float {
551539
quote! { ((#left) != 0.0) }
552540
} else {
553541
quote! { (((#left) as i64) != 0) }
554542
};
555-
let right_bool = if expr_tokens_are_boolean(right_tokens) {
556-
right
557-
} else if is_float {
543+
let right_bool = if is_float {
558544
quote! { ((#right) != 0.0) }
559545
} else {
560546
quote! { (((#right) as i64) != 0) }
@@ -574,6 +560,18 @@ fn translate_expr(
574560
};
575561
}
576562

563+
// Bare comparisons produce `bool` in Rust but `int` (0 or 1) in C.
564+
// Cast to the value type so the result can be used in arithmetic.
565+
if expr_tokens_are_boolean(tokens) {
566+
let body = translate_tokens(tokens, ctx)?;
567+
let vt = ctx.vt();
568+
return if matches!(ctx.value_type, "f64" | "f32") {
569+
Ok(quote! { (#body) as i64 as #vt })
570+
} else {
571+
Ok(quote! { (#body) as #vt })
572+
};
573+
}
574+
577575
translate_tokens(tokens, ctx)
578576
}
579577

@@ -682,7 +680,8 @@ fn find_top_level_logical_op(tokens: &[OwnedToken]) -> Option<(usize, bool)> {
682680
/// `((bool && bool) as i64)`).
683681
fn expr_tokens_are_boolean(tokens: &[OwnedToken]) -> bool {
684682
has_top_level_comparison(tokens) &&
685-
find_top_level_logical_op(tokens).is_none()
683+
find_top_level_logical_op(tokens).is_none() &&
684+
find_ternary(tokens).is_none()
686685
}
687686

688687
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)