attrs
attrs copied to clipboard
Type checking unexpectedly passing when misusing `default` and `converter`
I was playing around with type annotations and Attrs, and I think I may have found an issue.
Given the following Python 2/3 script:
from __future__ import absolute_import
from pprint import pprint as pp
import attr
def _convert_int(val):
# type: (int) -> int
if val <= 0:
return -1
return val
@attr.s
class A(object):
x = attr.ib(default=0, converter=_convert_int, type=int)
y = attr.ib(default=None, converter=_convert_int, type=int) # `default` is incompatible with our declared `type`
def main():
# type: () -> None
a1 = A(5, 5) # correct: type checks
pp(a1)
a2 = A(5, None) # correct: doesn't type check
pp(a2)
a3 = A(5) # incorrect: type checks but shouldn't!
pp(a3)
if __name__ == '__main__':
main()
I'd expect MyPy to complain about the relationship between default and converter for y. Nevertheless, I'm getting:

Which is one error less than expected.
Should I perhaps report this to MyPy or Typeshed as well/instead?
Is this related to #518? 🤔
I remember that there were issues with converters and typing but I don't remember the details.
Yeah this is the same bug. Converters are extremely hard to handle. :)
Just cause I am reading through issues: is the root problem not using converters.optional? Or alternatively, adding a None check in the local validator?
from pprint import pprint as pp
import attr
from attr import converters
def _convert_int(val):
# type: (int) -> int
if val <= 0:
return -1
return val
@attr.s
class A(object):
x = attr.ib(default=0, converter=_convert_int, type=int)
y = attr.ib(default=None, converter=converters.optional(_convert_int), type=int) # `default` is incompatible with our declared `type`
def main():
# type: () -> None
a1 = A(5, 5) # correct: type checks
pp(a1)
a2 = A(5, None) # correct: doesn't type check
pp(a2)
a3 = A(5) # incorrect: type checks but shouldn't!
pp(a3)
if __name__ == '__main__':
main()
seems to produce the expected response
@stevetarver In my example, I want y to not allow None.
In any case, I believe this goes beyond being an issue with converters only, since I would have expected the following excerpt to fail at class definition:
import attr
@attr.s
class Foo(object):
bar = attr.ib(default=None, type=int)
a = Foo(5)
b = Foo(None) # L10
c = Foo()
Strict MyPy v0.782 output (mypy --strict ~/tmp/test.py):
/Users/apizarro/tmp/test.py:10: error: Argument 1 to "Foo" has incompatible type "None"; expected "int"
Found 1 error in 1 file (checked 1 source file)