Skip to content

Commit 14defb4

Browse files
committed
Merge remote-tracking branch 'upstream/master' into conditional-overloads
2 parents f8e27b2 + 080bb0e commit 14defb4

File tree

309 files changed

+6687
-4832
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

309 files changed

+6687
-4832
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,16 @@ jobs:
7575
toxenv: py36
7676
tox_extra_args: "-n 2 mypyc/test/test_run.py mypyc/test/test_external.py"
7777
debug_build: true
78-
- name: Type check our own code
78+
- name: Type check our own code (py37-ubuntu)
7979
python: '3.7'
8080
arch: x64
8181
os: ubuntu-latest
8282
toxenv: type
83+
- name: Type check our own code (py37-windows-64)
84+
python: '3.7'
85+
arch: x64
86+
os: windows-latest
87+
toxenv: type
8388
- name: Code style with flake8
8489
python: '3.7'
8590
arch: x64

docs/source/command_line.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,16 @@ of the above sections.
524524
# 'items' has type list[str]
525525
items = [item.split() for item in items]
526526
# 'items' now has type list[list[str]]
527-
...
527+
528+
The variable must be used before it can be redefined:
529+
530+
.. code-block:: python
531+
532+
def process(items: list[str]) -> None:
533+
items = "mypy" # invalid redefinition to str because the variable hasn't been used yet
534+
print(items)
535+
items = "100" # valid, items now has type str
536+
items = int(items) # valid, items now has type int
528537
529538
.. option:: --local-partial-types
530539

docs/source/config_file.rst

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,9 @@ section of the command line docs.
237237
238238
[tool.mypy]
239239
exclude = [
240-
"^file1\\.py$", # TOML's double-quoted strings require escaping backslashes
241-
'^file2\.py$', # but TOML's single-quoted strings do not
240+
"^one\.py$", # TOML's double-quoted strings require escaping backslashes
241+
'two\.pyi$', # but TOML's single-quoted strings do not
242+
'^three\.',
242243
]
243244
244245
A single, multi-line string:
@@ -247,9 +248,10 @@ section of the command line docs.
247248
248249
[tool.mypy]
249250
exclude = '''(?x)(
250-
^file1\.py$
251-
|^file2\.py$,
252-
)'''
251+
^one\.py$ # files named "one.py"
252+
| two\.pyi$ # or files ending with "two.pyi"
253+
| ^three\. # or files starting with "three."
254+
)''' # TOML's single-quoted strings do not require escaping backslashes
253255
254256
See :ref:`using-a-pyproject-toml`.
255257

@@ -613,6 +615,24 @@ section of the command line docs.
613615

614616
Allows variables to be redefined with an arbitrary type, as long as the redefinition
615617
is in the same block and nesting level as the original definition.
618+
Example where this can be useful:
619+
620+
.. code-block:: python
621+
622+
def process(items: list[str]) -> None:
623+
# 'items' has type list[str]
624+
items = [item.split() for item in items]
625+
# 'items' now has type list[list[str]]
626+
627+
The variable must be used before it can be redefined:
628+
629+
.. code-block:: python
630+
631+
def process(items: list[str]) -> None:
632+
items = "mypy" # invalid redefinition to str because the variable hasn't been used yet
633+
print(items)
634+
items = "100" # valid, items now has type str
635+
items = int(items) # valid, items now has type int
616636
617637
.. confval:: local_partial_types
618638

docs/source/generics.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,10 @@ protocols mostly follow the normal rules for generic classes. Example:
673673
y: Box[int] = ...
674674
x = y # Error -- Box is invariant
675675
676+
Per :pep:`PEP 544: Generic protocols <544#generic-protocols>`, ``class
677+
ClassName(Protocol[T])`` is allowed as a shorthand for ``class
678+
ClassName(Protocol, Generic[T])``.
679+
676680
The main difference between generic protocols and ordinary generic
677681
classes is that mypy checks that the declared variances of generic
678682
type variables in a protocol match how they are used in the protocol

mypy/checker.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
Context, Decorator, PrintStmt, BreakStmt, PassStmt, ContinueStmt,
2525
ComparisonExpr, StarExpr, EllipsisExpr, RefExpr, PromoteExpr,
2626
Import, ImportFrom, ImportAll, ImportBase, TypeAlias,
27-
ARG_POS, ARG_STAR, LITERAL_TYPE, LDEF, MDEF, GDEF,
27+
ARG_POS, ARG_STAR, ARG_NAMED, LITERAL_TYPE, LDEF, MDEF, GDEF,
2828
CONTRAVARIANT, COVARIANT, INVARIANT, TypeVarExpr, AssignmentExpr,
29-
is_final_node, ARG_NAMED, MatchStmt)
29+
is_final_node, MatchStmt)
3030
from mypy import nodes
3131
from mypy import operators
3232
from mypy.literals import literal, literal_hash, Key
@@ -86,7 +86,7 @@
8686
from mypy import state, errorcodes as codes
8787
from mypy.traverser import has_return_statement, all_return_statements
8888
from mypy.errorcodes import ErrorCode
89-
from mypy.util import is_typeshed_file
89+
from mypy.util import is_typeshed_file, is_dunder, is_sunder
9090

9191
T = TypeVar('T')
9292

@@ -1833,6 +1833,10 @@ def visit_class_def(self, defn: ClassDef) -> None:
18331833
# that completely swap out the type. (e.g. Callable[[Type[A]], Type[B]])
18341834
if typ.is_protocol and typ.defn.type_vars:
18351835
self.check_protocol_variance(defn)
1836+
if not defn.has_incompatible_baseclass and defn.info.is_enum:
1837+
for base in defn.info.mro[1:-1]: # we don't need self and `object`
1838+
if base.is_enum and base.fullname not in ENUM_BASES:
1839+
self.check_final_enum(defn, base)
18361840

18371841
def check_final_deletable(self, typ: TypeInfo) -> None:
18381842
# These checks are only for mypyc. Only perform some checks that are easier
@@ -1890,6 +1894,43 @@ def check_init_subclass(self, defn: ClassDef) -> None:
18901894
# all other bases have already been checked.
18911895
break
18921896

1897+
def check_final_enum(self, defn: ClassDef, base: TypeInfo) -> None:
1898+
for sym in base.names.values():
1899+
if self.is_final_enum_value(sym):
1900+
self.fail(
1901+
'Cannot extend enum with existing members: "{}"'.format(
1902+
base.name,
1903+
),
1904+
defn,
1905+
)
1906+
break
1907+
1908+
def is_final_enum_value(self, sym: SymbolTableNode) -> bool:
1909+
if isinstance(sym.node, (FuncBase, Decorator)):
1910+
return False # A method is fine
1911+
if not isinstance(sym.node, Var):
1912+
return True # Can be a class or anything else
1913+
1914+
# Now, only `Var` is left, we need to check:
1915+
# 1. Private name like in `__prop = 1`
1916+
# 2. Dunder name like `__hash__ = some_hasher`
1917+
# 3. Sunder name like `_order_ = 'a, b, c'`
1918+
# 4. If it is a method / descriptor like in `method = classmethod(func)`
1919+
if (
1920+
is_private(sym.node.name)
1921+
or is_dunder(sym.node.name)
1922+
or is_sunder(sym.node.name)
1923+
# TODO: make sure that `x = @class/staticmethod(func)`
1924+
# and `x = property(prop)` both work correctly.
1925+
# Now they are incorrectly counted as enum members.
1926+
or isinstance(get_proper_type(sym.node.type), FunctionLike)
1927+
):
1928+
return False
1929+
1930+
if self.is_stub or sym.node.has_explicit_value:
1931+
return True
1932+
return False
1933+
18931934
def check_protocol_variance(self, defn: ClassDef) -> None:
18941935
"""Check that protocol definition is compatible with declared
18951936
variances of type variables.

mypy/checkexpr.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from contextlib import contextmanager
55
import itertools
66
from typing import (
7-
Any, cast, Dict, Set, List, Tuple, Callable, Union, Optional, Sequence, Iterator
7+
cast, Dict, Set, List, Tuple, Callable, Union, Optional, Sequence, Iterator
88
)
99
from typing_extensions import ClassVar, Final, overload, TypeAlias as _TypeAlias
1010

@@ -378,6 +378,8 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) ->
378378
if isinstance(e.callee, MemberExpr) and e.callee.name == 'format':
379379
self.check_str_format_call(e)
380380
ret_type = get_proper_type(ret_type)
381+
if isinstance(ret_type, UnionType):
382+
ret_type = make_simplified_union(ret_type.items)
381383
if isinstance(ret_type, UninhabitedType) and not ret_type.ambiguous:
382384
self.chk.binder.unreachable()
383385
# Warn on calls to functions that always return None. The check
@@ -1605,8 +1607,7 @@ def check_overload_call(self,
16051607
# Record if we succeeded. Next we need to see if maybe normal procedure
16061608
# gives a narrower type.
16071609
if unioned_return:
1608-
# TODO: fix signature of zip() in typeshed.
1609-
returns, inferred_types = cast(Any, zip)(*unioned_return)
1610+
returns, inferred_types = zip(*unioned_return)
16101611
# Note that we use `combine_function_signatures` instead of just returning
16111612
# a union of inferred callables because for example a call
16121613
# Union[int -> int, str -> str](Union[int, str]) is invalid and

mypy/erasetype.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from typing import Optional, Container, Callable
1+
from typing import Optional, Container, Callable, List, Dict, cast
22

33
from mypy.types import (
44
Type, TypeVisitor, UnboundType, AnyType, NoneType, TypeVarId, Instance, TypeVarType,
55
CallableType, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType,
66
DeletedType, TypeTranslator, UninhabitedType, TypeType, TypeOfAny, LiteralType, ProperType,
7-
get_proper_type, TypeAliasType, ParamSpecType
7+
get_proper_type, get_proper_types, TypeAliasType, ParamSpecType
88
)
99
from mypy.nodes import ARG_STAR, ARG_STAR2
1010

@@ -161,3 +161,34 @@ def visit_type_alias_type(self, t: TypeAliasType) -> Type:
161161
# Type aliases can't contain literal values, because they are
162162
# always constructed as explicit types.
163163
return t
164+
165+
def visit_union_type(self, t: UnionType) -> Type:
166+
new = cast(UnionType, super().visit_union_type(t))
167+
# Erasure can result in many duplicate items; merge them.
168+
# Call make_simplified_union only on lists of instance types
169+
# that all have the same fullname, to avoid simplifying too
170+
# much.
171+
instances = [item for item in new.items
172+
if isinstance(get_proper_type(item), Instance)]
173+
# Avoid merge in simple cases such as optional types.
174+
if len(instances) > 1:
175+
instances_by_name: Dict[str, List[Instance]] = {}
176+
new_items = get_proper_types(new.items)
177+
for item in new_items:
178+
if isinstance(item, Instance) and not item.args:
179+
instances_by_name.setdefault(item.type.fullname, []).append(item)
180+
merged: List[Type] = []
181+
for item in new_items:
182+
if isinstance(item, Instance) and not item.args:
183+
types = instances_by_name.get(item.type.fullname)
184+
if types is not None:
185+
if len(types) == 1:
186+
merged.append(item)
187+
else:
188+
from mypy.typeops import make_simplified_union
189+
merged.append(make_simplified_union(types))
190+
del instances_by_name[item.type.fullname]
191+
else:
192+
merged.append(item)
193+
return UnionType.make_union(merged)
194+
return new

mypy/errors.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@
1616
from mypy.util import DEFAULT_SOURCE_OFFSET, is_typeshed_file
1717

1818
T = TypeVar("T")
19+
1920
allowed_duplicates: Final = ["@overload", "Got:", "Expected:"]
2021

22+
# Keep track of the original error code when the error code of a message is changed.
23+
# This is used to give notes about out-of-date "type: ignore" comments.
24+
original_error_codes: Final = {codes.LITERAL_REQ: codes.MISC}
25+
2126

2227
class ErrorInfo:
2328
"""Representation of a single error message."""
@@ -388,6 +393,24 @@ def add_error_info(self, info: ErrorInfo) -> None:
388393
info.hidden = True
389394
self.report_hidden_errors(info)
390395
self._add_error_info(file, info)
396+
ignored_codes = self.ignored_lines.get(file, {}).get(info.line, [])
397+
if ignored_codes and info.code:
398+
# Something is ignored on the line, but not this error, so maybe the error
399+
# code is incorrect.
400+
msg = f'Error code "{info.code.code}" not covered by "type: ignore" comment'
401+
if info.code in original_error_codes:
402+
# If there seems to be a "type: ignore" with a stale error
403+
# code, report a more specific note.
404+
old_code = original_error_codes[info.code].code
405+
if old_code in ignored_codes:
406+
msg = (f'Error code changed to {info.code.code}; "type: ignore" comment ' +
407+
'may be out of date')
408+
note = ErrorInfo(
409+
info.import_ctx, info.file, info.module, info.type, info.function_or_member,
410+
info.line, info.column, 'note', msg,
411+
code=None, blocker=False, only_once=False, allow_dups=False
412+
)
413+
self._add_error_info(file, note)
391414

392415
def has_many_errors(self) -> bool:
393416
if self.many_errors_threshold < 0:

mypy/fastparse.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ def ast3_parse(source: Union[str, bytes], filename: str, mode: str,
123123
MatchClass = ast3.MatchClass
124124
MatchAs = ast3.MatchAs
125125
MatchOr = ast3.MatchOr
126+
AstNode = Union[ast3.expr, ast3.stmt, ast3.pattern, ast3.ExceptHandler]
126127
else:
127128
Match = Any
128129
MatchValue = Any
@@ -133,6 +134,7 @@ def ast3_parse(source: Union[str, bytes], filename: str, mode: str,
133134
MatchClass = Any
134135
MatchAs = Any
135136
MatchOr = Any
137+
AstNode = Union[ast3.expr, ast3.stmt, ast3.ExceptHandler]
136138
except ImportError:
137139
try:
138140
from typed_ast import ast35 # type: ignore[attr-defined] # noqa: F401
@@ -357,7 +359,7 @@ def visit(self, node: Optional[AST]) -> Any:
357359
self.visitor_cache[typeobj] = visitor
358360
return visitor(node)
359361

360-
def set_line(self, node: N, n: Union[ast3.expr, ast3.stmt, ast3.ExceptHandler]) -> N:
362+
def set_line(self, node: N, n: AstNode) -> N:
361363
node.line = n.lineno
362364
node.column = n.col_offset
363365
node.end_line = getattr(n, "end_lineno", None) if isinstance(n, ast3.expr) else None

mypy/fscache.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,13 @@ def _fake_init(self, path: str) -> os.stat_result:
143143
assert not os.path.exists(path), path # Not cached!
144144
dirname = os.path.normpath(dirname)
145145
st = self.stat(dirname) # May raise OSError
146-
# Get stat result as a sequence so we can modify it.
147-
# (Alas, typeshed's os.stat_result is not a sequence yet.)
148-
tpl = tuple(st) # type: ignore[arg-type, var-annotated]
149-
seq: List[float] = list(tpl)
146+
# Get stat result as a list so we can modify it.
147+
seq: List[float] = list(st)
150148
seq[stat.ST_MODE] = stat.S_IFREG | 0o444
151149
seq[stat.ST_INO] = 1
152150
seq[stat.ST_NLINK] = 1
153151
seq[stat.ST_SIZE] = 0
154-
tpl = tuple(seq)
155-
st = os.stat_result(tpl)
152+
st = os.stat_result(seq)
156153
self.stat_cache[path] = st
157154
# Make listdir() and read() also pretend this file exists.
158155
self.fake_package_cache.add(dirname)

mypy/ipc.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ def read(self, size: int = 100000) -> bytes:
5454
if sys.platform == 'win32':
5555
while True:
5656
ov, err = _winapi.ReadFile(self.connection, size, overlapped=True)
57-
# TODO: remove once typeshed supports Literal types
58-
assert isinstance(ov, _winapi.Overlapped)
59-
assert isinstance(err, int)
6057
try:
6158
if err == _winapi.ERROR_IO_PENDING:
6259
timeout = int(self.timeout * 1000) if self.timeout else _winapi.INFINITE

mypy/join.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,15 @@ def join_types(s: Type, t: Type, instance_joiner: Optional[InstanceJoiner] = Non
175175
s = mypy.typeops.true_or_false(s)
176176
t = mypy.typeops.true_or_false(t)
177177

178+
if isinstance(s, UnionType) and not isinstance(t, UnionType):
179+
s, t = t, s
180+
178181
if isinstance(s, AnyType):
179182
return s
180183

181184
if isinstance(s, ErasedType):
182185
return t
183186

184-
if isinstance(s, UnionType) and not isinstance(t, UnionType):
185-
s, t = t, s
186-
187187
if isinstance(s, NoneType) and not isinstance(t, NoneType):
188188
s, t = t, s
189189

mypy/meet.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -508,15 +508,14 @@ def visit_param_spec(self, t: ParamSpecType) -> ProperType:
508508

509509
def visit_instance(self, t: Instance) -> ProperType:
510510
if isinstance(self.s, Instance):
511-
si = self.s
512-
if t.type == si.type:
511+
if t.type == self.s.type:
513512
if is_subtype(t, self.s) or is_subtype(self.s, t):
514513
# Combine type arguments. We could have used join below
515514
# equivalently.
516515
args: List[Type] = []
517516
# N.B: We use zip instead of indexing because the lengths might have
518517
# mismatches during daemon reprocessing.
519-
for ta, sia in zip(t.args, si.args):
518+
for ta, sia in zip(t.args, self.s.args):
520519
args.append(self.meet(ta, sia))
521520
return Instance(t.type, args)
522521
else:

0 commit comments

Comments
 (0)