Repository: python/mypy

Issue:
mypyc generates code that triggers a CPython assertion
**Crash Report**

`mypyc` generates code that triggers a CPython assertion when a subclass inherits `__call__` from a superclass.

**To Reproduce**

```console
$ cat test.py
class Base:
    def __call__(self) -> None:
        return None

class Derived(Base):
    pass
$ mypyc test.py >/dev/null
$ python3-dbg -c 'import test'
../Objects/typeobject.c:5392: PyType_Ready: Assertion "type->tp_call != ((void *)0)" failed
```

This ends with a segfault as well, but the segfault isn't really interesting - `_PyObject_ASSERT` on failure makes a call to `_PyObject_Dump`, which calls into `PyObject_Print`, which fails because we're in the middle of `PyType_Ready` and `tp_dict` hasn't been created yet.

The assertion error seems to be a genuine bug, though I'm not sure whether it's a bug in CPython or in mypyc. The failing assertion is:

```cpp
    if (type->tp_flags & Py_TPFLAGS_HAVE_VECTORCALL) {
        _PyObject_ASSERT((PyObject *)type, type->tp_vectorcall_offset > 0);
        _PyObject_ASSERT((PyObject *)type, type->tp_call != NULL);
    }
```

The second of these two assertions fails (the first succeeds). I'm not sure whether the bug is in CPython (perhaps the assertion is wrong, and it's OK to have `tp_call` set to NULL when `Py_TPFLAGS_HAVE_VECTORCALL` is set if `PyType_Ready` will inherit `tp_call` from the parent class), or whether the bug is in mypyc (perhaps it's not legal to set `Py_TPFLAGS_HAVE_VECTORCALL` when inheriting `tp_call`).

**Your Environment**

- Mypy version used: mypy 0.942
- Mypy command-line flags: None
- Mypy configuration options from `mypy.ini` (and other config files): None
- Python version used: python3.9-dbg, python3.10-dbg
- Operating system and version: RHEL 7, Debian Bullseye


Discussion:
This is the root cause of https://github.com/psf/black/issues/2845
I wonder if the assertion is checking a valid assumption, but in the wrong place: I wonder if it should be checking whether `tp_call` is non-null _after_ `PyType_Ready` populates derived class slots, instead of before - that is, whether it should be checking this as a post-condition of `PyType_Ready` rather than a precondition.
I'm working on this.

Resolving patch:
diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py
--- a/mypyc/codegen/emitclass.py
+++ b/mypyc/codegen/emitclass.py
@@ -322,6 +322,10 @@ def emit_line() -> None:
         fields['tp_vectorcall_offset'] = 'offsetof({}, vectorcall)'.format(
             cl.struct_name(emitter.names))
         flags.append('_Py_TPFLAGS_HAVE_VECTORCALL')
+        if not fields.get('tp_vectorcall'):
+            # This is just a placeholder to please CPython. It will be
+            # overriden during setup.
+            fields['tp_call'] = 'PyVectorcall_Call'        
     fields['tp_flags'] = ' | '.join(flags)
 
     emitter.emit_line(f"static PyTypeObject {emitter.type_struct_name(cl)}_template_ = {{")


Test patch:
diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test
--- a/mypyc/test-data/run-classes.test
+++ b/mypyc/test-data/run-classes.test
@@ -2206,3 +2206,13 @@ def test_serializable_sub_class_call_new() -> None:
     base: NonSerializable = sub
     with assertRaises(AttributeError):
         base.s
+
+[case testClassWithInherited__call__]
+class Base:
+    def __call__(self) -> int:
+        return 1
+
+class Derived(Base):
+    pass
+
+assert Derived()() == 1