Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into 3.8-collections-i…
Browse files Browse the repository at this point in the history
…mports
  • Loading branch information
cdce8p committed Dec 29, 2024
2 parents c04fb4d + b9056f9 commit 0d0b2ed
Show file tree
Hide file tree
Showing 35 changed files with 887 additions and 616 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/mypy_primer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ jobs:
name: Save PR number
run: |
echo ${{ github.event.pull_request.number }} | tee pr_number.txt
- if: ${{ matrix.shard-index == 0 }}
name: Upload mypy_primer diff + PR number
- name: Upload mypy_primer diff + PR number
uses: actions/upload-artifact@v4
if: ${{ matrix.shard-index == 0 }}
with:
name: mypy_primer_diffs-${{ matrix.shard-index }}
path: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/mypy_primer_comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const MAX_CHARACTERS = 30000
const MAX_CHARACTERS = 50000
const MAX_CHARACTERS_PER_PROJECT = MAX_CHARACTERS / 3
const fs = require('fs')
Expand Down
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
exclude: '^(mypyc/external/)|(mypy/typeshed/)|misc/typeshed_patches' # Exclude all vendored code from lints
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.8.0
rev: 24.10.0
hooks:
- id: black
exclude: '^(test-data/)'
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
rev: v0.8.4
hooks:
- id: ruff
args: [--exit-non-zero-on-fix]
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.29.4
rev: 0.30.0
hooks:
- id: check-dependabot
- id: check-github-workflows
- repo: https://github.com/rhysd/actionlint
rev: v1.7.3
rev: v1.7.4
hooks:
- id: actionlint
args: [
Expand Down
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,25 @@

## Next release

...
### `--strict-bytes`

By default, mypy treats an annotation of ``bytes`` as permitting ``bytearray`` and ``memoryview``.
[PEP 688](https://peps.python.org/pep-0688) specified the removal of this special case.
Use this flag to disable this behavior. `--strict-bytes` will be enabled by default in **mypy 2.0**.

Contributed by Ali Hamdan (PR [18137](https://github.com/python/mypy/pull/18263/)) and
Shantanu Jain (PR [13952](https://github.com/python/mypy/pull/13952)).

### Improvements to partial type handling in loops

This change results in mypy better modelling control flow within loops and hence detecting several
issues it previously did not detect. In some cases, this change may require use of an additional
explicit annotation of a variable.

Contributed by Christoph Tyralla (PR [18180](https://github.com/python/mypy/pull/18180)).

(Speaking of partial types, another reminder that mypy plans on enabling `--local-partial-types`
by default in **mypy 2.0**).

## Mypy 1.14

Expand Down
7 changes: 4 additions & 3 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -5346,9 +5346,10 @@ def visit_lambda_expr(self, e: LambdaExpr) -> Type:
self.chk.return_types.append(AnyType(TypeOfAny.special_form))
# Type check everything in the body except for the final return
# statement (it can contain tuple unpacking before return).
with self.chk.binder.frame_context(
can_skip=True, fall_through=0
), self.chk.scope.push_function(e):
with (
self.chk.binder.frame_context(can_skip=True, fall_through=0),
self.chk.scope.push_function(e),
):
# Lambdas can have more than one element in body,
# when we add "fictional" AssignmentStatement nodes, like in:
# `lambda (a, b): a`
Expand Down
41 changes: 4 additions & 37 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import annotations

import copy
import re
import sys
import warnings
Expand Down Expand Up @@ -242,13 +241,6 @@ def parse(
path=fnam,
).visit(ast)
except SyntaxError as e:
# alias to please mypyc
is_py38_or_earlier = sys.version_info < (3, 9)
if is_py38_or_earlier and e.filename == "<fstring>":
# In Python 3.8 and earlier, syntax errors in f-strings have lineno relative to the
# start of the f-string. This would be misleading, as mypy will report the error as the
# lineno within the file.
e.lineno = None
message = e.msg
if feature_version > sys.version_info.minor and message.startswith("invalid syntax"):
python_version_str = f"{options.python_version[0]}.{options.python_version[1]}"
Expand Down Expand Up @@ -2070,40 +2062,15 @@ def visit_Index(self, n: ast3.Index) -> Type:
def visit_Slice(self, n: ast3.Slice) -> Type:
return self.invalid_type(n, note="did you mean to use ',' instead of ':' ?")

# Subscript(expr value, slice slice, expr_context ctx) # Python 3.8 and before
# Subscript(expr value, expr slice, expr_context ctx) # Python 3.9 and later
def visit_Subscript(self, n: ast3.Subscript) -> Type:
if sys.version_info >= (3, 9): # Really 3.9a5 or later
sliceval: Any = n.slice
# Python 3.8 or earlier use a different AST structure for subscripts
elif isinstance(n.slice, ast3.Index):
sliceval: Any = n.slice.value
elif isinstance(n.slice, ast3.Slice):
sliceval = copy.deepcopy(n.slice) # so we don't mutate passed AST
if getattr(sliceval, "col_offset", None) is None:
# Fix column information so that we get Python 3.9+ message order
sliceval.col_offset = sliceval.lower.col_offset
else:
assert isinstance(n.slice, ast3.ExtSlice)
dims = cast(List[ast3.expr], copy.deepcopy(n.slice.dims))
for s in dims:
# These fields don't actually have a col_offset attribute but we add
# it manually.
if getattr(s, "col_offset", None) is None:
if isinstance(s, ast3.Index):
s.col_offset = s.value.col_offset
elif isinstance(s, ast3.Slice):
assert s.lower is not None
s.col_offset = s.lower.col_offset
sliceval = ast3.Tuple(dims, n.ctx)

empty_tuple_index = False
if isinstance(sliceval, ast3.Tuple):
params = self.translate_expr_list(sliceval.elts)
if len(sliceval.elts) == 0:
if isinstance(n.slice, ast3.Tuple):
params = self.translate_expr_list(n.slice.elts)
if len(n.slice.elts) == 0:
empty_tuple_index = True
else:
params = [self.visit(sliceval)]
params = [self.visit(n.slice)]

value = self.visit(n.value)
if isinstance(value, UnboundType) and not value.args:
Expand Down
13 changes: 9 additions & 4 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from collections.abc import Sequence
from gettext import gettext
from io import TextIOWrapper
from typing import IO, Any, Final, NoReturn, TextIO
from typing import IO, Any, Final, NoReturn, Protocol, TextIO

from mypy import build, defaults, state, util
from mypy.config_parser import (
Expand All @@ -36,6 +36,11 @@
from mypy.split_namespace import SplitNamespace
from mypy.version import __version__


class _SupportsWrite(Protocol):
def write(self, s: str, /) -> object: ...


orig_stat: Final = os.stat
MEM_PROFILE: Final = False # If True, dump memory profile

Expand Down Expand Up @@ -373,17 +378,17 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
# =====================
# Help-printing methods
# =====================
def print_usage(self, file: IO[str] | None = None) -> None:
def print_usage(self, file: _SupportsWrite | None = None) -> None:
if file is None:
file = self.stdout
self._print_message(self.format_usage(), file)

def print_help(self, file: IO[str] | None = None) -> None:
def print_help(self, file: _SupportsWrite | None = None) -> None:
if file is None:
file = self.stdout
self._print_message(self.format_help(), file)

def _print_message(self, message: str, file: IO[str] | None = None) -> None:
def _print_message(self, message: str, file: _SupportsWrite | None = None) -> None:
if message:
if file is None:
file = self.stderr
Expand Down
2 changes: 1 addition & 1 deletion mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -2701,7 +2701,7 @@ def format_literal_value(typ: LiteralType) -> str:
if func.is_type_obj():
# The type of a type object type can be derived from the
# return type (this always works).
return format(TypeType.make_normalized(erase_type(func.items[0].ret_type)))
return format(TypeType.make_normalized(func.items[0].ret_type))
elif isinstance(func, CallableType):
if func.type_guard is not None:
return_type = f"TypeGuard[{format(func.type_guard)}]"
Expand Down
32 changes: 16 additions & 16 deletions mypy/mixedtraverser.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ def __init__(self) -> None:

# Symbol nodes

def visit_var(self, var: Var) -> None:
def visit_var(self, var: Var, /) -> None:
self.visit_optional_type(var.type)

def visit_func(self, o: FuncItem) -> None:
def visit_func(self, o: FuncItem, /) -> None:
super().visit_func(o)
self.visit_optional_type(o.type)

def visit_class_def(self, o: ClassDef) -> None:
def visit_class_def(self, o: ClassDef, /) -> None:
# TODO: Should we visit generated methods/variables as well, either here or in
# TraverserVisitor?
super().visit_class_def(o)
Expand All @@ -46,67 +46,67 @@ def visit_class_def(self, o: ClassDef) -> None:
for base in info.bases:
base.accept(self)

def visit_type_alias_expr(self, o: TypeAliasExpr) -> None:
def visit_type_alias_expr(self, o: TypeAliasExpr, /) -> None:
super().visit_type_alias_expr(o)
self.in_type_alias_expr = True
o.node.target.accept(self)
self.in_type_alias_expr = False

def visit_type_var_expr(self, o: TypeVarExpr) -> None:
def visit_type_var_expr(self, o: TypeVarExpr, /) -> None:
super().visit_type_var_expr(o)
o.upper_bound.accept(self)
for value in o.values:
value.accept(self)

def visit_typeddict_expr(self, o: TypedDictExpr) -> None:
def visit_typeddict_expr(self, o: TypedDictExpr, /) -> None:
super().visit_typeddict_expr(o)
self.visit_optional_type(o.info.typeddict_type)

def visit_namedtuple_expr(self, o: NamedTupleExpr) -> None:
def visit_namedtuple_expr(self, o: NamedTupleExpr, /) -> None:
super().visit_namedtuple_expr(o)
assert o.info.tuple_type
o.info.tuple_type.accept(self)

def visit__promote_expr(self, o: PromoteExpr) -> None:
def visit__promote_expr(self, o: PromoteExpr, /) -> None:
super().visit__promote_expr(o)
o.type.accept(self)

def visit_newtype_expr(self, o: NewTypeExpr) -> None:
def visit_newtype_expr(self, o: NewTypeExpr, /) -> None:
super().visit_newtype_expr(o)
self.visit_optional_type(o.old_type)

# Statements

def visit_assignment_stmt(self, o: AssignmentStmt) -> None:
def visit_assignment_stmt(self, o: AssignmentStmt, /) -> None:
super().visit_assignment_stmt(o)
self.visit_optional_type(o.type)

def visit_for_stmt(self, o: ForStmt) -> None:
def visit_for_stmt(self, o: ForStmt, /) -> None:
super().visit_for_stmt(o)
self.visit_optional_type(o.index_type)

def visit_with_stmt(self, o: WithStmt) -> None:
def visit_with_stmt(self, o: WithStmt, /) -> None:
super().visit_with_stmt(o)
for typ in o.analyzed_types:
typ.accept(self)

# Expressions

def visit_cast_expr(self, o: CastExpr) -> None:
def visit_cast_expr(self, o: CastExpr, /) -> None:
super().visit_cast_expr(o)
o.type.accept(self)

def visit_assert_type_expr(self, o: AssertTypeExpr) -> None:
def visit_assert_type_expr(self, o: AssertTypeExpr, /) -> None:
super().visit_assert_type_expr(o)
o.type.accept(self)

def visit_type_application(self, o: TypeApplication) -> None:
def visit_type_application(self, o: TypeApplication, /) -> None:
super().visit_type_application(o)
for t in o.types:
t.accept(self)

# Helpers

def visit_optional_type(self, t: Type | None) -> None:
def visit_optional_type(self, t: Type | None, /) -> None:
if t:
t.accept(self)
2 changes: 1 addition & 1 deletion mypy/modulefinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,7 @@ def default_lib_path(
return path


@functools.lru_cache(maxsize=None)
@functools.cache
def get_search_dirs(python_executable: str | None) -> tuple[list[str], list[str]]:
"""Find package directories for given python. Guaranteed to return absolute paths.
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.8+ versions. Since we cannot make any
This file MUST remain compatible with all Python 3.9+ 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.8. This file is run each mypy run, so it should be kept
of the standard library found in Python 3.9. This file is run each mypy run, so it should be kept
as fast as possible.
"""
import sys
Expand Down
Loading

0 comments on commit 0d0b2ed

Please sign in to comment.