-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Infer types from issubclass() calls #3005
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
158c889
c5525bd
1776e90
3334b8e
3587549
81e1923
e4ffdc5
9440a52
c831401
a4a7214
f94ca45
a260b79
fbd0b91
e48d983
b0ce683
aca05c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2696,7 +2696,8 @@ def visit_member_expr(self, expr: MemberExpr) -> None: | |
# one type checker run. If we reported errors here, | ||
# the build would terminate after semantic analysis | ||
# and we wouldn't be able to report any type errors. | ||
full_name = '%s.%s' % (file.fullname() if file is not None else None, expr.name) | ||
full_name = '%s.%s' % (file.fullname() if file is not None | ||
else None, expr.name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you don't need this formatting change, it only creates noise in the PR. Otherwise now it looks good to me |
||
mod_name = " '%s'" % file.fullname() if file is not None else '' | ||
if full_name in obsolete_name_mapping: | ||
self.fail("Module%s has no attribute %r (it's now called %r)" % ( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1412,3 +1412,154 @@ def f(x: Union[int, A], a: Type[A]) -> None: | |
|
||
[builtins fixtures/isinstancelist.pyi] | ||
|
||
|
||
[case testIssubclass] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to split this test into few smaller unit tests? |
||
from typing import Type, ClassVar | ||
|
||
class Goblin: | ||
level: int | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you need both attributes here? Some test cases could be simplified if you have only one instance variable. |
||
|
||
class GoblinAmbusher(Goblin): | ||
job: ClassVar[str] = 'Ranger' | ||
|
||
def test_issubclass(cls: Type[Goblin]) -> None: | ||
if issubclass(cls, GoblinAmbusher): | ||
cls.level | ||
cls.job | ||
ga = cls() | ||
ga.level = 15 | ||
ga.job | ||
ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance | ||
else: | ||
cls.level | ||
cls.job # E: Type[Goblin] has no attribute "job" | ||
g = cls() | ||
g.level = 15 | ||
g.job # E: "Goblin" has no attribute "job" | ||
|
||
|
||
[builtins fixtures/isinstancelist.pyi] | ||
|
||
|
||
[case testIssubclassDeepHierarchy] | ||
from typing import Type, ClassVar | ||
|
||
class Mob: | ||
pass | ||
|
||
class Goblin(Mob): | ||
level: int | ||
|
||
class GoblinAmbusher(Goblin): | ||
job: ClassVar[str] = 'Ranger' | ||
|
||
def test_issubclass(cls: Type[Mob]) -> None: | ||
if issubclass(cls, Goblin): | ||
cls.level | ||
cls.job # E: Type[Goblin] has no attribute "job" | ||
g = cls() | ||
g.level = 15 | ||
g.job # E: "Goblin" has no attribute "job" | ||
if issubclass(cls, GoblinAmbusher): | ||
cls.level | ||
cls.job | ||
g = cls() | ||
g.level = 15 | ||
g.job | ||
g.job = 'Warrior' # E: Cannot assign to class variable "job" via instance | ||
else: | ||
cls.job # E: Type[Mob] has no attribute "job" | ||
cls.level # E: Type[Mob] has no attribute "level" | ||
m = cls() | ||
m.level = 15 # E: "Mob" has no attribute "level" | ||
m.job # E: "Mob" has no attribute "job" | ||
if issubclass(cls, GoblinAmbusher): | ||
cls.job | ||
cls.level | ||
ga = cls() | ||
ga.level = 15 | ||
ga.job | ||
ga.job = 'Warrior' # E: Cannot assign to class variable "job" via instance | ||
|
||
if issubclass(cls, GoblinAmbusher): | ||
cls.level | ||
cls.job | ||
ga = cls() | ||
ga.level = 15 | ||
ga.job | ||
ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance | ||
|
||
[builtins fixtures/isinstancelist.pyi] | ||
|
||
|
||
[case testIssubclassTuple] | ||
from typing import Type, ClassVar | ||
|
||
class Mob: | ||
pass | ||
|
||
class Goblin(Mob): | ||
level: int | ||
|
||
class GoblinAmbusher(Goblin): | ||
job: ClassVar[str] = 'Ranger' | ||
|
||
class GoblinDigger(Goblin): | ||
job: ClassVar[str] = 'Thief' | ||
|
||
def test_issubclass(cls: Type[Mob]) -> None: | ||
if issubclass(cls, (Goblin, GoblinAmbusher)): | ||
cls.level | ||
cls.job # E: Some element of union has no attribute "job" | ||
g = cls() | ||
g.level = 15 | ||
g.job # E: "Goblin" has no attribute "job" | ||
if issubclass(cls, GoblinAmbusher): | ||
cls.level | ||
reveal_type(cls) # E: Revealed type is 'Type[__main__.GoblinAmbusher]' | ||
cls.job | ||
ga = cls() | ||
ga.level = 15 | ||
ga.job | ||
ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance | ||
else: | ||
cls.job # E: Type[Mob] has no attribute "job" | ||
cls.level # E: Type[Mob] has no attribute "level" | ||
m = cls() | ||
m.level = 15 # E: "Mob" has no attribute "level" | ||
m.job # E: "Mob" has no attribute "job" | ||
if issubclass(cls, GoblinAmbusher): | ||
cls.job | ||
cls.level | ||
ga = cls() | ||
ga.level = 15 | ||
ga.job | ||
ga.job = "Warrior" # E: Cannot assign to class variable "job" via instance | ||
|
||
if issubclass(cls, (GoblinDigger, GoblinAmbusher)): | ||
cls.level | ||
cls.job | ||
g = cls() | ||
g.level = 15 | ||
g.job | ||
g.job = "Warrior" # E: Cannot assign to class variable "job" via instance | ||
|
||
[builtins fixtures/isinstancelist.pyi] | ||
|
||
|
||
[case testIssubclassBuiltins] | ||
from typing import List, Type | ||
|
||
class MyList(List): pass | ||
class MyIntList(List[int]): pass | ||
|
||
def f(cls: Type[object]) -> None: | ||
if issubclass(cls, MyList): | ||
cls()[0] | ||
else: | ||
cls()[0] # E: Value of type "object" is not indexable | ||
|
||
if issubclass(cls, MyIntList): | ||
cls()[0] + 1 | ||
|
||
[builtins fixtures/isinstancelist.pyi] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are sure this is an unreachable branch, then you could put
assert False
here. If this could happen as a result of an error that is reported elsewhere, then adjust the comment correspondingly.