Skip to content

Give self argument the correct type when using check_untyped_defs #7530

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
Type, AnyType, CallableType, FunctionLike, Overloaded, TupleType, TypedDictType,
Instance, NoneType, strip_type, TypeType, TypeOfAny,
UnionType, TypeVarId, TypeVarType, PartialType, DeletedType, UninhabitedType, TypeVarDef,
function_type, is_named_instance, union_items, TypeQuery, LiteralType,
is_named_instance, union_items, TypeQuery, LiteralType,
is_optional, remove_optional, TypeTranslator, StarType, get_proper_type, ProperType,
get_proper_types, is_literal_type
)
Expand All @@ -50,7 +50,7 @@
from mypy.typeops import (
map_type_from_supertype, bind_self, erase_to_bound, make_simplified_union,
erase_def_to_union_or_bound, erase_to_union_or_bound,
true_only, false_only,
true_only, false_only, function_type,
)
from mypy import message_registry
from mypy.subtypes import (
Expand Down Expand Up @@ -535,6 +535,11 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
else:
impl = impl_type

# Prevent extra noise from inconsistent use of @classmethod by copying
# the first arg from the method being checked against.
if sig1.arg_types and defn.info:
impl = impl.copy_modified(arg_types=[sig1.arg_types[0]] + impl.arg_types[1:])

# Is the overload alternative's arguments subtypes of the implementation's?
if not is_callable_compatible(impl, sig1,
is_compat=is_subtype_no_promote,
Expand Down Expand Up @@ -3934,7 +3939,14 @@ def enter_partial_types(self, *, is_function: bool = False,
self.partial_types.append(PartialTypeScope({}, is_function, is_local))
yield

permissive = (self.options.allow_untyped_globals and not is_local)
# Don't complain about not being able to infer partials if it is
# at the toplevel (with allow_untyped_globals) or if it is in an
# untyped function being checked with check_untyped_defs.
permissive = (self.options.allow_untyped_globals and not is_local) or (
self.options.check_untyped_defs
and self.dynamic_funcs
and self.dynamic_funcs[-1]
)

partial_types, _, _ = self.partial_types.pop()
if not self.current_node_deferred:
Expand Down
3 changes: 2 additions & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
Type, AnyType, CallableType, Overloaded, NoneType, TypeVarDef,
TupleType, TypedDictType, Instance, TypeVarType, ErasedType, UnionType,
PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, LiteralType, LiteralValue,
is_named_instance, function_type, callable_type, FunctionLike,
is_named_instance, FunctionLike,
StarType, is_optional, remove_optional, is_generic_instance, get_proper_type, ProperType,
get_proper_types
)
Expand Down Expand Up @@ -59,6 +59,7 @@
from mypy.plugin import Plugin, MethodContext, MethodSigContext, FunctionContext
from mypy.typeops import (
tuple_fallback, make_simplified_union, true_only, false_only, erase_to_union_or_bound,
function_type, callable_type,
)
import mypy.errorcodes as codes

Expand Down
4 changes: 2 additions & 2 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from mypy.types import (
Type, Instance, AnyType, TupleType, TypedDictType, CallableType, FunctionLike, TypeVarDef,
Overloaded, TypeVarType, UnionType, PartialType, TypeOfAny, LiteralType,
DeletedType, NoneType, TypeType, function_type, get_type_vars, get_proper_type, ProperType
DeletedType, NoneType, TypeType, get_type_vars, get_proper_type, ProperType
)
from mypy.nodes import (
TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile, TypeVarExpr,
Expand All @@ -24,7 +24,7 @@
from mypy import meet
from mypy.typeops import (
tuple_fallback, bind_self, erase_to_bound, class_callable, type_object_type_from_function,
make_simplified_union,
make_simplified_union, function_type,
)

if TYPE_CHECKING: # import for forward declaration only
Expand Down
7 changes: 4 additions & 3 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,12 @@
from mypy.errorcodes import ErrorCode
from mypy import message_registry, errorcodes as codes
from mypy.types import (
FunctionLike, UnboundType, TypeVarDef, TupleType, UnionType, StarType, function_type,
FunctionLike, UnboundType, TypeVarDef, TupleType, UnionType, StarType,
CallableType, Overloaded, Instance, Type, AnyType, LiteralType, LiteralValue,
TypeTranslator, TypeOfAny, TypeType, NoneType, PlaceholderType, TPDICT_NAMES, ProperType,
get_proper_type, get_proper_types
)
from mypy.typeops import function_type
from mypy.type_visitor import TypeQuery
from mypy.nodes import implicit_module_attrs
from mypy.typeanal import (
Expand Down Expand Up @@ -615,11 +616,11 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None:
# Only non-static methods are special.
functype = func.type
if not func.is_static:
if func.name() == '__init_subclass__':
func.is_class = True
if not func.arguments:
self.fail('Method must have at least one argument', func)
elif isinstance(functype, CallableType):
if func.name() == '__init_subclass__':
func.is_class = True
self_type = get_proper_type(functype.arg_types[0])
if isinstance(self_type, AnyType):
leading_type = fill_typevars(info) # type: Type
Expand Down
3 changes: 2 additions & 1 deletion mypy/semanal_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

from mypy.nodes import Expression, Decorator, CallExpr, FuncDef, RefExpr, Var, ARG_POS
from mypy.types import (
Type, CallableType, AnyType, TypeOfAny, TypeVarType, function_type, ProperType, get_proper_type
Type, CallableType, AnyType, TypeOfAny, TypeVarType, ProperType, get_proper_type
)
from mypy.typeops import function_type
from mypy.typevars import has_no_typevars
from mypy.semanal_shared import SemanticAnalyzerInterface

Expand Down
6 changes: 3 additions & 3 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing_extensions import Final

from mypy.types import (
Type, AnyType, UnboundType, TypeVisitor, FormalArgument, NoneType, function_type,
Type, AnyType, UnboundType, TypeVisitor, FormalArgument, NoneType,
Instance, TypeVarType, CallableType, TupleType, TypedDictType, UnionType, Overloaded,
ErasedType, PartialType, DeletedType, UninhabitedType, TypeType, is_named_instance,
FunctionLike, TypeOfAny, LiteralType, ProperType, get_proper_type
Expand Down Expand Up @@ -616,8 +616,8 @@ def find_node_type(node: Union[Var, FuncBase], itype: Instance, subtype: Type) -
from mypy.typeops import bind_self

if isinstance(node, FuncBase):
typ = function_type(node,
fallback=Instance(itype.type.mro[-1], [])) # type: Optional[Type]
typ = mypy.typeops.function_type(
node, fallback=Instance(itype.type.mro[-1], [])) # type: Optional[Type]
else:
typ = node.type
typ = get_proper_type(typ)
Expand Down
52 changes: 50 additions & 2 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
from mypy.types import (
TupleType, Instance, FunctionLike, Type, CallableType, TypeVarDef, Overloaded,
TypeVarType, TypeType, UninhabitedType, FormalArgument, UnionType, NoneType,
ProperType, get_proper_type, get_proper_types, copy_type
AnyType, TypeOfAny, TypeType, ProperType, get_proper_type, get_proper_types, copy_type
)
from mypy.nodes import (
TypeInfo, TypeVar, ARG_STAR,
FuncBase, FuncItem, OverloadedFuncDef, TypeInfo, TypeVar, ARG_STAR, ARG_STAR2,
)
from mypy.maptype import map_instance_to_supertype
from mypy.expandtype import expand_type_by_instance, expand_type
from mypy.sharedparse import argument_elide_name

from mypy.typevars import fill_typevars

Expand Down Expand Up @@ -369,3 +370,50 @@ def erase_to_union_or_bound(typ: TypeVarType) -> ProperType:
return make_simplified_union(typ.values)
else:
return get_proper_type(typ.upper_bound)


def function_type(func: FuncBase, fallback: Instance) -> FunctionLike:
if func.type:
assert isinstance(func.type, FunctionLike)
return func.type
else:
# Implicit type signature with dynamic types.
if isinstance(func, FuncItem):
return callable_type(func, fallback)
else:
# Broken overloads can have self.type set to None.
# TODO: should we instead always set the type in semantic analyzer?
assert isinstance(func, OverloadedFuncDef)
any_type = AnyType(TypeOfAny.from_error)
dummy = CallableType([any_type, any_type],
[ARG_STAR, ARG_STAR2],
[None, None], any_type,
fallback,
line=func.line, is_ellipsis_args=True)
# Return an Overloaded, because some callers may expect that
# an OverloadedFuncDef has an Overloaded type.
return Overloaded([dummy])


def callable_type(fdef: FuncItem, fallback: Instance,
ret_type: Optional[Type] = None) -> CallableType:
# TODO: somewhat unfortunate duplication with prepare_method_signature in semanal
if fdef.info and not fdef.is_static:
self_type = fill_typevars(fdef.info) # type: Type
if fdef.is_class or fdef.name() == '__new__':
self_type = TypeType.make_normalized(self_type)
args = [self_type] + [AnyType(TypeOfAny.unannotated)] * (len(fdef.arg_names)-1)
else:
args = [AnyType(TypeOfAny.unannotated)] * len(fdef.arg_names)

return CallableType(
args,
fdef.arg_kinds,
[None if argument_elide_name(n) else n for n in fdef.arg_names],
ret_type or AnyType(TypeOfAny.unannotated),
fallback,
name=fdef.name(),
line=fdef.line,
column=fdef.column,
implicit=True,
)
39 changes: 0 additions & 39 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
INVARIANT, SymbolNode, ARG_POS, ARG_OPT, ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT,
FuncDef,
)
from mypy.sharedparse import argument_elide_name
from mypy.util import IdMapper
from mypy.bogus_type import Bogus

Expand Down Expand Up @@ -2081,44 +2080,6 @@ def copy_type(t: TP) -> TP:
return copy.copy(t)


def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike:
if func.type:
assert isinstance(func.type, FunctionLike)
return func.type
else:
# Implicit type signature with dynamic types.
if isinstance(func, mypy.nodes.FuncItem):
return callable_type(func, fallback)
else:
# Broken overloads can have self.type set to None.
# TODO: should we instead always set the type in semantic analyzer?
assert isinstance(func, mypy.nodes.OverloadedFuncDef)
any_type = AnyType(TypeOfAny.from_error)
dummy = CallableType([any_type, any_type],
[ARG_STAR, ARG_STAR2],
[None, None], any_type,
fallback,
line=func.line, is_ellipsis_args=True)
# Return an Overloaded, because some callers may expect that
# an OverloadedFuncDef has an Overloaded type.
return Overloaded([dummy])


def callable_type(fdef: mypy.nodes.FuncItem, fallback: Instance,
ret_type: Optional[Type] = None) -> CallableType:
return CallableType(
[AnyType(TypeOfAny.unannotated)] * len(fdef.arg_names),
fdef.arg_kinds,
[None if argument_elide_name(n) else n for n in fdef.arg_names],
ret_type or AnyType(TypeOfAny.unannotated),
fallback,
name=fdef.name(),
line=fdef.line,
column=fdef.column,
implicit=True,
)


def replace_alias_tvars(tp: Type, vars: List[str], subs: List[Type],
newline: int, newcolumn: int) -> ProperType:
"""Replace type variables in a generic type alias tp with substitutions subs
Expand Down
47 changes: 46 additions & 1 deletion test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -2610,7 +2610,7 @@ t = Test()
t.crash = 'test' # E: "Test" has no attribute "crash"

class A:
def __setattr__(self): ... # E: Invalid signature "def (self: Any) -> Any" for "__setattr__"
def __setattr__(self): ... # E: Invalid signature "def (self: __main__.A) -> Any" for "__setattr__"
a = A()
a.test = 4 # E: "A" has no attribute "test"

Expand Down Expand Up @@ -6178,3 +6178,48 @@ class B(A):
def meth(cls: Type[T]) -> List[T]: ...

[builtins fixtures/isinstancelist.pyi]

[case testCheckUntypedDefsSelf1]
# flags: --check-untyped-defs

from typing import Generic, TypeVar
T = TypeVar('T')

class Desc:
def __get__(self, x, y):
# type: (...) -> bool
pass

class Foo:
y = Desc()

def __init__(self):
self.x = 0

def foo(self):
reveal_type(self.x) # N: Revealed type is 'builtins.int'
reveal_type(self.y) # N: Revealed type is 'builtins.bool'
self.bar()
self.baz() # E: "Foo" has no attribute "baz"

@classmethod
def bar(cls):
cls.baz() # E: "Type[Foo]" has no attribute "baz"

class C(Generic[T]):
x: T
def meth(self):
self.x + 1 # E: Unsupported left operand type for + ("T")
[builtins fixtures/classmethod.pyi]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add also tests for two other reported problems:

  • An attribute initialized with 0 in untyped __init__ has revealed type int in another untyped method
  • Descriptors also work as expected in untyped methods.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if an attribute is initialized to None in __init__ the inferred type should probably be either Optional[Any] or Any (the first one may generate many false positives). If an attribute is initialized to [], the inferred type should probably be List[Any], and I think that we shouldn't require a type annotation.


[case testCheckUntypedDefsSelf2]
# flags: --check-untyped-defs

class Foo:
def __init__(self):
self.x = None
self.y = []

reveal_type(Foo().x) # N: Revealed type is 'Union[Any, None]'
reveal_type(Foo().y) # N: Revealed type is 'builtins.list[Any]'
[builtins fixtures/list.pyi]
6 changes: 3 additions & 3 deletions test-data/unit/typexport-basic.test
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,7 @@ class A:
def f(self): pass
A.f
[out]
MemberExpr(5) : def (self: Any) -> Any
MemberExpr(5) : def (self: A) -> Any

[case testOverloadedUnboundMethod]
## MemberExpr
Expand Down Expand Up @@ -921,7 +921,7 @@ class A:
def f(self, *args): pass
A.f
[out]
MemberExpr(10) : Overload(def (self: Any) -> Any, def (self: Any, Any) -> Any)
MemberExpr(10) : Overload(def (self: A) -> Any, def (self: A, Any) -> Any)

[case testUnboundMethodWithInheritance]
## MemberExpr
Expand Down Expand Up @@ -986,7 +986,7 @@ class A(Generic[t]):
def f(self, x): pass
A.f(None, None)
[out]
MemberExpr(7) : def (self: Any, x: Any) -> Any
MemberExpr(7) : def (self: A[t`1], x: Any) -> Any

[case testGenericMethodOfGenericClass]
## MemberExpr
Expand Down