You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Attribute access with untyped methods and --check-untyped-defs #5401
@gvanrossum Please note that I called mypy with --check-untyped-defs! When calling it without this option, I get a warning about reveal_type always returning Any.
Yes, I also only used reveal_type to show the problem. I get errors for returning Any from untyped methods that go away as soon as I annotate the function definition.
It'll report some errors, so not totally broken. It looks like the difference is in the handling of self. In an annotated method, it is given the type of the class in which it is defined, but in an untyped method self is given the type Any just like any other argument.
Since this behavior is enabled explicitly by a flag (--check-untyped-defs) I don't believe the Any type for self was by design -- more likely we forgot to special-case self, since we do (reasonably) use Any for the type of all other arguments.
I have analyzed this problem a bit and solution seems non-straightforward. Currently for annotated methods the argument type of self is adjusted in the semantic analyzer:
It's possible to add a type to unannotated methods here as well, but that wreaks havoc with later checks for unannotated or partially annotated methods when using --disallow-untyped-defs or --disallow-incomplete-defs.
The checker later constructs a type for functions and methods that don't have one yet:
As far as I can see the class type is not available in this method, so it needs to be injected somehow. But I don't like the fact that the self type gets injected in two completely separate locations, depending on whether the method is annotated at all or not.
I see three possible solutions:
Extract the self type injection from the semantic analyzer and keep calling it from the analyzer for (partially) annotated methods and call it from the type checker for unannotated ones.
Synthesize type information for all unannotated functions and methods in the semantic analyzer and flag them as unannotated. This requires to change all checks for func.type in the type checker to use this new flag. In my limited understanding of mypy internals this is the "correct" fix, but also quite risky.
Move self type injection to the type checker and never alter the actual FuncDef instances.
For code bases with lots of classes (e.g. Django code bases) this is really misleading.
You pass --check-untyped-defs and think your code is being type checked, but a huge portion of it isn't.
If you annotate any of a function's args or its return type, self correctly resolves to the type of its class. Would it not be feasible to have mypy do this for all untyped functions when passing the --check-untyped-defs flag, for example by adding -> Any to all of these functions behind the scenes before analyzing the code?
@kylebebak It took me a few tries to understand what you're proposing, but basically you're saying that with --check-untyped-defs, mypy should use the same heuristic to determine the type of self (and cls, for class methods) that it uses for annotated functions, and that seems reasonable (and is also what @srittau has been saying). I now concur.
Alas, I am not very familiar with this part of mypy, and I have no idea how easy it would be to implement any of @srittau's proposals. I'm setting the priority to "normal", hopefully @JukkaL or @ilevkivskyi will be able to help.
According to @srittau , it would be simple to modify prepare_method_signature so that cls and self are typed for unannotated methods as well, the drawback being that this would break the --disallow-untyped-defs and --disallow-incomplete-defs checks.
If this is the only drawback to this solution, and other solutions that allow --check-untyped-defs and --disallow-untyped-defs to function simultaneously are much more costly, then I'd propose we go with the first solution.
We just add a warning to mypy that explains --check-untyped-defs invalidates the disallow flags if they're passed together. We could also add this explanation to the config section of the docs.
The reasoning being that the purpose of one runs sort of contrary, or at least perpendicular, to the purpose of the other. --check-untyped-defs gets you type checking on unannotated methods, which is really important for code intelligence and catching bugs in real time, while the disallow flags are more for enforcing code standards, and can be run somewhere in the CI pipeline.
I don't think it's very important that they work together, and as we've all sort of agreed, currently --check-untyped-defs doesn't work period. Fixing --check-untyped-defs even if it can't be used together with the disallow flags seems like a definite improvement.
Activity
gvanrossum commentedon Jul 29, 2018
Yes, mypy doesn't type-check unannotated methods, but it still processes reveal_type() there.
srittau commentedon Jul 29, 2018
@gvanrossum Please note that I called mypy with
--check-untyped-defs! When calling it without this option, I get a warning about reveal_type always returning Any.gvanrossum commentedon Jul 29, 2018
Oh, sorry.
bdarnell commentedon Jul 29, 2018
This isn't just for descriptors; it happens for plain attributes too:
gvanrossum commentedon Jul 29, 2018
So should the subject just be "reveal_type() in untyped methods with --check-untyped-defs incorrectly shows Any"?
bdarnell commentedon Jul 29, 2018
It's not just
reveal_type. If we replace reveal_type with an actual type error, it will only be reported in the annotated method:srittau commentedon Jul 29, 2018
Yes, I also only used
reveal_typeto show the problem. I get errors for returningAnyfrom untyped methods that go away as soon as I annotate the function definition.[-]Descriptors in untyped methods[/-][+]Attribute access with untyped methods and --check-untyped-defs[/+]gvanrossum commentedon Jul 29, 2018
bdarnell commentedon Jul 29, 2018
It'll report some errors, so not totally broken. It looks like the difference is in the handling of
self. In an annotated method, it is given the type of the class in which it is defined, but in an untyped methodselfis given the typeAnyjust like any other argument.JukkaL commentedon Jul 30, 2018
The
Anytype forselfmay be by design (to avoid false positives), but it clearly seems surprising. At least it should be clearly documented.gvanrossum commentedon Jul 30, 2018
Since this behavior is enabled explicitly by a flag (
--check-untyped-defs) I don't believe theAnytype forselfwas by design -- more likely we forgot to special-caseself, since we do (reasonably) useAnyfor the type of all other arguments.srittau commentedon Nov 11, 2018
I have analyzed this problem a bit and solution seems non-straightforward. Currently for annotated methods the argument type of
selfis adjusted in the semantic analyzer:mypy/mypy/semanal.py
Lines 488 to 501 in e5e4532
It's possible to add a type to unannotated methods here as well, but that wreaks havoc with later checks for unannotated or partially annotated methods when using
--disallow-untyped-defsor--disallow-incomplete-defs.The checker later constructs a type for functions and methods that don't have one yet:
mypy/mypy/checker.py
Lines 755 to 765 in e5e4532
As far as I can see the class type is not available in this method, so it needs to be injected somehow. But I don't like the fact that the self type gets injected in two completely separate locations, depending on whether the method is annotated at all or not.
I see three possible solutions:
func.typein the type checker to use this new flag. In my limited understanding of mypy internals this is the "correct" fix, but also quite risky.FuncDefinstances.kylebebak commentedon Jun 7, 2019
For code bases with lots of classes (e.g. Django code bases) this is really misleading.
You pass
--check-untyped-defsand think your code is being type checked, but a huge portion of it isn't.If you annotate any of a function's args or its return type,
selfcorrectly resolves to the type of its class. Would it not be feasible to have mypy do this for all untyped functions when passing the--check-untyped-defsflag, for example by adding-> Anyto all of these functions behind the scenes before analyzing the code?IMO as is
--check-untyped-defsis totally broken.gvanrossum commentedon Jun 7, 2019
@kylebebak It took me a few tries to understand what you're proposing, but basically you're saying that with
--check-untyped-defs, mypy should use the same heuristic to determine the type ofself(andcls, for class methods) that it uses for annotated functions, and that seems reasonable (and is also what @srittau has been saying). I now concur.Alas, I am not very familiar with this part of mypy, and I have no idea how easy it would be to implement any of @srittau's proposals. I'm setting the priority to "normal", hopefully @JukkaL or @ilevkivskyi will be able to help.
kylebebak commentedon Jun 7, 2019
Sorry if that wasn't as clear as it could have been... And yes, that's exactly what I'm proposing.
Hopefully it's not too difficult to change this behavior!
gvanrossum commentedon Jun 8, 2019
kylebebak commentedon Jun 8, 2019
According to @srittau , it would be simple to modify
prepare_method_signatureso thatclsandselfare typed for unannotated methods as well, the drawback being that this would break the--disallow-untyped-defsand--disallow-incomplete-defschecks.If this is the only drawback to this solution, and other solutions that allow
--check-untyped-defsand--disallow-untyped-defsto function simultaneously are much more costly, then I'd propose we go with the first solution.We just add a warning to
mypythat explains--check-untyped-defsinvalidates the disallow flags if they're passed together. We could also add this explanation to the config section of the docs.The reasoning being that the purpose of one runs sort of contrary, or at least perpendicular, to the purpose of the other.
--check-untyped-defsgets you type checking on unannotated methods, which is really important for code intelligence and catching bugs in real time, while the disallow flags are more for enforcing code standards, and can be run somewhere in the CI pipeline.I don't think it's very important that they work together, and as we've all sort of agreed, currently
--check-untyped-defsdoesn't work period. Fixing--check-untyped-defseven if it can't be used together with the disallow flags seems like a definite improvement.Give self argument the correct type when using check_untyped_defs (#7530