| from mypy.plugin import Plugin, FunctionContext |
| from mypy.types import ( |
| Type, Instance, CallableType, UnionType, get_proper_type, ProperType, |
| get_proper_types, TupleType, NoneTyp, AnyType |
| ) |
| from mypy.nodes import TypeInfo |
| from mypy.subtypes import is_proper_subtype |
| |
| from typing_extensions import Type as typing_Type |
| from typing import Optional, Callable |
| |
| |
| 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 |
| if fullname == 'mypy.types.get_proper_type': |
| return proper_type_hook |
| if fullname == 'mypy.types.get_proper_types': |
| return proper_types_hook |
| return None |
| |
| |
| def isinstance_proper_hook(ctx: FunctionContext) -> Type: |
| right = get_proper_type(ctx.arg_types[1][0]) |
| for arg in ctx.arg_types[0]: |
| if (is_improper_type(arg) or |
| isinstance(get_proper_type(arg), AnyType) and is_dangerous_target(right)): |
| if is_special_target(right): |
| 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_special_target(right: ProperType) -> bool: |
| """Whitelist some special cases for use in isinstance() with improper types.""" |
| if isinstance(right, CallableType) and right.is_type_obj(): |
| if right.type_object().fullname() == 'builtins.tuple': |
| # Used with Union[Type, Tuple[Type, ...]]. |
| return True |
| 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 True |
| if right.type_object().fullname() in ('mypy.types.UnboundType', |
| 'mypy.types.TypeVarType', |
| 'mypy.types.RawExpressionType', |
| 'mypy.types.EllipsisType', |
| 'mypy.types.StarType', |
| 'mypy.types.TypeList', |
| 'mypy.types.CallableArgument', |
| 'mypy.types.PartialType', |
| 'mypy.types.ErasedType'): |
| # Special case: these are not valid targets for a type alias and thus safe. |
| # TODO: introduce a SyntheticType base to simplify this? |
| return True |
| elif isinstance(right, TupleType): |
| return all(is_special_target(t) for t in get_proper_types(right.items)) |
| return False |
| |
| |
| 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 is_dangerous_target(typ: ProperType) -> bool: |
| """Is this a dangerous target (right argument) for an isinstance() check?""" |
| if isinstance(typ, TupleType): |
| return any(is_dangerous_target(get_proper_type(t)) for t in typ.items) |
| if isinstance(typ, CallableType) and typ.is_type_obj(): |
| return typ.type_object().has_base('mypy.types.Type') |
| return False |
| |
| |
| def proper_type_hook(ctx: FunctionContext) -> Type: |
| """Check if this get_proper_type() call is not redundant.""" |
| arg_types = ctx.arg_types[0] |
| if arg_types: |
| arg_type = get_proper_type(arg_types[0]) |
| proper_type = get_proper_type_instance(ctx) |
| if is_proper_subtype(arg_type, UnionType.make_union([NoneTyp(), proper_type])): |
| # Minimize amount of spurious errors from overload machinery. |
| # TODO: call the hook on the overload as a whole? |
| if isinstance(arg_type, (UnionType, Instance)): |
| ctx.api.fail('Redundant call to get_proper_type()', ctx.context) |
| return ctx.default_return_type |
| |
| |
| def proper_types_hook(ctx: FunctionContext) -> Type: |
| """Check if this get_proper_types() call is not redundant.""" |
| arg_types = ctx.arg_types[0] |
| if arg_types: |
| arg_type = arg_types[0] |
| proper_type = get_proper_type_instance(ctx) |
| item_type = UnionType.make_union([NoneTyp(), proper_type]) |
| ok_type = ctx.api.named_generic_type('typing.Iterable', [item_type]) |
| if is_proper_subtype(arg_type, ok_type): |
| ctx.api.fail('Redundant call to get_proper_types()', ctx.context) |
| return ctx.default_return_type |
| |
| |
| def get_proper_type_instance(ctx: FunctionContext) -> Instance: |
| types = ctx.api.modules['mypy.types'] # type: ignore |
| proper_type_info = types.names['ProperType'] |
| assert isinstance(proper_type_info.node, TypeInfo) |
| return Instance(proper_type_info.node, []) |
| |
| |
| def plugin(version: str) -> typing_Type[ProperTypePlugin]: |
| return ProperTypePlugin |