Repository: sympy/sympy

Issue:
Rust code printing fails to parenthesize correctly when TypeCast is an argument to Mul, generating wrong code
the following python code produces invalid rust code

```python
import sympy
from sympy.printing import rust_code, ccode

# 2**(-2*log(modulus)/log(2) + 4.44) +
# (2**(2*ceiling(-0.025167785*lwe_dimension + log(modulus)/log(2) + 4.10067100000001)) + 0.5)/(3*modulus**2)

modulus = sympy.Symbol("modulus")
lwe_dimension = sympy.Symbol("lwe_dimension")

exp = (
    2
    ** (
        2
        * sympy.ceiling(
            -0.025167785 * lwe_dimension + sympy.log(modulus) / sympy.log(2) + 4.10067100000001
        )
    )
    + 0.5
) / (3 * modulus**2)

print("sympy  ", exp)
print("rust   ", rust_code(exp))
print("C      ", ccode(exp))
```

```console
sympy   (2**(2*ceiling(-0.025167785*lwe_dimension + log(modulus)/log(2) + 4.10067100000001)) + 0.5)/(3*modulus**2)
rust    (1_f64/3.0)*0.5 + (2*(-0.025167785*lwe_dimension + modulus.ln()/2_i32.ln() + 4.10067100000001).ceil()).exp2()*modulus.powi(-2)
C       (1.0/3.0)*(pow(2, 2*ceil(-0.025167785000000002*lwe_dimension + log(modulus)/M_LN2 + 4.1006710000000099)) + 0.5)/pow(modulus, 2)
```

expected ouput for rust:

```
(1_f64/3.0)*(0.5 + (2*(-0.025167785*lwe_dimension + modulus.ln()/2_i32.ln() + 4.10067100000001).ceil()).exp2())*modulus.powi(-2)
```

From the rust code printer:

```python
    def _print_Mul(self, expr):
        contains_floats = any(arg.is_real and not arg.is_integer for arg in expr.args)
        if contains_floats:
            expr = reduce(operator.mul,(self._cast_to_float(arg) if arg != -1 else arg for arg in expr.args))

        return super()._print_Mul(expr)

...
    def _cast_to_float(self, expr):
        if not expr.is_number:
            return TypeCast(expr, real)
        elif expr.is_integer:
            return Float(expr)
        return expr

```

the above formula triggers the _cast_to_float which introduces TypeCast for the term taht does not get parenthesized.

the problem is TypeCast is a class introduced in the rust code printer that has no precedence information, it falls back to Expr in the MRO it seems, which then ends up falling back to Atom which has precedence of 1000 vs Mul which is 50, so no parenthesizing is done.

From where I'm standing the TypeCast way to convert floats does not make much sense for this particular case (also does not appear in the output, meaning it ends up being stripped anyways).


good thing is I can bypass the type cast myself in python, but this is currently broken and needs a regression test, given TypeCast is outside any precedence information from sympy I don't know if this is the best way of handling this.

Discussion:

Fix (patch):
diff --git a/.mailmap b/.mailmap
index 8ac713a45714..0d09b411603e 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1204,6 +1204,7 @@ Pauli Virtanen <pav@iki.fi>
 Pavel Fedotov <fedotovp@gmail.com>
 Pavel Tkachenko <paveltkachenko@email.com>
 Pearu Peterson <pearu.peterson@gmail.com> pearu.peterson <devnull@localhost>
+Pedro H. S. Prestes <pedrohsprestes@gmail.com> Pedro H. Prestes <146496459+phprestes@users.noreply.github.com>
 Pedro Rosa <pedro_sxbr@usp.br>
 Peeyush Kushwaha <peeyush.p97@gmail.com>
 Peleg Michaeli <freepeleg@gmail.com> <peleg@palgo-at.com>
diff --git a/sympy/printing/rust.py b/sympy/printing/rust.py
index 5bfd481bec6b..d1719b555320 100644
--- a/sympy/printing/rust.py
+++ b/sympy/printing/rust.py
@@ -45,7 +45,7 @@
 from sympy.core.numbers import equal_valued
 from sympy.functions.elementary.integers import ceiling, floor
 from sympy.printing.codeprinter import CodePrinter
-from sympy.printing.precedence import PRECEDENCE
+from sympy.printing.precedence import PRECEDENCE, precedence
 
 # Rust's methods for integer and float can be found at here :
 #
@@ -256,6 +256,8 @@ def __init__(self, expr, type_) -> None:
         self._assumptions = expr._assumptions
         if self.explicit:
             setattr(self, 'precedence', PRECEDENCE["Func"] + 10)
+        else:
+            setattr(self, 'precedence', precedence(self.expr))
 
     @property
     def expr(self):
@@ -268,7 +270,6 @@ def type_(self):
     def sort_key(self, order=None):
         return self.args[0].sort_key(order=order)
 
-
 class RustCodePrinter(CodePrinter):
     """A printer to convert SymPy expressions to strings of Rust code"""
     printmethod = "_rust_code"


Test changes:
diff --git a/sympy/printing/tests/test_rust.py b/sympy/printing/tests/test_rust.py
index c81d592faca0..f0c921c9b3c3 100644
--- a/sympy/printing/tests/test_rust.py
+++ b/sympy/printing/tests/test_rust.py
@@ -1,8 +1,8 @@
-from sympy.core import (S, pi, oo, symbols, Rational, Integer,
+from sympy.core import (S, pi, oo, symbols, Rational, Integer, Symbol,
                         GoldenRatio, EulerGamma, Catalan, Lambda, Dummy,
                         Eq, Ne, Le, Lt, Gt, Ge, Mod)
 from sympy.functions import (Piecewise, sin, cos, Abs, exp, ceiling, sqrt,
-                             sign, floor)
+                             sign, floor, log)
 from sympy.logic import ITE
 from sympy.testing.pytest import raises
 from sympy.utilities.lambdify import implemented_function
@@ -361,3 +361,8 @@ def test_sparse_matrix():
     # gh-15791
     with raises(NotImplementedError):
         rust_code(SparseMatrix([[1, 2, 3]]))
+
+def test_parenthesize_typecast():
+    modulus = Symbol("modulus")
+    exp = (2 ** (2.0 * ceiling(-y + log(x) / log(2.0) + 4)) + 0.5) / (3 * modulus**2)
+    assert(rust_code(exp) == '(1_f64/3.0)*(0.5 + (2.0*(-y + 1.44269504088896*x.ln()).ceil() + 8.0).exp2())*modulus.powi(-2)')