This was reported by Agustin Barto:
class A1:
pass
class B:
a1 = None # type: A1 # Works fine
class C:
A1 = None # type: A1 # Complains about Invalid type "A1"
A1Alias = A1
class D:
A1 = None # type: A1Alias # Works
I wonder if the body of C should be valid?
There's a big code smell though, using the class name as the name for a variable containing instances of that class. I'm not sure if we should encourage that.
Reminds me a bit of #1776 though.
Same issue found with a method definition
class A: pass
class B:
def a(self) -> A: ...
def A(self) -> A: ...
def b(self) -> A: ...
gives
/tmp/asd.py: note: In function "A":
/tmp/asd.py:5: error: Invalid type "A"
/tmp/as3.py: note: In function "b":
/tmp/asd.py:6: error: Invalid type "A"
Also, the error is only raised after the line the name is redefined and then can't be used again.
It is clearly a code smell, but it was found in stdlib while typing datetime.datetime.tzinfo which return a tzinfo.
I'd introduce a type alias to work around the issue. For example:
class A: pass
_A = A
class B:
def a(self) -> _A: ...
def A(self) -> _A: ...
def b(self) -> _A: ...
@JukkaL that's working, I was finding a better example, but it seems that it was more related to #1637; thanks
I found other cases of this, even when the name is declared as an attribute. For example this:
class Sentence:
def __init__(self):
self.subject = "Bob"
self.verb = "writes"
self.object = "a letter"
def __eq__(self, other: object) -> bool:
return isinstance(other, Sentence) and other.verb == self.verb
This fails with Invalid type "object" on the definition of __eq__, just because there is an "self.object" attribute in the class, even if python's scope rules indicate that the annotation refers to the object builtin, not the object attribute.
(As a side note, this could get even more confusing with PEP526, you'll have a object: str declaration at the class body level)
Ran into this one tonight in the ulid-py library after chasing my tail a bit.
Example:
@property
def bytes(self) -> bytes:
"""
Computes the bytes value of the underlying :class:`~memoryview`.
:return: Memory in bytes form
:rtype: :class:`~bytes`
"""
return self.memory.tobytes()
Output:
⇒ mypy ulid
ulid/ulid.py:113: error: Invalid type "bytes"
It may be code smell (property matching a builtin type name) but it's following the same pattern as the stdlib uuid.UUID class.
I'll likely just define some type hint aliases to work-around the problem but it would be nice to see addressed if possible, or at the very least, a more "helpful" error message if this case is detectable vs. other "Invalid type" errors.
I think that in this case mypy doesn't treat the scopes the same way as Python does at runtime, and the example
@property # or without this
def bytes(self) -> bytes:
should be supported. Though note that the following will be wrong at runtime:
class C:
def bytes(self) -> bytes: ...
def frombytes(self, x: bytes): ...
Here, C.__annotations__['frombytes'] is a reference to the bytes method, not to the bytes type! So the problem is order-dependent, and defining an alias is probably a good defensive solution.
@gvanrossum Thanks for the detailed explanation; defining aliases for those name clashes worked out.
This works at least a little better now since mypy switched to the new semantic analyzer. Otherwise, the alias trick seems like a reasonable workaround.
Most helpful comment
I think that in this case mypy doesn't treat the scopes the same way as Python does at runtime, and the example
should be supported. Though note that the following will be wrong at runtime:
Here,
C.__annotations__['frombytes']is a reference to thebytesmethod, not to thebytestype! So the problem is order-dependent, and defining an alias is probably a good defensive solution.