Repository: cython/cython

Issue:
[BUG] postincrement in C++ handled incorrectly
**Describe the bug**
In Cython both `cython.operator.preincrement` and `cython.operator.postincrement` are handled as `preincrement`.  However in C++ these have two different operators (see https://en.cppreference.com/w/cpp/language/operators):

|Expression | As member function |
|----------------|----------------------------|
| `@a`            | (a).operator@()        |
| `a@`            | (a).operator@(0)      |

**To Reproduce**
Since both operators are treated as `@a` the following code does not compile
```cython
cimport cython

cdef extern from "*":
    cdef cppclass example:
        example operator++(int)

def test():
    cdef example a
    cython.operator.postincrement(a)
```
while the following incorrectly does compile:
```cython
cimport cython

cdef extern from "*":
    cdef cppclass example:
        example operator++()

def test():
    cdef example a
    cython.operator.postincrement(a)
```

**Expected behavior**
The operator behavior should match the behavior used in C++. So `postincrement` should only work when the corresponding operator exists.

**Environment (please complete the following information):**
 - OS: Linux
 - Python version 3.9
 - Cython version 3.0.0a9



Discussion:
If I understand this correctly, the issue is in Cython identifying whether the operator is available? Looking at https://github.com/cython/cython/blob/b6a4215211839afaa850538cdbb065e3513e0dd0/Cython/Compiler/ExprNodes.py#L10494-L10498 I think the correct C++ is generated?

Resolving patch:
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -10423,6 +10423,7 @@ class UnopNode(ExprNode):
 
     subexprs = ['operand']
     infix = True
+    is_inc_dec_op = False
 
     def calculate_constant_result(self):
         func = compile_time_unary_operators[self.operator]
@@ -10534,7 +10535,10 @@ def type_error(self):
         self.type = PyrexTypes.error_type
 
     def analyse_cpp_operation(self, env, overload_check=True):
-        entry = env.lookup_operator(self.operator, [self.operand])
+        operand_types = [self.operand.type]
+        if self.is_inc_dec_op and not self.is_prefix:
+            operand_types.append(PyrexTypes.c_int_type)
+        entry = env.lookup_operator_for_types(self.pos, self.operator, operand_types)
         if overload_check and not entry:
             self.type_error()
             return
@@ -10548,7 +10552,12 @@ def analyse_cpp_operation(self, env, overload_check=True):
         else:
             self.exception_check = ''
             self.exception_value = ''
-        cpp_type = self.operand.type.find_cpp_operation_type(self.operator)
+        if self.is_inc_dec_op and not self.is_prefix:
+            cpp_type = self.operand.type.find_cpp_operation_type(
+                self.operator, operand_type=PyrexTypes.c_int_type
+            )
+        else:
+            cpp_type = self.operand.type.find_cpp_operation_type(self.operator)
         if overload_check and cpp_type is None:
             error(self.pos, "'%s' operator not defined for %s" % (
                 self.operator, type))
@@ -10690,6 +10699,17 @@ def calculate_result_code(self):
 
 class DecrementIncrementNode(CUnopNode):
     #  unary ++/-- operator
+    is_inc_dec_op = True
+
+    def type_error(self):
+        if not self.operand.type.is_error:
+            if self.is_prefix:
+                error(self.pos, "No match for 'operator%s' (operand type is '%s')" %
+                    (self.operator, self.operand.type))
+            else:
+                error(self.pos, "No 'operator%s(int)' declared for postfix '%s' (operand type is '%s')" %
+                    (self.operator, self.operator, self.operand.type))
+        self.type = PyrexTypes.error_type
 
     def analyse_c_operation(self, env):
         if self.operand.type.is_numeric:


Test patch:
diff --git a/tests/errors/cpp_increment.pyx b/tests/errors/cpp_increment.pyx
new file mode 100644
--- /dev/null
+++ b/tests/errors/cpp_increment.pyx
@@ -0,0 +1,33 @@
+# mode: error
+
+cimport cython
+
+cdef extern from *:
+    cdef cppclass Foo:
+        Foo operator++()
+        Foo operator--()
+
+    cdef cppclass Bar:
+        Bar operator++(int)
+        Bar operator--(int)
+
+cdef void foo():
+    cdef Foo f
+    cdef Bar b
+    cython.operator.postincrement(f)
+    cython.operator.postincrement(b)
+    cython.operator.postdecrement(f)
+    cython.operator.postdecrement(b)
+
+    cython.operator.preincrement(f)
+    cython.operator.preincrement(b)
+    cython.operator.predecrement(f)
+    cython.operator.predecrement(b)
+
+
+_ERRORS = u"""
+17:19: No 'operator++(int)' declared for postfix '++' (operand type is 'Foo')
+19:19: No 'operator--(int)' declared for postfix '--' (operand type is 'Foo')
+23:19: No match for 'operator++' (operand type is 'Bar')
+25:19: No match for 'operator--' (operand type is 'Bar')
+"""