Repository: cython/cython

Issue:
[BUG] cascaded comparisons fail to compile in 3.0.0b2
### Describe the bug

```
$ git describe
3.0.0b2-26-g2ee78381c
```

The following lines:

https://github.com/PyWavelets/pywt/blob/7abdc623e5ba705d6ae61b58c19b45bdcadf1c67/pywt/_extensions/_pywt.pyx#L400-L402

```cython
            if not (1 == dec_lo.ndim == dec_hi.ndim ==
                         rec_lo.ndim == rec_hi.ndim):
                raise ValueError("All filters in filter bank must be 1D.")
```

fail with:

```
      <string>:366: UserWarning: Unrecognized setuptools command, proceeding with generating Cython sources and expanding templates
      WARNING  Overriding pythran description with argspec information for: numpy.random.binomial
      WARNING  Overriding pythran description with argspec information for: numpy.random.bytes
      WARNING  Overriding pythran description with argspec information for: numpy.random.chisquare
      WARNING  Overriding pythran description with argspec information for: numpy.random.choice
      WARNING  Overriding pythran description with argspec information for: numpy.random.dirichlet
      WARNING  Overriding pythran description with argspec information for: numpy.random.exponential
      WARNING  Overriding pythran description with argspec information for: numpy.random.f
      WARNING  Overriding pythran description with argspec information for: numpy.random.gamma
      WARNING  Overriding pythran description with argspec information for: numpy.random.geometric
      WARNING  Overriding pythran description with argspec information for: numpy.random.pareto
      WARNING  Overriding pythran description with argspec information for: numpy.random.gumbel
      WARNING  Overriding pythran description with argspec information for: numpy.random.poisson
      WARNING  Overriding pythran description with argspec information for: numpy.random.negative_binomial
      WARNING  Overriding pythran description with argspec information for: numpy.random.normal
      WARNING  Overriding pythran description with argspec information for: numpy.random.laplace
      WARNING  Overriding pythran description with argspec information for: numpy.random.logistic
      WARNING  Overriding pythran description with argspec information for: numpy.random.lognormal
      WARNING  Overriding pythran description with argspec information for: numpy.random.logseries
      WARNING  Overriding pythran description with argspec information for: numpy.random.power
      WARNING  Overriding pythran description with argspec information for: numpy.random.ranf
      WARNING  Overriding pythran description with argspec information for: numpy.random.randint
      WARNING  Overriding pythran description with argspec information for: numpy.random.random
      WARNING  Overriding pythran description with argspec information for: numpy.random.random_integers
      WARNING  Overriding pythran description with argspec information for: numpy.random.random_sample
      WARNING  Overriding pythran description with argspec information for: numpy.random.rayleigh
      WARNING  Overriding pythran description with argspec information for: numpy.random.sample
      WARNING  Overriding pythran description with argspec information for: numpy.random.standard_exponential
      WARNING  Overriding pythran description with argspec information for: numpy.random.standard_gamma
      WARNING  Overriding pythran description with argspec information for: numpy.random.standard_normal
      WARNING  Overriding pythran description with argspec information for: numpy.random.uniform
      WARNING  Overriding pythran description with argspec information for: numpy.random.weibull
      warning: pywt/_extensions/config.pxi:3:0: The 'DEF' statement is deprecated and will be removed in a future Cython version. Consider using global variables, constants, and in-place literals instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/_pywt.pxd:15:0: The 'IF' statement is deprecated and will be removed in a future Cython version. Consider using runtime conditions or C macros instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/config.pxi:3:0: The 'DEF' statement is deprecated and will be removed in a future Cython version. Consider using global variables, constants, and in-place literals instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/c_wt.pxd:89:4: The 'IF' statement is deprecated and will be removed in a future Cython version. Consider using runtime conditions or C macros instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/config.pxi:3:0: The 'DEF' statement is deprecated and will be removed in a future Cython version. Consider using global variables, constants, and in-place literals instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/_pywt.pxd:15:0: The 'IF' statement is deprecated and will be removed in a future Cython version. Consider using runtime conditions or C macros instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/config.pxi:3:0: The 'DEF' statement is deprecated and will be removed in a future Cython version. Consider using global variables, constants, and in-place literals instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/_dwt.pyx:60:4: The 'IF' statement is deprecated and will be removed in a future Cython version. Consider using runtime conditions or C macros instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/_dwt.pyx:144:4: The 'IF' statement is deprecated and will be removed in a future Cython version. Consider using runtime conditions or C macros instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/_dwt.pyx:223:4: The 'IF' statement is deprecated and will be removed in a future Cython version. Consider using runtime conditions or C macros instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/_dwt.pyx:314:4: The 'IF' statement is deprecated and will be removed in a future Cython version. Consider using runtime conditions or C macros instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/_dwt.pyx:389:8: The 'IF' statement is deprecated and will be removed in a future Cython version. Consider using runtime conditions or C macros instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/_dwt.pyx:479:8: The 'IF' statement is deprecated and will be removed in a future Cython version. Consider using runtime conditions or C macros instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/config.pxi:3:0: The 'DEF' statement is deprecated and will be removed in a future Cython version. Consider using global variables, constants, and in-place literals instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/c_wt.pxd:89:4: The 'IF' statement is deprecated and will be removed in a future Cython version. Consider using runtime conditions or C macros instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/config.pxi:3:0: The 'DEF' statement is deprecated and will be removed in a future Cython version. Consider using global variables, constants, and in-place literals instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/_pywt.pxd:15:0: The 'IF' statement is deprecated and will be removed in a future Cython version. Consider using runtime conditions or C macros instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/config.pxi:3:0: The 'DEF' statement is deprecated and will be removed in a future Cython version. Consider using global variables, constants, and in-place literals instead. See https://github.com/cython/cython/issues/4310
      warning: pywt/_extensions/c_wt.pxd:89:4: The 'IF' statement is deprecated and will be removed in a future Cython version. Consider using runtime conditions or C macros instead. See https://github.com/cython/cython/issues/4310

      Error compiling Cython file:
      ------------------------------------------------------------
      ...
                      rec_lo = np.asarray(filters[2], dtype=np.float64)
                      rec_hi = np.asarray(filters[3], dtype=np.float64)
                  except TypeError:
                      raise ValueError("Filter bank with numeric values required.")

                  if not (1 == dec_lo.ndim == dec_hi.ndim ==
                                           ^
      ------------------------------------------------------------

      pywt/_extensions/_pywt.pyx:400:37: Compiler crash in AnalyseExpressionsTransform

      ModuleNode.body = StatListNode(_pywt.pyx:6:0)
      StatListNode.stats[23] = StatListNode(_pywt.pyx:331:5)
      StatListNode.stats[0] = CClassDefNode(_pywt.pyx:331:5,
          as_name = 'Wavelet',
          class_name = 'Wavelet',
          doc = '\n    Wavelet(name, filter_bank=None) object describe properties of\n    a wavelet identified by name.\n\n    In order to use a built-in wavelet the parameter name must be\n    a valid name from the wavelist() list.\n    To create a custom wavelet object, filter_bank parameter must\n    be specified. It can be either a list of four filters or an object\n    that a `filter_bank` attribute which returns a list of four\n    filters - just like the Wavelet instance itself.\n\n    ',
          module_name = '',
          objstruct_name = 'WaveletObject',
          punycode_class_name = 'Wavelet',
          typeobj_name = 'WaveletType',
          visibility = 'public')
      CClassDefNode.body = StatListNode(_pywt.pyx:332:4)
      StatListNode.stats[0] = DefNode(_pywt.pyx:345:4,
          modifiers = [...]/0,
          name = '__cinit__',
          np_args_idx = [...]/0,
          num_required_args = 1,
          outer_attrs = [...]/2,
          py_wrapper_required = True,
          reqd_kw_flags_cname = '0',
          used = True)
      File 'Nodes.py', line 397, in analyse_expressions: StatListNode(_pywt.pyx:346:8)
      File 'Nodes.py', line 7027, in analyse_expressions: IfStatNode(_pywt.pyx:354:8)
      File 'Nodes.py', line 397, in analyse_expressions: StatListNode(_pywt.pyx:369:12)
      File 'Nodes.py', line 7025, in analyse_expressions: IfStatNode(_pywt.pyx:400:12)
      File 'Nodes.py', line 7071, in analyse_expressions: IfClauseNode(_pywt.pyx:400:15,
          is_terminator = True)
      File 'ExprNodes.py', line 657, in analyse_temp_boolean_expression: NotNode(_pywt.pyx:400:15,
          infix = True,
          operator = '!',
          result_is_used = True,
          use_managed_ref = True)
      File 'ExprNodes.py', line 10703, in analyse_types: NotNode(_pywt.pyx:400:15,
          infix = True,
          operator = '!',
          result_is_used = True,
          use_managed_ref = True)
      File 'ExprNodes.py', line 13404, in coerce_to_boolean: PrimaryCmpNode(_pywt.pyx:400:22,
          is_temp = 1,
          operator = '==',
          result_is_used = True,
          special_bool_cmp_function = '__Pyx_PyInt_BoolEqCObj',
          use_managed_ref = True)
      File 'ExprNodes.py', line 13544, in optimise_comparison: CascadedCmpNode(_pywt.pyx:400:37,
          operator = '==')
      File 'ExprNodes.py', line 13096, in find_special_bool_compare_function: CascadedCmpNode(_pywt.pyx:400:37,
          operator = '==')

      Compiler crash traceback from this point on:
        File "/home/tcaswell/.virtualenvs/bleeding/lib/python3.12/site-packages/Cython/Compiler/ExprNodes.py", line 13096, in find_special_bool_compare_function
          self.operand1,
          ^^^^^^^^^^^^^
      AttributeError: 'CascadedCmpNode' object has no attribute 'operand1'
      Compiling pywt/_extensions/_pywt.pyx because it changed.
      Compiling pywt/_extensions/_dwt.pyx because it changed.
      Compiling pywt/_extensions/_swt.pyx because it changed.
      Compiling pywt/_extensions/_cwt.pyx because it changed.
      [1/4] Cythonizing pywt/_extensions/_cwt.pyx
      [2/4] Cythonizing pywt/_extensions/_dwt.pyx
      [3/4] Cythonizing pywt/_extensions/_pywt.pyx
      Traceback (most recent call last):
        File "/home/tcaswell/.virtualenvs/bleeding/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 353, in <module>
          main()
        File "/home/tcaswell/.virtualenvs/bleeding/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 335, in main
          json_out['return_val'] = hook(**hook_input['kwargs'])
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/home/tcaswell/.virtualenvs/bleeding/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 149, in prepare_metadata_for_build_wheel
          return hook(metadata_directory, config_settings)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/home/tcaswell/.virtualenvs/bleeding/lib/python3.12/site-packages/setuptools/build_meta.py", line 377, in prepare_metadata_for_build_wheel
          self.run_setup()
        File "/home/tcaswell/.virtualenvs/bleeding/lib/python3.12/site-packages/setuptools/build_meta.py", line 485, in run_setup
          self).run_setup(setup_script=setup_script)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/home/tcaswell/.virtualenvs/bleeding/lib/python3.12/site-packages/setuptools/build_meta.py", line 335, in run_setup
          exec(code, locals())
        File "<string>", line 478, in <module>
        File "<string>", line 470, in setup_package
        File "/home/tcaswell/.virtualenvs/bleeding/lib/python3.12/site-packages/Cython/Build/Dependencies.py", line 1134, in cythonize
          cythonize_one(*args)
        File "/home/tcaswell/.virtualenvs/bleeding/lib/python3.12/site-packages/Cython/Build/Dependencies.py", line 1301, in cythonize_one
          raise CompileError(None, pyx_file)
      Cython.Compiler.Errors.CompileError: pywt/_extensions/_pywt.pyx
      [end of output]

```

### Code to reproduce the behaviour:

```cython
            if not (1 == dec_lo.ndim == dec_hi.ndim ==
                         rec_lo.ndim == rec_hi.ndim):
                raise ValueError("All filters in filter bank must be 1D.")
```


### Expected behaviour

_No response_

### OS

linux

### Python version

main branch

### Cython version

3.0.0b2-26-g2ee78381c

### Additional context

_No response_


Discussion:
```diff
diff --git a/pywt/_extensions/_pywt.pyx b/pywt/_extensions/_pywt.pyx
index 17e22cc..cb6fd14 100644
--- a/pywt/_extensions/_pywt.pyx
+++ b/pywt/_extensions/_pywt.pyx
@@ -397,8 +397,12 @@ cdef public class Wavelet [type WaveletType, object WaveletObject]:
             except TypeError:
                 raise ValueError("Filter bank with numeric values required.")

-            if not (1 == dec_lo.ndim == dec_hi.ndim ==
-                         rec_lo.ndim == rec_hi.ndim):
+            if not (
+                    1 == dec_lo.ndim and
+                    1 == dec_hi.ndim and
+                    1 == rec_lo.ndim and
+                    1 == rec_hi.ndim
+            ):
                 raise ValueError("All filters in filter bank must be 1D.")

             filter_length = len(dec_lo)
```

This patch fixes pywt.  I'm happy to push this downstream to them, but I think chained `==` should work in cython?
My suspicion is that this can be fixed in Cython by replacing `self.operand1` with `operand1` (and no `self`).

This would certainly be intended to work 

Resolving patch:
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -13087,7 +13087,7 @@ def find_special_bool_compare_function(self, env, operand1, result_is_bool=False
                         "Eq" if self.operator == "==" else "Ne",
                         self,
                         PyrexTypes.c_bint_type,
-                        self.operand1,
+                        operand1,
                         self.operand2
                     )
                     if result:
@@ -13162,8 +13162,10 @@ def generate_operation_code(self, code, result_code,
 
         elif operand1.type.is_pyobject and op not in ('is', 'is_not'):
             assert op not in ('in', 'not_in'), op
-            code.putln("%s = PyObject_RichCompare(%s, %s, %s); %s%s" % (
+            assert self.type.is_pyobject or self.type is PyrexTypes.c_bint_type
+            code.putln("%s = PyObject_RichCompare%s(%s, %s, %s); %s%s" % (
                     result_code,
+                    "" if self.type.is_pyobject else "Bool",
                     operand1.py_result(),
                     operand2.py_result(),
                     richcmp_constants[op],
@@ -13259,6 +13261,12 @@ def compile_time_value(self, denv):
         operand1 = self.operand1.compile_time_value(denv)
         return self.cascaded_compile_time_value(operand1, denv)
 
+    def unify_cascade_type(self):
+        cdr = self.cascade
+        while cdr:
+            cdr.type = self.type
+            cdr = cdr.cascade
+
     def analyse_types(self, env):
         self.operand1 = self.operand1.analyse_types(env)
         self.operand2 = self.operand2.analyse_types(env)
@@ -13337,10 +13345,7 @@ def analyse_types(self, env):
             self.type = PyrexTypes.py_object_type
         else:
             self.type = PyrexTypes.c_bint_type
-        cdr = self.cascade
-        while cdr:
-            cdr.type = self.type
-            cdr = cdr.cascade
+        self.unify_cascade_type()
         if self.is_pycmp or self.cascade or self.special_bool_cmp_function:
             # 1) owned reference, 2) reused value, 3) potential function error return value
             self.is_temp = 1
@@ -13399,6 +13404,7 @@ def coerce_to_boolean(self, env):
                         self.operand2, env, result_is_bool=True)
                     if operand2 is not self.operand2:
                         self.coerced_operand2 = operand2
+                self.unify_cascade_type()
                 return self
         # TODO: check if we can optimise parts of the cascade here
         return ExprNode.coerce_to_boolean(self, env)


Test patch:
diff --git a/tests/run/cascmp.pyx b/tests/run/cascmp.pyx
--- a/tests/run/cascmp.pyx
+++ b/tests/run/cascmp.pyx
@@ -36,3 +36,69 @@ def const_cascade(x):
         1 <= 1 <= x <= 2 <= 3 > x <= 2 <= 2,
         1 <= 1 <= x <= 1 <= 1 <= x <= 2,
     )
+
+def eq_if_statement(a, b, c):
+    """
+    >>> eq_if_statement(1, 2, 3)
+    False
+    >>> eq_if_statement(2, 3, 4)
+    False
+    >>> eq_if_statement(1, 1, 2)
+    False
+    >>> eq_if_statement(1, "not an int", 2)
+    False
+    >>> eq_if_statement(2, 1, 1)
+    False
+    >>> eq_if_statement(1, 1, 1)
+    True
+    """
+    if 1 == a == b == c:
+        return True
+    else:
+        return False
+
+def eq_if_statement_semi_optimized(a, int b, int c):
+    """
+    Some but not all of the cascade ends up optimized
+    (probably not as much as should be). The test is mostly
+    that it keeps the types consistent throughout
+
+    >>> eq_if_statement_semi_optimized(1, 2, 3)
+    False
+    >>> eq_if_statement_semi_optimized(2, 3, 4)
+    False
+    >>> eq_if_statement_semi_optimized(1, 1, 2)
+    False
+    >>> eq_if_statement_semi_optimized("not an int", 1, 2)
+    False
+    >>> eq_if_statement_semi_optimized(2, 1, 1)
+    False
+    >>> eq_if_statement_semi_optimized(1, 1, 1)
+    True
+    """
+    if 1 == a == b == c == 1:
+        return True
+    else:
+        return False
+
+def eq_if_statement_semi_optimized2(a, b, c):
+    """
+    Here only "b==c" fails to optimize
+
+    >>> eq_if_statement_semi_optimized2(1, 2, 3)
+    False
+    >>> eq_if_statement_semi_optimized2(2, 3, 4)
+    False
+    >>> eq_if_statement_semi_optimized2(1, 1, 2)
+    False
+    >>> eq_if_statement_semi_optimized2(1, "not an int", 2)
+    False
+    >>> eq_if_statement_semi_optimized2(2, 1, 1)
+    False
+    >>> eq_if_statement_semi_optimized2(1, 1, 1)
+    True
+    """
+    if 1 == a == 1 == b == c:
+        return True
+    else:
+        return False