KotlinIsland / basedmypy

Based Python static type checker with baseline, sane default settings and based typing features
https://kotlinisland.github.io/basedmypy/
Other
144 stars 4 forks source link

Support asymmetric properties #489

Open sterliakov opened 1 year ago

sterliakov commented 1 year ago

Currently asymmetric properties are not supported by mypy. It mypy this is considered a bug with high priority (issue).

basemypy decided to go this way further and assume that properties are symmetric even without annotations on setter. To quote docs:

The types in overload implementations (including properties) can be inferred:

class A:
   @property
   def foo(self) -> int: ...
   @foo.setter
   def foo(self, value): ...  # no need for annotations

I suggest to think about this solution and consider allowing asymmetric properties, because cases like below with value normalization are quire common.

Code sample:

from collections.abc import Iterable

class A:
    def __init__(self):
        self._foo: list[str] = []

    @property
    def foo(self) -> list[str]:
        return self._foo

    @foo.setter
    def foo(self, __val: Iterable[str]):
        self._foo = list(__val)

a = A()
reveal_type(a.foo)  # N: Revealed type is "list[str]"
a.foo = ('a',)  # E: Incompatible types in assignment (expression has type "(str,)", variable has type "list[str]")  [assignment]
KotlinIsland commented 1 year ago

I think that asymmetric properties sound very based and I have personally voted for that issue. I'd totally add support for them if that's what the community is asking for.

The current functionality of basedmypy is that the types on setters and deleters are inferred from the type of the getter. This is not enforced and is only a convenience feature, you are free to override the inference by manually specifying the annotations:

class A:
    _foo = 0
    @property
    def foo(self) -> int:
        return self._foo
    @foo.setter
    def(self, value: object):
        self._foo = value if isinstance(value, int) else 0

Obviously you will currently see the same error as you would with mypy.