Repository navigation
Defer subclass methods if superclass has not been analyzed - #5637
Conversation
JukkaL
left a comment
There was a problem hiding this comment.
Thanks for working on this issue -- when users hit this issue, it's very confusing. It's totally unclear why mypy is complaining and how to fix the problem.
Left detailed comments since deferred nodes are a very tricky part of mypy. Once you are updating this code it's a good opportunity to add some more documentation.
|
|
||
| def check_method_override_for_base_with_name( | ||
| self, defn: Union[FuncBase, Decorator], name: str, base: TypeInfo) -> None: | ||
| self, defn: Union[FuncBase, Decorator], name: str, base: TypeInfo) -> bool: |
There was a problem hiding this comment.
Document the return type.
It might be better to use concrete types in the union type, at least if it would let us remove the assert below.
| del self.globals | ||
|
|
||
| def refresh_partial(self, node: Union[MypyFile, FuncItem, OverloadedFuncDef], | ||
| def refresh_partial(self, node: Union[MypyFile, FuncItem, OverloadedFuncDef, Decorator], |
There was a problem hiding this comment.
We should never get a Decorator here, since this is only used in fine-grained mode? We should either remove Decorator from the union (preferable) or assert that node is not a Decorator. I think that it would be better to have the assert as early as possible, i.e. in mypy.server.update.
| self.patches = [] | ||
|
|
||
| def refresh_partial(self, node: Union[MypyFile, FuncItem, OverloadedFuncDef], | ||
| def refresh_partial(self, node: Union[MypyFile, FuncItem, OverloadedFuncDef, Decorator], |
There was a problem hiding this comment.
Similar to the comment related to mypy.semanal.py.
|
|
||
|
|
||
| def strip_target(node: Union[MypyFile, FuncItem, OverloadedFuncDef]) -> None: | ||
| def strip_target(node: Union[MypyFile, FuncItem, OverloadedFuncDef, Decorator]) -> None: |
There was a problem hiding this comment.
Similar to the comment related to mypy.senanal.py.
|
|
||
| def target_from_node(module: str, | ||
| node: Union[FuncDef, MypyFile, OverloadedFuncDef, LambdaExpr] | ||
| node: Union[FuncDef, MypyFile, OverloadedFuncDef, LambdaExpr, Decorator] |
There was a problem hiding this comment.
Again, could we prevent this function from being called with a Decorator argument earlier on?
There was a problem hiding this comment.
I can remove Decorator from all these unions, but this will require a couple of asserts.
There was a problem hiding this comment.
I think that it's better to have the caller ensure that the function is called with the correct types. The annotation as it is now is arguably misleading.
There was a problem hiding this comment.
It is arguably not more misleading than it was before, it already accepts LambdaExpr, but then fails with assert False on it (as well as the other functions above, they accept LambdaExpr while they shouldn't). Anyway, I will try to clean this up.
There was a problem hiding this comment.
Ah, good point! In that case maybe LambdaExpr should also be removed from the annotation?
There was a problem hiding this comment.
Yes, I will remove both.
| DeferredNode = NamedTuple( | ||
| 'DeferredNode', | ||
| [ | ||
| # In batch mode only FuncDef and LambdaExpr are supported |
There was a problem hiding this comment.
This comment seems out of date.
|
|
||
| DEFAULT_LAST_PASS = 1 # type: Final # Pass numbers start at 0 | ||
|
|
||
| DeferredNodeType = Union[FuncDef, LambdaExpr, MypyFile, OverloadedFuncDef, Decorator] |
There was a problem hiding this comment.
Add comment about which node types can be deferred in fine-grained vs. normal modes.
Also mention that nested functions can't be deferred -- only top-level functions and methods of classes not defined within a function can be deferred.
| type_name = self.errors.type_name[-1] | ||
| else: | ||
| type_name = None | ||
| # Shouldn't we freeze the entire scope? |
There was a problem hiding this comment.
This comment is kind of vague. Maybe this should be a TODO comment?
There was a problem hiding this comment.
I am not sure what was the intent. This comment is two years old, maybe just remove it?
There was a problem hiding this comment.
Maybe reword it like this: "We don't freeze the entire scope since only top-level functions and methods can be deferred. Only module/class level scope information is needed. Module-level scope information is preserved in the TypeChecker instance." (I think that this is correct.)
| @@ -1257,11 +1258,16 @@ def check_method_override(self, defn: Union[FuncBase, Decorator]) -> None: | |||
| """Check if function definition is compatible with base classes.""" | |||
There was a problem hiding this comment.
Update docstring to mention that this may defer the method if a signature is not available in a base class.
|
@JukkaL Thanks for review! I addressed the comments. I decided to have a separate I also added a bunch of tests combining overloads, decorators, variables, double deferrals, and super calls (also with various combinations of these). |
JukkaL
left a comment
There was a problem hiding this comment.
Thanks for the cleanups! Looks better. I left a few more comments. Feel free to merge once you've addressed them.
| def check_method_override(self, defn: Union[FuncDef, OverloadedFuncDef, Decorator]) -> None: | ||
| """Check if function definition is compatible with base classes. | ||
|
|
||
| This may defer the method if a signature is not available in at least on base class. |
| def deco(f: Callable[..., T]) -> Callable[..., Tuple[T, int]]: ... | ||
| [out] | ||
|
|
||
| [case testCyclicDecoratorSuperDeferred] |
There was a problem hiding this comment.
Did you verify that these test cases trigger deferral? Using from ... import can be used to make it more explicit in which order modules are processed by mypy.
There was a problem hiding this comment.
I actually checked only first two tests I added, I will also double-check rest just in case.
There was a problem hiding this comment.
They all get deferred expected number of times (1, 2, or 3), I switched to from ... import in half of the tests anyway.
| DEFAULT_LAST_PASS = 1 # type: Final # Pass numbers start at 0 | ||
|
|
||
| DeferredNodeType = Union[FuncDef, LambdaExpr, OverloadedFuncDef, Decorator] | ||
| FineDeferredNodeType = Union[FuncDef, MypyFile, OverloadedFuncDef] |
There was a problem hiding this comment.
Style nit: I'd prefer to call this FineGrainedDeferredNodeType or something.
We don't use "fine" as a shorted version of "fine-grained" elsewhere and it can be confusing.
|
|
||
| # Same as above, but for fine-grained mode targets. Only top-level functions/methods | ||
| # and module top levels are allowed as such. | ||
| FineDeferredNode = NamedTuple( |
There was a problem hiding this comment.
Similar to above, I'd prefer to name this FineGrainedDeferredNode.
Fixes #5560
Fixes #5548
Half of the PR is updating various function signatures to also accept
Decorator. I still prohibitDecoratoras a target in fine-grained mode, but I think there should be no harm to defer it in normal mode.