Skip to content

Fix 7190:__init_subclass__ is not type-checked #7452

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

Merged
merged 31 commits into from
Sep 21, 2019
Merged
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a709e11
simulate a Call to Base.__init_subclass__
gantsevdenis Sep 3, 2019
dd3df66
added fixture
gantsevdenis Sep 3, 2019
1dfae8c
update existing test; class B would fail at runtime
gantsevdenis Sep 3, 2019
081e19c
added tests
gantsevdenis Sep 3, 2019
145d254
base, not typ
gantsevdenis Sep 3, 2019
bd66bca
flake8
gantsevdenis Sep 3, 2019
1d12de7
trailing space
gantsevdenis Sep 3, 2019
7dccdb7
fix type
gantsevdenis Sep 3, 2019
f5cc79a
added test for metaclass
gantsevdenis Sep 4, 2019
1c62e37
make signature conform to typeshed
gantsevdenis Sep 4, 2019
6729fe8
refactor into a method + check for metaclass + added comments
gantsevdenis Sep 4, 2019
502aa3f
space
gantsevdenis Sep 4, 2019
dfb6551
missing return + annotation
gantsevdenis Sep 4, 2019
b860c3a
Update mypy/checker.py
gantsevdenis Sep 5, 2019
012b817
Update mypy/checker.py
gantsevdenis Sep 6, 2019
a8fcb3e
Update mypy/checker.py
gantsevdenis Sep 6, 2019
ef295b5
Update mypy/checker.py
gantsevdenis Sep 6, 2019
ddc1dce
Update mypy/checker.py
gantsevdenis Sep 6, 2019
2c0c20f
added test with imports
gantsevdenis Sep 6, 2019
67a761b
check for "object" too
gantsevdenis Sep 6, 2019
79208fd
stylistic changes
gantsevdenis Sep 6, 2019
8eb7b8d
added test
gantsevdenis Sep 6, 2019
9a7ab31
simplify logic
gantsevdenis Sep 15, 2019
2281a86
modify test
gantsevdenis Sep 15, 2019
f8f330a
sync typeshed to master
gantsevdenis Sep 15, 2019
0a16c0c
Merge from upstream/master
gantsevdenis Sep 15, 2019
0850dff
flake8
gantsevdenis Sep 15, 2019
798ff33
Update mypy/checker.py
gantsevdenis Sep 21, 2019
6f5bda9
Update mypy/checker.py
gantsevdenis Sep 21, 2019
ab8a6c8
move comment line
gantsevdenis Sep 21, 2019
7da5d11
undo pin move
gantsevdenis Sep 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
check for "object" too
  • Loading branch information
gantsevdenis committed Sep 6, 2019
commit 67a761b454be3a5ad6db0f859fc415cc5d42e609
17 changes: 10 additions & 7 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1696,12 +1696,11 @@ def check_init_subclass(self, defn: ClassDef) -> None:
typ = defn.info
# At runtime, only Base.__init_subclass__ will be called
# we skip the current class itself.
found = False
for base in typ.mro[1:]:
# 'object.__init_subclass__ is a dummy method with no arguments, always defined
# there is no use to call it
if base.name() != 'object' \
and base.defn.info: # there are "NOT_READY" instances
# during the tests, so I filter them out...
# there are "NOT_READY" instances
# during the tests, so I filter them out...
if base.defn.info:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I have an idea why this may be missing, but anyway this is probably not important since you don't need this anyway, everywhere where you use base.defn.info you can use just base, this is the same object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

understood!

for method_name, method_symbol_node in base.defn.info.names.items():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need this to be a cycle, just use method_symbol_node = base.names.get('__init_subclass__').

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or even simpler (if you are not going to use method_symbol_node):

if '__init_subclass__' not in base.names:
    continue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right, I have modified accordingly

if method_name == '__init_subclass__':
name_expr = NameExpr(defn.name)
Expand All @@ -1723,9 +1722,13 @@ def check_init_subclass(self, defn: ClassDef) -> None:
self.expr_checker.accept(call_expr,
Copy link
Member

@ilevkivskyi ilevkivskyi Sep 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using accept() may actually be dangerous. For example if the base class is not in scope (defined in another module and not imported in the current one), this may trigger Name not defined or maybe even a crash. There are two possible solutions here:

  • Point name_expr.node to the base class TypeInfo before calling accept()
  • Use check_call() or similar methods

Please add a test for this.

Copy link
Contributor Author

@gantsevdenis gantsevdenis Sep 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm sorry, I don't understand. I tried to produce a mypy runtime exception with 2 modules, one of which defines a Base, and the other one doesn't import that Base, and it only gave me a nice error Name 'Base' is not defined. Which os OK I guess? this is what would happen at runtime anyway (NameError: name 'Base' is not defined)

I added a test for that

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which os OK I guess?

It is not what I wanted. I wanted something like this:

# file bases.py
class Base:
    def __init_subclass__(cls, **kwargs) -> None: ...
class MidBase(Base):
    ...
# file main.py
from bases import MidBase
class Main(MidBase, test=False):
    ...

allow_none_return=True,
always_allow_any=True)
# We are only interested in the first Base having __init_subclass__
# all other (highest) bases have already been checked.
# there is only one such method method
found = True
break
# We are only interested in the first Base having __init_subclass__
# all other (highest) bases have already been checked.
if found:
break
return

def check_protocol_variance(self, defn: ClassDef) -> None:
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy