Summary
- Context:
IfCondition::translate in src/modules/condition/ifcond.rs is responsible for translating if statements into Bash code, including an optimization for conditions known at compile-time.
- Bug: The translation process for statically known conditions (
true or false) completely skips translating the condition expression itself, which causes all side effects within that expression to be lost.
- Actual vs. expected: Statically optimized
if conditions lose side effects like command executions and function calls, whereas they should be preserved even if the condition's value is constant.
- Impact: Valid Amber code that relies on side effects within an
if condition will produce incorrect Bash scripts where those side effects never occur.
Code with bug
match self.expr.analyze_control_flow() {
Some(true) => self
.true_block
.as_ref()
.map(|b| b.translate(meta)) // <-- BUG 🔴 [Loses side effects of self.expr and has wrong indentation]
.unwrap_or(FragmentKind::Empty),
Some(false) => self
.false_block
.as_ref()
.map(|b| b.translate(meta)) // <-- BUG 🔴 [Loses side effects of self.expr and has wrong indentation]
.unwrap_or(FragmentKind::Empty),
Evidence
Side effect loss
When compiling the following Amber code:
fun my_func(): Bool {
echo "Side effect"
return false
}
if my_func() or true {
echo "Done"
}
The compiler determines that my_func() or true is statically true (since or true is always true). It then skips the translation of the expression, resulting in Bash code where my_func is defined but never called:
my_func__0_v0() {
echo "Side effect"
ret_my_func0_v0=0
return 0
}
# The call to my_func__0_v0 is missing here!
echo "Done"
The expected behavior is that my_func should be called before echo "Done".
Indentation bug
Additionally, the optimization returns the translated block directly, which results in incorrect indentation in the generated Bash. For example:
Generates:
Instead of:
The leading spaces are preserved because the block was translated with increase_indent: true, but the if statement that would have justified the indentation was removed.
Why has this bug gone undetected?
This bug primarily affects expressions that combine compile-time constants with side-effect-ful operations (like commands or functions) in a way that allows the compiler to determine the result statically. In typical scripts, if conditions are either fully dynamic or simple literals, making this edge case rare but critical for correctness.
Recommended fix
The translate method should always call self.expr.translate(meta) to ensure side effects are pushed to the statement queue, even if the result is ignored. Furthermore, when unwrapping a block due to static optimization, its indentation should be adjusted.
fn translate(&self, meta: &mut TranslateMetadata) -> FragmentKind {
let _ = self.expr.translate(meta); // Preserve side effects 🟢
match self.expr.analyze_control_flow() {
Some(true) => {
if let Some(true_block) = &self.true_block {
let mut frag = true_block.translate(meta);
if let FragmentKind::Block(ref mut block) = frag {
block.increase_indent = false; // Fix indentation 🟢
}
frag
} else {
FragmentKind::Empty
}
}
// ... similar for Some(false) ...
Related bugs
Similar aggressive optimizations that lose side effects are also present in src/modules/condition/ifchain.rs and src/modules/expression/ternop/ternary.rs.
History
This bug was introduced in commit 09af993 (@Ph0enixKM, 2025-12-19, PR #940). The change added control flow analysis to optimize statically known conditions, but it incorrectly skipped calling translate() on the condition expression, losing its side effects, and failed to handle block indentation when unwrapping optimized branches.
Summary
IfCondition::translateinsrc/modules/condition/ifcond.rsis responsible for translatingifstatements into Bash code, including an optimization for conditions known at compile-time.trueorfalse) completely skips translating the condition expression itself, which causes all side effects within that expression to be lost.ifconditions lose side effects like command executions and function calls, whereas they should be preserved even if the condition's value is constant.ifcondition will produce incorrect Bash scripts where those side effects never occur.Code with bug
Evidence
Side effect loss
When compiling the following Amber code:
The compiler determines that
my_func() or trueis staticallytrue(sinceor trueis always true). It then skips the translation of the expression, resulting in Bash code wheremy_funcis defined but never called:The expected behavior is that
my_funcshould be called beforeecho "Done".Indentation bug
Additionally, the optimization returns the translated block directly, which results in incorrect indentation in the generated Bash. For example:
Generates:
Instead of:
The leading spaces are preserved because the block was translated with
increase_indent: true, but theifstatement that would have justified the indentation was removed.Why has this bug gone undetected?
This bug primarily affects expressions that combine compile-time constants with side-effect-ful operations (like commands or functions) in a way that allows the compiler to determine the result statically. In typical scripts,
ifconditions are either fully dynamic or simple literals, making this edge case rare but critical for correctness.Recommended fix
The
translatemethod should always callself.expr.translate(meta)to ensure side effects are pushed to the statement queue, even if the result is ignored. Furthermore, when unwrapping a block due to static optimization, its indentation should be adjusted.Related bugs
Similar aggressive optimizations that lose side effects are also present in
src/modules/condition/ifchain.rsandsrc/modules/expression/ternop/ternary.rs.History
This bug was introduced in commit 09af993 (@Ph0enixKM, 2025-12-19, PR #940). The change added control flow analysis to optimize statically known conditions, but it incorrectly skipped calling
translate()on the condition expression, losing its side effects, and failed to handle block indentation when unwrapping optimized branches.