Skip to content

Commit 855076b

Browse files
authored
Give self argument the correct type when using check_untyped_defs (python#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 python#7309, python#5401, python#4637, python#1514
1 parent 0c2ec6a commit 855076b

File tree

10 files changed

+127
-58
lines changed

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy