Skip to content

Commit 855076b

Browse files
authored
Give self argument the correct type when using check_untyped_defs (#7530)
This is accomplished by updating the helper that constructs a callable type from a func def to fill in the self argument. Also suppress errors about `need type annotation` in most cases in a checked but untyped method. This will make check_untyped_defs a lot more useful. Closes #7309, #5401, #4637, #1514
1 parent 0c2ec6a commit 855076b

10 files changed

+127
-58
lines changed

mypy/checker.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
Type, AnyType, CallableType, FunctionLike, Overloaded, TupleType, TypedDictType,
3535
Instance, NoneType, strip_type, TypeType, TypeOfAny,
3636
UnionType, TypeVarId, TypeVarType, PartialType, DeletedType, UninhabitedType, TypeVarDef,
37-
function_type, is_named_instance, union_items, TypeQuery, LiteralType,
37+
is_named_instance, union_items, TypeQuery, LiteralType,
3838
is_optional, remove_optional, TypeTranslator, StarType, get_proper_type, ProperType,
3939
get_proper_types, is_literal_type
4040
)
@@ -50,7 +50,7 @@
5050
from mypy.typeops import (
5151
map_type_from_supertype, bind_self, erase_to_bound, make_simplified_union,
5252
erase_def_to_union_or_bound, erase_to_union_or_bound,
53-
true_only, false_only,
53+
true_only, false_only, function_type,
5454
)
5555
from mypy import message_registry
5656
from mypy.subtypes import (
@@ -535,6 +535,11 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
535535
else:
536536
impl = impl_type
537537

538+
# Prevent extra noise from inconsistent use of @classmethod by copying
539+
# the first arg from the method being checked against.
540+
if sig1.arg_types and defn.info:
541+
impl = impl.copy_modified(arg_types=[sig1.arg_types[0]] + impl.arg_types[1:])
542+
538543
# Is the overload alternative's arguments subtypes of the implementation's?
539544
if not is_callable_compatible(impl, sig1,
540545
is_compat=is_subtype_no_promote,
@@ -3934,7 +3939,14 @@ def enter_partial_types(self, *, is_function: bool = False,
39343939
self.partial_types.append(PartialTypeScope({}, is_function, is_local))
39353940
yield
39363941

3937-
permissive = (self.options.allow_untyped_globals and not is_local)
3942+
# Don't complain about not being able to infer partials if it is
3943+
# at the toplevel (with allow_untyped_globals) or if it is in an
3944+
# untyped function being checked with check_untyped_defs.
3945+
permissive = (self.options.allow_untyped_globals and not is_local) or (
3946+
self.options.check_untyped_defs
3947+
and self.dynamic_funcs
3948+
and self.dynamic_funcs[-1]
3949+
)
39383950

39393951
partial_types, _, _ = self.partial_types.pop()
39403952
if not self.current_node_deferred:

mypy/checkexpr.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
Type, AnyType, CallableType, Overloaded, NoneType, TypeVarDef,
1818
TupleType, TypedDictType, Instance, TypeVarType, ErasedType, UnionType,
1919
PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, LiteralType, LiteralValue,
20-
is_named_instance, function_type, callable_type, FunctionLike,
20+
is_named_instance, FunctionLike,
2121
StarType, is_optional, remove_optional, is_generic_instance, get_proper_type, ProperType,
2222
get_proper_types
2323
)
@@ -59,6 +59,7 @@
5959
from mypy.plugin import Plugin, MethodContext, MethodSigContext, FunctionContext
6060
from mypy.typeops import (
6161
tuple_fallback, make_simplified_union, true_only, false_only, erase_to_union_or_bound,
62+
function_type, callable_type,
6263
)
6364
import mypy.errorcodes as codes
6465

mypy/checkmember.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from mypy.types import (
77
Type, Instance, AnyType, TupleType, TypedDictType, CallableType, FunctionLike, TypeVarDef,
88
Overloaded, TypeVarType, UnionType, PartialType, TypeOfAny, LiteralType,
9-
DeletedType, NoneType, TypeType, function_type, get_type_vars, get_proper_type, ProperType
9+
DeletedType, NoneType, TypeType, get_type_vars, get_proper_type, ProperType
1010
)
1111
from mypy.nodes import (
1212
TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile, TypeVarExpr,
@@ -24,7 +24,7 @@
2424
from mypy import meet
2525
from mypy.typeops import (
2626
tuple_fallback, bind_self, erase_to_bound, class_callable, type_object_type_from_function,
27-
make_simplified_union,
27+
make_simplified_union, function_type,
2828
)
2929

3030
if TYPE_CHECKING: # import for forward declaration only

mypy/semanal.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,12 @@
8484
from mypy.errorcodes import ErrorCode
8585
from mypy import message_registry, errorcodes as codes
8686
from mypy.types import (
87-
FunctionLike, UnboundType, TypeVarDef, TupleType, UnionType, StarType, function_type,
87+
FunctionLike, UnboundType, TypeVarDef, TupleType, UnionType, StarType,
8888
CallableType, Overloaded, Instance, Type, AnyType, LiteralType, LiteralValue,
8989
TypeTranslator, TypeOfAny, TypeType, NoneType, PlaceholderType, TPDICT_NAMES, ProperType,
9090
get_proper_type, get_proper_types
9191
)
92+
from mypy.typeops import function_type
9293
from mypy.type_visitor import TypeQuery
9394
from mypy.nodes import implicit_module_attrs
9495
from mypy.typeanal import (
@@ -615,11 +616,11 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo) -> None:
615616
# Only non-static methods are special.
616617
functype = func.type
617618
if not func.is_static:
619+
if func.name() == '__init_subclass__':
620+
func.is_class = True
618621
if not func.arguments:
619622
self.fail('Method must have at least one argument', func)
620623
elif isinstance(functype, CallableType):
621-
if func.name() == '__init_subclass__':
622-
func.is_class = True
623624
self_type = get_proper_type(functype.arg_types[0])
624625
if isinstance(self_type, AnyType):
625626
leading_type = fill_typevars(info) # type: Type

mypy/semanal_infer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
from mypy.nodes import Expression, Decorator, CallExpr, FuncDef, RefExpr, Var, ARG_POS
66
from mypy.types import (
7-
Type, CallableType, AnyType, TypeOfAny, TypeVarType, function_type, ProperType, get_proper_type
7+
Type, CallableType, AnyType, TypeOfAny, TypeVarType, ProperType, get_proper_type
88
)
9+
from mypy.typeops import function_type
910
from mypy.typevars import has_no_typevars
1011
from mypy.semanal_shared import SemanticAnalyzerInterface
1112

mypy/subtypes.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing_extensions import Final
55

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

618618
if isinstance(node, FuncBase):
619-
typ = function_type(node,
620-
fallback=Instance(itype.type.mro[-1], [])) # type: Optional[Type]
619+
typ = mypy.typeops.function_type(
620+
node, fallback=Instance(itype.type.mro[-1], [])) # type: Optional[Type]
621621
else:
622622
typ = node.type
623623
typ = get_proper_type(typ)

mypy/typeops.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
from mypy.types import (
1111
TupleType, Instance, FunctionLike, Type, CallableType, TypeVarDef, Overloaded,
1212
TypeVarType, TypeType, UninhabitedType, FormalArgument, UnionType, NoneType,
13-
ProperType, get_proper_type, get_proper_types, copy_type
13+
AnyType, TypeOfAny, TypeType, ProperType, get_proper_type, get_proper_types, copy_type
1414
)
1515
from mypy.nodes import (
16-
TypeInfo, TypeVar, ARG_STAR,
16+
FuncBase, FuncItem, OverloadedFuncDef, TypeInfo, TypeVar, ARG_STAR, ARG_STAR2,
1717
)
1818
from mypy.maptype import map_instance_to_supertype
1919
from mypy.expandtype import expand_type_by_instance, expand_type
20+
from mypy.sharedparse import argument_elide_name
2021

2122
from mypy.typevars import fill_typevars
2223

@@ -369,3 +370,50 @@ def erase_to_union_or_bound(typ: TypeVarType) -> ProperType:
369370
return make_simplified_union(typ.values)
370371
else:
371372
return get_proper_type(typ.upper_bound)
373+
374+
375+
def function_type(func: FuncBase, fallback: Instance) -> FunctionLike:
376+
if func.type:
377+
assert isinstance(func.type, FunctionLike)
378+
return func.type
379+
else:
380+
# Implicit type signature with dynamic types.
381+
if isinstance(func, FuncItem):
382+
return callable_type(func, fallback)
383+
else:
384+
# Broken overloads can have self.type set to None.
385+
# TODO: should we instead always set the type in semantic analyzer?
386+
assert isinstance(func, OverloadedFuncDef)
387+
any_type = AnyType(TypeOfAny.from_error)
388+
dummy = CallableType([any_type, any_type],
389+
[ARG_STAR, ARG_STAR2],
390+
[None, None], any_type,
391+
fallback,
392+
line=func.line, is_ellipsis_args=True)
393+
# Return an Overloaded, because some callers may expect that
394+
# an OverloadedFuncDef has an Overloaded type.
395+
return Overloaded([dummy])
396+
397+
398+
def callable_type(fdef: FuncItem, fallback: Instance,
399+
ret_type: Optional[Type] = None) -> CallableType:
400+
# TODO: somewhat unfortunate duplication with prepare_method_signature in semanal
401+
if fdef.info and not fdef.is_static:
402+
self_type = fill_typevars(fdef.info) # type: Type
403+
if fdef.is_class or fdef.name() == '__new__':
404+
self_type = TypeType.make_normalized(self_type)
405+
args = [self_type] + [AnyType(TypeOfAny.unannotated)] * (len(fdef.arg_names)-1)
406+
else:
407+
args = [AnyType(TypeOfAny.unannotated)] * len(fdef.arg_names)
408+
409+
return CallableType(
410+
args,
411+
fdef.arg_kinds,
412+
[None if argument_elide_name(n) else n for n in fdef.arg_names],
413+
ret_type or AnyType(TypeOfAny.unannotated),
414+
fallback,
415+
name=fdef.name(),
416+
line=fdef.line,
417+
column=fdef.column,
418+
implicit=True,
419+
)

mypy/types.py

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
INVARIANT, SymbolNode, ARG_POS, ARG_OPT, ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT,
1818
FuncDef,
1919
)
20-
from mypy.sharedparse import argument_elide_name
2120
from mypy.util import IdMapper
2221
from mypy.bogus_type import Bogus
2322

@@ -2081,44 +2080,6 @@ def copy_type(t: TP) -> TP:
20812080
return copy.copy(t)
20822081

20832082

2084-
def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike:
2085-
if func.type:
2086-
assert isinstance(func.type, FunctionLike)
2087-
return func.type
2088-
else:
2089-
# Implicit type signature with dynamic types.
2090-
if isinstance(func, mypy.nodes.FuncItem):
2091-
return callable_type(func, fallback)
2092-
else:
2093-
# Broken overloads can have self.type set to None.
2094-
# TODO: should we instead always set the type in semantic analyzer?
2095-
assert isinstance(func, mypy.nodes.OverloadedFuncDef)
2096-
any_type = AnyType(TypeOfAny.from_error)
2097-
dummy = CallableType([any_type, any_type],
2098-
[ARG_STAR, ARG_STAR2],
2099-
[None, None], any_type,
2100-
fallback,
2101-
line=func.line, is_ellipsis_args=True)
2102-
# Return an Overloaded, because some callers may expect that
2103-
# an OverloadedFuncDef has an Overloaded type.
2104-
return Overloaded([dummy])
2105-
2106-
2107-
def callable_type(fdef: mypy.nodes.FuncItem, fallback: Instance,
2108-
ret_type: Optional[Type] = None) -> CallableType:
2109-
return CallableType(
2110-
[AnyType(TypeOfAny.unannotated)] * len(fdef.arg_names),
2111-
fdef.arg_kinds,
2112-
[None if argument_elide_name(n) else n for n in fdef.arg_names],
2113-
ret_type or AnyType(TypeOfAny.unannotated),
2114-
fallback,
2115-
name=fdef.name(),
2116-
line=fdef.line,
2117-
column=fdef.column,
2118-
implicit=True,
2119-
)
2120-
2121-
21222083
def replace_alias_tvars(tp: Type, vars: List[str], subs: List[Type],
21232084
newline: int, newcolumn: int) -> ProperType:
21242085
"""Replace type variables in a generic type alias tp with substitutions subs

test-data/unit/check-classes.test

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2610,7 +2610,7 @@ t = Test()
26102610
t.crash = 'test' # E: "Test" has no attribute "crash"
26112611

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

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

61806180
[builtins fixtures/isinstancelist.pyi]
6181+
6182+
[case testCheckUntypedDefsSelf1]
6183+
# flags: --check-untyped-defs
6184+
6185+
from typing import Generic, TypeVar
6186+
T = TypeVar('T')
6187+
6188+
class Desc:
6189+
def __get__(self, x, y):
6190+
# type: (...) -> bool
6191+
pass
6192+
6193+
class Foo:
6194+
y = Desc()
6195+
6196+
def __init__(self):
6197+
self.x = 0
6198+
6199+
def foo(self):
6200+
reveal_type(self.x) # N: Revealed type is 'builtins.int'
6201+
reveal_type(self.y) # N: Revealed type is 'builtins.bool'
6202+
self.bar()
6203+
self.baz() # E: "Foo" has no attribute "baz"
6204+
6205+
@classmethod
6206+
def bar(cls):
6207+
cls.baz() # E: "Type[Foo]" has no attribute "baz"
6208+
6209+
class C(Generic[T]):
6210+
x: T
6211+
def meth(self):
6212+
self.x + 1 # E: Unsupported left operand type for + ("T")
6213+
[builtins fixtures/classmethod.pyi]
6214+
6215+
[case testCheckUntypedDefsSelf2]
6216+
# flags: --check-untyped-defs
6217+
6218+
class Foo:
6219+
def __init__(self):
6220+
self.x = None
6221+
self.y = []
6222+
6223+
reveal_type(Foo().x) # N: Revealed type is 'Union[Any, None]'
6224+
reveal_type(Foo().y) # N: Revealed type is 'builtins.list[Any]'
6225+
[builtins fixtures/list.pyi]

test-data/unit/typexport-basic.test

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,7 @@ class A:
893893
def f(self): pass
894894
A.f
895895
[out]
896-
MemberExpr(5) : def (self: Any) -> Any
896+
MemberExpr(5) : def (self: A) -> Any
897897

898898
[case testOverloadedUnboundMethod]
899899
## MemberExpr
@@ -921,7 +921,7 @@ class A:
921921
def f(self, *args): pass
922922
A.f
923923
[out]
924-
MemberExpr(10) : Overload(def (self: Any) -> Any, def (self: Any, Any) -> Any)
924+
MemberExpr(10) : Overload(def (self: A) -> Any, def (self: A, Any) -> Any)
925925

926926
[case testUnboundMethodWithInheritance]
927927
## MemberExpr
@@ -986,7 +986,7 @@ class A(Generic[t]):
986986
def f(self, x): pass
987987
A.f(None, None)
988988
[out]
989-
MemberExpr(7) : def (self: Any, x: Any) -> Any
989+
MemberExpr(7) : def (self: A[t`1], x: Any) -> Any
990990

991991
[case testGenericMethodOfGenericClass]
992992
## MemberExpr

0 commit comments

Comments
 (0)