|
| 1 | +.. mypy-correctness: |
| 2 | +
|
| 3 | +Improving the Quality of Signatures with mypy |
| 4 | +============================================= |
| 5 | + |
| 6 | +Preliminary |
| 7 | +----------- |
| 8 | + |
| 9 | +The Python Interface files of PySide are generated by a few scripts. |
| 10 | +When ``.pyi`` files were started in 2017, a minimal syntax check was |
| 11 | +possible because these files could be run in ``Python`` itself. |
| 12 | + |
| 13 | +Some changes to the format of ``.pyi`` files made that impossible, leaving |
| 14 | +``PySide``'s ``.pyi`` files quite unchecked for years. Only correct parsing of |
| 15 | +all functions could be checked by the generator. |
| 16 | + |
| 17 | +The introduction of the ``mypy`` tool as a rigorous error checker for the |
| 18 | +generated files brought many improvements, but also some surprizes. |
| 19 | + |
| 20 | + |
| 21 | +Running the mypy Tests |
| 22 | +---------------------- |
| 23 | + |
| 24 | +The ``mypy`` tests are automatically run by the Qt company CI framework (COIN). |
| 25 | +When you have ``mypy`` installed, the tests are run when building with tests. |
| 26 | +In debug mode, this can take more than 30 s, therefore we provide the |
| 27 | +translation option |
| 28 | + |
| 29 | +.. code-block:: shell |
| 30 | +
|
| 31 | + --skip-mypy-test |
| 32 | +
|
| 33 | +which can be used when repeatedly translating. But note that ``mypy`` has a |
| 34 | +good cache that suppresses analysis of unchanged ``.pyi`` files. |
| 35 | + |
| 36 | + |
| 37 | +Types of mypy Errors |
| 38 | +-------------------- |
| 39 | + |
| 40 | +Duplication Errors |
| 41 | +~~~~~~~~~~~~~~~~~~ |
| 42 | + |
| 43 | +Many functions have multiple signatures, which are later translated to multiple |
| 44 | +``typing.overload`` versions in the ``.pyi`` file. |
| 45 | +Due to the mapping of ``C++`` functions to ``Python`` it sometimes happens |
| 46 | +that similar ``C++`` functions become ``Python`` duplicates. This was simple |
| 47 | +to filter out, but ``mypy`` still finds duplicates which differ only in parameter |
| 48 | +names. This is now handled by the function ``remove_ambiguous_signatures()`` |
| 49 | +in module ``layout`` that compares the so-called ``annotations`` which ignore |
| 50 | +parameter names. |
| 51 | + |
| 52 | + |
| 53 | +Shadowing Errors |
| 54 | +~~~~~~~~~~~~~~~~ |
| 55 | + |
| 56 | +A quite subtle error type is the shadowing of multiple signatures. This is due |
| 57 | +to the sequential nature of ``.pyi`` files:: |
| 58 | + |
| 59 | + * In ``C++``, the order of functions does not matter at all. The best fit is |
| 60 | + automatically used. |
| 61 | + |
| 62 | + * In Python stub files, the alternatives of multiple signatures are sequentially |
| 63 | + checked in ``@typing.overload`` chains of functions. |
| 64 | + This can produce shadowing when an annotation contains another. |
| 65 | + |
| 66 | +An Example: :class:`PySide6.QtCore.QCborSimpleType` is shadowed by int |
| 67 | +when int is listed first. That is due to the Method Resolution Order ``mro()``:: |
| 68 | + |
| 69 | + * int.mro() [<class 'int'>, <class 'object'>] |
| 70 | + |
| 71 | + * QCborSimpleType.mro() [<enum 'QCborSimpleType'>, <enum 'IntEnum'>, |
| 72 | + <class 'int'>, <enum 'ReprEnum'>, |
| 73 | + <enum 'Enum'>, <class 'object'>] |
| 74 | + |
| 75 | +You see that the ``mro()`` has an ordering effect on the multiple signatures. |
| 76 | +The enum inherits from ``int`` and should come before the ``int`` entry. |
| 77 | +The whole task of bringing the multiple signatures into a conflict-free order |
| 78 | +is a sort of ``Topological Sorting``. |
| 79 | + |
| 80 | +We build a sorting key using the length of the ``mro`` of the argument annotations |
| 81 | +and some additional heuristics. They can be inspected in function ``get_ordering_key()`` |
| 82 | +that is called by ``sort_by_inheritance()`` in module ``layout``. |
| 83 | + |
| 84 | + |
| 85 | +Unsolvable Errors |
| 86 | +----------------- |
| 87 | + |
| 88 | +Some errors are pointed out by mypy that we cannot solve. The only chance we have is |
| 89 | +to disable these errors partially or even completely. They are marked in the ``.pyi`` files, |
| 90 | +see below. |
| 91 | + |
| 92 | + |
| 93 | +Contradiction to Qt |
| 94 | +~~~~~~~~~~~~~~~~~~~ |
| 95 | + |
| 96 | +Errors are found by mypy where Qt has a different opinion. The error types |
| 97 | +"override" and "overload-overlap" needed to be disabled because we cannot |
| 98 | +change what Qt thinks is right. |
| 99 | + |
| 100 | +Examples: |
| 101 | + |
| 102 | +:: |
| 103 | + |
| 104 | + Error code "override" cannot be fixed because the problem |
| 105 | + is situated in Qt itself: |
| 106 | + |
| 107 | + Signature of "open" incompatible with supertype "QFile" |
| 108 | + |
| 109 | + Error code "overload-overlap" also cannot be fixed because |
| 110 | + we have no chance to modify return-types: |
| 111 | + |
| 112 | + Overloaded function signatures 1 and 6 overlap with |
| 113 | + incompatible return types |
| 114 | + |
| 115 | +They are globally disabled by the comment:: |
| 116 | + |
| 117 | + # mypy: disable-error-code="override, overload-overlap" |
| 118 | + |
| 119 | +Other errors like "misc" are too broad to be prematurely disabled. |
| 120 | +See below how we handle them. |
| 121 | + |
| 122 | + |
| 123 | +Disagreement with __add__ and __iadd__ |
| 124 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 125 | + |
| 126 | +There are internal rules for ``Python`` which can only be recognized when |
| 127 | +``mypy`` points them out as "misc". There are functions which come in pairs: |
| 128 | + |
| 129 | +.. code-block:: python |
| 130 | +
|
| 131 | + __add__, __iadd__, __sub__, __isub__, __mul__, __imul__, ... |
| 132 | +
|
| 133 | +and more. There is this rule:: |
| 134 | + |
| 135 | + if __add__ and __iadd__ exist in a type, the signatures must be the same. |
| 136 | + |
| 137 | +In 95 % this rule is fulfilled, but in a few cases it is not. There we have |
| 138 | +to compute these cases, and if they disagree we generate a disabling ``mypy`` |
| 139 | +inline comment "# type: ignore[misc]". You can see this functionality in |
| 140 | +``ExactEnumerator.klass`` of module ``enum_sig``. |
| 141 | + |
| 142 | + |
| 143 | +Disagreement with inconsistent overloads |
| 144 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 145 | + |
| 146 | +If there is a mixed overloading of methods and static or class methods, mypy |
| 147 | +believes this is an error. In a few cases we have this situation, and since |
| 148 | +this is again treated as a "misc" error, we only disable this when it |
| 149 | +happens. See function ``is_inconsistent_overload()`` of module |
| 150 | +``pyi_generator`` which checks if "self" is always or never an argument. |
| 151 | +This is again marked by an inline comment "# type: ignore[misc]". |
| 152 | + |
| 153 | + |
| 154 | +Conclusion and Future |
| 155 | +--------------------- |
| 156 | + |
| 157 | +This effort has brought the reported ``mypy`` errors from 601 down to zero, which |
| 158 | +is really an improvement. But there can be done more. Although we now know that we |
| 159 | +are generating syntactically and semantically quite correct files, we still do not know |
| 160 | +whether the real types really fulfil the requirements of ``mypy``. |
| 161 | + |
| 162 | +There is a ``stubtest`` module in ``mypy`` which we might perhaps use to do even |
| 163 | +more tests. These would check if the implementation and stub files agree. |
| 164 | + |
| 165 | + |
| 166 | +Literature |
| 167 | +---------- |
| 168 | + |
| 169 | +* `mypy error codes <https://mypy.readthedocs.io/en/stable/error_code_list.html>`__ |
| 170 | + We use these by default enabled codes. |
| 171 | + |
| 172 | +* `typing — Support for type hints <https://docs.python.org/3/library/typing.html>`__ |
| 173 | + The Python documentation of the typing module |
| 174 | + |
| 175 | +* `Typing cheat sheet <https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html>`__ |
| 176 | + A quick overview of type hints (hosted at the mypy docs) |
| 177 | + |
| 178 | +* "Type System Reference" section of `the mypy docs <https://mypy.readthedocs.io/en/stable/index.html>`__ |
| 179 | + The Python typing system is standardised via PEPs, so this reference should |
| 180 | + broadly apply to most Python type checkers. (Some parts may still be specific to mypy.) |
| 181 | + |
| 182 | +* `Static Typing with Python <https://typing.readthedocs.io/en/latest/>`__ |
| 183 | + Type-checker-agnostic documentation written by the community detailing type system features, useful typing related tools and typing best practices. |
| 184 | + |
| 185 | +* `Specification for the Python type system <https://typing.readthedocs.io/en/latest/spec/index.html>`__ |
| 186 | + The complete specification. Quite exhaustive. |
0 commit comments