Improve handling of attribute access on class objects#14988
Improve handling of attribute access on class objects#14988ilevkivskyi merged 3 commits intopython:masterfrom
Conversation
| if ( | ||
| isinstance(node.node, Var) | ||
| and not node.node.is_classvar | ||
| and not hook |
There was a problem hiding this comment.
and not hook was required here, or this test started to fail:
mypy/test-data/unit/check-custom-plugin.test
Lines 974 to 990 in 89469ac
| # In typeshed's stub for builtins.pyi, __annotations__ is `dict[str, Any]`, | ||
| # but it's inferred as `Mapping[str, object]` here due to the fixture we're using | ||
| reveal_type(X.__annotations__) # N: Revealed type is "typing.Mapping[builtins.str, builtins.object]" | ||
|
|
||
| [builtins fixtures/dict.pyi] |
There was a problem hiding this comment.
This test needed to be altered slightly, as with this change, mypy starts inferring the correct type of X.__annotations__ according to the fixture that this test uses:
mypy/test-data/unit/fixtures/dict.pyi
Lines 16 to 17 in 89469ac
| bar: ClassVar[str] = 'bar' | ||
| eggs: str | ||
| spam: ClassVar[str] = 'spam' |
There was a problem hiding this comment.
You could argue that bar and spam here violate LSP (since setting a class variable on a class is ~basically the same as setting an instance variable on a metaclass). This PR doesn't change the behaviour there, however: mypy emits no errors here on master, and doesn't with this PR applied either.
This comment has been minimized.
This comment has been minimized.
|
Mypy_primer analysis:
|
JelleZijlstra
left a comment
There was a problem hiding this comment.
Looks good, but this isn't an area of mypy that I'm familiar with.
Maybe a test case involving a generic metaclass would be helpful.
|
Thanks for the review, appreciate it!
The new behaviour doesn't work with generic metaclasses. But I don't think that's really anything to do with this PR: mypy in general hates generic metaclasses: from typing import TypeVar, Generic
T = TypeVar("T")
class Meta(type, Generic[T]):
attr: T
class Foo(metaclass=Meta[int]): ... # error: Dynamic metaclass not supported for "Foo" [misc]https://mypy-play.net/?mypy=latest&python=3.11&gist=f360b7b7366c4618ac0b6d79f0e404b9 Mypy emits the same "dynamic metaclasses not supported" error with this PR, so I think users will have fair warning that what they're trying to do isn't supported, if they do something like that. I'm not sure if a test would be helpful here or not, since I guess ideally mypy would support generic metaclasses, and I don't want to assert incorrect or undesirable behaviour in a test. |
|
I tried to reproduce the |
|
Diff from mypy_primer, showing the effect of this PR on open source code: steam.py (https://github.com/Gobot1234/steam.py)
- steam/id.py:123: error: Access to generic instance variables via class is ambiguous [misc]
pandera (https://github.com/pandera-dev/pandera)
+ pandera/api/pandas/model.py:416: error: Need type annotation for "attrs" (hint: "attrs: Dict[<type>, <type>] = ...") [var-annotated]
discord.py (https://github.com/Rapptz/discord.py)
- discord/ext/commands/cog.py:358: error: Incompatible types in assignment (expression has type "Group | discord.app_commands.commands.Command[Cog, [VarArg(Any), KwArg(Any)], Any]", variable has type "discord.ext.commands.core.Command[Self, [VarArg(Any), KwArg(Any)], Any]") [assignment]
+ discord/ext/commands/cog.py:358: error: Incompatible types in assignment (expression has type "Group | discord.app_commands.commands.Command[Any, [VarArg(Any), KwArg(Any)], Any]", variable has type "discord.ext.commands.core.Command[Self, [VarArg(Any), KwArg(Any)], Any]") [assignment]
|
|
Thanks @ilevkivskyi and @JelleZijlstra! |
Fixes #14056
#14056 was originally reported as a mypyc issue (because that's how it presented itself to the user), but the underlying bug is really a bug to do with how mypy understands metaclasses. On mypy
master:This PR fixes that incorrect behaviour.
Since this is really a mypy bug rather than a mypyc bug, I haven't added a mypyc test, but I'm happy to if that would be useful. (I'll need some guidance as to exactly where it should go -- I don't know much about mypyc internals!)