Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Next Release

### Drop Support for Targeting Python 3.9

Mypy no longer supports type checking code with `--python-version 3.9`.
Use `--python-version 3.10` or newer.

## Mypy 1.20

We’ve just uploaded mypy 1.20.0 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)).
Expand Down
23 changes: 4 additions & 19 deletions docs/source/builtin_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ See :ref:`dynamic-typing` for more details.
Generic types
.............

In Python 3.9 and later, built-in collection type objects support
indexing:
Built-in collection type objects support indexing:

====================== ===============================
Type Description
Expand All @@ -65,13 +64,11 @@ strings and ``dict[Any, Any]`` is a dictionary of dynamically typed
Python protocols. For example, a ``str`` object or a ``list[str]`` object is
valid when ``Iterable[str]`` or ``Sequence[str]`` is expected.
You can import them from :py:mod:`collections.abc` instead of importing from
:py:mod:`typing` in Python 3.9.
:py:mod:`typing`.

See :ref:`generic-builtins` for more details, including how you can
use these in annotations also in Python 3.7 and 3.8.
See :ref:`generic-builtins` for more details.

These legacy types defined in :py:mod:`typing` are needed if you need to support
Python 3.8 and earlier:
These legacy types defined in :py:mod:`typing` are also supported:

====================== ===============================
Type Description
Expand All @@ -80,17 +77,5 @@ Type Description
``Tuple[int, int]`` tuple of two ``int`` objects (``Tuple[()]`` is the empty tuple)
``Tuple[int, ...]`` tuple of an arbitrary number of ``int`` objects
``Dict[str, int]`` dictionary from ``str`` keys to ``int`` values
``Iterable[int]`` iterable object containing ints
``Sequence[bool]`` sequence of booleans (read-only)
``Mapping[str, int]`` mapping from ``str`` keys to ``int`` values (read-only)
``Type[C]`` type object of ``C`` (``C`` is a class/type variable/union of types)
====================== ===============================

``List`` is an alias for the built-in type ``list`` that supports
indexing (and similarly for ``dict``/``Dict`` and
``tuple``/``Tuple``).

Note that even though ``Iterable``, ``Sequence`` and ``Mapping`` look
similar to abstract base classes defined in :py:mod:`collections.abc`
(formerly ``collections``), they are not identical, since the latter
don't support indexing prior to Python 3.9.
17 changes: 8 additions & 9 deletions docs/source/cheat_sheet_py3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,18 @@ Useful built-in types
x: str = "test"
x: bytes = b"test"

# For collections on Python 3.9+, the type of the collection item is in brackets
# For collections, the type of the collection item is in brackets
x: list[int] = [1]
x: set[int] = {6, 7}

# For mappings, we need the types of both keys and values
x: dict[str, float] = {"field": 2.0} # Python 3.9+
x: dict[str, float] = {"field": 2.0}

# For tuples of fixed size, we specify the types of all the elements
x: tuple[int, str, float] = (3, "yes", 7.5) # Python 3.9+
x: tuple[int, str, float] = (3, "yes", 7.5)

# For tuples of variable size, we use one type and ellipsis
x: tuple[int, ...] = (1, 2, 3) # Python 3.9+
x: tuple[int, ...] = (1, 2, 3)

# On Python 3.8 and earlier, the name of the collection type is
# capitalized, and the type is imported from the 'typing' module
Expand All @@ -67,13 +67,12 @@ Useful built-in types

from typing import Union, Optional

# On Python 3.10+, use the | operator when something could be one of a few types
x: list[int | str] = [3, 5, "test", "fun"] # Python 3.10+
# On earlier versions, use Union
# Use the | operator when something could be one of a few types
x: list[int | str] = [3, 5, "test", "fun"]
# Union is equivalent
x: list[Union[int, str]] = [3, 5, "test", "fun"]

# Use X | None for a value that could be None on Python 3.10+
# Use Optional[X] on 3.9 and earlier; Optional[X] is the same as 'X | None'
# Use X | None for a value that could be None; Optional[X] is the same as X | None
x: str | None = "something" if some_condition() else None
if x is not None:
# Mypy understands x won't be None here because of the if-statement
Expand Down
2 changes: 1 addition & 1 deletion docs/source/common_issues.rst
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@ subtly different, and it's important to understand how they differ to avoid pitf

.. code-block:: python
from typing import TypeAlias # "from typing_extensions" in Python 3.9 and earlier
from typing import TypeAlias
class A: ...
Alias: TypeAlias = A
Expand Down
4 changes: 2 additions & 2 deletions docs/source/config_file.rst
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ Platform configuration

Specifies the Python version used to parse and check the target
program. The string should be in the format ``MAJOR.MINOR`` --
for example ``3.9``. The default is the version of the Python
for example ``3.10``. The default is the version of the Python
interpreter used to run mypy.

This option may only be set in the global section (``[mypy]``).
Expand Down Expand Up @@ -1255,7 +1255,7 @@ of your repo (or append it to the end of an existing ``pyproject.toml`` file) an
# mypy global options:
[tool.mypy]
python_version = "3.9"
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
exclude = [
Expand Down
3 changes: 1 addition & 2 deletions docs/source/generics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1419,8 +1419,7 @@ class in more recent versions of Python:

.. code-block:: python

>>> # Only relevant for Python 3.8 and below
>>> # If using Python 3.9 or newer, prefer the 'list[int]' syntax
>>> # Prefer the 'list[int]' syntax
>>> from typing import List
>>> List[int]
typing.List[int]
Expand Down
6 changes: 0 additions & 6 deletions docs/source/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,6 @@ union type. For example, ``int`` is a subtype of ``int | str``:
else:
return user_id
.. note::

If using Python 3.9 or earlier, use ``typing.Union[int, str]`` instead of
``int | str``, or use ``from __future__ import annotations`` at the top of
the file (see :ref:`runtime_troubles`).

The :py:mod:`typing` module contains many other useful types.

For a quick overview, look through the :ref:`mypy cheatsheet <cheat-sheet-py3>`.
Expand Down
10 changes: 4 additions & 6 deletions docs/source/kinds_of_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,8 @@ such as ``int | None``. This is called an *optional type*:
return None # Error: None not compatible with int
return len(s)

To support Python 3.9 and earlier, you can use the :py:data:`~typing.Optional`
type modifier instead, such as ``Optional[int]`` (``Optional[X]`` is
the preferred shorthand for ``Union[X, None]``):
You can also use the :py:data:`~typing.Optional` type modifier, such as
``Optional[int]`` (``Optional[X]`` is the shorthand for ``Union[X, None]``):

.. code-block:: python

Expand Down Expand Up @@ -515,12 +514,11 @@ distinguish them from implicit type aliases:
it can't be used in contexts which require a class object. For example, it's
not valid as a base class and it can't be used to construct instances.

There is also use an older syntax for defining explicit type aliases, which was
introduced in Python 3.10 (:pep:`613`):
There is also use an older syntax for defining explicit type aliases (:pep:`613`):

.. code-block:: python

from typing import TypeAlias # "from typing_extensions" in Python 3.9 and earlier
from typing import TypeAlias

AliasType: TypeAlias = list[dict[tuple[int, str], set[int]]] | tuple[str, list[str]]

Expand Down
5 changes: 0 additions & 5 deletions docs/source/protocols.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,6 @@ you need to define to implement each protocol.
.. note::
``typing`` also contains deprecated aliases to protocols and ABCs defined in
:py:mod:`collections.abc`, such as :py:class:`Iterable[T] <typing.Iterable>`.
These are only necessary in Python 3.8 and earlier, since the protocols in
``collections.abc`` didn't yet support subscripting (``[]``) in Python 3.8,
but the aliases in ``typing`` have always supported
subscripting. In Python 3.9 and later, the aliases in ``typing`` don't provide
any extra functionality.

Simple user-defined protocols
*****************************
Expand Down
8 changes: 0 additions & 8 deletions docs/source/type_inference_and_annotations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,6 @@ In these cases you can give the type explicitly using a type annotation:
l: list[int] = [] # Create empty list of int
d: dict[str, int] = {} # Create empty dictionary (str -> int)

.. note::

Using type arguments (e.g. ``list[int]``) on builtin collections like
:py:class:`list`, :py:class:`dict`, :py:class:`tuple`, and :py:class:`set`
only works in Python 3.9 and later. For Python 3.8 and earlier, you must use
:py:class:`~typing.List` (e.g. ``List[int]``), :py:class:`~typing.Dict`, and
so on.


Compatibility of container types
********************************
Expand Down
2 changes: 1 addition & 1 deletion docs/source/type_narrowing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ The same example with ``TypeGuard``:

.. code-block:: python
from typing import TypeGuard # use `typing_extensions` for Python 3.9 and below
from typing import TypeGuard
def is_str_list(val: list[object]) -> TypeGuard[list[str]]:
"""Determines whether all objects in the list are strings"""
Expand Down
14 changes: 2 additions & 12 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,13 +555,7 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) ->
and node
and isinstance(node.node, TypeAlias)
and not node.node.no_args
and not (
isinstance(union_target := get_proper_type(node.node.target), UnionType)
and (
union_target.uses_pep604_syntax
or self.chk.options.python_version >= (3, 10)
)
)
and not isinstance(get_proper_type(node.node.target), UnionType)
):
self.msg.type_arguments_not_allowed(e)
if isinstance(typ, RefExpr) and isinstance(typ.node, TypeInfo):
Expand Down Expand Up @@ -5021,11 +5015,7 @@ class LongName(Generic[T]): ...
return TypeType(item, line=item.line, column=item.column)
elif isinstance(item, AnyType):
return AnyType(TypeOfAny.from_another_any, source_any=item)
elif (
isinstance(item, UnionType)
and item.uses_pep604_syntax
and self.chk.options.python_version >= (3, 10)
):
elif isinstance(item, UnionType) and item.uses_pep604_syntax:
return self.chk.named_generic_type("types.UnionType", item.items)
else:
if alias_definition:
Expand Down
2 changes: 1 addition & 1 deletion mypy/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

# Earliest Python 3.x version supported via --python-version 3.x. To run
# mypy, at least version PYTHON3_VERSION is needed.
PYTHON3_VERSION_MIN: Final = (3, 9) # Keep in sync with typeshed's python support
PYTHON3_VERSION_MIN: Final = (3, 10) # Keep in sync with supported target versions

CACHE_DIR: Final = ".mypy_cache"

Expand Down
6 changes: 1 addition & 5 deletions mypy/exprtotype.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,7 @@ def expr_to_unanalyzed_type(
return base
else:
raise TypeTranslationError()
elif (
isinstance(expr, OpExpr)
and expr.op == "|"
and ((options.python_version >= (3, 10)) or allow_new_syntax)
):
elif isinstance(expr, OpExpr) and expr.op == "|":
return UnionType(
[
expr_to_unanalyzed_type(
Expand Down
4 changes: 1 addition & 3 deletions mypy/modulefinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,4 @@ def parse_version(version: str) -> tuple[int, int]:

def typeshed_py_version(options: Options) -> tuple[int, int]:
"""Return Python version used for checking whether module supports typeshed."""
# Typeshed no longer covers Python 3.x versions before 3.9, so 3.9 is
# the earliest we can support.
return max(options.python_version, (3, 9))
return max(options.python_version, (3, 10))
4 changes: 1 addition & 3 deletions mypy/plugins/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,9 +370,7 @@ def attr_class_maker_callback_impl(
_add_attrs_magic_attribute(ctx, [(attr.name, info[attr.name].type) for attr in attributes])
if slots:
_add_slots(ctx, attributes)
if match_args and ctx.api.options.python_version[:2] >= (3, 10):
# `.__match_args__` is only added for python3.10+, but the argument
# exists for earlier versions as well.
if match_args:
_add_match_args(ctx, attributes)

# Save the attributes so that subclasses can reuse them.
Expand Down
24 changes: 4 additions & 20 deletions mypy/plugins/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,6 @@ def transform(self) -> bool:
"slots": self._get_bool_arg("slots", False),
"match_args": self._get_bool_arg("match_args", True),
}
py_version = self._api.options.python_version

# If there are no attributes, it may be that the semantic analyzer has not
# processed them yet. In order to work around this, we can simply skip generating
Expand Down Expand Up @@ -368,16 +367,12 @@ def transform(self) -> bool:
self._propertize_callables(attributes)

if decorator_arguments["slots"]:
self.add_slots(info, attributes, correct_version=py_version >= (3, 10))
self.add_slots(info, attributes)

self.reset_init_only_vars(info, attributes)

if (
decorator_arguments["match_args"]
and (
"__match_args__" not in info.names or info.names["__match_args__"].plugin_generated
)
and py_version >= (3, 10)
if decorator_arguments["match_args"] and (
"__match_args__" not in info.names or info.names["__match_args__"].plugin_generated
):
str_type = self._api.named_type("builtins.str")
literals: list[Type] = [
Expand Down Expand Up @@ -445,18 +440,7 @@ def _add_internal_post_init_method(self, attributes: list[DataclassAttribute]) -
return_type=NoneType(),
)

def add_slots(
self, info: TypeInfo, attributes: list[DataclassAttribute], *, correct_version: bool
) -> None:
if not correct_version:
# This means that version is lower than `3.10`,
# it is just a non-existent argument for `dataclass` function.
self._api.fail(
'Keyword argument "slots" for "dataclass" is only valid in Python 3.10 and higher',
self._reason,
)
return

def add_slots(self, info: TypeInfo, attributes: list[DataclassAttribute]) -> None:
existing_slots = info.names.get("__slots__")
slots_defined_by_plugin = existing_slots is not None and existing_slots.plugin_generated
if existing_slots is not None and not slots_defined_by_plugin:
Expand Down
4 changes: 2 additions & 2 deletions mypy/pyinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

"""Utilities to find the site and prefix information of a Python executable.
This file MUST remain compatible with all Python 3.9+ versions. Since we cannot make any
This file MUST remain compatible with all Python 3.10+ versions. Since we cannot make any
assumptions about the Python being executed, this module should not use *any* dependencies outside
of the standard library found in Python 3.9. This file is run each mypy run, so it should be kept
of the standard library found in Python 3.10. This file is run each mypy run, so it should be kept
as fast as possible.
"""
import sys
Expand Down
5 changes: 1 addition & 4 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4206,10 +4206,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
eager=eager,
python_3_12_type_alias=pep_695,
)
if isinstance(s.rvalue, (IndexExpr, CallExpr, OpExpr)) and (
not isinstance(rvalue, OpExpr)
or (self.options.python_version >= (3, 10) or self.is_stub_file)
):
if isinstance(s.rvalue, (IndexExpr, CallExpr, OpExpr)):
# Note: CallExpr is for "void = type(None)" and OpExpr is for "X | Y" union syntax.
if not isinstance(s.rvalue.analyzed, TypeAliasExpr):
# Any existing node will be updated in-place below.
Expand Down
3 changes: 1 addition & 2 deletions mypy/semanal_namedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,8 +569,7 @@ def add_field(
add_field(Var("_source", strtype), is_initialized_in_class=True)
add_field(Var("__annotations__", ordereddictype), is_initialized_in_class=True)
add_field(Var("__doc__", strtype), is_initialized_in_class=True)
if self.options.python_version >= (3, 10):
add_field(Var("__match_args__", match_args_type), is_initialized_in_class=True)
add_field(Var("__match_args__", match_args_type), is_initialized_in_class=True)

assert info.tuple_type is not None # Set by update_tuple_type() above.
shared_self_type = TypeVarType(
Expand Down
8 changes: 4 additions & 4 deletions mypy/semanal_pass1.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ class SemanticAnalyzerPreAnalysis(TraverserVisitor):
import sys

def do_stuff() -> None:
if sys.version_info >= (3, 10):
import xyz # Only available in Python 3.10+
if sys.version_info >= (3, 11):
import xyz # Only available in Python 3.11+
xyz.whatever()
...

The block containing 'import xyz' is unreachable in Python 3 mode. The import
shouldn't be processed in Python 3 mode, even if the module happens to exist.
The block containing 'import xyz' is unreachable in Python 3.10 mode. The import
shouldn't be processed in Python 3.10 mode, even if the module happens to exist.
"""

def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) -> None:
Expand Down
4 changes: 1 addition & 3 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1716,9 +1716,7 @@ def verify_typealias(
return
if isinstance(stub_target, mypy.types.UnionType):
# complain if runtime is not a Union or UnionType
if runtime_origin is not Union and (
not (sys.version_info >= (3, 10) and isinstance(runtime, types.UnionType))
):
if runtime_origin is not Union and not isinstance(runtime, types.UnionType):
yield Error(object_path, "is not a Union", stub, runtime, stub_desc=str(stub_target))
# could check Union contents here...
return
Expand Down
Loading
Loading