-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Fix 3903: untyped defs on derived classes #7548
Conversation
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.
Thanks for working on this! I have few general comments:
- I think this should be independent of
--check-untyped-defs
. One might want to have the latter not enables and only check the methods that have a typed definition in a superclass. - I think the benefit should be not only checking the bodies using "inherited" types, but also external access should be typed, i.e.
Sub.meth(...)
calls should be checked using the signature ofSuper.meth
. For this reason I think the copying should happen earlier, during semantic analysis. - What should we do if the signature is impossible to copy? In particular what if signatures are not identical, but still Liskov-compatible?
- What if the method in a subclass annotates only some arguments? Should this trigger an error, should we copy the rest, should we just assume the missing ones are
Any
? - You need to make your implementation works correctly in incremental and daemon modes (the latter one might require some tricky changes to type dependency tracking).
- You need to be sure this works well with generic classes, where you wound need to not just copy the argument/return types, but map them correctly taking into account type variable binding along MRO.
I think most of the questions are answered in #3903 (comment) (there are also couple additional points). |
Thank you for guiding!
|
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.
Although you said:
Looked simple enough
This is relatively tricky feature. I left few large-scale comments. This looks still not ready fo a detailed review.
@@ -442,7 +442,9 @@ def add_invertible_flag(flag: str, | |||
add_invertible_flag('--disallow-untyped-decorators', default=False, strict_flag=True, | |||
help="Disallow decorating typed functions with untyped decorators", | |||
group=untyped_group) | |||
|
|||
add_invertible_flag('--inherit-signatures', default=False, strict_flag=True, |
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.
I don't think this needs to be a strict flag, because --disallow-untyped-defs
is already part of --strict
IIRC.
@@ -160,6 +160,45 @@ | |||
Tag = int | |||
|
|||
|
|||
def substitute_unbounds(t: UnboundType, names_to_types: Mapping[str, Instance]) -> ProperType: |
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.
Sorry for misleading suggestion, doing the copy during semantic analyzis is still bad. I was thinking about superclass being in a different module that is type-checked before the module with subclass, but it obviously doesn't work always.
Also this function is just one big hack. I was thinking about something along the lines of mypy.checker.TypeChecker.bind_and_map_method()
.
For the latter to work well I see a possible strategy:
- Add two flags (or maybe better one three state flag) to functions indicating whether this function can be "improved", i.e. it has some untyped arguments, while a supertype has those typed, and whether the "copying" has already been performed.
- The first flag should be set during semantic analysis, this should be thought carefully to work well in import cycles.
- During type checking, whenever we encounter a reference to a method
Sub.meth
for which we know it can be "improved", but wasn't yet, we trigger a deferral. - Finally, we perform the copying while analyzing the method itself (in a place where you did initially, but with a more sophisticated logic like currently)
- Be sure that
aststrip.py
resets the flags, anddeps.py
generates dependencies so that subclass method callsites are all rechecked when the signature in supertype changes (this may be already the case, at least partially).
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.
@gantsevdenis I can't find your comment that I have got by e-mail, but here is the response. Self-types is something like this:
T = TypeVar('T')
class Base:
def copy(self: T) -> T: ...
class Sub(Base): ...
x = Sub().copy() # inferred type of 'x' is 'Sub'
Then class B will be transformed to: | ||
class B(A[int,str]): | ||
def foo(self) -> str:pass | ||
def bar(self,*args)->Callable[[str],int]:pass |
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.
Please use PEP 8 everywhere including in docstring and in tests! (You can however skip empty lines in tests whenever it makes sense).
assert isinstance(parent, FuncDef), "got %s" % type(parent) | ||
pnb_args, cnb_args = len(parent.arg_kinds), len(func.arg_kinds) | ||
if cnb_args < pnb_args: | ||
self.fail("Signature of \"%s\" incompatible with supertype \"%s\"" % |
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.
All the message generating logic should live messages.py
. Also I think we should emit a more specific error message, explaining that we can't copy the signature, and mentioning what went wrong (not enough arguments, etc).
Also in Python you can use 'single quotes to avoid "escaping" double quotes'
(also below).
def return_VT(self, *args,**kwargs): return 5 | ||
def return_VT_wrong(self,*args,**kwargs): return "" # E: Incompatible return value type (got "str", expected "int") | ||
def return_Tuple(self,*args,**kwargs): return ("", 5) # E: Incompatible return value type (got "Tuple[str, int]", expected "Tuple[int, str]") | ||
[builtins fixtures/dict.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.
Several important scenarios are still missing, for example:
- Methods with self-types
- Generic inheritance where subclass has new type variables with non-trivial mapping to old ones
- Chained inheritance where
C
inheritsB
that inheritsA
- Multiple inheritance
- Incremental mode tests, see
check-incremental.test
- Daemon mode tests, see
fine-grained.test
- Import cycles
- Tests that inherited signature is used on external and internal access (including situations with deferrals)
Also please note there are some merge conflicts. |
@@ -276,6 +276,10 @@ definitions or calls. | |||
It will assume all arguments have type ``Any`` and always infer ``Any`` | |||
as the return type. | |||
|
|||
``--inherit-signatures`` | |||
This allows to use base class function signature in derived classes, without explicitly |
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.
Slight typo - "This allows the use of..." instead of "This allows to use..."
This is awesome work and would go a long way in resolving all my use cases for python/typing#270. Thanks for this! Edit: this doesn't actually cover python/typing#270 but much of the logic here would probably be shared with any implementation of the features suggested therein. |
I would love to see this as well, would eliminate a lot of DRY violations that are currently required for typing. |
I'm going to close this because it has been open with changes requested for quite some time. Anyone should feel free to pick it back up, though! |
fix #3903
Looked simple enough, but not sure about the part:
If removed, produces AssertionErrors