Skip to content

Commit 7aeb65f

Browse files
committed
Merge branch 'master' into module-alias
* master: Support accessing modules imported in class bodies within methods. (python#3450) Make Type[X] compatible with metaclass of X (python#3346) Sync typeshed (python#3479) Handle flags in pytest passthrough (python#3467)
2 parents 4006d0a + c38edf3 commit 7aeb65f

File tree

8 files changed

+166
-45
lines changed

8 files changed

+166
-45
lines changed

mypy/semanal.py

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2989,21 +2989,34 @@ def visit_member_expr(self, expr: MemberExpr) -> None:
29892989
if full_name in obsolete_name_mapping:
29902990
self.fail("Module%s has no attribute %r (it's now called %r)" % (
29912991
mod_name, expr.name, obsolete_name_mapping[full_name]), expr)
2992-
elif isinstance(base, RefExpr) and isinstance(base.node, TypeInfo):
2993-
n = base.node.names.get(expr.name)
2994-
if n is not None and (n.kind == MODULE_REF or isinstance(n.node, TypeInfo)):
2995-
# This branch handles the case C.bar where C is a class and
2996-
# bar is a type definition or a module resulting from
2997-
# `import bar` inside class C. Here base.node is a TypeInfo,
2998-
# and again we look up the name in its namespace.
2999-
# This is done only when bar is a module or a type; other
3000-
# things (e.g. methods) are handled by other code in checkmember.
3001-
n = self.normalize_type_alias(n, expr)
3002-
if not n:
3003-
return
3004-
expr.kind = n.kind
3005-
expr.fullname = n.fullname
3006-
expr.node = n.node
2992+
elif isinstance(base, RefExpr):
2993+
# This branch handles the case C.bar (or cls.bar or self.bar inside
2994+
# a classmethod/method), where C is a class and bar is a type
2995+
# definition or a module resulting from `import bar` (or a module
2996+
# assignment) inside class C. We look up bar in the class' TypeInfo
2997+
# namespace. This is done only when bar is a module or a type;
2998+
# other things (e.g. methods) are handled by other code in
2999+
# checkmember.
3000+
type_info = None
3001+
if isinstance(base.node, TypeInfo):
3002+
# C.bar where C is a class
3003+
type_info = base.node
3004+
elif isinstance(base.node, Var) and self.type and self.function_stack:
3005+
# check for self.bar or cls.bar in method/classmethod
3006+
func_def = self.function_stack[-1]
3007+
if not func_def.is_static and isinstance(func_def.type, CallableType):
3008+
formal_arg = func_def.type.argument_by_name(base.node.name())
3009+
if formal_arg and formal_arg.pos == 0:
3010+
type_info = self.type
3011+
if type_info:
3012+
n = type_info.names.get(expr.name)
3013+
if n is not None and (n.kind == MODULE_REF or isinstance(n.node, TypeInfo)):
3014+
n = self.normalize_type_alias(n, expr)
3015+
if not n:
3016+
return
3017+
expr.kind = n.kind
3018+
expr.fullname = n.fullname
3019+
expr.node = n.node
30073020

30083021
def visit_op_expr(self, expr: OpExpr) -> None:
30093022
expr.left.accept(self)

mypy/subtypes.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,6 @@ def is_subtype(left: Type, right: Type,
6969
elif is_subtype_of_item:
7070
return True
7171
# otherwise, fall through
72-
# Treat builtins.type the same as Type[Any]
73-
elif is_named_instance(left, 'builtins.type'):
74-
return is_subtype(TypeType(AnyType()), right)
75-
elif is_named_instance(right, 'builtins.type'):
76-
return is_subtype(left, TypeType(AnyType()))
7772
return left.accept(SubtypeVisitor(right, type_parameter_checker,
7873
ignore_pos_arg_names=ignore_pos_arg_names))
7974

@@ -158,16 +153,18 @@ def visit_instance(self, left: Instance) -> bool:
158153
item = right.item
159154
if isinstance(item, TupleType):
160155
item = item.fallback
161-
if isinstance(item, Instance):
162-
return is_subtype(left, item.type.metaclass_type)
163-
elif isinstance(item, AnyType):
164-
# Special case: all metaclasses are subtypes of Type[Any]
165-
mro = left.type.mro or []
166-
return any(base.fullname() == 'builtins.type' for base in mro)
167-
else:
168-
return False
169-
else:
170-
return False
156+
if is_named_instance(left, 'builtins.type'):
157+
return is_subtype(TypeType(AnyType()), right)
158+
if left.type.is_metaclass():
159+
if isinstance(item, AnyType):
160+
return True
161+
if isinstance(item, Instance):
162+
# Special-case enum since we don't have better way of expressing it
163+
if (is_named_instance(left, 'enum.EnumMeta')
164+
and is_named_instance(item, 'enum.Enum')):
165+
return True
166+
return is_named_instance(item, 'builtins.object')
167+
return False
171168

172169
def visit_type_var(self, left: TypeVarType) -> bool:
173170
right = self.right
@@ -263,8 +260,8 @@ def visit_overloaded(self, left: Overloaded) -> bool:
263260
elif isinstance(right, TypeType):
264261
# All the items must have the same type object status, so
265262
# it's sufficient to query only (any) one of them.
266-
# This is unsound, we don't check the __init__ signature.
267-
return left.is_type_obj() and is_subtype(left.items()[0].ret_type, right.item)
263+
# This is unsound, we don't check all the __init__ signatures.
264+
return left.is_type_obj() and is_subtype(left.items()[0], right)
268265
else:
269266
return False
270267

@@ -284,11 +281,14 @@ def visit_type_type(self, left: TypeType) -> bool:
284281
# This is unsound, we don't check the __init__ signature.
285282
return is_subtype(left.item, right.ret_type)
286283
if isinstance(right, Instance):
287-
if right.type.fullname() == 'builtins.object':
288-
# treat builtins.object the same as Any.
284+
if right.type.fullname() in ['builtins.object', 'builtins.type']:
289285
return True
290286
item = left.item
291-
return isinstance(item, Instance) and is_subtype(item, right.type.metaclass_type)
287+
if isinstance(item, TypeVarType):
288+
item = item.upper_bound
289+
if isinstance(item, Instance):
290+
metaclass = item.type.metaclass_type
291+
return metaclass is not None and is_subtype(metaclass, right)
292292
return False
293293

294294

mypy/waiter.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from multiprocessing import cpu_count
1010
import pipes
1111
import re
12-
from subprocess import Popen, STDOUT
12+
from subprocess import Popen, STDOUT, DEVNULL
1313
import sys
1414
import tempfile
1515
import time
@@ -25,20 +25,22 @@ class LazySubprocess:
2525
"""Wrapper around a subprocess that runs a test task."""
2626

2727
def __init__(self, name: str, args: List[str], *, cwd: str = None,
28-
env: Dict[str, str] = None, passthrough: bool = False) -> None:
28+
env: Dict[str, str] = None, passthrough: Optional[int] = None) -> None:
2929
self.name = name
3030
self.args = args
3131
self.cwd = cwd
3232
self.env = env
3333
self.start_time = None # type: float
3434
self.end_time = None # type: float
35+
# None means no passthrough
36+
# otherwise, it represents verbosity level
3537
self.passthrough = passthrough
3638

3739
def start(self) -> None:
38-
if self.passthrough:
39-
self.outfile = None
40-
else:
40+
if self.passthrough is None or self.passthrough < 0:
4141
self.outfile = tempfile.TemporaryFile()
42+
else:
43+
self.outfile = None
4244
self.start_time = time.perf_counter()
4345
self.process = Popen(self.args, cwd=self.cwd, env=self.env,
4446
stdout=self.outfile, stderr=STDOUT)
@@ -51,7 +53,7 @@ def status(self) -> Optional[int]:
5153
return self.process.returncode
5254

5355
def read_output(self) -> str:
54-
if self.passthrough:
56+
if not self.outfile:
5557
return ''
5658
file = self.outfile
5759
file.seek(0)

runtests.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def add_pytest(self, name: str, pytest_args: List[str], coverage: bool = False)
101101
else:
102102
args = [sys.executable, '-m', 'pytest'] + pytest_args
103103

104-
self.waiter.add(LazySubprocess(full_name, args, env=self.env, passthrough=True),
104+
self.waiter.add(LazySubprocess(full_name, args, env=self.env, passthrough=self.verbosity),
105105
sequential=True)
106106

107107
def add_python(self, name: str, *args: str, cwd: Optional[str] = None) -> None:
@@ -412,6 +412,13 @@ def main() -> None:
412412
pyt_arglist.append('--lf')
413413
if ff:
414414
pyt_arglist.append('--ff')
415+
if verbosity >= 1:
416+
pyt_arglist.extend(['-v'] * verbosity)
417+
elif verbosity < 0:
418+
pyt_arglist.extend(['-q'] * (-verbosity))
419+
if parallel_limit:
420+
if '-n' not in pyt_arglist:
421+
pyt_arglist.append('-n{}'.format(parallel_limit))
415422

416423
driver = Driver(whitelist=whitelist, blacklist=blacklist, lf=lf, ff=ff,
417424
arglist=arglist, pyt_arglist=pyt_arglist, verbosity=verbosity,

test-data/unit/check-classes.test

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3145,11 +3145,11 @@ class A(metaclass=M): pass
31453145

31463146
reveal_type(A[M]) # E: Revealed type is 'builtins.int'
31473147

3148-
[case testMetaclassSelftype]
3148+
[case testMetaclassSelfType]
31493149
from typing import TypeVar, Type
31503150

31513151
class M(type): pass
3152-
T = TypeVar('T', bound='A')
3152+
T = TypeVar('T')
31533153

31543154
class M1(M):
31553155
def foo(cls: Type[T]) -> T: ...
@@ -3215,6 +3215,80 @@ class M(type):
32153215
class A(metaclass=M): pass
32163216
reveal_type(type(A).x) # E: Revealed type is 'builtins.int'
32173217

3218+
[case testMetaclassStrictSupertypeOfTypeWithClassmethods]
3219+
from typing import Type, TypeVar
3220+
TA = TypeVar('TA', bound='A')
3221+
TTA = TypeVar('TTA', bound='Type[A]')
3222+
TM = TypeVar('TM', bound='M')
3223+
3224+
class M(type):
3225+
def g1(cls: 'Type[A]') -> A: pass # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M'
3226+
def g2(cls: Type[TA]) -> TA: pass # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M'
3227+
def g3(cls: TTA) -> TTA: pass # E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M'
3228+
def g4(cls: TM) -> TM: pass
3229+
m: M
3230+
3231+
class A(metaclass=M):
3232+
def foo(self): pass
3233+
3234+
reveal_type(A.g1) # E: Revealed type is 'def () -> __main__.A'
3235+
reveal_type(A.g2) # E: Revealed type is 'def () -> __main__.A*'
3236+
reveal_type(A.g3) # E: Revealed type is 'def () -> def () -> __main__.A'
3237+
reveal_type(A.g4) # E: Revealed type is 'def () -> def () -> __main__.A'
3238+
3239+
class B(metaclass=M):
3240+
def foo(self): pass
3241+
3242+
B.g1 # Should be error: Argument 0 to "g1" of "M" has incompatible type "B"; expected Type[A]
3243+
B.g2 # Should be error: Argument 0 to "g2" of "M" has incompatible type "B"; expected Type[TA]
3244+
B.g3 # Should be error: Argument 0 to "g3" of "M" has incompatible type "B"; expected "TTA"
3245+
reveal_type(B.g4) # E: Revealed type is 'def () -> def () -> __main__.B'
3246+
3247+
# 4 examples of unsoundness - instantiation, classmethod, staticmethod and ClassVar:
3248+
3249+
ta: Type[A] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[A])
3250+
a: A = ta()
3251+
reveal_type(ta.g1) # E: Revealed type is 'def () -> __main__.A'
3252+
reveal_type(ta.g2) # E: Revealed type is 'def () -> __main__.A*'
3253+
reveal_type(ta.g3) # E: Revealed type is 'def () -> Type[__main__.A]'
3254+
reveal_type(ta.g4) # E: Revealed type is 'def () -> Type[__main__.A]'
3255+
3256+
x: M = ta
3257+
x.g1 # should be error: Argument 0 to "g1" of "M" has incompatible type "M"; expected Type[A]
3258+
x.g2 # should be error: Argument 0 to "g2" of "M" has incompatible type "M"; expected Type[TA]
3259+
x.g3 # should be error: Argument 0 to "g3" of "M" has incompatible type "M"; expected "TTA"
3260+
reveal_type(x.g4) # E: Revealed type is 'def () -> __main__.M*'
3261+
3262+
def r(ta: Type[TA], tta: TTA) -> None:
3263+
x: M = ta
3264+
y: M = tta
3265+
3266+
class Class(metaclass=M):
3267+
@classmethod
3268+
def f1(cls: Type[Class]) -> None: pass
3269+
@classmethod
3270+
def f2(cls: M) -> None: pass
3271+
cl: Type[Class] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[Class])
3272+
reveal_type(cl.f1) # E: Revealed type is 'def ()'
3273+
reveal_type(cl.f2) # E: Revealed type is 'def ()'
3274+
x1: M = cl
3275+
3276+
class Static(metaclass=M):
3277+
@staticmethod
3278+
def f() -> None: pass
3279+
s: Type[Static] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[Static])
3280+
reveal_type(s.f) # E: Revealed type is 'def ()'
3281+
x2: M = s
3282+
3283+
from typing import ClassVar
3284+
class Cvar(metaclass=M):
3285+
x = 1 # type: ClassVar[int]
3286+
cv: Type[Cvar] = m # E: Incompatible types in assignment (expression has type "M", variable has type Type[Cvar])
3287+
cv.x
3288+
x3: M = cv
3289+
3290+
[builtins fixtures/classmethod.pyi]
3291+
32183292
-- Synthetic types crashes
32193293
-- -----------------------
32203294

test-data/unit/check-modules.test

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,6 +1417,29 @@ reveal_type(types) # E: Revealed type is 'types.ModuleType'
14171417

14181418
[builtins fixtures/module.pyi]
14191419

1420+
[case testClassImportAccessedInMethod]
1421+
class C:
1422+
import m
1423+
def foo(self) -> None:
1424+
x = self.m.a
1425+
reveal_type(x) # E: Revealed type is 'builtins.str'
1426+
# ensure we distinguish self from other variables
1427+
y = 'hello'
1428+
z = y.m.a # E: "str" has no attribute "m"
1429+
@classmethod
1430+
def cmethod(cls) -> None:
1431+
y = cls.m.a
1432+
reveal_type(y) # E: Revealed type is 'builtins.str'
1433+
@staticmethod
1434+
def smethod(foo: int) -> None:
1435+
# we aren't confused by first arg of a staticmethod
1436+
y = foo.m.a # E: "int" has no attribute "m"
1437+
1438+
[file m.py]
1439+
a = 'foo'
1440+
1441+
[builtins fixtures/module.pyi]
1442+
14201443
[case testModuleAssignment]
14211444
import m
14221445
m2 = m

test-data/unit/fixtures/module.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,5 @@ class tuple: pass
1717
class dict(Generic[T, S]): pass
1818
class ellipsis: pass
1919

20+
classmethod = object()
21+
staticmethod = object()

0 commit comments

Comments
 (0)