-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementing background infrastructure for recursive types: Part 1 (#…
…7330) During planning discussions one of the main concerns about recursive types was the fact that we have hundreds of places where certain types are special-cased using `isinstance()`, and fixing all of them will take weeks. So I did a little experiment this weekend, to understand how bad it _actually_ is. I wrote a simple mypy plugin for mypy self-check, and it discovered 800+ such call sites. This looks pretty bad, but it turns out that fixing half of them (roughly 400 plugin errors) took me less than 2 days. This is kind of a triumph of our tooling :-) (i.e. mypy plugin + PyCharm plugin). Taking into account results of this experiment I propose to actually go ahead and implement recursive types. Here are some comments: * There will be four subsequent PRs: second part of `isinstance()` cleanup, implementing visitors and related methods everywhere, actual core implementation, adding extra tests for tricky recursion patterns. * The core idea of implementation stays the same as we discussed with @JukkaL: `TypeAliasType` and `TypeAlias` node will essentially match logic between `Instance` and `TypeInfo` (but structurally, as for protocols) * I wanted to make `PlaceholderType` a non-`ProperType`, but it didn't work immediately because we call `make_union()` during semantic analysis. If this seems important, this can be done with a bit more effort. * I make `TypeType.item` a proper type (following PEP 484, only very limited things can be passed to `Type[...]`). I also make `UnionType.items` proper types, mostly because of `make_simplified_union()`. Finally, I make `FuncBase.type` a proper type, I think a type alias can never appear there. * It is sometimes hard to decide where exactly is to call `get_proper_type()`, I tried to balance calling them not too soon and not too late, depending of every individual case. Please review, I am open to modifying logic in some places.
- Loading branch information
1 parent
7fb7e26
commit e04bf78
Showing
43 changed files
with
659 additions
and
320 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
from mypy.plugin import Plugin, FunctionContext | ||
from mypy.types import Type, Instance, CallableType, UnionType, get_proper_type | ||
|
||
import os.path | ||
from typing_extensions import Type as typing_Type | ||
from typing import Optional, Callable | ||
|
||
FILE_WHITELIST = [ | ||
'checker.py', | ||
'checkexpr.py', | ||
'checkmember.py', | ||
'messages.py', | ||
'semanal.py', | ||
'typeanal.py' | ||
] | ||
|
||
|
||
class ProperTypePlugin(Plugin): | ||
""" | ||
A plugin to ensure that every type is expanded before doing any special-casing. | ||
This solves the problem that we have hundreds of call sites like: | ||
if isinstance(typ, UnionType): | ||
... # special-case union | ||
But after introducing a new type TypeAliasType (and removing immediate expansion) | ||
all these became dangerous because typ may be e.g. an alias to union. | ||
""" | ||
def get_function_hook(self, fullname: str | ||
) -> Optional[Callable[[FunctionContext], Type]]: | ||
if fullname == 'builtins.isinstance': | ||
return isinstance_proper_hook | ||
return None | ||
|
||
|
||
def isinstance_proper_hook(ctx: FunctionContext) -> Type: | ||
if os.path.split(ctx.api.path)[-1] in FILE_WHITELIST: | ||
return ctx.default_return_type | ||
for arg in ctx.arg_types[0]: | ||
if is_improper_type(arg): | ||
right = get_proper_type(ctx.arg_types[1][0]) | ||
if isinstance(right, CallableType) and right.is_type_obj(): | ||
if right.type_object().fullname() in ('mypy.types.Type', | ||
'mypy.types.ProperType', | ||
'mypy.types.TypeAliasType'): | ||
# Special case: things like assert isinstance(typ, ProperType) are always OK. | ||
return ctx.default_return_type | ||
if right.type_object().fullname() in ('mypy.types.UnboundType', | ||
'mypy.types.TypeVarType'): | ||
# Special case: these are not valid targets for a type alias and thus safe. | ||
return ctx.default_return_type | ||
ctx.api.fail('Never apply isinstance() to unexpanded types;' | ||
' use mypy.types.get_proper_type() first', ctx.context) | ||
return ctx.default_return_type | ||
|
||
|
||
def is_improper_type(typ: Type) -> bool: | ||
"""Is this a type that is not a subtype of ProperType?""" | ||
typ = get_proper_type(typ) | ||
if isinstance(typ, Instance): | ||
info = typ.type | ||
return info.has_base('mypy.types.Type') and not info.has_base('mypy.types.ProperType') | ||
if isinstance(typ, UnionType): | ||
return any(is_improper_type(t) for t in typ.items) | ||
return False | ||
|
||
|
||
def plugin(version: str) -> typing_Type[ProperTypePlugin]: | ||
return ProperTypePlugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.