From acf87a30541ff1177450a2bb288c63a108641526 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 28 Aug 2021 14:13:08 +0100 Subject: [PATCH 01/15] Much better initial implementation of typing.Self --- mypy/checkmember.py | 9 +++++- mypy/constraints.py | 5 +++- mypy/erasetype.py | 5 +++- mypy/expandtype.py | 12 ++++++-- mypy/fixup.py | 5 +++- mypy/indirection.py | 3 ++ mypy/join.py | 5 +++- mypy/meet.py | 5 +++- mypy/mixedtraverser.py | 6 +++- mypy/semanal.py | 50 +++++++++++++++++++++++++++++---- mypy/subtypes.py | 10 ++++++- mypy/type_visitor.py | 16 +++++++++-- mypy/typeanal.py | 13 +++++++-- mypy/typeops.py | 9 +++++- mypy/types.py | 31 ++++++++++++++++++-- mypy/typeshed/stdlib/typing.pyi | 5 ++++ mypy/typetraverser.py | 5 +++- 17 files changed, 169 insertions(+), 25 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 55e0df47ecf9..42afb9e40714 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -6,7 +6,7 @@ from mypy.types import ( Type, Instance, AnyType, TupleType, TypedDictType, CallableType, FunctionLike, TypeVarLikeType, Overloaded, TypeVarType, UnionType, PartialType, TypeOfAny, LiteralType, - DeletedType, NoneType, TypeType, has_type_vars, get_proper_type, ProperType + DeletedType, NoneType, TypeType, has_type_vars, get_proper_type, ProperType, SelfType ) from mypy.nodes import ( TypeInfo, FuncBase, Var, FuncDef, SymbolNode, SymbolTable, Context, @@ -141,6 +141,8 @@ def _analyze_member_access(name: str, typ = get_proper_type(typ) if isinstance(typ, Instance): return analyze_instance_member_access(name, typ, mx, override_info) + elif isinstance(typ, SelfType): + return analyze_instance_member_access(name, typ.instance, mx, override_info) elif isinstance(typ, AnyType): # The base object has dynamic type. return AnyType(TypeOfAny.from_another_any, source_any=typ) @@ -457,6 +459,9 @@ def analyze_descriptor_access(instance_type: Type, descriptor_type = get_proper_type(descriptor_type) if isinstance(descriptor_type, UnionType): + for idx, item in enumerate(descriptor_type.items): + if isinstance(item, SelfType): + descriptor_type.items[idx] = instance_type # Map the access over union types return make_simplified_union([ analyze_descriptor_access(instance_type, typ, builtin_type, @@ -465,6 +470,8 @@ def analyze_descriptor_access(instance_type: Type, ]) elif not isinstance(descriptor_type, Instance): return descriptor_type + elif isinstance(descriptor_type, SelfType): + return instance_type if not descriptor_type.type.has_readable_member('__get__'): return descriptor_type diff --git a/mypy/constraints.py b/mypy/constraints.py index 48e1be357d3b..26ae349c074e 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -7,7 +7,7 @@ CallableType, Type, TypeVisitor, UnboundType, AnyType, NoneType, TypeVarType, Instance, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType, DeletedType, UninhabitedType, TypeType, TypeVarId, TypeQuery, is_named_instance, TypeOfAny, LiteralType, - ProperType, get_proper_type, TypeAliasType, TypeGuardType + ProperType, get_proper_type, TypeAliasType, TypeGuardType, SelfType ) from mypy.maptype import map_instance_to_supertype import mypy.subtypes @@ -320,6 +320,9 @@ def visit_type_var(self, template: TypeVarType) -> List[Constraint]: assert False, ("Unexpected TypeVarType in ConstraintBuilderVisitor" " (should have been handled in infer_constraints)") + def visit_self_type(self, template: SelfType) -> List[Constraint]: + return self.visit_instance(template.instance) + # Non-leaf types def visit_instance(self, template: Instance) -> List[Constraint]: diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 70b7c3b6de32..1ab12d74fcd4 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -4,7 +4,7 @@ Type, TypeVisitor, UnboundType, AnyType, NoneType, TypeVarId, Instance, TypeVarType, CallableType, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType, DeletedType, TypeTranslator, UninhabitedType, TypeType, TypeOfAny, LiteralType, ProperType, - get_proper_type, TypeAliasType, TypeGuardType + get_proper_type, TypeAliasType, TypeGuardType, SelfType ) from mypy.nodes import ARG_STAR, ARG_STAR2 @@ -57,6 +57,9 @@ def visit_instance(self, t: Instance) -> ProperType: def visit_type_var(self, t: TypeVarType) -> ProperType: return AnyType(TypeOfAny.special_form) + def visit_self_type(self, t: SelfType) -> ProperType: + return self.visit_instance(t.instance) + def visit_callable_type(self, t: CallableType) -> ProperType: # We must preserve the fallback type for overload resolution to work. any_type = AnyType(TypeOfAny.special_form) diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 5679d5802782..d092cb3b22b7 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -5,7 +5,7 @@ NoneType, TypeVarType, Overloaded, TupleType, TypedDictType, UnionType, ErasedType, PartialType, DeletedType, UninhabitedType, TypeType, TypeVarId, FunctionLike, TypeVarType, LiteralType, get_proper_type, ProperType, - TypeAliasType, ParamSpecType + TypeAliasType, ParamSpecType, SelfType ) @@ -44,8 +44,11 @@ def freshen_function_type_vars(callee: F) -> F: # TODO(shantanu): fix for ParamSpecType if isinstance(v, ParamSpecType): continue - assert isinstance(v, TypeVarType) - tv = TypeVarType.new_unification_variable(v) + if isinstance(v, SelfType): + tv = v + else: + assert isinstance(v, TypeVarType) + tv = TypeVarType.new_unification_variable(v) tvs.append(tv) tvmap[v.id] = tv fresh = cast(CallableType, expand_type(callee, tvmap)).copy_modified(variables=tvs) @@ -98,6 +101,9 @@ def visit_type_var(self, t: TypeVarType) -> Type: else: return repl + def visit_self_type(self, t: SelfType) -> Type: + return t + def visit_callable_type(self, t: CallableType) -> Type: return t.copy_modified(arg_types=self.expand_types(t.arg_types), ret_type=t.ret_type.accept(self), diff --git a/mypy/fixup.py b/mypy/fixup.py index 948a1471ce41..0bdf5b086bd7 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -11,7 +11,7 @@ from mypy.types import ( CallableType, Instance, Overloaded, TupleType, TypeGuardType, TypedDictType, TypeVarType, UnboundType, UnionType, TypeVisitor, LiteralType, - TypeType, NOT_READY, TypeAliasType, AnyType, TypeOfAny + TypeType, NOT_READY, TypeAliasType, AnyType, TypeOfAny, SelfType ) from mypy.visitor import NodeVisitor from mypy.lookup import lookup_fully_qualified @@ -245,6 +245,9 @@ def visit_type_var(self, tvt: TypeVarType) -> None: if tvt.upper_bound is not None: tvt.upper_bound.accept(self) + def visit_self_type(self, t: SelfType) -> None: + return t.instance.accept(self) + def visit_unbound_type(self, o: UnboundType) -> None: for a in o.args: a.accept(self) diff --git a/mypy/indirection.py b/mypy/indirection.py index 952bccc36c06..fe3cc0a2ef95 100644 --- a/mypy/indirection.py +++ b/mypy/indirection.py @@ -64,6 +64,9 @@ def visit_deleted_type(self, t: types.DeletedType) -> Set[str]: def visit_type_var(self, t: types.TypeVarType) -> Set[str]: return self._visit(t.values) | self._visit(t.upper_bound) + def visit_self_type(self, t: types.SelfType) -> Set[str]: + return set() + def visit_instance(self, t: types.Instance) -> Set[str]: out = self._visit(t.args) if t.type: diff --git a/mypy/join.py b/mypy/join.py index 2cbc1a9edc8f..495889539379 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -7,7 +7,7 @@ Type, AnyType, NoneType, TypeVisitor, Instance, UnboundType, TypeVarType, CallableType, TupleType, TypedDictType, ErasedType, UnionType, FunctionLike, Overloaded, LiteralType, PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, get_proper_type, - ProperType, get_proper_types, TypeAliasType, PlaceholderType, TypeGuardType + ProperType, get_proper_types, TypeAliasType, PlaceholderType, TypeGuardType, SelfType ) from mypy.maptype import map_instance_to_supertype from mypy.subtypes import ( @@ -249,6 +249,9 @@ def visit_type_var(self, t: TypeVarType) -> ProperType: else: return self.default(self.s) + def visit_self_type(self, t: SelfType) -> ProperType: + return self.join(self.s, t.instance) + def visit_instance(self, t: Instance) -> ProperType: if isinstance(self.s, Instance): if self.instance_joiner is None: diff --git a/mypy/meet.py b/mypy/meet.py index 70d75a2570bf..335be196b213 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -5,7 +5,7 @@ Type, AnyType, TypeVisitor, UnboundType, NoneType, TypeVarType, Instance, CallableType, TupleType, TypedDictType, ErasedType, UnionType, PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, Overloaded, FunctionLike, LiteralType, - ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeGuardType + ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeGuardType, SelfType ) from mypy.subtypes import is_equivalent, is_subtype, is_callable_compatible, is_proper_subtype from mypy.erasetype import erase_type @@ -488,6 +488,9 @@ def visit_type_var(self, t: TypeVarType) -> ProperType: else: return self.default(self.s) + def visit_self_type(self, t: SelfType) -> ProperType: + return self.meet(self.s, t.instance) + def visit_instance(self, t: Instance) -> ProperType: if isinstance(self.s, Instance): si = self.s diff --git a/mypy/mixedtraverser.py b/mypy/mixedtraverser.py index 57fdb28e0e45..9d2572eb4beb 100644 --- a/mypy/mixedtraverser.py +++ b/mypy/mixedtraverser.py @@ -5,7 +5,7 @@ CastExpr, TypeApplication, TypeAliasExpr, TypeVarExpr, TypedDictExpr, NamedTupleExpr, PromoteExpr, NewTypeExpr ) -from mypy.types import Type +from mypy.types import Type, SelfType from mypy.traverser import TraverserVisitor from mypy.typetraverser import TypeTraverserVisitor @@ -41,6 +41,10 @@ def visit_type_var_expr(self, o: TypeVarExpr) -> None: for value in o.values: value.accept(self) + def visit_self_type(self, o: SelfType) -> None: + super().visit_self_type(o) + o.instance.accept(self) + def visit_typeddict_expr(self, o: TypedDictExpr) -> None: super().visit_typeddict_expr(o) self.visit_optional_type(o.info.typeddict_type) diff --git a/mypy/semanal.py b/mypy/semanal.py index 49ec5c88f30f..320517a8f53d 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -91,7 +91,7 @@ FunctionLike, UnboundType, TypeVarType, TupleType, UnionType, StarType, CallableType, Overloaded, Instance, Type, AnyType, LiteralType, LiteralValue, TypeTranslator, TypeOfAny, TypeType, NoneType, PlaceholderType, TPDICT_NAMES, ProperType, - get_proper_type, get_proper_types, TypeAliasType + get_proper_type, get_proper_types, TypeAliasType, SelfType ) from mypy.typeops import function_type from mypy.type_visitor import TypeQuery @@ -654,19 +654,35 @@ def analyze_func_def(self, defn: FuncDef) -> None: def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: """Check basic signature validity and tweak annotation of self/cls argument.""" # Only non-static methods are special. - functype = func.type if not func.is_static: if func.name in ['__init_subclass__', '__class_getitem__']: func.is_class = True if not func.arguments: self.fail('Method must have at least one argument', func) - elif isinstance(functype, CallableType): - self_type = get_proper_type(functype.arg_types[0]) + elif isinstance(func.type, CallableType): + self_type = get_proper_type(func.type.arg_types[0]) if isinstance(self_type, AnyType): leading_type: Type = fill_typevars(info) if func.is_class or func.name == '__new__': leading_type = self.class_type(leading_type) - func.type = replace_implicit_first_type(functype, leading_type) + func.type = replace_implicit_first_type(func.type, leading_type) + + leading_type = func.type.arg_types[0] + if not isinstance(leading_type, (Instance, TypeType)): + return + self_type = leading_type if not func.is_class else leading_type.item + fullname = None + # bind any SelfTypes + for idx, arg in enumerate(func.type.arg_types): + if self.is_self_type(arg): + if fullname is None: + fullname = self.lookup_qualified(arg.name, arg).node.fullname + func.type.arg_types[idx] = SelfType(self_type, fullname=fullname) + + if self.is_self_type(func.type.ret_type): + if fullname is None: + fullname = self.lookup_qualified(func.type.ret_type.name, func.type.ret_type).node.fullname + func.type.ret_type = SelfType(self_type, fullname=fullname) def set_original_def(self, previous: Optional[Node], new: Union[FuncDef, Decorator]) -> bool: """If 'new' conditionally redefine 'previous', set 'previous' as original @@ -1559,6 +1575,18 @@ def configure_base_classes(self, self.set_dummy_mro(defn.info) return self.calculate_class_mro(defn, self.object_type) + return + for base in info.mro: + for name, type in base.names.items(): + if isinstance(type, SelfType): # bind Self + info.names[name] = SelfType(self.named_type(defn.fullname), type.fullname) + elif isinstance(type, UnionType): + info.names[name] = UnionType([ + item + if not isinstance(item, SelfType) + else SelfType(self.named_type(defn.fullname), type.fullname) + for item in type.items + ]) def configure_tuple_base_class(self, defn: ClassDef, @@ -3268,6 +3296,14 @@ def is_final_type(self, typ: Optional[Type]) -> bool: return False return sym.node.fullname in ('typing.Final', 'typing_extensions.Final') + def is_self_type(self, typ: Optional[Type]) -> None: + if not isinstance(typ, UnboundType): + return False + sym = self.lookup_qualified(typ.name, typ) + if not sym or not sym.node: + return False + return sym.node.fullname in ('typing.Self', 'typing_extensions.Self') + def fail_invalid_classvar(self, context: Context) -> None: self.fail('ClassVar can only be used for assignments in class body', context) @@ -3593,6 +3629,8 @@ def visit_name_expr(self, expr: NameExpr) -> None: def bind_name_expr(self, expr: NameExpr, sym: SymbolTableNode) -> None: """Bind name expression to a symbol table node.""" + if sym.node.fullname in ('typing.Self', 'typing_extensions.Self') and not self.is_class_scope(): + self.fail('{} is unbound'.format(expr.name), expr) if isinstance(sym.node, TypeVarExpr) and self.tvar_scope.get_binding(sym): self.fail('"{}" is a type variable and only valid in type ' 'context'.format(expr.name), expr) @@ -5095,12 +5133,14 @@ def __init__(self) -> None: super().__init__(any) def visit_placeholder_type(self, t: PlaceholderType) -> bool: + print("returning true") return True def has_placeholder(typ: Type) -> bool: """Check if a type contains any placeholder types (recursively).""" return typ.accept(HasPlaceholders()) + return t def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 49689db7ea41..3b64058b1ad7 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -7,7 +7,7 @@ Type, AnyType, TypeGuardType, UnboundType, TypeVisitor, FormalArgument, NoneType, Instance, TypeVarType, CallableType, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType, DeletedType, UninhabitedType, TypeType, is_named_instance, - FunctionLike, TypeOfAny, LiteralType, get_proper_type, TypeAliasType + FunctionLike, TypeOfAny, LiteralType, get_proper_type, TypeAliasType, SelfType ) import mypy.applytype import mypy.constraints @@ -250,6 +250,8 @@ def visit_instance(self, left: Instance) -> bool: return False return True right = self.right + if isinstance(right, SelfType): + return self._is_subtype(left, right.instance) if isinstance(right, TupleType) and mypy.typeops.tuple_fallback(right).type.is_enum: return self._is_subtype(left, mypy.typeops.tuple_fallback(right)) if isinstance(right, Instance): @@ -305,6 +307,9 @@ def visit_type_var(self, left: TypeVarType) -> bool: return True return self._is_subtype(left.upper_bound, self.right) + def visit_self_type(self, left: SelfType) -> bool: + return self._is_subtype(left.instance, self.right) + def visit_callable_type(self, left: CallableType) -> bool: right = self.right if isinstance(right, CallableType): @@ -1310,6 +1315,9 @@ def visit_type_var(self, left: TypeVarType) -> bool: return True return self._is_proper_subtype(left.upper_bound, self.right) + def visit_self_type(self, t: SelfType) -> bool: + return self._is_proper_subtype(t.instance, self.right) + def visit_callable_type(self, left: CallableType) -> bool: right = self.right if isinstance(right, CallableType): diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index 0c5cfedcf385..15931d8f4ae3 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -11,7 +11,7 @@ other modules refer to them. """ -from abc import abstractmethod +from abc import ABCMeta, abstractmethod from mypy.backports import OrderedDict from typing import Generic, TypeVar, cast, Any, List, Callable, Iterable, Optional, Set, Sequence from mypy_extensions import trait, mypyc_attr @@ -23,13 +23,13 @@ RawExpressionType, Instance, NoneType, TypeType, UnionType, TypeVarType, PartialType, DeletedType, UninhabitedType, TypeVarLikeType, UnboundType, ErasedType, StarType, EllipsisType, TypeList, CallableArgument, - PlaceholderType, TypeAliasType, get_proper_type + PlaceholderType, TypeAliasType, get_proper_type, SelfType ) @trait @mypyc_attr(allow_interpreted_subclasses=True) -class TypeVisitor(Generic[T]): +class TypeVisitor(Generic[T], metaclass=ABCMeta): """Visitor class for types (Type subclasses). The parameter T is the return type of the visit methods. @@ -63,6 +63,10 @@ def visit_deleted_type(self, t: DeletedType) -> T: def visit_type_var(self, t: TypeVarType) -> T: pass + @abstractmethod + def visit_self_type(self, t: SelfType) -> T: + pass + @abstractmethod def visit_instance(self, t: Instance) -> T: pass @@ -183,6 +187,9 @@ def visit_instance(self, t: Instance) -> Type: def visit_type_var(self, t: TypeVarType) -> Type: return t + def visit_self_type(self, t: SelfType) -> Type: + return t + def visit_partial_type(self, t: PartialType) -> Type: return t @@ -298,6 +305,9 @@ def visit_deleted_type(self, t: DeletedType) -> T: def visit_type_var(self, t: TypeVarType) -> T: return self.query_types([t.upper_bound] + t.values) + def visit_self_type(self, t: SelfType) -> T: + return self.strategy([]) + def visit_partial_type(self, t: PartialType) -> T: return self.strategy([]) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 07dc704e42ea..3eed303150ca 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -16,7 +16,7 @@ CallableType, NoneType, ErasedType, DeletedType, TypeList, TypeVarType, SyntheticTypeVisitor, StarType, PartialType, EllipsisType, UninhabitedType, TypeType, TypeGuardType, TypeVarLikeType, CallableArgument, TypeQuery, union_items, TypeOfAny, LiteralType, RawExpressionType, - PlaceholderType, Overloaded, get_proper_type, TypeAliasType, TypeVarLikeType, ParamSpecType + PlaceholderType, Overloaded, get_proper_type, TypeAliasType, TypeVarLikeType, ParamSpecType, SelfType ) from mypy.nodes import ( @@ -353,6 +353,12 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt " and at least one annotation", t) return AnyType(TypeOfAny.from_error) return self.anal_type(t.args[0]) + elif fullname in ('typing_extensions.Self', 'typing.Self'): + try: + bound = self.named_type(self.api.type.fullname) + except AttributeError: + return self.fail("Self is unbound", t) + return SelfType(bound, fullname, line=t.line, column=t.column) elif self.anal_type_guard_arg(t, fullname) is not None: # In most contexts, TypeGuard[...] acts as an alias for bool (ignoring its args) return self.named_type('builtins.bool') @@ -528,6 +534,9 @@ def visit_type_alias_type(self, t: TypeAliasType) -> Type: def visit_type_var(self, t: TypeVarType) -> Type: return t + def visit_self_type(self, t: SelfType) -> Type: + raise + def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type: # Every Callable can bind its own type variables, if they're not in the outer scope with self.tvar_scope_frame(): @@ -932,7 +941,7 @@ def bind_function_type_variables( var_node = self.lookup_qualified(var.name, defn) assert var_node, "Binding for function type variable not found within function" var_expr = var_node.node - assert isinstance(var_expr, TypeVarLikeExpr) + assert isinstance(var_expr, TypeVarLikeExpr), f"got {var.__class__} {var_expr.__class__}" self.tvar_scope.bind_new(var.name, var_expr) return fun_type.variables typevars = self.infer_type_variables(fun_type) diff --git a/mypy/typeops.py b/mypy/typeops.py index 20772c82c765..38c28631480e 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -14,7 +14,7 @@ TupleType, Instance, FunctionLike, Type, CallableType, TypeVarLikeType, Overloaded, TypeVarType, UninhabitedType, FormalArgument, UnionType, NoneType, TypedDictType, AnyType, TypeOfAny, TypeType, ProperType, LiteralType, get_proper_type, get_proper_types, - copy_type, TypeAliasType, TypeQuery, ParamSpecType + copy_type, TypeAliasType, TypeQuery, ParamSpecType, SelfType ) from mypy.nodes import ( FuncBase, FuncItem, OverloadedFuncDef, TypeInfo, ARG_STAR, ARG_STAR2, ARG_POS, @@ -272,6 +272,13 @@ def expand(target: Type) -> Type: variables=variables, ret_type=ret_type, bound_args=[original_type]) + for arg_type in res.arg_types: + if isinstance(arg_type, UnionType): + for idx, item in enumerate(arg_type.items): + if isinstance(item, SelfType): + arg_type.items[idx] = original_type + if isinstance(res.ret_type, SelfType): + res.ret_type = original_type return cast(F, res) diff --git a/mypy/types.py b/mypy/types.py index e6cf92e879b5..793d28bae91c 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -427,6 +427,31 @@ def deserialize(cls, data: JsonDict) -> 'TypeVarType': ) +class SelfType(ProperType): + def __init__(self, instance: 'Instance', fullname: str, line: int = -1, column: int = -1) -> None: + super().__init__(line, column) + self.name = "Self" + self.fullname = fullname + self.instance: 'Instance' = instance + self.line = line + self.column = column + + def accept(self, visitor: 'TypeVisitor[T]') -> T: + return visitor.visit_self_type(self) + + def serialize(self) -> JsonDict: + return { + '.class': 'SelfType', + 'fullname': self.fullname, + 'instance': self.instance.serialize(), + } + + @classmethod + def deserialize(cls, data: JsonDict) -> 'SelfType': + assert data['.class'] == 'SelfType' + return cls(deserialize_type(data['instance']), data['fullname']) + + class ParamSpecType(TypeVarLikeType): """Definition of a single ParamSpec variable.""" @@ -963,8 +988,6 @@ class CallableType(FunctionLike): 'definition', # For error messages. May be None. 'variables', # Type variables for a generic function 'is_ellipsis_args', # Is this Callable[..., t] (with literal '...')? - 'is_classmethod_class', # Is this callable constructed for the benefit - # of a classmethod's 'cls' argument? 'implicit', # Was this type implicitly generated instead of explicitly # specified by the user? 'special_sig', # Non-None for signatures that require special handling @@ -1032,6 +1055,7 @@ def __init__(self, else: self.def_extras = {} self.type_guard = type_guard + # self.created = __import__('inspect').stack() def copy_modified(self, arg_types: Bogus[Sequence[Type]] = _dummy, @@ -2053,6 +2077,9 @@ def visit_type_var(self, t: TypeVarType) -> str: s += '(upper_bound={})'.format(t.upper_bound.accept(self)) return s + def visit_self_type(self, t: SelfType) -> str: + return "Self@{}".format(t.instance.accept(self)) if t.instance else "Self@unbound" + def visit_callable_type(self, t: CallableType) -> str: s = '' bare_asterisk = False diff --git a/mypy/typeshed/stdlib/typing.pyi b/mypy/typeshed/stdlib/typing.pyi index a494557f93df..ba5d9e0de978 100644 --- a/mypy/typeshed/stdlib/typing.pyi +++ b/mypy/typeshed/stdlib/typing.pyi @@ -81,6 +81,11 @@ if sys.version_info >= (3, 10): TypeAlias: _SpecialForm = ... TypeGuard: _SpecialForm = ... +if sys.version_info >= (3, 11): # TODO remove this + ... +# Self is also a (non-subscriptable) special form. +Self: object = ... + # Return type that indicates a function does not return. # This type is equivalent to the None type, but the no-op Union is necessary to # distinguish the None type from the None value. diff --git a/mypy/typetraverser.py b/mypy/typetraverser.py index e8f22a62e7c4..22297ed9329c 100644 --- a/mypy/typetraverser.py +++ b/mypy/typetraverser.py @@ -6,7 +6,7 @@ Type, SyntheticTypeVisitor, AnyType, UninhabitedType, NoneType, ErasedType, DeletedType, TypeVarType, LiteralType, Instance, CallableType, TupleType, TypedDictType, UnionType, Overloaded, TypeType, CallableArgument, UnboundType, TypeList, StarType, EllipsisType, - PlaceholderType, PartialType, RawExpressionType, TypeAliasType, TypeGuardType + PlaceholderType, PartialType, RawExpressionType, TypeAliasType, TypeGuardType, SelfType ) @@ -45,6 +45,9 @@ def visit_literal_type(self, t: LiteralType) -> None: def visit_instance(self, t: Instance) -> None: self.traverse_types(t.args) + def visit_self_type(self, t: SelfType) -> None: + return self.visit_instance(t.instance) + def visit_callable_type(self, t: CallableType) -> None: # FIX generics self.traverse_types(t.arg_types) From 7a6d7717204ca175325252912ca1d404d1dd4088 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 2 Oct 2021 18:15:42 +0100 Subject: [PATCH 02/15] Some extra stuff --- mypy/sametypes.py | 5 ++++- mypy/semanal.py | 1 - mypy/typeanal.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mypy/sametypes.py b/mypy/sametypes.py index f599cc2f7b14..9cac58d6b27a 100644 --- a/mypy/sametypes.py +++ b/mypy/sametypes.py @@ -4,7 +4,7 @@ Type, TypeGuardType, UnboundType, AnyType, NoneType, TupleType, TypedDictType, UnionType, CallableType, TypeVarType, Instance, TypeVisitor, ErasedType, Overloaded, PartialType, DeletedType, UninhabitedType, TypeType, LiteralType, - ProperType, get_proper_type, TypeAliasType) + ProperType, get_proper_type, TypeAliasType, SelfType) from mypy.typeops import tuple_fallback, make_simplified_union @@ -96,6 +96,9 @@ def visit_type_var(self, left: TypeVarType) -> bool: return (isinstance(self.right, TypeVarType) and left.id == self.right.id) + def visit_self_type(self, left: SelfType) -> bool: + return isinstance(self.right, SelfType) and self.right.instance == left.instance + def visit_callable_type(self, left: CallableType) -> bool: # FIX generics if isinstance(self.right, CallableType): diff --git a/mypy/semanal.py b/mypy/semanal.py index 320517a8f53d..8a32d22caeb1 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -5133,7 +5133,6 @@ def __init__(self) -> None: super().__init__(any) def visit_placeholder_type(self, t: PlaceholderType) -> bool: - print("returning true") return True diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 3eed303150ca..72ef6bb5ceae 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -535,7 +535,7 @@ def visit_type_var(self, t: TypeVarType) -> Type: return t def visit_self_type(self, t: SelfType) -> Type: - raise + return t def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type: # Every Callable can bind its own type variables, if they're not in the outer scope From b1d67ca043cb2f4fd3f37f6543ed95cb53ffebd3 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe <50501825+Gobot1234@users.noreply.github.com> Date: Fri, 5 Nov 2021 16:08:20 +0000 Subject: [PATCH 03/15] Slight improvements --- mypy/semanal.py | 28 ++++++++++++++++++++++++---- mypy/types.py | 1 - 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 8a32d22caeb1..ce659794861b 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -670,18 +670,38 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: leading_type = func.type.arg_types[0] if not isinstance(leading_type, (Instance, TypeType)): return - self_type = leading_type if not func.is_class else leading_type.item + self_type = leading_type.item if isinstance(leading_type, TypeType) else leading_type fullname = None # bind any SelfTypes for idx, arg in enumerate(func.type.arg_types): if self.is_self_type(arg): + if func.is_static: + self.fail( + "Self types in the annotations of staticmethods are not supported, " + "please replace the type with {}".format(self_type.type.name), + func + ) + func.type.arg_types[idx] = self.named_type( + self_type.type.name + ) # we replace them here for them + continue if fullname is None: fullname = self.lookup_qualified(arg.name, arg).node.fullname func.type.arg_types[idx] = SelfType(self_type, fullname=fullname) if self.is_self_type(func.type.ret_type): if fullname is None: + self.lookup_qualified(func.type.ret_type.name, func.type.ret_type) fullname = self.lookup_qualified(func.type.ret_type.name, func.type.ret_type).node.fullname + if func.is_static: + self.fail( + "Self types in the annotations of staticmethods are not supported, " + "please replace the type with {}".format(self_type.type.name), + func, + ) + func.type.ret_type = self.named_type(self_type.type.name) + return + func.type.ret_type = SelfType(self_type, fullname=fullname) def set_original_def(self, previous: Optional[Node], new: Union[FuncDef, Decorator]) -> bool: @@ -3296,7 +3316,7 @@ def is_final_type(self, typ: Optional[Type]) -> bool: return False return sym.node.fullname in ('typing.Final', 'typing_extensions.Final') - def is_self_type(self, typ: Optional[Type]) -> None: + def is_self_type(self, typ: Optional[Type]) -> bool: if not isinstance(typ, UnboundType): return False sym = self.lookup_qualified(typ.name, typ) @@ -3629,8 +3649,8 @@ def visit_name_expr(self, expr: NameExpr) -> None: def bind_name_expr(self, expr: NameExpr, sym: SymbolTableNode) -> None: """Bind name expression to a symbol table node.""" - if sym.node.fullname in ('typing.Self', 'typing_extensions.Self') and not self.is_class_scope(): - self.fail('{} is unbound'.format(expr.name), expr) + # if sym.node.fullname in ('typing.Self', 'typing_extensions.Self') and not self.is_class_scope(): + # self.fail('{} is unbound'.format(expr.name), expr) if isinstance(sym.node, TypeVarExpr) and self.tvar_scope.get_binding(sym): self.fail('"{}" is a type variable and only valid in type ' 'context'.format(expr.name), expr) diff --git a/mypy/types.py b/mypy/types.py index 793d28bae91c..e504c7e42441 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1055,7 +1055,6 @@ def __init__(self, else: self.def_extras = {} self.type_guard = type_guard - # self.created = __import__('inspect').stack() def copy_modified(self, arg_types: Bogus[Sequence[Type]] = _dummy, From 6a7a0841788cfdff26ce13c2509833fc23aae38e Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe <50501825+Gobot1234@users.noreply.github.com> Date: Sun, 5 Dec 2021 17:06:58 +0000 Subject: [PATCH 04/15] Remove removed type --- mypy/erasetype.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 2d559ece4715..dcc1a62758fd 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -4,7 +4,7 @@ Type, TypeVisitor, UnboundType, AnyType, NoneType, TypeVarId, Instance, TypeVarType, CallableType, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType, DeletedType, TypeTranslator, UninhabitedType, TypeType, TypeOfAny, LiteralType, ProperType, - get_proper_type, TypeAliasType, TypeGuardType, ParamSpecType, SelfType + get_proper_type, TypeAliasType, ParamSpecType, SelfType ) from mypy.nodes import ARG_STAR, ARG_STAR2 From 2ac45c9b9308e726105f2e4fee23fdf21aaa0d61 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe <50501825+Gobot1234@users.noreply.github.com> Date: Sun, 5 Dec 2021 17:09:53 +0000 Subject: [PATCH 05/15] Update meet.py --- mypy/meet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/meet.py b/mypy/meet.py index 815a21c149c6..c3995a91c5b8 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -5,7 +5,7 @@ Type, AnyType, TypeVisitor, UnboundType, NoneType, TypeVarType, Instance, CallableType, TupleType, TypedDictType, ErasedType, UnionType, PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, Overloaded, FunctionLike, LiteralType, - ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeGuardedType, SelfType + ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeGuardedType, SelfType, ParamSpecType ) from mypy.subtypes import is_equivalent, is_subtype, is_callable_compatible, is_proper_subtype From 1ebe0342a7f588f668c4e137b93bfe5212541447 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe <50501825+Gobot1234@users.noreply.github.com> Date: Sun, 5 Dec 2021 23:07:37 +0000 Subject: [PATCH 06/15] Update mypy/type_visitor.py Co-authored-by: Nikita Sobolev --- mypy/type_visitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index c2222e5ef61c..a0e8d37e9045 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -65,7 +65,7 @@ def visit_type_var(self, t: TypeVarType) -> T: @abstractmethod def visit_self_type(self, t: SelfType) -> T: - pass + pass def visit_param_spec(self, t: ParamSpecType) -> T: pass From 0e6d128bbd00952b4ea3e5434c1ec1a91108c943 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe <50501825+Gobot1234@users.noreply.github.com> Date: Mon, 6 Dec 2021 16:49:55 +0000 Subject: [PATCH 07/15] Fix most of the CI issues and respond to comments --- mypy/semanal.py | 6 ++++-- mypy/server/astdiff.py | 8 +++++++- mypy/server/astmerge.py | 5 ++++- mypy/server/deps.py | 10 +++++++++- mypy/type_visitor.py | 2 +- mypy/typeanal.py | 2 +- mypy/typeops.py | 2 ++ mypy/types.py | 11 ++++++++--- runner.py | 13 +++++++++++++ test.py | 38 ++++++++++++++++++++++++++++++++++++++ 10 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 runner.py create mode 100644 test.py diff --git a/mypy/semanal.py b/mypy/semanal.py index ce659794861b..29c9982ddec0 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -673,6 +673,7 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: self_type = leading_type.item if isinstance(leading_type, TypeType) else leading_type fullname = None # bind any SelfTypes + assert isinstance(func.type, CallableType) for idx, arg in enumerate(func.type.arg_types): if self.is_self_type(arg): if func.is_static: @@ -692,7 +693,9 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: if self.is_self_type(func.type.ret_type): if fullname is None: self.lookup_qualified(func.type.ret_type.name, func.type.ret_type) - fullname = self.lookup_qualified(func.type.ret_type.name, func.type.ret_type).node.fullname + table_node = self.lookup_qualified(func.type.ret_type.name, func.type.ret_type) + assert isinstance(table_node, SymbolTableNode) and table_node.node + fullname = cast(str, table_node.node.fullname) if func.is_static: self.fail( "Self types in the annotations of staticmethods are not supported, " @@ -5159,7 +5162,6 @@ def visit_placeholder_type(self, t: PlaceholderType) -> bool: def has_placeholder(typ: Type) -> bool: """Check if a type contains any placeholder types (recursively).""" return typ.accept(HasPlaceholders()) - return t def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike: diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 2588479eed13..05e17deca04e 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -57,7 +57,7 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method' FuncBase, OverloadedFuncDef, FuncItem, MypyFile, UNBOUND_IMPORTED ) from mypy.types import ( - Type, TypeGuardType, TypeVisitor, UnboundType, AnyType, NoneType, UninhabitedType, + SelfType, Type, TypeGuardType, TypeVisitor, UnboundType, AnyType, NoneType, UninhabitedType, ErasedType, DeletedType, Instance, TypeVarType, CallableType, TupleType, TypedDictType, UnionType, Overloaded, PartialType, TypeType, LiteralType, TypeAliasType ) @@ -306,6 +306,12 @@ def visit_type_var(self, typ: TypeVarType) -> SnapshotItem: snapshot_type(typ.upper_bound), typ.variance) + def visit_self_type(self, typ: SelfType) -> SnapshotItem: + return ('SelfType', + typ.fullname, + snapshot_type(typ.instance) + ) + def visit_callable_type(self, typ: CallableType) -> SnapshotItem: # FIX generics return ('CallableType', diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index cfcb01c2ee50..55ce1dc5a4f3 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -56,7 +56,7 @@ ) from mypy.traverser import TraverserVisitor from mypy.types import ( - Type, SyntheticTypeVisitor, Instance, AnyType, NoneType, CallableType, ErasedType, DeletedType, + SelfType, Type, SyntheticTypeVisitor, Instance, AnyType, NoneType, CallableType, ErasedType, DeletedType, TupleType, TypeType, TypeVarType, TypedDictType, UnboundType, UninhabitedType, UnionType, Overloaded, TypeVarType, TypeList, CallableArgument, EllipsisType, StarType, LiteralType, RawExpressionType, PartialType, PlaceholderType, TypeAliasType, TypeGuardType @@ -410,6 +410,9 @@ def visit_type_var(self, typ: TypeVarType) -> None: for value in typ.values: value.accept(self) + def visit_self_type(self, typ: SelfType) -> None: + typ.instance.accept(self) + def visit_typeddict_type(self, typ: TypedDictType) -> None: for value_type in typ.items.values(): value_type.accept(self) diff --git a/mypy/server/deps.py b/mypy/server/deps.py index f67fda425ecd..5b50661afabf 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -96,7 +96,7 @@ class 'mod.Cls'. This can also refer to an attribute inherited from a ) from mypy.traverser import TraverserVisitor from mypy.types import ( - Type, Instance, AnyType, NoneType, TypeVisitor, CallableType, DeletedType, PartialType, + SelfType, Type, Instance, AnyType, NoneType, TypeVisitor, CallableType, DeletedType, PartialType, TupleType, TypeType, TypeVarType, TypedDictType, UnboundType, UninhabitedType, UnionType, FunctionLike, Overloaded, TypeOfAny, LiteralType, ErasedType, get_proper_type, ProperType, TypeAliasType, TypeGuardType @@ -957,6 +957,14 @@ def visit_type_var(self, typ: TypeVarType) -> List[str]: triggers.extend(self.get_type_triggers(val)) return triggers + def visit_self_type(self, typ: SelfType) -> List[str]: + triggers = [] + if typ.fullname: + triggers.append(make_trigger(typ.fullname)) + if typ.instance: + triggers.extend(self.get_type_triggers(typ.instance)) + return triggers + def visit_typeddict_type(self, typ: TypedDictType) -> List[str]: triggers = [] for item in typ.items.values(): diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index 15931d8f4ae3..f80c643ab096 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -29,7 +29,7 @@ @trait @mypyc_attr(allow_interpreted_subclasses=True) -class TypeVisitor(Generic[T], metaclass=ABCMeta): +class TypeVisitor(Generic[T]): """Visitor class for types (Type subclasses). The parameter T is the return type of the visit methods. diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 72ef6bb5ceae..fdfb292a21a6 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -355,7 +355,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt return self.anal_type(t.args[0]) elif fullname in ('typing_extensions.Self', 'typing.Self'): try: - bound = self.named_type(self.api.type.fullname) + bound = self.named_type(self.api.type.fullname) # type: ignore except AttributeError: return self.fail("Self is unbound", t) return SelfType(bound, fullname, line=t.line, column=t.column) diff --git a/mypy/typeops.py b/mypy/typeops.py index 38c28631480e..79088546ec81 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -276,8 +276,10 @@ def expand(target: Type) -> Type: if isinstance(arg_type, UnionType): for idx, item in enumerate(arg_type.items): if isinstance(item, SelfType): + assert original_type is not None arg_type.items[idx] = original_type if isinstance(res.ret_type, SelfType): + assert original_type is not None res.ret_type = original_type return cast(F, res) diff --git a/mypy/types.py b/mypy/types.py index e504c7e42441..e31a695f9e5c 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -428,11 +428,14 @@ def deserialize(cls, data: JsonDict) -> 'TypeVarType': class SelfType(ProperType): + name: ClassVar[str] = 'Self' + + __slots__ = ('fullname', 'instance') + def __init__(self, instance: 'Instance', fullname: str, line: int = -1, column: int = -1) -> None: super().__init__(line, column) - self.name = "Self" self.fullname = fullname - self.instance: 'Instance' = instance + self.instance = instance self.line = line self.column = column @@ -449,7 +452,9 @@ def serialize(self) -> JsonDict: @classmethod def deserialize(cls, data: JsonDict) -> 'SelfType': assert data['.class'] == 'SelfType' - return cls(deserialize_type(data['instance']), data['fullname']) + instance = deserialize_type(data['instance']) + assert isinstance(instance, Instance) + return cls(instance, data['fullname']) class ParamSpecType(TypeVarLikeType): diff --git a/runner.py b/runner.py new file mode 100644 index 000000000000..b4e5031ea384 --- /dev/null +++ b/runner.py @@ -0,0 +1,13 @@ +import sys + +from mypy.version import __version__ +from mypy.build import build, BuildSource, Options + +print(__version__) + +options = Options() +options.show_traceback = True +options.raise_exceptions = True +options.verbosity = 10 +result = build([BuildSource("test.py", None, )], options, stderr=sys.stderr, stdout=sys.stdout) +print(*result.errors, sep="\n") \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 000000000000..b14ea3ef6f7f --- /dev/null +++ b/test.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +from typing import Self, TypeVar, Protocol + +T = TypeVar("T") + + +class InstanceOf(Protocol[T]): + @property # type: ignore + def __class__(self) -> T: ... # type: ignore + + +class MyMetaclass(type): + bar: str + + def __new__(mcs: type[MyMetaclass], *args, **kwargs) -> MyMetaclass: + cls = super().__new__(mcs, *args, **kwargs) + cls.bar = "Hello" + return cls + + def __mul__( + cls, + count: int, + ) -> list[InstanceOf[Self]]: + print(cls) + return [cls()] * count + + def __call__(cls, *args, **kwargs) -> InstanceOf[Self]: + return super().__call__(*args, **kwargs) + + +class Foo(metaclass=MyMetaclass): + THIS: int + +reveal_type(Foo) +reveal_type(Foo()) +foos = Foo * 3 +reveal_type(foos) From ff779e8f30eed84b98c1d374c9d5666ac3b29b73 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe <50501825+Gobot1234@users.noreply.github.com> Date: Fri, 18 Feb 2022 14:14:26 +0000 Subject: [PATCH 08/15] I think this works properly now --- mypy/checkmember.py | 3 +- mypy/server/deps.py | 1 + mypy/type_visitor.py | 79 +++++++++++++++++++++++++++++++++ mypy/typeanal.py | 7 +-- mypy/typeops.py | 11 +---- mypy/typeshed/stdlib/typing.pyi | 3 +- runner.py | 2 +- test.py | 40 +++++++---------- 8 files changed, 106 insertions(+), 40 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 719e3a2090f5..0b54ed3b029d 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -146,6 +146,7 @@ def _analyze_member_access(name: str, if isinstance(typ, Instance): return analyze_instance_member_access(name, typ, mx, override_info) elif isinstance(typ, SelfType): + mx.self_type = typ.instance return analyze_instance_member_access(name, typ.instance, mx, override_info) elif isinstance(typ, AnyType): # The base object has dynamic type. @@ -340,7 +341,7 @@ def analyze_none_member_access(name: str, typ: NoneType, mx: MemberContext) -> T return _analyze_member_access(name, mx.named_type('builtins.object'), mx) -def analyze_member_var_access(name: str, +def analyze_member_var_access(name: str, # what here? itype: Instance, info: TypeInfo, mx: MemberContext) -> Type: diff --git a/mypy/server/deps.py b/mypy/server/deps.py index 4264d019f495..f26f390594cd 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -967,6 +967,7 @@ def visit_self_type(self, typ: SelfType) -> List[str]: triggers.append(make_trigger(typ.fullname)) if typ.instance: triggers.extend(self.get_type_triggers(typ.instance)) + return triggers def visit_typeddict_type(self, typ: TypedDictType) -> List[str]: triggers = [] diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index fa03224bd83c..c5d3d70e1758 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -370,3 +370,82 @@ def query_types(self, types: Iterable[Type]) -> T: self.seen_aliases.add(t) res.append(t.accept(self)) return self.strategy(res) + + +TypeT = TypeVar("TypeT", bound=Type) + + +class SelfTypeVisitor(TypeVisitor[Any]): + def __init__(self, self_type: Instance) -> None: + # NOTE this visitor will mutate `func` + self.self_type = self_type + + def visit_unbound_type(self, t: UnboundType) -> None: + pass + + def visit_any(self, t: AnyType) -> None: + pass + + def visit_none_type(self, t: NoneType) -> None: + pass + + def visit_uninhabited_type(self, t: UninhabitedType) -> None: + pass + + def visit_erased_type(self, t: ErasedType) -> None: + pass + + def visit_deleted_type(self, t: DeletedType) -> None: + pass + + def visit_type_var(self, t: TypeVarType) -> None: + pass + + def visit_self_type(self, t: SelfType) -> None: + pass # should this raise? + + def visit_param_spec(self, t: ParamSpecType) -> None: + pass + + def visit_instance(self, t: Instance) -> None: + t.args = self.replace(t.args) + + def visit_callable_type(self, t: CallableType) -> None: + t.arg_types = self.replace(t.arg_types) + t.ret_type, = self.replace([t.ret_type]) + + def visit_overloaded(self, t: Overloaded) -> None: + for item in t.items: + item.accept(self) + + def visit_tuple_type(self, t: TupleType) -> None: + t.items = self.replace(t.items) + + def visit_typeddict_type(self, t: TypedDictType) -> None: + for key, value in zip(t.items, self.replace(t.items.values())): + t.items[key] = value + + def visit_literal_type(self, t: LiteralType) -> None: + pass + + def visit_union_type(self, t: UnionType) -> None: + t.items = self.replace(t.items) + + def visit_partial_type(self, t: PartialType) -> None: + pass + + def visit_type_type(self, t: TypeType) -> None: + t.item, = self.replace([t.item]) + + def visit_type_alias_type(self, t: TypeAliasType) -> None: + pass # TODO this is probably invalid + + def replace(self, types: Iterable[TypeT]) -> List[TypeT]: + ret: List[TypeT] = [] + for type in types: + if isinstance(type, SelfType): + type = self.self_type + else: + type.accept(self) + ret.append(type) # type: ignore # not sure if this is actually unsafe + return ret diff --git a/mypy/typeanal.py b/mypy/typeanal.py index d0ad8e58b87c..a85258a5719e 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -375,10 +375,11 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt return AnyType(TypeOfAny.from_error) return RequiredType(self.anal_type(t.args[0]), required=False) elif fullname in ('typing_extensions.Self', 'typing.Self'): - try: - bound = self.named_type(self.api.type.fullname) # type: ignore - except AttributeError: + from mypy.semanal import SemanticAnalyzer # circular import + + if not isinstance(self.api, SemanticAnalyzer): return self.fail("Self is unbound", t) + bound = self.named_type(self.api.type.fullname) return SelfType(bound, fullname, line=t.line, column=t.column) elif self.anal_type_guard_arg(t, fullname) is not None: # In most contexts, TypeGuard[...] acts as an alias for bool (ignoring its args) diff --git a/mypy/typeops.py b/mypy/typeops.py index ba3b2548b9ee..8a7ee3ead147 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -9,6 +9,7 @@ from typing_extensions import Type as TypingType import itertools import sys +from mypy.type_visitor import SelfTypeVisitor from mypy.types import ( TupleType, Instance, FunctionLike, Type, CallableType, TypeVarLikeType, Overloaded, @@ -253,15 +254,7 @@ def expand(target: Type) -> Type: variables=variables, ret_type=ret_type, bound_args=[original_type]) - for arg_type in res.arg_types: - if isinstance(arg_type, UnionType): - for idx, item in enumerate(arg_type.items): - if isinstance(item, SelfType): - assert original_type is not None - arg_type.items[idx] = original_type - if isinstance(res.ret_type, SelfType): - assert original_type is not None - res.ret_type = original_type + res.accept(SelfTypeVisitor(original_type)) return cast(F, res) diff --git a/mypy/typeshed/stdlib/typing.pyi b/mypy/typeshed/stdlib/typing.pyi index c2e2b618e22d..b4e72f79c386 100644 --- a/mypy/typeshed/stdlib/typing.pyi +++ b/mypy/typeshed/stdlib/typing.pyi @@ -86,7 +86,8 @@ if sys.version_info >= (3, 10): if sys.version_info >= (3, 11): # Self is also a (non-subscriptable) special form. - Self: object = ... + ... +Self: object = ... # These type variables are used by the container types. _S = TypeVar("_S") diff --git a/runner.py b/runner.py index b4e5031ea384..2802c50989d8 100644 --- a/runner.py +++ b/runner.py @@ -8,6 +8,6 @@ options = Options() options.show_traceback = True options.raise_exceptions = True -options.verbosity = 10 +# options.verbosity = 10 result = build([BuildSource("test.py", None, )], options, stderr=sys.stderr, stdout=sys.stdout) print(*result.errors, sep="\n") \ No newline at end of file diff --git a/test.py b/test.py index b14ea3ef6f7f..1339739459ec 100644 --- a/test.py +++ b/test.py @@ -1,38 +1,28 @@ from __future__ import annotations -from typing import Self, TypeVar, Protocol +from typing import Generic, TypeVar +from typing_extensions import Self +from abc import ABC T = TypeVar("T") +K = TypeVar("K") -class InstanceOf(Protocol[T]): - @property # type: ignore - def __class__(self) -> T: ... # type: ignore +class ItemSet(Generic[T]): + def first(self) -> T: ... -class MyMetaclass(type): - bar: str +class BaseItem(ABC): + @property + def set(self) -> ItemSet[Self]: ... - def __new__(mcs: type[MyMetaclass], *args, **kwargs) -> MyMetaclass: - cls = super().__new__(mcs, *args, **kwargs) - cls.bar = "Hello" - return cls - def __mul__( - cls, - count: int, - ) -> list[InstanceOf[Self]]: - print(cls) - return [cls()] * count +class FooItem(BaseItem): + name: str - def __call__(cls, *args, **kwargs) -> InstanceOf[Self]: - return super().__call__(*args, **kwargs) + def test(self) -> None: ... -class Foo(metaclass=MyMetaclass): - THIS: int - -reveal_type(Foo) -reveal_type(Foo()) -foos = Foo * 3 -reveal_type(foos) +reveal_type(FooItem().set.first().name) +reveal_type(BaseItem().set) +reveal_type(FooItem().set.first().test()) From 67661328dbec0f3fbec02087cd6e63c9a0c2995f Mon Sep 17 00:00:00 2001 From: Erik Kemperman Date: Fri, 20 May 2022 11:42:23 +0200 Subject: [PATCH 09/15] Make tests pass --- mypy/checkmember.py | 6 ++-- mypy/copytype.py | 5 +++- mypy/semanal.py | 65 ++++++++++++++++++++++++++----------------- mypy/type_visitor.py | 51 ++++++++++++++++++--------------- mypy/typeanal.py | 12 +++++--- mypy/typeops.py | 3 +- mypy/types.py | 18 ++++++++---- mypy/typetraverser.py | 2 +- runner.py | 2 +- 9 files changed, 99 insertions(+), 65 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index b3d32b0a673c..25c1b03a3c0f 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -7,7 +7,7 @@ Type, Instance, AnyType, TupleType, TypedDictType, CallableType, FunctionLike, TypeVarLikeType, Overloaded, TypeVarType, UnionType, PartialType, TypeOfAny, LiteralType, DeletedType, NoneType, TypeType, has_type_vars, get_proper_type, ProperType, ParamSpecType, - SelfType, ENUM_REMOVED_PROPS + SelfType, ENUM_REMOVED_PROPS, ) from mypy.nodes import ( TypeInfo, FuncBase, Var, FuncDef, SymbolNode, SymbolTable, Context, @@ -342,7 +342,7 @@ def analyze_none_member_access(name: str, typ: NoneType, mx: MemberContext) -> T return _analyze_member_access(name, mx.named_type('builtins.object'), mx) -def analyze_member_var_access(name: str, # what here? +def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, mx: MemberContext) -> Type: @@ -470,7 +470,7 @@ def analyze_descriptor_access(descriptor_type: Type, if isinstance(descriptor_type, UnionType): for idx, item in enumerate(descriptor_type.items): - if isinstance(item, SelfType): + if isinstance(get_proper_type(item), SelfType): descriptor_type.items[idx] = instance_type # Map the access over union types return make_simplified_union([ diff --git a/mypy/copytype.py b/mypy/copytype.py index 85d7d531c5a3..0592c3b3e862 100644 --- a/mypy/copytype.py +++ b/mypy/copytype.py @@ -4,7 +4,7 @@ ProperType, UnboundType, AnyType, NoneType, UninhabitedType, ErasedType, DeletedType, Instance, TypeVarType, ParamSpecType, PartialType, CallableType, TupleType, TypedDictType, LiteralType, UnionType, Overloaded, TypeType, TypeAliasType, UnpackType, Parameters, - TypeVarTupleType + TypeVarTupleType, SelfType ) from mypy.type_visitor import TypeVisitor @@ -75,6 +75,9 @@ def visit_unpack_type(self, t: UnpackType) -> ProperType: dup = UnpackType(t.type) return self.copy_common(t, dup) + def visit_self_type(self, t: SelfType) -> ProperType: + return self.copy_common(t, SelfType(t.instance, t.fullname, t.line, t.column)) + def visit_partial_type(self, t: PartialType) -> ProperType: return self.copy_common(t, PartialType(t.type, t.var, t.value_type)) diff --git a/mypy/semanal.py b/mypy/semanal.py index 6967a19654f6..1e2cd261865b 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -99,7 +99,7 @@ TypeTranslator, TypeOfAny, TypeType, NoneType, PlaceholderType, TPDICT_NAMES, ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeVarLikeType, Parameters, ParamSpecType, PROTOCOL_NAMES, TYPE_ALIAS_NAMES, FINAL_TYPE_NAMES, FINAL_DECORATOR_NAMES, REVEAL_TYPE_NAMES, - ASSERT_TYPE_NAMES, OVERLOAD_NAMES, is_named_instance, SelfType, + ASSERT_TYPE_NAMES, OVERLOAD_NAMES, is_named_instance, SelfType, SELF_TYPE_NAMES, ) from mypy.typeops import function_type, get_type_vars from mypy.type_visitor import TypeQuery @@ -710,18 +710,22 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: leading_type = self.class_type(leading_type) func.type = replace_implicit_first_type(func.type, leading_type) + assert isinstance(func.type, CallableType) leading_type = func.type.arg_types[0] - if not isinstance(leading_type, (Instance, TypeType)): + proper_leading_type = get_proper_type(leading_type) + if not isinstance(proper_leading_type, (Instance, TypeType)): return - self_type = leading_type.item if isinstance(leading_type, TypeType) else leading_type + if isinstance(proper_leading_type, TypeType): + self_type = proper_leading_type.item + else: + self_type = proper_leading_type fullname = None # bind any SelfTypes - assert isinstance(func.type, CallableType) for idx, arg in enumerate(func.type.arg_types): if self.is_self_type(arg): if func.is_static: self.fail( - "Self types in the annotations of staticmethods are not supported, " + "Self-type annotations of staticmethods are not supported, " "please replace the type with {}".format(self_type.type.name), func ) @@ -730,24 +734,32 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: ) # we replace them here for them continue if fullname is None: - fullname = self.lookup_qualified(arg.name, arg).node.fullname + aname = getattr(arg, 'name', 'EEEK') + if aname == 'EEEK': + self.fail("Oh no {} has no name".format(arg), func) + table_node = self.lookup_qualified(aname, arg) + assert isinstance(table_node, SymbolTableNode) and table_node.node + fullname = table_node.node.fullname + assert isinstance(self_type, Instance) func.type.arg_types[idx] = SelfType(self_type, fullname=fullname) if self.is_self_type(func.type.ret_type): if fullname is None: - self.lookup_qualified(func.type.ret_type.name, func.type.ret_type) - table_node = self.lookup_qualified(func.type.ret_type.name, func.type.ret_type) + tname = getattr(func.type.ret_type, 'name', 'ARGH') + if tname == 'ARGH': + self.fail("Oh no {} has no name".format(func.type.ret_type), func) + table_node = self.lookup_qualified(tname, func.type.ret_type) assert isinstance(table_node, SymbolTableNode) and table_node.node - fullname = cast(str, table_node.node.fullname) + fullname = table_node.node.fullname if func.is_static: self.fail( - "Self types in the annotations of staticmethods are not supported, " + "Self-type annotations of staticmethods are not supported, " "please replace the type with {}".format(self_type.type.name), func, ) func.type.ret_type = self.named_type(self_type.type.name) return - + assert isinstance(self_type, Instance) func.type.ret_type = SelfType(self_type, fullname=fullname) def set_original_def(self, previous: Optional[Node], new: Union[FuncDef, Decorator]) -> bool: @@ -1647,18 +1659,19 @@ def configure_base_classes(self, self.set_dummy_mro(defn.info) return self.calculate_class_mro(defn, self.object_type) - return - for base in info.mro: - for name, type in base.names.items(): - if isinstance(type, SelfType): # bind Self - info.names[name] = SelfType(self.named_type(defn.fullname), type.fullname) - elif isinstance(type, UnionType): - info.names[name] = UnionType([ - item - if not isinstance(item, SelfType) - else SelfType(self.named_type(defn.fullname), type.fullname) - for item in type.items - ]) + + # return? + # for base in info.mro: + # for name, type in base.names.items(): + # if isinstance(type, SelfType): # bind Self + # info.names[name] = SelfType(self.named_type(defn.fullname), type.fullname) + # elif isinstance(type, UnionType): + # info.names[name] = UnionType([ + # item + # if not isinstance(item, SelfType) + # else SelfType(self.named_type(defn.fullname), type.fullname) + # for item in type.items + # ]) def configure_tuple_base_class(self, defn: ClassDef, @@ -3515,7 +3528,7 @@ def is_self_type(self, typ: Optional[Type]) -> bool: sym = self.lookup_qualified(typ.name, typ) if not sym or not sym.node: return False - return sym.node.fullname in ('typing.Self', 'typing_extensions.Self') + return sym.node.fullname in SELF_TYPE_NAMES def fail_invalid_classvar(self, context: Context) -> None: self.fail(message_registry.CLASS_VAR_OUTSIDE_OF_CLASS, context) @@ -3917,8 +3930,8 @@ def visit_name_expr(self, expr: NameExpr) -> None: def bind_name_expr(self, expr: NameExpr, sym: SymbolTableNode) -> None: """Bind name expression to a symbol table node.""" - # if sym.node.fullname in ('typing.Self', 'typing_extensions.Self') and not self.is_class_scope(): - # self.fail('{} is unbound'.format(expr.name), expr) + if sym.node and sym.node.fullname in SELF_TYPE_NAMES and not self.is_class_scope(): + self.fail('{} is unbound'.format(expr.name), expr) if isinstance(sym.node, TypeVarExpr) and self.tvar_scope.get_binding(sym): self.fail('"{}" is a type variable and only valid in type ' 'context'.format(expr.name), expr) diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index 84e6fb637740..b86285fab1f5 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -11,9 +11,10 @@ other modules refer to them. """ -from abc import ABCMeta, abstractmethod +from abc import abstractmethod from mypy.backports import OrderedDict -from typing import Generic, TypeVar, cast, Any, List, Callable, Iterable, Optional, Set, Sequence +from typing import Generic, TypeVar, cast, Any, List, Callable, Iterable, Optional,\ + Set, Sequence from mypy_extensions import trait, mypyc_attr T = TypeVar('T') @@ -403,11 +404,8 @@ def query_types(self, types: Iterable[Type]) -> T: return self.strategy(res) -TypeT = TypeVar("TypeT", bound=Type) - - class SelfTypeVisitor(TypeVisitor[Any]): - def __init__(self, self_type: Instance) -> None: + def __init__(self, self_type: Type) -> None: # NOTE this visitor will mutate `func` self.self_type = self_type @@ -438,45 +436,54 @@ def visit_self_type(self, t: SelfType) -> None: def visit_param_spec(self, t: ParamSpecType) -> None: pass + def visit_type_var_tuple(self, t: TypeVarTupleType) -> T: + pass + + def visit_unpack_type(self, t: UnpackType) -> T: + pass + + def visit_parameters(self, t: Parameters) -> T: + pass + def visit_instance(self, t: Instance) -> None: - t.args = self.replace(t.args) + t.args = tuple(self.replace_types(t.args)) def visit_callable_type(self, t: CallableType) -> None: - t.arg_types = self.replace(t.arg_types) - t.ret_type, = self.replace([t.ret_type]) + t.arg_types = self.replace_types(t.arg_types) + t.ret_type = self.replace_type(t.ret_type) def visit_overloaded(self, t: Overloaded) -> None: for item in t.items: item.accept(self) def visit_tuple_type(self, t: TupleType) -> None: - t.items = self.replace(t.items) + t.items = self.replace_types(t.items) def visit_typeddict_type(self, t: TypedDictType) -> None: - for key, value in zip(t.items, self.replace(t.items.values())): + for key, value in zip(t.items, self.replace_types(t.items.values())): t.items[key] = value def visit_literal_type(self, t: LiteralType) -> None: pass def visit_union_type(self, t: UnionType) -> None: - t.items = self.replace(t.items) + t.items = self.replace_types(t.items) def visit_partial_type(self, t: PartialType) -> None: pass def visit_type_type(self, t: TypeType) -> None: - t.item, = self.replace([t.item]) + t.item = get_proper_type(self.replace_type(t.item)) def visit_type_alias_type(self, t: TypeAliasType) -> None: pass # TODO this is probably invalid - def replace(self, types: Iterable[TypeT]) -> List[TypeT]: - ret: List[TypeT] = [] - for type in types: - if isinstance(type, SelfType): - type = self.self_type - else: - type.accept(self) - ret.append(type) # type: ignore # not sure if this is actually unsafe - return ret + def replace_types(self, types: Iterable[Type]) -> List[Type]: + return [self.replace_type(typ) for typ in types] + + def replace_type(self, typ: Type) -> Type: + if isinstance(typ, SelfType): # type: ignore + typ = self.self_type + else: + typ.accept(self) + return typ diff --git a/mypy/typeanal.py b/mypy/typeanal.py index e278157d1823..b9d78271a4eb 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -17,7 +17,7 @@ Parameters, TypeQuery, union_items, TypeOfAny, LiteralType, RawExpressionType, PlaceholderType, Overloaded, get_proper_type, TypeAliasType, RequiredType, TypeVarLikeType, ParamSpecType, ParamSpecFlavor, UnpackType, TypeVarTupleType, - callable_with_ellipsis, TYPE_ALIAS_NAMES, FINAL_TYPE_NAMES, + callable_with_ellipsis, TYPE_ALIAS_NAMES, FINAL_TYPE_NAMES, SELF_TYPE_NAMES, LITERAL_TYPE_NAMES, ANNOTATED_TYPE_NAMES, SelfType, ) @@ -428,11 +428,15 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt self.fail("NotRequired[] must have exactly one type argument", t) return AnyType(TypeOfAny.from_error) return RequiredType(self.anal_type(t.args[0]), required=False) - elif fullname in ('typing_extensions.Self', 'typing.Self'): + elif fullname in SELF_TYPE_NAMES: from mypy.semanal import SemanticAnalyzer # circular import if not isinstance(self.api, SemanticAnalyzer): - return self.fail("Self is unbound", t) + self.fail("Self is unbound", t) + return AnyType(TypeOfAny.from_error) + if not isinstance(self.api.type, TypeInfo): + self.fail("Self is not enclosed in a class", t) + return AnyType(TypeOfAny.from_error) bound = self.named_type(self.api.type.fullname) return SelfType(bound, fullname, line=t.line, column=t.column) elif self.anal_type_guard_arg(t, fullname) is not None: @@ -1141,7 +1145,7 @@ def bind_function_type_variables( var_node = self.lookup_qualified(var.name, defn) assert var_node, "Binding for function type variable not found within function" var_expr = var_node.node - assert isinstance(var_expr, TypeVarLikeExpr), f"got {var.__class__} {var_expr.__class__}" + assert isinstance(var_expr, TypeVarLikeExpr) self.tvar_scope.bind_new(var.name, var_expr) return fun_type.variables typevars = self.infer_type_variables(fun_type) diff --git a/mypy/typeops.py b/mypy/typeops.py index 9a877279a150..5895b9755f17 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -259,7 +259,8 @@ def expand(target: Type) -> Type: variables=variables, ret_type=ret_type, bound_args=[original_type]) - res.accept(SelfTypeVisitor(original_type)) + if original_type: + res.accept(SelfTypeVisitor(original_type)) return cast(F, res) diff --git a/mypy/types.py b/mypy/types.py index d7519b219c22..6653b71da5e4 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -104,6 +104,12 @@ 'typing_extensions.final', ) +# Supported Self type names. +SELF_TYPE_NAMES: Final = ( + 'typing.Self', + 'typing_extensions.Self', +) + # Supported Literal type names. LITERAL_TYPE_NAMES: Final = ( 'typing.Literal', @@ -554,12 +560,11 @@ class SelfType(ProperType): __slots__ = ('fullname', 'instance') - def __init__(self, instance: 'Instance', fullname: str, line: int = -1, column: int = -1) -> None: + def __init__(self, instance: 'Instance', fullname: str, + line: int = -1, column: int = -1) -> None: super().__init__(line, column) self.fullname = fullname self.instance = instance - self.line = line - self.column = column def accept(self, visitor: 'TypeVisitor[T]') -> T: return visitor.visit_self_type(self) @@ -574,9 +579,8 @@ def serialize(self) -> JsonDict: @classmethod def deserialize(cls, data: JsonDict) -> 'SelfType': assert data['.class'] == 'SelfType' - instance = deserialize_type(data['instance']) - assert isinstance(instance, Instance) - return cls(instance, data['fullname']) + assert isinstance(data['instance'], str) + return SelfType(Instance.deserialize(data['instance']), data['fullname']) class ParamSpecFlavor: @@ -1486,6 +1490,8 @@ class CallableType(FunctionLike): 'definition', # For error messages. May be None. 'variables', # Type variables for a generic function 'is_ellipsis_args', # Is this Callable[..., t] (with literal '...')? + 'is_classmethod_class', # Is this callable constructed for the benefit + # of a classmethod's 'cls' argument? 'implicit', # Was this type implicitly generated instead of explicitly # specified by the user? 'special_sig', # Non-None for signatures that require special handling diff --git a/mypy/typetraverser.py b/mypy/typetraverser.py index fde135b40b6a..57d20b6a3f50 100644 --- a/mypy/typetraverser.py +++ b/mypy/typetraverser.py @@ -56,7 +56,7 @@ def visit_instance(self, t: Instance) -> None: self.traverse_types(t.args) def visit_self_type(self, t: SelfType) -> None: - return self.visit_instance(t.instance) + self.visit_instance(t.instance) def visit_callable_type(self, t: CallableType) -> None: # FIX generics diff --git a/runner.py b/runner.py index 2802c50989d8..6960269c86be 100644 --- a/runner.py +++ b/runner.py @@ -10,4 +10,4 @@ options.raise_exceptions = True # options.verbosity = 10 result = build([BuildSource("test.py", None, )], options, stderr=sys.stderr, stdout=sys.stdout) -print(*result.errors, sep="\n") \ No newline at end of file +print(*result.errors, sep="\n") From cada36a6bd72fe1b092b7101716257795b527668 Mon Sep 17 00:00:00 2001 From: Erik Kemperman Date: Fri, 20 May 2022 16:17:54 +0200 Subject: [PATCH 10/15] Unit tests --- mypy/test/testcheck.py | 1 + test-data/unit/check-selftyping.test | 14 ++++++++++++++ test-data/unit/fixtures/typing-full.pyi | 1 + 3 files changed, 16 insertions(+) create mode 100644 test-data/unit/check-selftyping.test diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index cc0c5875f53b..1d6647188578 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -70,6 +70,7 @@ 'check-newtype.test', 'check-class-namedtuple.test', 'check-selftype.test', + 'check-selftyping.test', 'check-python2.test', 'check-columns.test', 'check-functions.test', diff --git a/test-data/unit/check-selftyping.test b/test-data/unit/check-selftyping.test new file mode 100644 index 000000000000..e46d8a3cc524 --- /dev/null +++ b/test-data/unit/check-selftyping.test @@ -0,0 +1,14 @@ +-- PEP 673 -- +[case testSelfTypeMethodReturn] +from typing import Self +class C: + def m(self) -> Self: + return self + +reveal_type(C().m()) +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] +[out] +main:6: note: Revealed type is "__main__.C" + + diff --git a/test-data/unit/fixtures/typing-full.pyi b/test-data/unit/fixtures/typing-full.pyi index 66b02638ebc7..65310e083e63 100644 --- a/test-data/unit/fixtures/typing-full.pyi +++ b/test-data/unit/fixtures/typing-full.pyi @@ -31,6 +31,7 @@ Literal = 0 TypedDict = 0 NoReturn = 0 NewType = 0 +Self = 0 T = TypeVar('T') T_co = TypeVar('T_co', covariant=True) From fb6d552a3663bd709de92df195021b1f1753a84f Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Thu, 23 Jun 2022 00:16:19 +0100 Subject: [PATCH 11/15] I don't think this is entirely correct but lets see --- mypy/type_visitor.py | 1 - mypy/typeops.py | 39 ++++++++++++++++++++------------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index 2514cbfe80eb..298d7b9dedac 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -406,7 +406,6 @@ def query_types(self, types: Iterable[Type]) -> T: class SelfTypeVisitor(TypeVisitor[Any]): def __init__(self, self_type: Type) -> None: - # NOTE this visitor will mutate `func` self.self_type = self_type def visit_unbound_type(self, t: UnboundType) -> None: diff --git a/mypy/typeops.py b/mypy/typeops.py index 0e7f68d004bd..3f75c5b5603d 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -5,30 +5,29 @@ since these may assume that MROs are ready. """ -from typing import cast, Optional, List, Sequence, Set, Iterable, TypeVar, Dict, Tuple, Any, Union -from typing_extensions import Type as TypingType import itertools import sys +from typing import (Any, Dict, Iterable, List, Optional, Sequence, Set, Tuple, + TypeVar, Union, cast) -from mypy.types import ( - TupleType, Instance, FunctionLike, Type, CallableType, TypeVarLikeType, Overloaded, - TypeVarType, UninhabitedType, FormalArgument, UnionType, NoneType, - AnyType, TypeOfAny, TypeType, ProperType, LiteralType, get_proper_type, get_proper_types, - TypeAliasType, TypeQuery, ParamSpecType, Parameters, UnpackType, TypeVarTupleType, - ENUM_REMOVED_PROPS, -) -from mypy.nodes import ( - FuncBase, FuncItem, FuncDef, OverloadedFuncDef, TypeInfo, ARG_STAR, ARG_STAR2, ARG_POS, - Expression, StrExpr, Var, Decorator, SYMBOL_FUNCBASE_TYPES -) -from mypy.maptype import map_instance_to_supertype -from mypy.expandtype import expand_type_by_instance, expand_type -from mypy.copytype import copy_type - -from mypy.typevars import fill_typevars -from mypy.type_visitor import SelfTypeVisitor +from typing_extensions import Type as TypingType +from mypy.copytype import copy_type +from mypy.expandtype import expand_type, expand_type_by_instance +from mypy.maptype import map_instance_to_supertype +from mypy.nodes import (ARG_POS, ARG_STAR, ARG_STAR2, SYMBOL_FUNCBASE_TYPES, + Decorator, Expression, FuncBase, FuncDef, FuncItem, + OverloadedFuncDef, StrExpr, TypeInfo, Var) from mypy.state import state +from mypy.type_visitor import SelfTypeVisitor +from mypy.types import (ENUM_REMOVED_PROPS, AnyType, CallableType, + FormalArgument, FunctionLike, Instance, LiteralType, + NoneType, Overloaded, Parameters, ParamSpecType, + ProperType, TupleType, Type, TypeAliasType, TypeOfAny, + TypeQuery, TypeType, TypeVarLikeType, TypeVarTupleType, + TypeVarType, UninhabitedType, UnionType, UnpackType, + get_proper_type, get_proper_types) +from mypy.typevars import fill_typevars def is_recursive_pair(s: Type, t: Type) -> bool: @@ -276,6 +275,8 @@ def expand(target: Type) -> Type: ret_type=ret_type, bound_args=[original_type]) if original_type: + if isinstance(original_type, TypeType): + original_type = original_type.item res.accept(SelfTypeVisitor(original_type)) return cast(F, res) From 6c717585d2e5d685263e0a49b29de608e168c8c0 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Mon, 27 Jun 2022 21:37:05 +0100 Subject: [PATCH 12/15] Fix tests --- mypy/semanal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index e287f2c57fec..b8a9283b9a45 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -719,7 +719,7 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: self_type = proper_leading_type.item else: self_type = proper_leading_type - fullname = None + fullname: Optional[str] = None # bind any SelfTypes for idx, arg in enumerate(func.type.arg_types): if self.is_self_type(arg): From 791c9e3578c07ae4b59bad6a211665330aa95410 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Fri, 1 Jul 2022 12:35:47 +0100 Subject: [PATCH 13/15] Fixes for signatures of form (type[Self]/Self) -> Self --- mypy/semanal.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index b8a9283b9a45..05185c38dcd2 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -713,6 +713,16 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: assert isinstance(func.type, CallableType) leading_type = func.type.arg_types[0] proper_leading_type = get_proper_type(leading_type) + if self.is_self_type(proper_leading_type): # method[[Self, ...], Self] case + proper_leading_type = func.type.arg_types[0] = self.named_type(info.fullname) + elif isinstance(proper_leading_type, UnboundType): # classmethod[[type[Self], ...], Self] case + node = self.lookup(proper_leading_type.name, func) + if ( + node is not None + and node.fullname in {"typing.Type", "builtins.type"} + and self.is_self_type(proper_leading_type.args[0]) + ): + proper_leading_type = func.type.arg_types[0] = self.class_type(self.named_type(info.fullname)) if not isinstance(proper_leading_type, (Instance, TypeType)): return if isinstance(proper_leading_type, TypeType): @@ -734,10 +744,7 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: ) # we replace them here for them continue if fullname is None: - aname = getattr(arg, 'name', 'EEEK') - if aname == 'EEEK': - self.fail("Oh no {} has no name".format(arg), func) - table_node = self.lookup_qualified(aname, arg) + table_node = self.lookup_qualified(arg.name, arg) assert isinstance(table_node, SymbolTableNode) and table_node.node fullname = table_node.node.fullname assert isinstance(self_type, Instance) @@ -745,10 +752,7 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: if self.is_self_type(func.type.ret_type): if fullname is None: - tname = getattr(func.type.ret_type, 'name', 'ARGH') - if tname == 'ARGH': - self.fail("Oh no {} has no name".format(func.type.ret_type), func) - table_node = self.lookup_qualified(tname, func.type.ret_type) + table_node = self.lookup_qualified(func.type.ret_type.name, func.type.ret_type) assert isinstance(table_node, SymbolTableNode) and table_node.node fullname = table_node.node.fullname if func.is_static: From 09e966ea8a6cfce92bb4c246846d14be1c59d0b7 Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Fri, 1 Jul 2022 23:53:29 +0100 Subject: [PATCH 14/15] Fix some CI --- mypy/semanal.py | 162 +++++++++++++++++++++++++----------------------- 1 file changed, 84 insertions(+), 78 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 05185c38dcd2..5bcc0896bb1d 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -49,88 +49,88 @@ """ from contextlib import contextmanager +from typing import (Any, Callable, Dict, Iterable, Iterator, List, Optional, + Set, Tuple, TypeVar, Union, cast) -from typing import ( - Any, List, Dict, Set, Tuple, cast, TypeVar, Union, Optional, Callable, Iterator, Iterable -) -from typing_extensions import Final, TypeAlias as _TypeAlias - -from mypy.nodes import ( - AssertTypeExpr, MypyFile, TypeInfo, Node, AssignmentStmt, FuncDef, OverloadedFuncDef, - ClassDef, Var, GDEF, FuncItem, Import, Expression, Lvalue, - ImportFrom, ImportAll, Block, LDEF, NameExpr, MemberExpr, - IndexExpr, TupleExpr, ListExpr, ExpressionStmt, ReturnStmt, - RaiseStmt, AssertStmt, OperatorAssignmentStmt, WhileStmt, - ForStmt, BreakStmt, ContinueStmt, IfStmt, TryStmt, WithStmt, DelStmt, - GlobalDecl, SuperExpr, DictExpr, CallExpr, RefExpr, OpExpr, UnaryExpr, - SliceExpr, CastExpr, RevealExpr, TypeApplication, Context, SymbolTable, - SymbolTableNode, ListComprehension, GeneratorExpr, - LambdaExpr, MDEF, Decorator, SetExpr, TypeVarExpr, - StrExpr, BytesExpr, PrintStmt, ConditionalExpr, PromoteExpr, - ComparisonExpr, StarExpr, ArgKind, ARG_POS, ARG_NAMED, type_aliases, - YieldFromExpr, NamedTupleExpr, NonlocalDecl, SymbolNode, - SetComprehension, DictionaryComprehension, TypeAlias, TypeAliasExpr, - YieldExpr, ExecStmt, BackquoteExpr, ImportBase, AwaitExpr, - IntExpr, FloatExpr, UnicodeExpr, TempNode, OverloadPart, - PlaceholderNode, COVARIANT, CONTRAVARIANT, INVARIANT, - get_nongen_builtins, get_member_expr_fullname, REVEAL_TYPE, - REVEAL_LOCALS, is_final_node, TypedDictExpr, type_aliases_source_versions, - typing_extensions_aliases, - EnumCallExpr, RUNTIME_PROTOCOL_DECOS, FakeExpression, Statement, AssignmentExpr, - ParamSpecExpr, EllipsisExpr, TypeVarLikeExpr, implicit_module_attrs, - MatchStmt, FuncBase, TypeVarTupleExpr -) -from mypy.patterns import ( - AsPattern, OrPattern, ValuePattern, SequencePattern, - StarredPattern, MappingPattern, ClassPattern, -) -from mypy.tvar_scope import TypeVarLikeScope -from mypy.typevars import fill_typevars -from mypy.visitor import NodeVisitor -from mypy.errors import Errors, report_internal_error -from mypy.messages import ( - best_matches, MessageBuilder, pretty_seq, SUGGESTED_TEST_FIXTURES, TYPES_FOR_UNIMPORTED_HINTS -) +from typing_extensions import Final +from typing_extensions import TypeAlias as _TypeAlias + +from mypy import errorcodes as codes +from mypy import message_registry from mypy.errorcodes import ErrorCode -from mypy import message_registry, errorcodes as codes -from mypy.types import ( - NEVER_NAMES, FunctionLike, UnboundType, TypeVarType, TupleType, UnionType, StarType, - CallableType, Overloaded, Instance, Type, AnyType, LiteralType, LiteralValue, - TypeTranslator, TypeOfAny, TypeType, NoneType, PlaceholderType, TPDICT_NAMES, ProperType, - get_proper_type, get_proper_types, TypeAliasType, TypeVarLikeType, Parameters, ParamSpecType, - PROTOCOL_NAMES, TYPE_ALIAS_NAMES, FINAL_TYPE_NAMES, FINAL_DECORATOR_NAMES, REVEAL_TYPE_NAMES, - ASSERT_TYPE_NAMES, OVERLOAD_NAMES, is_named_instance, SelfType, SELF_TYPE_NAMES, -) -from mypy.typeops import function_type, get_type_vars -from mypy.type_visitor import TypeQuery -from mypy.typeanal import ( - TypeAnalyser, analyze_type_alias, no_subscript_builtin_alias, - TypeVarLikeQuery, TypeVarLikeList, remove_dups, has_any_from_unimported_type, - check_for_explicit_any, type_constructors, fix_instance_types -) -from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError +from mypy.errors import Errors, report_internal_error +from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type +from mypy.messages import (SUGGESTED_TEST_FIXTURES, TYPES_FOR_UNIMPORTED_HINTS, + MessageBuilder, best_matches, pretty_seq) +from mypy.mro import MroError, calculate_mro +from mypy.nodes import (ARG_NAMED, ARG_POS, CONTRAVARIANT, COVARIANT, GDEF, + INVARIANT, LDEF, MDEF, REVEAL_LOCALS, REVEAL_TYPE, + RUNTIME_PROTOCOL_DECOS, ArgKind, AssertStmt, + AssertTypeExpr, AssignmentExpr, AssignmentStmt, + AwaitExpr, BackquoteExpr, Block, BreakStmt, BytesExpr, + CallExpr, CastExpr, ClassDef, ComparisonExpr, + ConditionalExpr, Context, ContinueStmt, Decorator, + DelStmt, DictExpr, DictionaryComprehension, + EllipsisExpr, EnumCallExpr, ExecStmt, Expression, + ExpressionStmt, FakeExpression, FloatExpr, ForStmt, + FuncBase, FuncDef, FuncItem, GeneratorExpr, GlobalDecl, + IfStmt, Import, ImportAll, ImportBase, ImportFrom, + IndexExpr, IntExpr, LambdaExpr, ListComprehension, + ListExpr, Lvalue, MatchStmt, MemberExpr, MypyFile, + NamedTupleExpr, NameExpr, Node, NonlocalDecl, + OperatorAssignmentStmt, OpExpr, OverloadedFuncDef, + OverloadPart, ParamSpecExpr, PlaceholderNode, + PrintStmt, PromoteExpr, RaiseStmt, RefExpr, ReturnStmt, + RevealExpr, SetComprehension, SetExpr, SliceExpr, + StarExpr, Statement, StrExpr, SuperExpr, SymbolNode, + SymbolTable, SymbolTableNode, TempNode, TryStmt, + TupleExpr, TypeAlias, TypeAliasExpr, TypeApplication, + TypedDictExpr, TypeInfo, TypeVarExpr, TypeVarLikeExpr, + TypeVarTupleExpr, UnaryExpr, UnicodeExpr, Var, + WhileStmt, WithStmt, YieldExpr, YieldFromExpr, + get_member_expr_fullname, get_nongen_builtins, + implicit_module_attrs, is_final_node, type_aliases, + type_aliases_source_versions, + typing_extensions_aliases) from mypy.options import Options -from mypy.plugin import ( - Plugin, ClassDefContext, SemanticAnalyzerPluginInterface, - DynamicClassDefContext -) -from mypy.util import ( - correct_relative_import, unmangle, module_prefix, is_typeshed_file, unnamed_function, - is_dunder, -) +from mypy.patterns import (AsPattern, ClassPattern, MappingPattern, OrPattern, + SequencePattern, StarredPattern, ValuePattern) +from mypy.plugin import (ClassDefContext, DynamicClassDefContext, Plugin, + SemanticAnalyzerPluginInterface) +from mypy.reachability import (ALWAYS_FALSE, ALWAYS_TRUE, MYPY_FALSE, + MYPY_TRUE, infer_condition_value, + infer_reachability_of_if_statement, + infer_reachability_of_match_statement) from mypy.scope import Scope -from mypy.semanal_shared import ( - SemanticAnalyzerInterface, set_callable_name, calculate_tuple_fallback, PRIORITY_FALLBACKS -) -from mypy.semanal_namedtuple import NamedTupleAnalyzer -from mypy.semanal_typeddict import TypedDictAnalyzer from mypy.semanal_enum import EnumCallAnalyzer +from mypy.semanal_namedtuple import NamedTupleAnalyzer from mypy.semanal_newtype import NewTypeAnalyzer -from mypy.reachability import ( - infer_reachability_of_if_statement, infer_reachability_of_match_statement, - infer_condition_value, ALWAYS_FALSE, ALWAYS_TRUE, MYPY_TRUE, MYPY_FALSE -) -from mypy.mro import calculate_mro, MroError +from mypy.semanal_shared import (PRIORITY_FALLBACKS, SemanticAnalyzerInterface, + calculate_tuple_fallback, set_callable_name) +from mypy.semanal_typeddict import TypedDictAnalyzer +from mypy.tvar_scope import TypeVarLikeScope +from mypy.type_visitor import TypeQuery +from mypy.typeanal import (TypeAnalyser, TypeVarLikeList, TypeVarLikeQuery, + analyze_type_alias, check_for_explicit_any, + fix_instance_types, has_any_from_unimported_type, + no_subscript_builtin_alias, remove_dups, + type_constructors) +from mypy.typeops import function_type, get_type_vars +from mypy.types import (ASSERT_TYPE_NAMES, FINAL_DECORATOR_NAMES, + FINAL_TYPE_NAMES, NEVER_NAMES, OVERLOAD_NAMES, + PROTOCOL_NAMES, REVEAL_TYPE_NAMES, SELF_TYPE_NAMES, + TPDICT_NAMES, TYPE_ALIAS_NAMES, AnyType, CallableType, + FunctionLike, Instance, LiteralType, LiteralValue, + NoneType, Overloaded, Parameters, ParamSpecType, + PlaceholderType, ProperType, SelfType, StarType, + TupleType, Type, TypeAliasType, TypeOfAny, + TypeTranslator, TypeType, TypeVarLikeType, TypeVarType, + UnboundType, UnionType, get_proper_type, + get_proper_types, is_named_instance) +from mypy.typevars import fill_typevars +from mypy.util import (correct_relative_import, is_dunder, is_typeshed_file, + module_prefix, unmangle, unnamed_function) +from mypy.visitor import NodeVisitor T = TypeVar('T') @@ -715,14 +715,18 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: proper_leading_type = get_proper_type(leading_type) if self.is_self_type(proper_leading_type): # method[[Self, ...], Self] case proper_leading_type = func.type.arg_types[0] = self.named_type(info.fullname) - elif isinstance(proper_leading_type, UnboundType): # classmethod[[type[Self], ...], Self] case + elif isinstance(proper_leading_type, UnboundType): + # classmethod[[type[Self], ...], Self] case node = self.lookup(proper_leading_type.name, func) if ( node is not None and node.fullname in {"typing.Type", "builtins.type"} + and proper_leading_type.args and self.is_self_type(proper_leading_type.args[0]) ): - proper_leading_type = func.type.arg_types[0] = self.class_type(self.named_type(info.fullname)) + proper_leading_type = func.type.arg_types[0] = get_proper_type( + self.class_type(self.named_type(info.fullname + ))) if not isinstance(proper_leading_type, (Instance, TypeType)): return if isinstance(proper_leading_type, TypeType): @@ -744,6 +748,7 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: ) # we replace them here for them continue if fullname is None: + assert isinstance(arg, UnboundType) table_node = self.lookup_qualified(arg.name, arg) assert isinstance(table_node, SymbolTableNode) and table_node.node fullname = table_node.node.fullname @@ -752,6 +757,7 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: if self.is_self_type(func.type.ret_type): if fullname is None: + assert isinstance(func.type.ret_type, UnboundType) table_node = self.lookup_qualified(func.type.ret_type.name, func.type.ret_type) assert isinstance(table_node, SymbolTableNode) and table_node.node fullname = table_node.node.fullname From ce2d5fafe4c7429e3746dc93f6bd17c45bd0babf Mon Sep 17 00:00:00 2001 From: James Hilton-Balfe Date: Wed, 6 Jul 2022 21:45:12 +0100 Subject: [PATCH 15/15] Fix some CI failures --- mypy/semanal.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 5bcc0896bb1d..6b11ae890c95 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -708,7 +708,8 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: leading_type: Type = fill_typevars(info) if func.is_class or func.name == '__new__': leading_type = self.class_type(leading_type) - func.type = replace_implicit_first_type(func.type, leading_type) + if not self_type.type_of_any == TypeOfAny.explicit: + func.type = replace_implicit_first_type(func.type, leading_type) assert isinstance(func.type, CallableType) leading_type = func.type.arg_types[0] @@ -725,8 +726,8 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: and self.is_self_type(proper_leading_type.args[0]) ): proper_leading_type = func.type.arg_types[0] = get_proper_type( - self.class_type(self.named_type(info.fullname - ))) + self.class_type(self.named_type(info.fullname)) + ) if not isinstance(proper_leading_type, (Instance, TypeType)): return if isinstance(proper_leading_type, TypeType): @@ -749,7 +750,7 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: continue if fullname is None: assert isinstance(arg, UnboundType) - table_node = self.lookup_qualified(arg.name, arg) + table_node = self.lookup(arg.name, func) assert isinstance(table_node, SymbolTableNode) and table_node.node fullname = table_node.node.fullname assert isinstance(self_type, Instance) @@ -758,7 +759,9 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None: if self.is_self_type(func.type.ret_type): if fullname is None: assert isinstance(func.type.ret_type, UnboundType) - table_node = self.lookup_qualified(func.type.ret_type.name, func.type.ret_type) + table_node = self.lookup_qualified( + func.type.ret_type.name, func.type.ret_type + ) assert isinstance(table_node, SymbolTableNode) and table_node.node fullname = table_node.node.fullname if func.is_static: