Skip to content

Commit f9396ef

Browse files
authored
Implementing background infrastructure for recursive types: Part 2 (#7366)
This completes the addition of expansion points before every `isinstance()` checks on types. This essentially does the same as #7330. Couple comments: * Many checks (probably more than half) in type checker are against `AnyType`, `NoneType`, and `UninhabitedType`, but technically these are valid targets for aliases, so I still do expansions there. * I added couple hooks to the plugin to flag redundant `get_proper_type()` and `get_proper_types()` calls, however sometimes when they trigger they add some additional spurious errors about non-matching overloads. I tried to fix this, but it looks like this requires calling `get_function_hook()` once on a whole overload (now it is called for every item). Next part will contain implementation of the visitors.
1 parent 95e8c7a commit f9396ef

16 files changed

+495
-268
lines changed

misc/proper_plugin.py

Lines changed: 86 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
from mypy.plugin import Plugin, FunctionContext
2-
from mypy.types import Type, Instance, CallableType, UnionType, get_proper_type
2+
from mypy.types import (
3+
Type, Instance, CallableType, UnionType, get_proper_type, ProperType,
4+
get_proper_types, TupleType, NoneTyp, AnyType
5+
)
6+
from mypy.nodes import TypeInfo
7+
from mypy.subtypes import is_proper_subtype
38

4-
import os.path
59
from typing_extensions import Type as typing_Type
610
from typing import Optional, Callable
711

8-
FILE_WHITELIST = [
9-
'checker.py',
10-
'checkexpr.py',
11-
'checkmember.py',
12-
'messages.py',
13-
'semanal.py',
14-
'typeanal.py'
15-
]
16-
1712

1813
class ProperTypePlugin(Plugin):
1914
"""
@@ -31,30 +26,53 @@ def get_function_hook(self, fullname: str
3126
) -> Optional[Callable[[FunctionContext], Type]]:
3227
if fullname == 'builtins.isinstance':
3328
return isinstance_proper_hook
29+
if fullname == 'mypy.types.get_proper_type':
30+
return proper_type_hook
31+
if fullname == 'mypy.types.get_proper_types':
32+
return proper_types_hook
3433
return None
3534

3635

3736
def isinstance_proper_hook(ctx: FunctionContext) -> Type:
38-
if os.path.split(ctx.api.path)[-1] in FILE_WHITELIST:
39-
return ctx.default_return_type
37+
right = get_proper_type(ctx.arg_types[1][0])
4038
for arg in ctx.arg_types[0]:
41-
if is_improper_type(arg):
42-
right = get_proper_type(ctx.arg_types[1][0])
43-
if isinstance(right, CallableType) and right.is_type_obj():
44-
if right.type_object().fullname() in ('mypy.types.Type',
45-
'mypy.types.ProperType',
46-
'mypy.types.TypeAliasType'):
47-
# Special case: things like assert isinstance(typ, ProperType) are always OK.
48-
return ctx.default_return_type
49-
if right.type_object().fullname() in ('mypy.types.UnboundType',
50-
'mypy.types.TypeVarType'):
51-
# Special case: these are not valid targets for a type alias and thus safe.
52-
return ctx.default_return_type
39+
if (is_improper_type(arg) or
40+
isinstance(get_proper_type(arg), AnyType) and is_dangerous_target(right)):
41+
if is_special_target(right):
42+
return ctx.default_return_type
5343
ctx.api.fail('Never apply isinstance() to unexpanded types;'
5444
' use mypy.types.get_proper_type() first', ctx.context)
5545
return ctx.default_return_type
5646

5747

48+
def is_special_target(right: ProperType) -> bool:
49+
"""Whitelist some special cases for use in isinstance() with improper types."""
50+
if isinstance(right, CallableType) and right.is_type_obj():
51+
if right.type_object().fullname() == 'builtins.tuple':
52+
# Used with Union[Type, Tuple[Type, ...]].
53+
return True
54+
if right.type_object().fullname() in ('mypy.types.Type',
55+
'mypy.types.ProperType',
56+
'mypy.types.TypeAliasType'):
57+
# Special case: things like assert isinstance(typ, ProperType) are always OK.
58+
return True
59+
if right.type_object().fullname() in ('mypy.types.UnboundType',
60+
'mypy.types.TypeVarType',
61+
'mypy.types.RawExpressionType',
62+
'mypy.types.EllipsisType',
63+
'mypy.types.StarType',
64+
'mypy.types.TypeList',
65+
'mypy.types.CallableArgument',
66+
'mypy.types.PartialType',
67+
'mypy.types.ErasedType'):
68+
# Special case: these are not valid targets for a type alias and thus safe.
69+
# TODO: introduce a SyntheticType base to simplify this?
70+
return True
71+
elif isinstance(right, TupleType):
72+
return all(is_special_target(t) for t in get_proper_types(right.items))
73+
return False
74+
75+
5876
def is_improper_type(typ: Type) -> bool:
5977
"""Is this a type that is not a subtype of ProperType?"""
6078
typ = get_proper_type(typ)
@@ -66,5 +84,48 @@ def is_improper_type(typ: Type) -> bool:
6684
return False
6785

6886

87+
def is_dangerous_target(typ: ProperType) -> bool:
88+
"""Is this a dangerous target (right argument) for an isinstance() check?"""
89+
if isinstance(typ, TupleType):
90+
return any(is_dangerous_target(get_proper_type(t)) for t in typ.items)
91+
if isinstance(typ, CallableType) and typ.is_type_obj():
92+
return typ.type_object().has_base('mypy.types.Type')
93+
return False
94+
95+
96+
def proper_type_hook(ctx: FunctionContext) -> Type:
97+
"""Check if this get_proper_type() call is not redundant."""
98+
arg_types = ctx.arg_types[0]
99+
if arg_types:
100+
arg_type = get_proper_type(arg_types[0])
101+
proper_type = get_proper_type_instance(ctx)
102+
if is_proper_subtype(arg_type, UnionType.make_union([NoneTyp(), proper_type])):
103+
# Minimize amount of spurious errors from overload machinery.
104+
# TODO: call the hook on the overload as a whole?
105+
if isinstance(arg_type, (UnionType, Instance)):
106+
ctx.api.fail('Redundant call to get_proper_type()', ctx.context)
107+
return ctx.default_return_type
108+
109+
110+
def proper_types_hook(ctx: FunctionContext) -> Type:
111+
"""Check if this get_proper_types() call is not redundant."""
112+
arg_types = ctx.arg_types[0]
113+
if arg_types:
114+
arg_type = arg_types[0]
115+
proper_type = get_proper_type_instance(ctx)
116+
item_type = UnionType.make_union([NoneTyp(), proper_type])
117+
ok_type = ctx.api.named_generic_type('typing.Iterable', [item_type])
118+
if is_proper_subtype(arg_type, ok_type):
119+
ctx.api.fail('Redundant call to get_proper_types()', ctx.context)
120+
return ctx.default_return_type
121+
122+
123+
def get_proper_type_instance(ctx: FunctionContext) -> Instance:
124+
types = ctx.api.modules['mypy.types'] # type: ignore
125+
proper_type_info = types.names['ProperType']
126+
assert isinstance(proper_type_info.node, TypeInfo)
127+
return Instance(proper_type_info.node, [])
128+
129+
69130
def plugin(version: str) -> typing_Type[ProperTypePlugin]:
70131
return ProperTypePlugin

0 commit comments

Comments
 (0)