Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: python/mypy
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: python/mypy
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: release-1.6
Choose a head ref
Can’t automatically merge. Don’t worry, you can still create the pull request.
  • 12 commits
  • 21 files changed
  • 5 contributors

Commits on Sep 2, 2023

  1. Fix ParamSpec inference for callback protocols (#15986)

    Fixes #15984
    
    Fix is straightforward, `ParamSpec` inference special-casing should put
    instances with `__call__` and callable types on same ground.
    ilevkivskyi authored and koogoro committed Sep 2, 2023
    Copy the full SHA
    807bd39 View commit details
  2. Try upgrading tox (#15992)

    Fixes #15990
    hauntsaninja authored and koogoro committed Sep 2, 2023
    Copy the full SHA
    b33373c View commit details

Commits on Oct 10, 2023

  1. Clear cache when adding --new-type-inference (#16059)

    Add `new_type_inference` to the list of options affecting the cache.
    cdce8p authored and JukkaL committed Oct 10, 2023
    Copy the full SHA
    00cba13 View commit details
  2. Fix mypyc regression with pretty (#16124)

    Fixes #15877
    
    Regression was introduced by #15070. Previously Errors objects created
    in mypyc build would just use all the default values, now they use the
    actual options object involved
    hauntsaninja authored and JukkaL committed Oct 10, 2023
    Copy the full SHA
    d376633 View commit details
  3. Match note error codes to import error codes (#16004)

    Fixes #16003. Follow up to #14740
    hauntsaninja authored and JukkaL committed Oct 10, 2023
    Copy the full SHA
    e78df55 View commit details
  4. Make PEP 695 constructs give a reasonable error message (#16013)

    Mypy does not yet support PEP 695
    
    Fixes #16011, linking #15238
    hauntsaninja authored and JukkaL committed Oct 10, 2023
    Copy the full SHA
    4c963c9 View commit details
  5. Optimize Unpack for failures (#15967)

    This is a small but possibly important PR. Wherever possible we should
    represent user error and/or failed type inference as `*tuple[Any,
    ...]`/`*tuple[<nothing>, ...]`, rather than
    `Unpack[Any]`/`Unpack[<nothing>]` or plain `Any`/`<nothing>`. This way
    we will not need any special casing for failure conditions in various
    places without risking a crash instead of a graceful failure (error
    message).
    ilevkivskyi authored and JukkaL committed Oct 10, 2023
    Copy the full SHA
    5b488ab View commit details
  6. Remove +dev from version

    JukkaL committed Oct 10, 2023
    Copy the full SHA
    ac2d56f View commit details

Commits on Oct 17, 2023

  1. Add +dev to version

    hauntsaninja committed Oct 17, 2023
    Copy the full SHA
    45f7a12 View commit details
  2. Fix crash on ParamSpec unification (#16251)

    Fixes #16245
    Fixes #16248
    
    Unfortunately I was a bit reckless with parentheses, but in my defense
    `unify_generic_callable()` is kind of broken for long time, as it can
    return "solutions" like ```{1: T`1}```. We need a more principled
    approach there (IIRC there is already an issue about this in the scope
    of `--new-type-inference`).
    
    (The fix is quite trivial so I am not going to wait for review too long
    to save time, unless there will be some issues in `mypy_primer` etc.)
    ilevkivskyi authored and hauntsaninja committed Oct 17, 2023
    Copy the full SHA
    eb81e63 View commit details
  3. Fix crash on ParamSpec unification (for real) (#16259)

    Fixes #16257
    
    Parenthesis strike back. I hope this is the last place where I had put
    them wrong.
    ilevkivskyi authored and hauntsaninja committed Oct 17, 2023
    Copy the full SHA
    6b6504e View commit details

Commits on Oct 18, 2023

  1. Remove +dev from version

    hauntsaninja committed Oct 18, 2023
    Copy the full SHA
    9b891fe View commit details
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ jobs:
with:
python-version: '3.8'
- name: Install tox
run: pip install --upgrade 'setuptools!=50' tox==4.4.4
run: pip install --upgrade 'setuptools!=50' tox==4.11.0
- name: Setup tox environment
run: tox run -e ${{ env.TOXENV }} --notest
- name: Test
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -130,7 +130,7 @@ jobs:
./misc/build-debug-python.sh $PYTHONVERSION $PYTHONDIR $VENV
source $VENV/bin/activate
- name: Install tox
run: pip install --upgrade 'setuptools!=50' tox==4.4.4
run: pip install --upgrade 'setuptools!=50' tox==4.11.0
- name: Compiled with mypyc
if: ${{ matrix.test_mypyc }}
run: |
@@ -182,7 +182,7 @@ jobs:
default: 3.11.1
command: python -c "import platform; print(f'{platform.architecture()=} {platform.machine()=}');"
- name: Install tox
run: pip install --upgrade 'setuptools!=50' tox==4.4.4
run: pip install --upgrade 'setuptools!=50' tox==4.11.0
- name: Setup tox environment
run: tox run -e py --notest
- name: Test
2 changes: 1 addition & 1 deletion mypy/build.py
Original file line number Diff line number Diff line change
@@ -2798,7 +2798,7 @@ def module_not_found(
for note in notes:
if "{stub_dist}" in note:
note = note.format(stub_dist=stub_distribution_name(module))
errors.report(line, 0, note, severity="note", only_once=True, code=codes.IMPORT)
errors.report(line, 0, note, severity="note", only_once=True, code=code)
if reason is ModuleNotFoundReason.APPROVED_STUBS_NOT_INSTALLED:
manager.missing_stub_packages.add(stub_distribution_name(module))
errors.set_import_context(save_import_context)
4 changes: 4 additions & 0 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
@@ -2185,6 +2185,10 @@ def get_arg_infer_passes(
# run(test, 1, 2)
# we will use `test` for inference, since it will allow to infer also
# argument *names* for P <: [x: int, y: int].
if isinstance(p_actual, Instance):
call_method = find_member("__call__", p_actual, p_actual, is_operator=True)
if call_method is not None:
p_actual = get_proper_type(call_method)
if (
isinstance(p_actual, CallableType)
and not p_actual.variables
5 changes: 2 additions & 3 deletions mypy/errors.py
Original file line number Diff line number Diff line change
@@ -469,7 +469,7 @@ def _add_error_info(self, file: str, info: ErrorInfo) -> None:
self.error_info_map[file].append(info)
if info.blocker:
self.has_blockers.add(file)
if info.code is IMPORT:
if info.code in (IMPORT, IMPORT_UNTYPED, IMPORT_NOT_FOUND):
self.seen_import_error = True

def _filter_error(self, file: str, info: ErrorInfo) -> bool:
@@ -909,8 +909,7 @@ def file_messages(self, path: str) -> list[str]:
return []
self.flushed_files.add(path)
source_lines = None
if self.options.pretty:
assert self.read_source
if self.options.pretty and self.read_source:
source_lines = self.read_source(path)
return self.format_messages(self.error_info_map[path], source_lines)

36 changes: 14 additions & 22 deletions mypy/expandtype.py
Original file line number Diff line number Diff line change
@@ -236,15 +236,15 @@ def visit_param_spec(self, t: ParamSpecType) -> Type:
return repl.copy_modified(
flavor=t.flavor,
prefix=t.prefix.copy_modified(
arg_types=self.expand_types(t.prefix.arg_types + repl.prefix.arg_types),
arg_types=self.expand_types(t.prefix.arg_types) + repl.prefix.arg_types,
arg_kinds=t.prefix.arg_kinds + repl.prefix.arg_kinds,
arg_names=t.prefix.arg_names + repl.prefix.arg_names,
),
)
elif isinstance(repl, Parameters):
assert t.flavor == ParamSpecFlavor.BARE
return Parameters(
self.expand_types(t.prefix.arg_types + repl.arg_types),
self.expand_types(t.prefix.arg_types) + repl.arg_types,
t.prefix.arg_kinds + repl.arg_kinds,
t.prefix.arg_names + repl.arg_names,
variables=[*t.prefix.variables, *repl.variables],
@@ -273,7 +273,7 @@ def visit_unpack_type(self, t: UnpackType) -> Type:
# example is non-normalized types when called from semanal.py.
return UnpackType(t.type.accept(self))

def expand_unpack(self, t: UnpackType) -> list[Type] | AnyType | UninhabitedType:
def expand_unpack(self, t: UnpackType) -> list[Type]:
assert isinstance(t.type, TypeVarTupleType)
repl = get_proper_type(self.variables.get(t.type.id, t.type))
if isinstance(repl, TupleType):
@@ -285,9 +285,9 @@ def expand_unpack(self, t: UnpackType) -> list[Type] | AnyType | UninhabitedType
):
return [UnpackType(typ=repl)]
elif isinstance(repl, (AnyType, UninhabitedType)):
# tuple[Any, ...] for Any would be better, but we don't have
# the type info to construct that type here.
return repl
# Replace *Ts = Any with *Ts = *tuple[Any, ...] and some for <nothing>.
# These types may appear here as a result of user error or failed inference.
return [UnpackType(t.type.tuple_fallback.copy_modified(args=[repl]))]
else:
raise RuntimeError(f"Invalid type replacement to expand: {repl}")

@@ -310,12 +310,7 @@ def interpolate_args_for_unpack(self, t: CallableType, var_arg: UnpackType) -> l
# We have plain Unpack[Ts]
assert isinstance(var_arg_type, TypeVarTupleType)
fallback = var_arg_type.tuple_fallback
expanded_items_res = self.expand_unpack(var_arg)
if isinstance(expanded_items_res, list):
expanded_items = expanded_items_res
else:
# We got Any or <nothing>
return prefix + [expanded_items_res] + suffix
expanded_items = self.expand_unpack(var_arg)
new_unpack = UnpackType(TupleType(expanded_items, fallback))
return prefix + [new_unpack] + suffix

@@ -332,20 +327,23 @@ def visit_callable_type(self, t: CallableType) -> CallableType:
# the replacement is ignored.
if isinstance(repl, Parameters):
# We need to expand both the types in the prefix and the ParamSpec itself
t = t.expand_param_spec(repl)
return t.copy_modified(
arg_types=self.expand_types(t.arg_types),
arg_types=self.expand_types(t.arg_types[:-2]) + repl.arg_types,
arg_kinds=t.arg_kinds[:-2] + repl.arg_kinds,
arg_names=t.arg_names[:-2] + repl.arg_names,
ret_type=t.ret_type.accept(self),
type_guard=(t.type_guard.accept(self) if t.type_guard is not None else None),
imprecise_arg_kinds=(t.imprecise_arg_kinds or repl.imprecise_arg_kinds),
variables=[*repl.variables, *t.variables],
)
elif isinstance(repl, ParamSpecType):
# We're substituting one ParamSpec for another; this can mean that the prefix
# changes, e.g. substitute Concatenate[int, P] in place of Q.
prefix = repl.prefix
clean_repl = repl.copy_modified(prefix=Parameters([], [], []))
return t.copy_modified(
arg_types=self.expand_types(t.arg_types[:-2] + prefix.arg_types)
arg_types=self.expand_types(t.arg_types[:-2])
+ prefix.arg_types
+ [
clean_repl.with_flavor(ParamSpecFlavor.ARGS),
clean_repl.with_flavor(ParamSpecFlavor.KWARGS),
@@ -394,14 +392,8 @@ def expand_types_with_unpack(
items: list[Type] = []
for item in typs:
if isinstance(item, UnpackType) and isinstance(item.type, TypeVarTupleType):
unpacked_items = self.expand_unpack(item)
if isinstance(unpacked_items, (AnyType, UninhabitedType)):
# TODO: better error for <nothing>, something like tuple of unknown?
return unpacked_items
else:
items.extend(unpacked_items)
items.extend(self.expand_unpack(item))
else:
# Must preserve original aliases when possible.
items.append(item.accept(self))
return items

31 changes: 31 additions & 0 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
@@ -144,6 +144,11 @@ def ast3_parse(
NamedExpr = ast3.NamedExpr
Constant = ast3.Constant

if sys.version_info >= (3, 12):
ast_TypeAlias = ast3.TypeAlias
else:
ast_TypeAlias = Any

if sys.version_info >= (3, 10):
Match = ast3.Match
MatchValue = ast3.MatchValue
@@ -936,6 +941,14 @@ def do_func_def(
arg_types = [AnyType(TypeOfAny.from_error)] * len(args)
return_type = AnyType(TypeOfAny.from_error)
else:
if sys.version_info >= (3, 12) and n.type_params:
self.fail(
ErrorMessage("PEP 695 generics are not yet supported", code=codes.VALID_TYPE),
n.type_params[0].lineno,
n.type_params[0].col_offset,
blocker=False,
)

arg_types = [a.type_annotation for a in args]
return_type = TypeConverter(
self.errors, line=n.returns.lineno if n.returns else lineno
@@ -1110,6 +1123,14 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef:
self.class_and_function_stack.append("C")
keywords = [(kw.arg, self.visit(kw.value)) for kw in n.keywords if kw.arg]

if sys.version_info >= (3, 12) and n.type_params:
self.fail(
ErrorMessage("PEP 695 generics are not yet supported", code=codes.VALID_TYPE),
n.type_params[0].lineno,
n.type_params[0].col_offset,
blocker=False,
)

cdef = ClassDef(
n.name,
self.as_required_block(n.body),
@@ -1717,6 +1738,16 @@ def visit_MatchOr(self, n: MatchOr) -> OrPattern:
node = OrPattern([self.visit(pattern) for pattern in n.patterns])
return self.set_line(node, n)

def visit_TypeAlias(self, n: ast_TypeAlias) -> AssignmentStmt:
self.fail(
ErrorMessage("PEP 695 type aliases are not yet supported", code=codes.VALID_TYPE),
n.lineno,
n.col_offset,
blocker=False,
)
node = AssignmentStmt([NameExpr(n.name.id)], self.visit(n.value))
return self.set_line(node, n)


class TypeConverter:
def __init__(
1 change: 1 addition & 0 deletions mypy/options.py
Original file line number Diff line number Diff line change
@@ -62,6 +62,7 @@ class BuildType:
| {
"platform",
"bazel",
"new_type_inference",
"plugins",
"disable_bytearray_promotion",
"disable_memoryview_promotion",
2 changes: 1 addition & 1 deletion mypy/report.py
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@
from mypy.version import __version__

try:
from lxml import etree # type: ignore[import]
from lxml import etree # type: ignore[import-untyped]

LXML_INSTALLED = True
except ImportError:
2 changes: 2 additions & 0 deletions mypy/semanal_main.py
Original file line number Diff line number Diff line change
@@ -381,6 +381,7 @@ def check_type_arguments(graph: Graph, scc: list[str], errors: Errors) -> None:
errors,
state.options,
is_typeshed_file(state.options.abs_custom_typeshed_dir, state.path or ""),
state.manager.semantic_analyzer.named_type,
)
with state.wrap_context():
with mypy.state.state.strict_optional_set(state.options.strict_optional):
@@ -399,6 +400,7 @@ def check_type_arguments_in_targets(
errors,
state.options,
is_typeshed_file(state.options.abs_custom_typeshed_dir, state.path or ""),
state.manager.semantic_analyzer.named_type,
)
with state.wrap_context():
with mypy.state.state.strict_optional_set(state.options.strict_optional):
21 changes: 14 additions & 7 deletions mypy/semanal_typeargs.py
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

from __future__ import annotations

from typing import Sequence
from typing import Callable, Sequence

from mypy import errorcodes as codes, message_registry
from mypy.errorcodes import ErrorCode
@@ -42,11 +42,18 @@


class TypeArgumentAnalyzer(MixedTraverserVisitor):
def __init__(self, errors: Errors, options: Options, is_typeshed_file: bool) -> None:
def __init__(
self,
errors: Errors,
options: Options,
is_typeshed_file: bool,
named_type: Callable[[str, list[Type]], Instance],
) -> None:
super().__init__()
self.errors = errors
self.options = options
self.is_typeshed_file = is_typeshed_file
self.named_type = named_type
self.scope = Scope()
# Should we also analyze function definitions, or only module top-levels?
self.recurse_into_functions = True
@@ -243,16 +250,16 @@ def visit_unpack_type(self, typ: UnpackType) -> None:
return
if isinstance(proper_type, TypeVarTupleType):
return
# TODO: this should probably be .has_base("builtins.tuple"), also elsewhere.
if isinstance(proper_type, Instance) and proper_type.type.fullname == "builtins.tuple":
return
if isinstance(proper_type, AnyType) and proper_type.type_of_any == TypeOfAny.from_error:
return
if not isinstance(proper_type, UnboundType):
# Avoid extra errors if there were some errors already.
if not isinstance(proper_type, (UnboundType, AnyType)):
# Avoid extra errors if there were some errors already. Also interpret plain Any
# as tuple[Any, ...] (this is better for the code in type checker).
self.fail(
message_registry.INVALID_UNPACK.format(format_type(proper_type, self.options)), typ
)
typ.type = AnyType(TypeOfAny.from_error)
typ.type = self.named_type("builtins.tuple", [AnyType(TypeOfAny.from_error)])

def check_type_var_values(
self, name: str, actuals: list[Type], arg_name: str, valids: list[Type], context: Context
4 changes: 3 additions & 1 deletion mypy/test/helpers.py
Original file line number Diff line number Diff line change
@@ -241,7 +241,9 @@ def num_skipped_suffix_lines(a1: list[str], a2: list[str]) -> int:


def testfile_pyversion(path: str) -> tuple[int, int]:
if path.endswith("python311.test"):
if path.endswith("python312.test"):
return 3, 12
elif path.endswith("python311.test"):
return 3, 11
elif path.endswith("python310.test"):
return 3, 10
4 changes: 3 additions & 1 deletion mypy/test/testcheck.py
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@
from mypy.test.update_data import update_testcase_output

try:
import lxml # type: ignore[import]
import lxml # type: ignore[import-untyped]
except ImportError:
lxml = None

@@ -43,6 +43,8 @@
typecheck_files.remove("check-python310.test")
if sys.version_info < (3, 11):
typecheck_files.remove("check-python311.test")
if sys.version_info < (3, 12):
typecheck_files.remove("check-python312.test")

# Special tests for platforms with case-insensitive filesystems.
if sys.platform not in ("darwin", "win32"):
2 changes: 1 addition & 1 deletion mypy/test/testcmdline.py
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@
)

try:
import lxml # type: ignore[import]
import lxml # type: ignore[import-untyped]
except ImportError:
lxml = None

4 changes: 2 additions & 2 deletions mypy/test/testreports.py
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
from mypy.test.helpers import Suite, assert_equal

try:
import lxml # type: ignore[import]
import lxml # type: ignore[import-untyped]
except ImportError:
lxml = None

@@ -22,7 +22,7 @@ def test_get_line_rate(self) -> None:

@pytest.mark.skipif(lxml is None, reason="Cannot import lxml. Is it installed?")
def test_as_xml(self) -> None:
import lxml.etree as etree # type: ignore[import]
import lxml.etree as etree # type: ignore[import-untyped]

cobertura_package = CoberturaPackage("foobar")
cobertura_package.covered_lines = 21
10 changes: 0 additions & 10 deletions mypy/types.py
Original file line number Diff line number Diff line change
@@ -2068,16 +2068,6 @@ def param_spec(self) -> ParamSpecType | None:
prefix = Parameters(self.arg_types[:-2], self.arg_kinds[:-2], self.arg_names[:-2])
return arg_type.copy_modified(flavor=ParamSpecFlavor.BARE, prefix=prefix)

def expand_param_spec(self, c: Parameters) -> CallableType:
variables = c.variables
return self.copy_modified(
arg_types=self.arg_types[:-2] + c.arg_types,
arg_kinds=self.arg_kinds[:-2] + c.arg_kinds,
arg_names=self.arg_names[:-2] + c.arg_names,
is_ellipsis_args=c.is_ellipsis_args,
variables=[*variables, *self.variables],
)

def with_unpacked_kwargs(self) -> NormalizedCallableType:
if not self.unpack_kwargs:
return cast(NormalizedCallableType, self)
2 changes: 1 addition & 1 deletion mypy/version.py
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
# - Release versions have the form "1.2.3".
# - Dev versions have the form "1.2.3+dev" (PLUS sign to conform to PEP 440).
# - Before 1.0 we had the form "0.NNN".
__version__ = "1.6.0+dev"
__version__ = "1.6.1"
base_version = __version__

mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
Loading