mypy icon indicating copy to clipboard operation
mypy copied to clipboard

"Name already defined" for PySide6 Property

Open rbits0 opened this issue 1 year ago • 1 comments

Bug Report

When using PySide6.QtCore.Property, I get a [no-redef] error on the property setter that says it's already defined. If this is a problem that PySide6 needs to fix I will create a bug report there instead.

To Reproduce

from PySide6 import QtCore, QtWidgets

class MyWidget(QtWidgets.QWidget):
    myNum: int = 0
    
    @QtCore.Property(int)
    def myProperty(self) -> int:
        return self.myNum
    
    @myProperty.setter
    def myProperty(self, value: int) -> None:
        self.myNum = value

Expected Behavior

If you use a python property, it gives no errors. Using QtCore.Property should also give no errors.

Actual Behavior

test.py:10: error: Name "myProperty" already defined on line 6  [no-redef]
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: 1.10.0
  • Mypy command-line flags: None
  • Mypy configuration options from mypy.ini (and other config files): None
  • Python version used: 3.11.9
  • PySide6 version used: 6.7.1 Link to PySide6: https://doc.qt.io/qtforpython-6/

rbits0 avatar Jun 19 '24 16:06 rbits0

Also when I try to assign a value to the property in one of my python files, I get error: Cannot assign to a method [method-assign]. However, I cannot reproduce this with another file.

rbits0 avatar Jun 19 '24 16:06 rbits0

bump, I still have the same issue on latest version of pyside and mypy, with the third party type stubs

kaneryu avatar Jul 03 '25 20:07 kaneryu

As a workaround, you can try using something like

from typing import TYPE_CHECKING
from PySide6 import QtCore, QtWidgets


if TYPE_CHECKING:
    QtIntProperty = property
else:
    QtIntProperty = QtCore.Property(int)


class MyWidget(QtWidgets.QWidget):
    myNum: int = 0

    @QtIntProperty
    def myProperty(self) -> int:
        return self.myNum
    
    @myProperty.setter
    def myProperty(self, value: int) -> None:
        self.myNum = value

Mypy special-cases @property. I'm not sure mypy could support arbitrary property-like decorators, but maybe there is some heuristic we can use to improve this situation.

cc @sterliakov since you did work on property checking recently

brianschubert avatar Jul 04 '25 00:07 brianschubert

Huh, this is hard. Unfortunately, properties implementation predates proper descriptors support, and it's one big pile of hack. If there was a decorator that is a drop-in replacement for property (like functools.cached_property with setter), it could've been supported by hardcoding another alias or exposing --property-decorator-fullnames flag. However, @QtCore.Property is substantially different - at least it needs an argument.

I'd say your workaround is the best we can offer right now.

With a minor tweak (below) we could also support a generalization of this pattern - using a bit of runtime magic to avoid defining alias for every property (make __class_getitem__ a constructor in if not TYPE_CHECKING), but that's borderline an antipattern I don't want to recommend in public... I'd only go that way if I needed a lot of different property types. If we start recognizing type applications in refers_to_fullname...

diff --git a/mypy/semanal.py b/mypy/semanal.py
index 431c5ec04..fb70cc64a 100644
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -7630,6 +7630,8 @@ def refers_to_fullname(node: Expression, fullnames: str | tuple[str, ...]) -> bo
     if not isinstance(fullnames, tuple):
         fullnames = (fullnames,)
 
+    if isinstance(node, IndexExpr):
+        node = node.base
     if not isinstance(node, RefExpr):
         return False
     if node.fullname in fullnames:

... the following would start working:

from typing import TYPE_CHECKING, TypeAlias


if TYPE_CHECKING:
    type QtProperty[T] = property
else:
    from PySide6 import QtCore
    
    class QtProperty(QtCore.Property):
        def __class_getitem__(cls, t):
            # Immediately forget about this class and return whatever runtime should've done
            return QtCore.Property(t)


class MyWidget:
    myNum: int = 0

    @QtProperty[int]
    def myProperty(self) -> int:
        return self.myNum
    
    @myProperty.setter
    def myProperty(self, value: int) -> None:
        self.myNum = value
        
reveal_type(MyWidget.myProperty)
reveal_type(MyWidget().myProperty)

Again, this is a bit of a hack, and I'm not sure it is worth supporting that way. How common is this Property? Can it be trivially replaced with builtins.property?

sterliakov avatar Jul 04 '25 01:07 sterliakov

The property cannot be replaced with the builtins.property. It is almost the same, with the addition that it is also a Qt property, which the builtin property does not do. QtCore.Property is very common, and is a intergral part of Qt, since it provides a few Qt-specific features like notifications, etc. Further reading: PySide6 Properties doc page

kaneryu avatar Jul 04 '25 06:07 kaneryu

Hm, okay, is the solution above acceptable? We can make it work with low efforts, while proper support will be quite challenging.

sterliakov avatar Jul 04 '25 23:07 sterliakov

That seems like a good idea, but how would we make it work with a situation like this?

    @Property(bool, notify=playingStatusChanged)
    def isPlaying(self):
        return self._playingStatus == PlayingStatus.PLAYING

This is the main way to use the a Property.

kaneryu avatar Jul 04 '25 23:07 kaneryu