"""Simple type inference for decorated functions during semantic analysis.""" from __future__ import annotations from mypy.nodes import ARG_POS, CallExpr, Decorator, Expression, FuncDef, RefExpr, Var from mypy.semanal_shared import SemanticAnalyzerInterface from mypy.typeops import function_type from mypy.types import ( AnyType, CallableType, ProperType, Type, TypeOfAny, TypeVarType, get_proper_type, ) from mypy.typevars import has_no_typevars def infer_decorator_signature_if_simple( dec: Decorator, analyzer: SemanticAnalyzerInterface ) -> None: """Try to infer the type of the decorated function. This lets us resolve additional references to decorated functions during type checking. Otherwise the type might not be available when we need it, since module top levels can't be deferred. This basically uses a simple special-purpose type inference engine just for decorators. """ if dec.var.is_property: # Decorators are expected to have a callable type (it's a little odd). if dec.func.type is None: dec.var.type = CallableType( [AnyType(TypeOfAny.special_form)], [ARG_POS], [None], AnyType(TypeOfAny.special_form), analyzer.named_type("builtins.function"), name=dec.var.name, ) elif isinstance(dec.func.type, CallableType): dec.var.type = dec.func.type return decorator_preserves_type = True for expr in dec.decorators: preserve_type = False if isinstance(expr, RefExpr) and isinstance(expr.node, FuncDef): if expr.node.type and is_identity_signature(expr.node.type): preserve_type = True if not preserve_type: decorator_preserves_type = False break if decorator_preserves_type: # No non-identity decorators left. We can trivially infer the type # of the function here. dec.var.type = function_type(dec.func, analyzer.named_type("builtins.function")) if dec.decorators: return_type = calculate_return_type(dec.decorators[0]) if return_type and isinstance(return_type, AnyType): # The outermost decorator will return Any so we know the type of the # decorated function. dec.var.type = AnyType(TypeOfAny.from_another_any, source_any=return_type) sig = find_fixed_callable_return(dec.decorators[0]) if sig: # The outermost decorator always returns the same kind of function, # so we know that this is the type of the decorated function. orig_sig = function_type(dec.func, analyzer.named_type("builtins.function")) sig.name = orig_sig.items[0].name dec.var.type = sig def is_identity_signature(sig: Type) -> bool: """Is type a callable of form T -> T (where T is a type variable)?""" sig = get_proper_type(sig) if isinstance(sig, CallableType) and sig.arg_kinds == [ARG_POS]: if isinstance(sig.arg_types[0], TypeVarType) and isinstance(sig.ret_type, TypeVarType): return sig.arg_types[0].id == sig.ret_type.id return False def calculate_return_type(expr: Expression) -> ProperType | None: """Return the return type if we can calculate it. This only uses information available during semantic analysis so this will sometimes return None because of insufficient information (as type inference hasn't run yet). """ if isinstance(expr, RefExpr): if isinstance(expr.node, FuncDef): typ = expr.node.type if typ is None: # No signature -> default to Any. return AnyType(TypeOfAny.unannotated) # Explicit Any return? if isinstance(typ, CallableType): return get_proper_type(typ.ret_type) return None elif isinstance(expr.node, Var): return get_proper_type(expr.node.type) elif isinstance(expr, CallExpr): return calculate_return_type(expr.callee) return None def find_fixed_callable_return(expr: Expression) -> CallableType | None: """Return the return type, if expression refers to a callable that returns a callable. But only do this if the return type has no type variables. Return None otherwise. This approximates things a lot as this is supposed to be called before type checking when full type information is not available yet. """ if isinstance(expr, RefExpr): if isinstance(expr.node, FuncDef): typ = expr.node.type if typ: if isinstance(typ, CallableType) and has_no_typevars(typ.ret_type): ret_type = get_proper_type(typ.ret_type) if isinstance(ret_type, CallableType): return ret_type elif isinstance(expr, CallExpr): t = find_fixed_callable_return(expr.callee) if t: ret_type = get_proper_type(t.ret_type) if isinstance(ret_type, CallableType): return ret_type return None