Skip to content

Commit 591bcef

Browse files
author
hauntsaninja
committed
Merge remote-tracking branch 'upstream/master' into typevar2
2 parents 4de42d8 + 67a6ad7 commit 591bcef

27 files changed

+520
-286
lines changed

docs/source/command_line.rst

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,20 @@ for full details, see :ref:`running-mypy`.
5858
For instance, to avoid discovering any files named `setup.py` you could
5959
pass ``--exclude '/setup\.py$'``. Similarly, you can ignore discovering
6060
directories with a given name by e.g. ``--exclude /build/`` or
61-
those matching a subpath with ``--exclude /project/vendor/``.
62-
63-
Note that this flag only affects recursive discovery, that is, when mypy is
64-
discovering files within a directory tree or submodules of a package to
65-
check. If you pass a file or module explicitly it will still be checked. For
66-
instance, ``mypy --exclude '/setup.py$' but_still_check/setup.py``.
61+
those matching a subpath with ``--exclude /project/vendor/``. To ignore
62+
multiple files / directories / paths, you can combine expressions with
63+
``|``, e.g ``--exclude '/setup\.py$|/build/'``.
64+
65+
Note that this flag only affects recursive directory tree discovery, that
66+
is, when mypy is discovering files within a directory tree or submodules of
67+
a package to check. If you pass a file or module explicitly it will still be
68+
checked. For instance, ``mypy --exclude '/setup.py$'
69+
but_still_check/setup.py``.
70+
71+
In particular, ``--exclude`` does not affect mypy's :ref:`import following
72+
<follow-imports>`. You can use a per-module :confval:`follow_imports` config
73+
option to additionally avoid mypy from following imports and checking code
74+
you do not wish to be checked.
6775

6876
Note that mypy will never recursively discover files and directories named
6977
"site-packages", "node_modules" or "__pycache__", or those whose name starts

docs/source/config_file.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,10 @@ section of the command line docs.
254254
``error``. For explanations see the discussion for the
255255
:option:`--follow-imports <mypy --follow-imports>` command line flag.
256256

257+
Using this option in a per-module section (potentially with a wildcard,
258+
as described at the top of this page) is a good way to prevent mypy from
259+
checking portions of your code.
260+
257261
If this option is used in a per-module section, the module name should
258262
match the name of the *imported* module, not the module containing the
259263
import statement.
@@ -924,10 +928,10 @@ Instead of using a ``mypy.ini`` file, a ``pyproject.toml`` file (as specified by
924928

925929
* Boolean values should be all lower case
926930

927-
Please see the `TOML Documentation`_ for more details and information on
931+
Please see the `TOML Documentation`_ for more details and information on
928932
what is allowed in a ``toml`` file. See `PEP 518`_ for more information on the layout
929933
and structure of the ``pyproject.toml`` file.
930-
934+
931935
Example ``pyproject.toml``
932936
**************************
933937

docs/source/more_types.rst

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,3 +1151,148 @@ section of the docs has a full description with an example, but in short, you wi
11511151
need to give each TypedDict the same key where each value has a unique
11521152
unique :ref:`Literal type <literal_types>`. Then, check that key to distinguish
11531153
between your TypedDicts.
1154+
1155+
1156+
User-Defined Type Guards
1157+
************************
1158+
1159+
Mypy supports User-Defined Type Guards
1160+
(:pep:`647`).
1161+
1162+
A type guard is a way for programs to influence conditional
1163+
type narrowing employed by a type checker based on runtime checks.
1164+
1165+
Basically, a ``TypeGuard`` is a "smart" alias for a ``bool`` type.
1166+
Let's have a look at the regular ``bool`` example:
1167+
1168+
.. code-block:: python
1169+
1170+
from typing import List
1171+
1172+
def is_str_list(val: List[object]) -> bool:
1173+
"""Determines whether all objects in the list are strings"""
1174+
return all(isinstance(x, str) for x in val)
1175+
1176+
def func1(val: List[object]) -> None:
1177+
if is_str_list(val):
1178+
reveal_type(val) # Reveals List[object]
1179+
print(" ".join(val)) # Error: incompatible type
1180+
1181+
The same example with ``TypeGuard``:
1182+
1183+
.. code-block:: python
1184+
1185+
from typing import List
1186+
from typing import TypeGuard # use `typing_extensions` for Python 3.9 and below
1187+
1188+
def is_str_list(val: List[object]) -> TypeGuard[List[str]]:
1189+
"""Determines whether all objects in the list are strings"""
1190+
return all(isinstance(x, str) for x in val)
1191+
1192+
def func1(val: List[object]) -> None:
1193+
if is_str_list(val):
1194+
reveal_type(val) # List[str]
1195+
print(" ".join(val)) # ok
1196+
1197+
How does it work? ``TypeGuard`` narrows the first function argument (``val``)
1198+
to the type specified as the first type parameter (``List[str]``).
1199+
1200+
.. note::
1201+
1202+
Narrowing is
1203+
`not strict <https://www.python.org/dev/peps/pep-0647/#enforcing-strict-narrowing>`_.
1204+
For example, you can narrow ``str`` to ``int``:
1205+
1206+
.. code-block:: python
1207+
1208+
def f(value: str) -> TypeGuard[int]:
1209+
return True
1210+
1211+
Note: since strict narrowing is not enforced, it's easy
1212+
to break type safety.
1213+
1214+
However, there are many ways a determined or uninformed developer can
1215+
subvert type safety -- most commonly by using cast or Any.
1216+
If a Python developer takes the time to learn about and implement
1217+
user-defined type guards within their code,
1218+
it is safe to assume that they are interested in type safety
1219+
and will not write their type guard functions in a way
1220+
that will undermine type safety or produce nonsensical results.
1221+
1222+
Generic TypeGuards
1223+
------------------
1224+
1225+
``TypeGuard`` can also work with generic types:
1226+
1227+
.. code-block:: python
1228+
1229+
from typing import Tuple, TypeVar
1230+
from typing import TypeGuard # use `typing_extensions` for `python<3.10`
1231+
1232+
_T = TypeVar("_T")
1233+
1234+
def is_two_element_tuple(val: Tuple[_T, ...]) -> TypeGuard[Tuple[_T, _T]]:
1235+
return len(val) == 2
1236+
1237+
def func(names: Tuple[str, ...]):
1238+
if is_two_element_tuple(names):
1239+
reveal_type(names) # Tuple[str, str]
1240+
else:
1241+
reveal_type(names) # Tuple[str, ...]
1242+
1243+
Typeguards with parameters
1244+
--------------------------
1245+
1246+
Type guard functions can accept extra arguments:
1247+
1248+
.. code-block:: python
1249+
1250+
from typing import Type, Set, TypeVar
1251+
from typing import TypeGuard # use `typing_extensions` for `python<3.10`
1252+
1253+
_T = TypeVar("_T")
1254+
1255+
def is_set_of(val: Set[Any], type: Type[_T]) -> TypeGuard[Set[_T]]:
1256+
return all(isinstance(x, type) for x in val)
1257+
1258+
items: Set[Any]
1259+
if is_set_of(items, str):
1260+
reveal_type(items) # Set[str]
1261+
1262+
TypeGuards as methods
1263+
---------------------
1264+
1265+
A method can also serve as the ``TypeGuard``:
1266+
1267+
.. code-block:: python
1268+
1269+
class StrValidator:
1270+
def is_valid(self, instance: object) -> TypeGuard[str]:
1271+
return isinstance(instance, str)
1272+
1273+
def func(to_validate: object) -> None:
1274+
if StrValidator().is_valid(to_validate):
1275+
reveal_type(to_validate) # Revealed type is "builtins.str"
1276+
1277+
.. note::
1278+
1279+
Note, that ``TypeGuard``
1280+
`does not narrow <https://www.python.org/dev/peps/pep-0647/#narrowing-of-implicit-self-and-cls-parameters>`_
1281+
types of ``self`` or ``cls`` implicit arguments.
1282+
1283+
If narrowing of ``self`` or ``cls`` is required,
1284+
the value can be passed as an explicit argument to a type guard function:
1285+
1286+
.. code-block:: python
1287+
1288+
class Parent:
1289+
def method(self) -> None:
1290+
reveal_type(self) # Revealed type is "Parent"
1291+
if is_child(self):
1292+
reveal_type(self) # Revealed type is "Child"
1293+
1294+
class Child(Parent):
1295+
...
1296+
1297+
def is_child(instance: Parent) -> TypeGuard[Child]:
1298+
return isinstance(instance, Child)

docs/source/running_mypy.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,10 @@ We do not recommend using ``skip`` unless you know what you are doing:
384384
while this option can be quite powerful, it can also cause many
385385
hard-to-debug errors.
386386

387+
Adjusting import following behaviour is often most useful when restricted to
388+
specific modules. This can be accomplished by setting a per-module
389+
:confval:`follow_imports` config option.
390+
387391

388392
.. _mapping-paths-to-modules:
389393

mypy/applytype.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from mypy.expandtype import expand_type
66
from mypy.types import (
77
Type, TypeVarId, TypeVarType, CallableType, AnyType, PartialType, get_proper_types,
8-
TypeVarLikeType, ProperType
8+
TypeVarLikeType, ProperType, ParamSpecType
99
)
1010
from mypy.nodes import Context
1111

@@ -19,6 +19,8 @@ def get_target_type(
1919
skip_unsatisfied: bool
2020
) -> Optional[Type]:
2121
# TODO(shantanu): fix for ParamSpecType
22+
if isinstance(tvar, ParamSpecType):
23+
return None
2224
assert isinstance(tvar, TypeVarType)
2325
values = get_proper_types(tvar.values)
2426
if values:

mypy/checker.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,13 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
466466
# At this point we should have set the impl already, and all remaining
467467
# items are decorators
468468

469+
if self.msg.errors.file in self.msg.errors.ignored_files:
470+
# This is a little hacky, however, the quadratic check here is really expensive, this
471+
# method has no side effects, so we should skip it if we aren't going to report
472+
# anything. In some other places we swallow errors in stubs, but this error is very
473+
# useful for stubs!
474+
return
475+
469476
# Compute some info about the implementation (if it exists) for use below
470477
impl_type: Optional[CallableType] = None
471478
if defn.impl:

mypy/checkexpr.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
Type, AnyType, CallableType, Overloaded, NoneType, TypeVarType, TypeGuardType,
1919
TupleType, TypedDictType, Instance, ErasedType, UnionType,
2020
PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, LiteralType, LiteralValue,
21-
is_named_instance, FunctionLike,
21+
is_named_instance, FunctionLike, ParamSpecType,
2222
StarType, is_optional, remove_optional, is_generic_instance, get_proper_type, ProperType,
2323
get_proper_types, flatten_nested_unions
2424
)
@@ -4471,14 +4471,16 @@ def merge_typevars_in_callables_by_name(
44714471
target = freshen_function_type_vars(target)
44724472

44734473
rename = {} # Dict[TypeVarId, TypeVar]
4474-
for tvdef in target.variables:
4475-
name = tvdef.fullname
4474+
for tv in target.variables:
4475+
name = tv.fullname
44764476
if name not in unique_typevars:
44774477
# TODO(shantanu): fix for ParamSpecType
4478-
assert isinstance(tvdef, TypeVarType)
4479-
unique_typevars[name] = tvdef
4480-
variables.append(tvdef)
4481-
rename[tvdef.id] = unique_typevars[name]
4478+
if isinstance(tv, ParamSpecType):
4479+
continue
4480+
assert isinstance(tv, TypeVarType)
4481+
unique_typevars[name] = tv
4482+
variables.append(tv)
4483+
rename[tv.id] = unique_typevars[name]
44824484

44834485
target = cast(CallableType, expand_type(target, rename))
44844486
output.append(target)

0 commit comments

Comments
 (0)