Skip to content

Drop support for --python-version 3.8 #19157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 28, 2025
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
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, 8) # Keep in sync with typeshed's python support
PYTHON3_VERSION_MIN: Final = (3, 9) # Keep in sync with typeshed's python support

CACHE_DIR: Final = ".mypy_cache"

Expand Down
4 changes: 2 additions & 2 deletions mypy/modulefinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,6 @@ 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.8, so 3.8 is
# 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, 8))
return max(options.python_version, (3, 9))
12 changes: 0 additions & 12 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,18 +148,6 @@ def set_line(
"builtins.frozenset": "typing.FrozenSet",
}

_nongen_builtins: Final = {"builtins.tuple": "typing.Tuple", "builtins.enumerate": ""}
_nongen_builtins.update((name, alias) for alias, name in type_aliases.items())
# Drop OrderedDict from this for backward compatibility
del _nongen_builtins["collections.OrderedDict"]
# HACK: consequence of hackily treating LiteralString as an alias for str
del _nongen_builtins["builtins.str"]


def get_nongen_builtins(python_version: tuple[int, int]) -> dict[str, str]:
# After 3.9 with pep585 generic builtins are allowed
return _nongen_builtins if python_version < (3, 9) else {}


RUNTIME_PROTOCOL_DECOS: Final = (
"typing.runtime_checkable",
Expand Down
26 changes: 0 additions & 26 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@
YieldExpr,
YieldFromExpr,
get_member_expr_fullname,
get_nongen_builtins,
implicit_module_attrs,
is_final_node,
type_aliases,
Expand Down Expand Up @@ -247,7 +246,6 @@
find_self_type,
fix_instance,
has_any_from_unimported_type,
no_subscript_builtin_alias,
type_constructors,
validate_instance,
)
Expand Down Expand Up @@ -5996,30 +5994,6 @@ def analyze_type_application(self, expr: IndexExpr) -> None:
expr.analyzed = TypeApplication(base, types)
expr.analyzed.line = expr.line
expr.analyzed.column = expr.column
# Types list, dict, set are not subscriptable, prohibit this if
# subscripted either via type alias...
if isinstance(base, RefExpr) and isinstance(base.node, TypeAlias):
alias = base.node
target = get_proper_type(alias.target)
if isinstance(target, Instance):
name = target.type.fullname
if (
alias.no_args
and name # this avoids bogus errors for already reported aliases
in get_nongen_builtins(self.options.python_version)
and not self.is_stub_file
and not alias.normalized
):
self.fail(no_subscript_builtin_alias(name, propose_alt=False), expr)
# ...or directly.
else:
n = self.lookup_type_node(base)
if (
n
and n.fullname in get_nongen_builtins(self.options.python_version)
and not self.is_stub_file
):
self.fail(no_subscript_builtin_alias(n.fullname, propose_alt=False), expr)

def analyze_type_application_args(self, expr: IndexExpr) -> list[Type] | None:
"""Analyze type arguments (index) in a type application.
Expand Down
70 changes: 7 additions & 63 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
Var,
check_arg_kinds,
check_arg_names,
get_nongen_builtins,
)
from mypy.options import INLINE_TYPEDDICT, Options
from mypy.plugin import AnalyzeTypeContext, Plugin, TypeAnalyzerPluginInterface
Expand Down Expand Up @@ -136,12 +135,6 @@
"mypy_extensions.KwArg": ARG_STAR2,
}

GENERIC_STUB_NOT_AT_RUNTIME_TYPES: Final = {
"queue.Queue",
"builtins._PathLike",
"asyncio.futures.Future",
}

SELF_TYPE_NAMES: Final = {"typing.Self", "typing_extensions.Self"}


Expand Down Expand Up @@ -186,17 +179,6 @@ def analyze_type_alias(
return res, analyzer.aliases_used


def no_subscript_builtin_alias(name: str, propose_alt: bool = True) -> str:
class_name = name.split(".")[-1]
msg = f'"{class_name}" is not subscriptable'
# This should never be called if the python_version is 3.9 or newer
nongen_builtins = get_nongen_builtins((3, 8))
replacement = nongen_builtins[name]
if replacement and propose_alt:
msg += f', use "{replacement}" instead'
return msg


class TypeAnalyser(SyntheticTypeVisitor[Type], TypeAnalyzerPluginInterface):
"""Semantic analyzer for types.

Expand Down Expand Up @@ -360,14 +342,6 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
hook = self.plugin.get_type_analyze_hook(fullname)
if hook is not None:
return hook(AnalyzeTypeContext(t, t, self))
if (
fullname in get_nongen_builtins(self.options.python_version)
and t.args
and not self.always_allow_new_syntax
):
self.fail(
no_subscript_builtin_alias(fullname, propose_alt=not self.defining_alias), t
)
tvar_def = self.tvar_scope.get_binding(sym)
if isinstance(sym.node, ParamSpecExpr):
if tvar_def is None:
Expand Down Expand Up @@ -2033,44 +2007,14 @@ def get_omitted_any(
unexpanded_type: Type | None = None,
) -> AnyType:
if disallow_any:
nongen_builtins = get_nongen_builtins(options.python_version)
if fullname in nongen_builtins:
typ = orig_type
# We use a dedicated error message for builtin generics (as the most common case).
alternative = nongen_builtins[fullname]
fail(
message_registry.IMPLICIT_GENERIC_ANY_BUILTIN.format(alternative),
typ,
code=codes.TYPE_ARG,
)
else:
typ = unexpanded_type or orig_type
type_str = typ.name if isinstance(typ, UnboundType) else format_type_bare(typ, options)
typ = unexpanded_type or orig_type
type_str = typ.name if isinstance(typ, UnboundType) else format_type_bare(typ, options)

fail(
message_registry.BARE_GENERIC.format(quote_type_string(type_str)),
typ,
code=codes.TYPE_ARG,
)
base_type = get_proper_type(orig_type)
base_fullname = (
base_type.type.fullname if isinstance(base_type, Instance) else fullname
)
# Ideally, we'd check whether the type is quoted or `from __future__ annotations`
# is set before issuing this note
if (
options.python_version < (3, 9)
and base_fullname in GENERIC_STUB_NOT_AT_RUNTIME_TYPES
):
# Recommend `from __future__ import annotations` or to put type in quotes
# (string literal escaping) for classes not generic at runtime
note(
"Subscripting classes that are not generic at runtime may require "
"escaping, see https://mypy.readthedocs.io/en/stable/runtime_troubles.html"
"#not-generic-runtime",
typ,
code=codes.TYPE_ARG,
)
fail(
message_registry.BARE_GENERIC.format(quote_type_string(type_str)),
typ,
code=codes.TYPE_ARG,
)

any_type = AnyType(TypeOfAny.from_error, line=typ.line, column=typ.column)
else:
Expand Down
9 changes: 0 additions & 9 deletions mypyc/test-data/run-misc.test
Original file line number Diff line number Diff line change
Expand Up @@ -984,15 +984,6 @@ elif sys.version_info[:2] == (3, 10):
elif sys.version_info[:2] == (3, 9):
def version() -> int:
return 9
elif sys.version_info[:2] == (3, 8):
def version() -> int:
return 8
elif sys.version_info[:2] == (3, 7):
def version() -> int:
return 7
elif sys.version_info[:2] == (3, 6):
def version() -> int:
return 6
else:
raise Exception("we don't support this version yet!")

Expand Down
2 changes: 1 addition & 1 deletion mypyc/test/testutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def build_ir_for_single_file2(
options.hide_error_codes = True
options.use_builtins_fixtures = True
options.strict_optional = True
options.python_version = compiler_options.python_version or (3, 8)
options.python_version = compiler_options.python_version or (3, 9)
options.export_types = True
options.preserve_asts = True
options.allow_empty_bodies = True
Expand Down
10 changes: 1 addition & 9 deletions test-data/unit/check-annotated.test
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,7 @@ def f4(a: Annotated[T, "metadata"]):
reveal_type(f4) # N: Revealed type is "def [T] (a: T`-1) -> Any"
[builtins fixtures/tuple.pyi]

[case testSliceAnnotated39]
# flags: --python-version 3.9
from typing_extensions import Annotated
a: Annotated[int, 1:2]
reveal_type(a) # N: Revealed type is "builtins.int"
[builtins fixtures/tuple.pyi]

[case testSliceAnnotated38]
# flags: --python-version 3.8
[case testSliceAnnotated]
from typing_extensions import Annotated
a: Annotated[int, 1:2]
reveal_type(a) # N: Revealed type is "builtins.int"
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-columns.test
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,10 @@ class D(A):
# N:5: def f(self) -> None

[case testColumnMissingTypeParameters]
# flags: --python-version 3.8 --disallow-any-generics
# flags: --disallow-any-generics
from typing import List, Callable
def f(x: List) -> None: pass # E:10: Missing type parameters for generic type "List"
def g(x: list) -> None: pass # E:10: Implicit generic "Any". Use "typing.List" and specify generic parameters
def g(x: list) -> None: pass # E:10: Missing type parameters for generic type "List"
if int():
c: Callable # E:8: Missing type parameters for generic type "Callable"
[builtins fixtures/list.pyi]
Expand Down
1 change: 0 additions & 1 deletion test-data/unit/check-dataclasses.test
Original file line number Diff line number Diff line change
Expand Up @@ -1911,7 +1911,6 @@ SecondClass().SECOND_CONST = 42 # E: Cannot assign to final attribute "SECOND_C
[builtins fixtures/dataclasses.pyi]

[case testDataclassFieldsProtocol]
# flags: --python-version 3.9
from dataclasses import dataclass
from typing import Any, Protocol

Expand Down
29 changes: 14 additions & 15 deletions test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,10 @@ a: A
a.x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment]

[case testErrorCodeMissingTypeArg]
# flags: --python-version 3.8 --disallow-any-generics
# flags: --disallow-any-generics
from typing import List, TypeVar
x: List # E: Missing type parameters for generic type "List" [type-arg]
y: list # E: Implicit generic "Any". Use "typing.List" and specify generic parameters [type-arg]
y: list # E: Missing type parameters for generic type "List" [type-arg]
T = TypeVar('T')
L = List[List[T]]
z: L # E: Missing type parameters for generic type "L" [type-arg]
Expand Down Expand Up @@ -970,22 +970,21 @@ def f(arg: int) -> int:
def f(arg: str) -> str:
...

[case testSliceInDict39]
# flags: --python-version 3.9 --show-column-numbers
from typing import Dict
b: Dict[int, x:y]
c: Dict[x:y]
[case testSliceInDictBuiltin]
# flags: --show-column-numbers
b: dict[int, x:y]
c: dict[x:y]

[builtins fixtures/dict.pyi]
[out]
main:3:14: error: Invalid type comment or annotation [valid-type]
main:3:14: note: did you mean to use ',' instead of ':' ?
main:4:4: error: "dict" expects 2 type arguments, but 1 given [type-arg]
main:4:9: error: Invalid type comment or annotation [valid-type]
main:4:9: note: did you mean to use ',' instead of ':' ?

[case testSliceInDict38]
# flags: --python-version 3.8 --show-column-numbers
main:2:14: error: Invalid type comment or annotation [valid-type]
main:2:14: note: did you mean to use ',' instead of ':' ?
main:3:4: error: "dict" expects 2 type arguments, but 1 given [type-arg]
main:3:9: error: Invalid type comment or annotation [valid-type]
main:3:9: note: did you mean to use ',' instead of ':' ?

[case testSliceInDictTyping]
# flags: --show-column-numbers
from typing import Dict
b: Dict[int, x:y]
c: Dict[x:y]
Expand Down
32 changes: 15 additions & 17 deletions test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -1501,16 +1501,14 @@ GroupsDict = Dict[str, GroupDataDict] # type: ignore


[case testCheckDisallowAnyGenericsStubOnly]
# flags: --disallow-any-generics --python-version 3.8
# flags: --disallow-any-generics
from asyncio import Future
from queue import Queue
x: Future[str]
y: Queue[int]

p: Future # E: Missing type parameters for generic type "Future" \
# N: Subscripting classes that are not generic at runtime may require escaping, see https://mypy.readthedocs.io/en/stable/runtime_troubles.html#not-generic-runtime
q: Queue # E: Missing type parameters for generic type "Queue" \
# N: Subscripting classes that are not generic at runtime may require escaping, see https://mypy.readthedocs.io/en/stable/runtime_troubles.html#not-generic-runtime
p: Future # E: Missing type parameters for generic type "Future"
q: Queue # E: Missing type parameters for generic type "Queue"
[file asyncio/__init__.pyi]
from asyncio.futures import Future as Future
[file asyncio/futures.pyi]
Expand All @@ -1524,28 +1522,28 @@ class Queue(Generic[_T]): ...
[builtins fixtures/async_await.pyi]
[typing fixtures/typing-full.pyi]

[case testDisallowAnyGenericsBuiltinTuplePre39]
# flags: --disallow-any-generics --python-version 3.8
[case testDisallowAnyGenericsBuiltinTuple]
# flags: --disallow-any-generics
s = tuple([1, 2, 3])
def f(t: tuple) -> None: pass # E: Implicit generic "Any". Use "typing.Tuple" and specify generic parameters
def f(t: tuple) -> None: pass # E: Missing type parameters for generic type "tuple"
[builtins fixtures/tuple.pyi]

[case testDisallowAnyGenericsBuiltinListPre39]
# flags: --disallow-any-generics --python-version 3.8
[case testDisallowAnyGenericsBuiltinList]
# flags: --disallow-any-generics
l = list([1, 2, 3])
def f(t: list) -> None: pass # E: Implicit generic "Any". Use "typing.List" and specify generic parameters
def f(t: list) -> None: pass # E: Missing type parameters for generic type "List"
[builtins fixtures/list.pyi]

[case testDisallowAnyGenericsBuiltinSetPre39]
# flags: --disallow-any-generics --python-version 3.8
[case testDisallowAnyGenericsBuiltinSet]
# flags: --disallow-any-generics
l = set({1, 2, 3})
def f(s: set) -> None: pass # E: Implicit generic "Any". Use "typing.Set" and specify generic parameters
def f(s: set) -> None: pass # E: Missing type parameters for generic type "Set"
[builtins fixtures/set.pyi]

[case testDisallowAnyGenericsBuiltinDictPre39]
# flags: --disallow-any-generics --python-version 3.8
[case testDisallowAnyGenericsBuiltinDict]
# flags: --disallow-any-generics
l = dict([('a', 1)])
def f(d: dict) -> None: pass # E: Implicit generic "Any". Use "typing.Dict" and specify generic parameters
def f(d: dict) -> None: pass # E: Missing type parameters for generic type "Dict"
[builtins fixtures/dict.pyi]

[case testCheckDefaultAllowAnyGeneric]
Expand Down
8 changes: 0 additions & 8 deletions test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -1827,7 +1827,6 @@ def Arg(x, y): pass
F = Callable[[Arg(int, 'x')], int] # E: Invalid argument constructor "__main__.Arg"

[case testCallableParsingFromExpr]
# flags: --python-version 3.9
from typing import Callable, List
from mypy_extensions import Arg, VarArg, KwArg
import mypy_extensions
Expand Down Expand Up @@ -1858,13 +1857,6 @@ Q = Callable[[Arg(int, type=int)], int] # E: Invalid type alias: expression is
R = Callable[[Arg(int, 'x', name='y')], int] # E: Invalid type alias: expression is not a valid type \
# E: Value of type "int" is not indexable \
# E: "Arg" gets multiple values for keyword argument "name"







[builtins fixtures/dict.pyi]

[case testCallableParsing]
Expand Down
Loading