Skip to content

Commit 39cd5f9

Browse files
authored
Merge branch 'python:master' into fix-20103
2 parents 53622b1 + 842a8fd commit 39cd5f9

File tree

6 files changed

+112
-14
lines changed

6 files changed

+112
-14
lines changed

mypy/checker.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,8 @@ class TypeChecker(NodeVisitor[None], TypeCheckerSharedApi):
395395

396396
# A helper state to produce unique temporary names on demand.
397397
_unique_id: int
398+
# Fake concrete type used when checking variance
399+
_variance_dummy_type: Instance | None
398400

399401
def __init__(
400402
self,
@@ -469,6 +471,7 @@ def __init__(
469471

470472
self.pattern_checker = PatternChecker(self, self.msg, self.plugin, options)
471473
self._unique_id = 0
474+
self._variance_dummy_type = None
472475

473476
@property
474477
def expr_checker(self) -> mypy.checkexpr.ExpressionChecker:
@@ -2918,17 +2921,19 @@ def check_protocol_variance(self, defn: ClassDef) -> None:
29182921
info = defn.info
29192922
object_type = Instance(info.mro[-1], [])
29202923
tvars = info.defn.type_vars
2924+
if self._variance_dummy_type is None:
2925+
_, dummy_info = self.make_fake_typeinfo("<dummy>", "Dummy", "Dummy", [])
2926+
self._variance_dummy_type = Instance(dummy_info, [])
2927+
dummy = self._variance_dummy_type
29212928
for i, tvar in enumerate(tvars):
29222929
if not isinstance(tvar, TypeVarType):
29232930
# Variance of TypeVarTuple and ParamSpec is underspecified by PEPs.
29242931
continue
29252932
up_args: list[Type] = [
2926-
object_type if i == j else AnyType(TypeOfAny.special_form)
2927-
for j, _ in enumerate(tvars)
2933+
object_type if i == j else dummy.copy_modified() for j, _ in enumerate(tvars)
29282934
]
29292935
down_args: list[Type] = [
2930-
UninhabitedType() if i == j else AnyType(TypeOfAny.special_form)
2931-
for j, _ in enumerate(tvars)
2936+
UninhabitedType() if i == j else dummy.copy_modified() for j, _ in enumerate(tvars)
29322937
]
29332938
up, down = Instance(info, up_args), Instance(info, down_args)
29342939
# TODO: add advanced variance checks for recursive protocols

mypy/expandtype.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,9 @@ def visit_type_var(self, t: TypeVarType) -> Type:
251251
if (tvar_id := repl.id) in self.recursive_tvar_guard:
252252
return self.recursive_tvar_guard[tvar_id] or repl
253253
self.recursive_tvar_guard[tvar_id] = None
254-
repl = repl.accept(self)
255-
if isinstance(repl, TypeVarType):
256-
repl.default = repl.default.accept(self)
254+
repl.default = repl.default.accept(self)
255+
expanded = repl.accept(self) # Note: `expanded is repl` may be true.
256+
repl = repl if isinstance(expanded, TypeVarType) else expanded
257257
self.recursive_tvar_guard[tvar_id] = repl
258258
return repl
259259

mypy/report.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,9 @@ def should_skip_path(path: str) -> bool:
141141

142142
def iterate_python_lines(path: str) -> Iterator[tuple[int, str]]:
143143
"""Return an iterator over (line number, line text) from a Python file."""
144-
try:
144+
if not os.path.isdir(path): # can happen with namespace packages
145145
with tokenize.open(path) as input_file:
146146
yield from enumerate(input_file, 1)
147-
except IsADirectoryError:
148-
# can happen with namespace packages
149-
pass
150147

151148

152149
class FuncCounterVisitor(TraverserVisitor):
@@ -172,11 +169,10 @@ def on_file(
172169
) -> None:
173170
# Count physical lines. This assumes the file's encoding is a
174171
# superset of ASCII (or at least uses \n in its line endings).
175-
try:
172+
if not os.path.isdir(tree.path): # can happen with namespace packages
176173
with open(tree.path, "rb") as f:
177174
physical_lines = len(f.readlines())
178-
except IsADirectoryError:
179-
# can happen with namespace packages
175+
else:
180176
physical_lines = 0
181177

182178
func_counter = FuncCounterVisitor()
@@ -424,6 +420,9 @@ def on_file(
424420
type_map: dict[Expression, Type],
425421
options: Options,
426422
) -> None:
423+
if os.path.isdir(tree.path): # can happen with namespace packages
424+
return
425+
427426
with open(tree.path) as f:
428427
tree_source = f.readlines()
429428

test-data/unit/check-protocols.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,19 @@ main:16: note: def meth(self, x: int) -> int
13731373
main:16: note: @overload
13741374
main:16: note: def meth(self, x: bytes) -> str
13751375

1376+
[case testProtocolWithMultiContravariantTypeVarOverloads]
1377+
from typing import overload, Protocol, TypeVar
1378+
1379+
T1 = TypeVar("T1", contravariant=True)
1380+
T2 = TypeVar("T2", contravariant=True)
1381+
1382+
class A(Protocol[T1, T2]):
1383+
@overload
1384+
def method(self, a: T1) -> None: ...
1385+
@overload
1386+
def method(self, a: T2) -> None: ...
1387+
1388+
13761389
-- Join and meet with protocol types
13771390
-- ---------------------------------
13781391

test-data/unit/check-typevar-defaults.test

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,38 @@ def func_c4(
416416
reveal_type(m) # N: Revealed type is "__main__.ClassC4[builtins.int, builtins.float]"
417417
[builtins fixtures/tuple.pyi]
418418

419+
[case testTypeVarDefaultsSwap]
420+
from typing import TypeVar, Generic
421+
422+
T = TypeVar("T")
423+
X = TypeVar("X", default=object)
424+
Y = TypeVar("Y", default=object)
425+
426+
427+
class Foo(Generic[T, Y]):
428+
def test(self) -> None:
429+
reveal_type( Foo[Y, T]() ) # N: Revealed type is "__main__.Foo[Y`2 = builtins.object, T`1]"
430+
431+
432+
class Bar(Generic[X, Y]):
433+
def test(self) -> None:
434+
reveal_type( Bar[Y, X]() ) # N: Revealed type is "__main__.Bar[Y`2 = builtins.object, X`1 = builtins.object]"
435+
436+
437+
[case testTypeVarDefaultsSwap2]
438+
from typing import TypeVar, Generic
439+
440+
X = TypeVar("X", default=object)
441+
Y = TypeVar("Y", default=object)
442+
U = TypeVar("U", default=object)
443+
V = TypeVar("V", default=object)
444+
445+
class Transform(Generic[X, Y]):
446+
def invert(self) -> "Transform[Y, X]": ...
447+
448+
class Foo(Transform[U, V], Generic[U, V]):
449+
def invert(self) -> "Foo[V, U]": ...
450+
419451
[case testTypeVarDefaultsClassRecursive1]
420452
# flags: --disallow-any-generics
421453
from typing import Generic, TypeVar, List

test-data/unit/reports.test

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,3 +547,52 @@ namespace_packages = True
547547
</table>
548548
</body>
549549
</html>
550+
551+
[case testLinecountReportCrashOnNamespacePackages]
552+
# cmd: mypy --linecount-report report -p folder
553+
-- Regression test for https://github.com/python/mypy/issues/15979
554+
[file folder/subfolder/something.py]
555+
class Something:
556+
pass
557+
558+
[case testReportIsADirectoryErrorCrashOnNamespacePackages]
559+
# cmd: mypy --linecoverage-report report -p folder
560+
-- Regression test for https://github.com/python/mypy/issues/18128
561+
-- "IsADirectoryError for namespace packages when using --linecoverage-report"
562+
[file folder/subfolder/something.py]
563+
class Something:
564+
pass
565+
566+
[case testReportCoberturaCrashOnNamespacePackages]
567+
# cmd: mypy --cobertura-xml-report report -p folder
568+
-- Regression test for https://github.com/python/mypy/issues/19843
569+
[file folder/subfolder/something.py]
570+
-- This output is not important, but due to the way tests are run we need to check it.
571+
[outfile report/cobertura.xml]
572+
<coverage timestamp="$TIMESTAMP" version="$VERSION" line-rate="1.0" branch-rate="0" lines-covered="0" lines-valid="0">
573+
<sources>
574+
<source>$PWD</source>
575+
</sources>
576+
<packages>
577+
<package complexity="1.0" name="folder" branch-rate="0" line-rate="1.0">
578+
<classes>
579+
<class complexity="1.0" filename="folder" name="folder" branch-rate="0" line-rate="1.0">
580+
<methods/>
581+
<lines/>
582+
</class>
583+
<class complexity="1.0" filename="folder/subfolder" name="subfolder" branch-rate="0" line-rate="1.0">
584+
<methods/>
585+
<lines/>
586+
</class>
587+
</classes>
588+
</package>
589+
<package complexity="1.0" name="folder.subfolder" branch-rate="0" line-rate="1.0">
590+
<classes>
591+
<class complexity="1.0" filename="folder/subfolder/something.py" name="something.py" branch-rate="0" line-rate="1.0">
592+
<methods/>
593+
<lines/>
594+
</class>
595+
</classes>
596+
</package>
597+
</packages>
598+
</coverage>

0 commit comments

Comments
 (0)