From f85e60549ee893ae8e801666a68fdbfba8cea857 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 11 Aug 2024 12:08:29 +0300 Subject: [PATCH] Use proper_type plugin --- returns/contrib/mypy/_features/curry.py | 18 ++++----- returns/contrib/mypy/_features/do_notation.py | 13 ++++--- returns/contrib/mypy/_features/flow.py | 3 +- returns/contrib/mypy/_features/kind.py | 27 +++++++------- returns/contrib/mypy/_features/partial.py | 37 +++++++++++-------- returns/contrib/mypy/_features/pipe.py | 27 +++++++------- returns/contrib/mypy/_typeops/analtype.py | 11 +++--- returns/contrib/mypy/_typeops/inference.py | 25 +++++++------ returns/contrib/mypy/_typeops/visitor.py | 17 +++++---- setup.cfg | 3 +- 10 files changed, 98 insertions(+), 83 deletions(-) diff --git a/returns/contrib/mypy/_features/curry.py b/returns/contrib/mypy/_features/curry.py index 52a183341..835a279bd 100644 --- a/returns/contrib/mypy/_features/curry.py +++ b/returns/contrib/mypy/_features/curry.py @@ -6,7 +6,7 @@ from mypy.plugin import FunctionContext from mypy.types import AnyType, CallableType, FunctionLike, Overloaded from mypy.types import Type as MypyType -from mypy.types import TypeOfAny +from mypy.types import TypeOfAny, get_proper_type from returns.contrib.mypy._structures.args import FuncArg from returns.contrib.mypy._typeops.transform_callable import ( @@ -20,14 +20,14 @@ def analyze(ctx: FunctionContext) -> MypyType: """Returns proper type for curried functions.""" - if not isinstance(ctx.arg_types[0][0], CallableType): - return ctx.default_return_type - if not isinstance(ctx.default_return_type, CallableType): - return ctx.default_return_type + default_return = get_proper_type(ctx.default_return_type) + arg_type = get_proper_type(ctx.arg_types[0][0]) + if not isinstance(arg_type, CallableType): + return default_return + if not isinstance(default_return, CallableType): + return default_return - return _CurryFunctionOverloads( - ctx.arg_types[0][0], ctx, - ).build_overloads() + return _CurryFunctionOverloads(arg_type, ctx).build_overloads() @final @@ -147,7 +147,7 @@ def _build_overloads_from_argtree(self, argtree: _ArgTree) -> None: # Will take `2` and apply its type to the previous function `1`. # Will result in `def x -> y -> A` # We also overloadify existing return types. - ret_type = argtree.case.ret_type + ret_type = get_proper_type(argtree.case.ret_type) temp_any = isinstance( ret_type, AnyType, ) and ret_type.type_of_any == TypeOfAny.implementation_artifact diff --git a/returns/contrib/mypy/_features/do_notation.py b/returns/contrib/mypy/_features/do_notation.py index 94cda7ab5..71a0aea2b 100644 --- a/returns/contrib/mypy/_features/do_notation.py +++ b/returns/contrib/mypy/_features/do_notation.py @@ -36,22 +36,23 @@ def analyze(ctx: MethodContext) -> MypyType: if generator expression has ``if`` conditions inside. """ + default_return = get_proper_type(ctx.default_return_type) if not ctx.args or not ctx.args[0]: - return ctx.default_return_type + return default_return expr = ctx.args[0][0] if not isinstance(expr, GeneratorExpr): ctx.api.fail(_LITERAL_GENERATOR_EXPR_REQUIRED, expr) - return ctx.default_return_type + return default_return if not isinstance(ctx.type, CallableType): - return ctx.default_return_type - if not isinstance(ctx.default_return_type, Instance): - return ctx.default_return_type + return default_return + if not isinstance(default_return, Instance): + return default_return return _do_notation( expr=expr, type_info=ctx.type.type_object(), - default_return_type=ctx.default_return_type, + default_return_type=default_return, ctx=ctx, ) diff --git a/returns/contrib/mypy/_features/flow.py b/returns/contrib/mypy/_features/flow.py index 3ac850684..ce814e977 100644 --- a/returns/contrib/mypy/_features/flow.py +++ b/returns/contrib/mypy/_features/flow.py @@ -1,5 +1,6 @@ from mypy.plugin import FunctionContext from mypy.types import Type as MypyType +from mypy.types import get_proper_type from returns.contrib.mypy._typeops.inference import PipelineInference @@ -46,7 +47,7 @@ def analyze(ctx: FunctionContext) -> MypyType: ) return PipelineInference( - ctx.arg_types[0][0], + get_proper_type(ctx.arg_types[0][0]), ).from_callable_sequence( real_arg_types, ctx.arg_kinds[1], diff --git a/returns/contrib/mypy/_features/kind.py b/returns/contrib/mypy/_features/kind.py index cb474da2e..5b3db6956 100644 --- a/returns/contrib/mypy/_features/kind.py +++ b/returns/contrib/mypy/_features/kind.py @@ -39,7 +39,7 @@ def attribute_access(ctx: AttributeContext) -> MypyType: """ assert isinstance(ctx.type, Instance) - instance = ctx.type.args[0] + instance = get_proper_type(ctx.type.args[0]) if isinstance(instance, TypeVarType): bound = get_proper_type(instance.upper_bound) @@ -78,18 +78,15 @@ def dekind(ctx: FunctionContext) -> MypyType: So, ``dekind(KindN[T, int])`` will fail. """ kind = get_proper_type(ctx.arg_types[0][0]) - correct_args = ( - isinstance(kind, Instance) and - isinstance(kind.args[0], Instance) - ) + assert isinstance(kind, Instance) # mypy requires these lines - if not correct_args: + kind_inst = get_proper_type(kind.args[0]) + + if not isinstance(kind_inst, Instance): ctx.api.fail(_KindErrors.dekind_not_instance, ctx.context) return AnyType(TypeOfAny.from_error) - assert isinstance(kind, Instance) # mypy requires these lines - assert isinstance(kind.args[0], Instance) - return kind.args[0].copy_modified(args=_crop_kind_args(kind)) + return kind_inst.copy_modified(args=_crop_kind_args(kind)) @asserts_fallback_to_any @@ -101,9 +98,10 @@ def kinded_signature(ctx: MethodSigContext) -> CallableType: See :class:`returns.primitives.hkt.Kinded` for more information. """ assert isinstance(ctx.type, Instance) - assert isinstance(ctx.type.args[0], FunctionLike) - wrapped_method = ctx.type.args[0] + wrapped_method = get_proper_type(ctx.type.args[0]) + assert isinstance(wrapped_method, FunctionLike) + if isinstance(wrapped_method, Overloaded): return ctx.default_signature @@ -138,10 +136,11 @@ def kinded_get_descriptor(ctx: MethodContext) -> MypyType: We do this due to ``__get__`` descriptor magic. """ assert isinstance(ctx.type, Instance) - assert isinstance(ctx.type.args[0], CallableType) - wrapped_method = ctx.type.args[0] - self_type = wrapped_method.arg_types[0] + wrapped_method = get_proper_type(ctx.type.args[0]) + assert isinstance(wrapped_method, CallableType) + + self_type = get_proper_type(wrapped_method.arg_types[0]) signature = bind_self( wrapped_method, is_classmethod=isinstance(self_type, TypeType), diff --git a/returns/contrib/mypy/_features/partial.py b/returns/contrib/mypy/_features/partial.py index bfcac09b0..5e0d970bb 100644 --- a/returns/contrib/mypy/_features/partial.py +++ b/returns/contrib/mypy/_features/partial.py @@ -2,9 +2,15 @@ from mypy.nodes import ARG_STAR, ARG_STAR2 from mypy.plugin import FunctionContext -from mypy.types import CallableType, FunctionLike, Instance, Overloaded -from mypy.types import Type as MypyType -from mypy.types import TypeType +from mypy.types import ( + CallableType, + FunctionLike, + Instance, + Overloaded, + ProperType, + TypeType, + get_proper_type, +) from returns.contrib.mypy._structures.args import FuncArg from returns.contrib.mypy._typeops.analtype import ( @@ -27,7 +33,7 @@ ) -def analyze(ctx: FunctionContext) -> MypyType: +def analyze(ctx: FunctionContext) -> ProperType: """ This hook is used to make typed curring a thing in `returns` project. @@ -40,26 +46,27 @@ def analyze(ctx: FunctionContext) -> MypyType: Internally we just reduce the original function's argument count. And drop some of them from function's signature. """ - if not isinstance(ctx.default_return_type, CallableType): - return ctx.default_return_type + default_return = get_proper_type(ctx.default_return_type) + if not isinstance(default_return, CallableType): + return default_return - function_def = ctx.arg_types[0][0] + function_def = get_proper_type(ctx.arg_types[0][0]) func_args = _AppliedArgs(ctx) if len(list(filter(len, ctx.arg_types))) == 1: return function_def # this means, that `partial(func)` is called elif not isinstance(function_def, _SUPPORTED_TYPES): - return ctx.default_return_type + return default_return elif isinstance(function_def, (Instance, TypeType)): # We force `Instance` and similar types to coercse to callable: function_def = func_args.get_callable_from_context() is_valid, applied_args = func_args.build_from_context() if not isinstance(function_def, (CallableType, Overloaded)) or not is_valid: - return ctx.default_return_type + return default_return return _PartialFunctionReducer( - ctx.default_return_type, + default_return, function_def, applied_args, ctx, @@ -118,7 +125,7 @@ def __init__( self._case_functions: List[CallableType] = [] self._fallbacks: List[CallableType] = [] - def new_partial(self) -> MypyType: + def new_partial(self) -> ProperType: """ Creates new partial functions. @@ -182,7 +189,7 @@ def _create_partial_case( return detach_callable(partial) return partial.copy_modified(variables=[]) - def _create_new_partial(self) -> MypyType: + def _create_new_partial(self) -> ProperType: """ Creates a new partial function-like from set of callables. @@ -220,12 +227,12 @@ def __init__(self, function_ctx: FunctionContext) -> None: self._function_ctx.arg_kinds[1:], ) - def get_callable_from_context(self) -> MypyType: + def get_callable_from_context(self) -> ProperType: """Returns callable type from the context.""" - return safe_translate_to_function( + return get_proper_type(safe_translate_to_function( self._function_ctx.arg_types[0][0], self._function_ctx, - ) + )) def build_from_context(self) -> Tuple[bool, List[FuncArg]]: """ diff --git a/returns/contrib/mypy/_features/pipe.py b/returns/contrib/mypy/_features/pipe.py index d75fe52a3..363df1a93 100644 --- a/returns/contrib/mypy/_features/pipe.py +++ b/returns/contrib/mypy/_features/pipe.py @@ -40,9 +40,9 @@ from mypy.nodes import ARG_POS from mypy.plugin import FunctionContext, MethodContext, MethodSigContext -from mypy.types import AnyType, CallableType, FunctionLike, Instance +from mypy.types import AnyType, CallableType, FunctionLike, Instance, ProperType from mypy.types import Type as MypyType -from mypy.types import TypeOfAny, UnionType, get_proper_type +from mypy.types import TypeOfAny, UnionType, get_proper_type, get_proper_types from returns.contrib.mypy._typeops.analtype import translate_to_function from returns.contrib.mypy._typeops.inference import PipelineInference @@ -51,21 +51,22 @@ def analyze(ctx: FunctionContext) -> MypyType: """This hook helps when we create the pipeline from sequence of funcs.""" - if not isinstance(ctx.default_return_type, Instance): - return ctx.default_return_type + default_return = get_proper_type(ctx.default_return_type) + if not isinstance(default_return, Instance): + return default_return if not ctx.arg_types[0]: # We do require to pass `*functions` arg. ctx.api.fail('Too few arguments for "pipe"', ctx.context) - return ctx.default_return_type + return default_return arg_types = [arg_type[0] for arg_type in ctx.arg_types if arg_type] first_step, last_step = _get_pipeline_def(arg_types, ctx) if not isinstance(first_step, FunctionLike): - return ctx.default_return_type + return default_return if not isinstance(last_step, FunctionLike): - return ctx.default_return_type + return default_return - return ctx.default_return_type.copy_modified( + return default_return.copy_modified( args=[ # First type argument represents first function arguments type: _unify_type(first_step, _get_first_arg_type), @@ -82,9 +83,9 @@ def infer(ctx: MethodContext) -> MypyType: if not isinstance(ctx.type, Instance): return ctx.default_return_type - pipeline_functions = ctx.type.args[2:] + pipeline_functions = get_proper_types(ctx.type.args[2:]) return PipelineInference( - ctx.arg_types[0][0], + get_proper_type(ctx.arg_types[0][0]), ).from_callable_sequence( pipeline_functions, list((ARG_POS,) * len(pipeline_functions)), @@ -117,12 +118,12 @@ def _unify_type( def _get_pipeline_def( arg_types: List[MypyType], ctx: FunctionContext, -) -> Tuple[MypyType, MypyType]: +) -> Tuple[ProperType, ProperType]: first_step = get_proper_type(arg_types[0]) last_step = get_proper_type(arg_types[-1]) if not isinstance(first_step, FunctionLike): - first_step = translate_to_function(first_step, ctx) # type: ignore + first_step = translate_to_function(first_step, ctx) if not isinstance(last_step, FunctionLike): - last_step = translate_to_function(last_step, ctx) # type: ignore + last_step = translate_to_function(last_step, ctx) return first_step, last_step diff --git a/returns/contrib/mypy/_typeops/analtype.py b/returns/contrib/mypy/_typeops/analtype.py index a675c6ef4..3ff654f92 100644 --- a/returns/contrib/mypy/_typeops/analtype.py +++ b/returns/contrib/mypy/_typeops/analtype.py @@ -3,8 +3,9 @@ from mypy.checkmember import analyze_member_access from mypy.nodes import ARG_NAMED, ARG_OPT -from mypy.types import CallableType, FunctionLike +from mypy.types import CallableType, FunctionLike, ProperType from mypy.types import Type as MypyType +from mypy.types import get_proper_type from typing_extensions import Literal from returns.contrib.mypy._structures.args import FuncArg @@ -99,9 +100,9 @@ def safe_translate_to_function( def translate_to_function( - function_def: MypyType, + function_def: ProperType, ctx: CallableContext, -) -> MypyType: +) -> ProperType: """ Tries to translate a type into callable by accessing ``__call__`` attr. @@ -109,7 +110,7 @@ def translate_to_function( This also preserves all type arguments as-is. """ checker = ctx.api.expr_checker # type: ignore - return analyze_member_access( + return get_proper_type(analyze_member_access( '__call__', function_def, ctx.context, @@ -120,4 +121,4 @@ def translate_to_function( original_type=function_def, chk=checker.chk, in_literal_context=checker.is_literal_context(), - ) + )) diff --git a/returns/contrib/mypy/_typeops/inference.py b/returns/contrib/mypy/_typeops/inference.py index 2968e59ae..8333415e3 100644 --- a/returns/contrib/mypy/_typeops/inference.py +++ b/returns/contrib/mypy/_typeops/inference.py @@ -1,13 +1,13 @@ -from typing import List, Mapping, Optional, Tuple, cast, final +from typing import Iterable, List, Mapping, Optional, cast, final from mypy.argmap import map_actuals_to_formals from mypy.constraints import infer_constraints_for_callable from mypy.expandtype import expand_type from mypy.nodes import ARG_POS, ArgKind from mypy.plugin import FunctionContext -from mypy.types import CallableType, FunctionLike +from mypy.types import CallableType, FunctionLike, ProperType from mypy.types import Type as MypyType -from mypy.types import TypeVarId +from mypy.types import TypeVarId, get_proper_type from typing_extensions import TypeAlias from returns.contrib.mypy._structures.args import FuncArg @@ -103,19 +103,19 @@ class PipelineInference: passes the first argument, and then infers types step by step. """ - def __init__(self, instance: MypyType) -> None: + def __init__(self, instance: ProperType) -> None: """We do need the first argument to start the inference.""" self._instance = instance def from_callable_sequence( self, - pipeline_types: Tuple[MypyType, ...], - pipeline_kinds: List[ArgKind], + pipeline_types: Iterable[ProperType], + pipeline_kinds: Iterable[ArgKind], ctx: CallableContext, - ) -> MypyType: + ) -> ProperType: """Pass pipeline functions to infer them one by one.""" parameter = FuncArg(None, self._instance, ARG_POS) - ret_type = ctx.default_return_type + ret_type = get_proper_type(ctx.default_return_type) for pipeline, kind in zip(pipeline_types, pipeline_kinds): ret_type = self._proper_type( @@ -129,7 +129,8 @@ def from_callable_sequence( parameter = FuncArg(None, ret_type, kind) return ret_type - def _proper_type(self, typ: MypyType) -> MypyType: - if isinstance(typ, CallableType): - return typ.ret_type - return typ # It might be `Instance` or `AnyType` or `Nothing` + def _proper_type(self, typ: MypyType) -> ProperType: + res_typ = get_proper_type(typ) + if isinstance(res_typ, CallableType): + return get_proper_type(res_typ.ret_type) + return res_typ # It might be `Instance` or `AnyType` or `Nothing` diff --git a/returns/contrib/mypy/_typeops/visitor.py b/returns/contrib/mypy/_typeops/visitor.py index b6655b93f..aaa2db47c 100644 --- a/returns/contrib/mypy/_typeops/visitor.py +++ b/returns/contrib/mypy/_typeops/visitor.py @@ -1,4 +1,4 @@ -from typing import Iterable, List, Optional +from typing import Dict, Iterable, List, Optional from mypy.typeops import erase_to_bound from mypy.types import ( @@ -11,8 +11,11 @@ NoneType, Overloaded, PartialType, + ProperType, TupleType, - Type, +) +from mypy.types import Type as MypyType +from mypy.types import ( TypedDictType, TypeOfAny, TypeType, @@ -38,7 +41,7 @@ ) -def translate_kind_instance(typ: Type) -> Type: # noqa: WPS, C901 +def translate_kind_instance(typ: MypyType) -> ProperType: # noqa: WPS, C901 """ We use this ugly hack to translate ``KindN[x, y]`` into ``x[y]``. @@ -82,7 +85,7 @@ def translate_kind_instance(typ: Type) -> Type: # noqa: WPS, C901 typ.column, ) elif isinstance(typ, TypedDictType): - dict_items = { + dict_items: Dict[str, MypyType] = { item_name: translate_kind_instance(item_type) for item_name, item_type in typ.items.items() } @@ -120,18 +123,18 @@ def translate_kind_instance(typ: Type) -> Type: # noqa: WPS, C901 return typ -def _translate_types(types: Iterable[Type]) -> List[Type]: +def _translate_types(types: Iterable[MypyType]) -> List[MypyType]: return [translate_kind_instance(typ) for typ in types] -def _process_kinded_type(kind: Instance) -> Type: +def _process_kinded_type(kind: Instance) -> ProperType: """Recursively process all type arguments in a kind.""" if not kind.args: return kind real_type = get_proper_type(kind.args[0]) if isinstance(real_type, TypeVarType): - return erase_to_bound(real_type) + return get_proper_type(erase_to_bound(real_type)) elif isinstance(real_type, Instance): return real_type.copy_modified( args=kind.args[1:len(real_type.args) + 1], diff --git a/setup.cfg b/setup.cfg index 26679ca00..654f0ce21 100644 --- a/setup.cfg +++ b/setup.cfg @@ -160,7 +160,8 @@ exclude_lines = # Custom plugins: plugins = - returns.contrib.mypy.returns_plugin + mypy.plugins.proper_plugin, + returns.contrib.mypy.returns_plugin, enable_error_code = truthy-bool,