Skip to content
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

Subtyping and inference of user defined variadic types #16076

Merged
merged 14 commits into from
Sep 13, 2023

Conversation

ilevkivskyi
Copy link
Member

@ilevkivskyi ilevkivskyi commented Sep 9, 2023

The second part of support for user defined variadic types comes as a single PR, it was hard to split into smaller parts. This part covers subtyping and inference (and relies on the first part: type analysis, normalization, and expansion, concluded by #15991). Note btw that the third (and last) part that covers actually using all the stuff in checkexpr.py will likely come as several smaller PRs.

Some comments on this PR:

  • First good news: it looks like instances subtyping/inference can be handled in a really simple way, we just need to find correct type arguments mapping for each type variable, and perform procedures argument by argument (note this heavily relies on the normalization). Also callable subtyping inference for variadic items effectively defers to corresponding tuple types. This way all code paths will ultimately go through variadic tuple subtyping/inference (there is still a bunch of boilerplate to do the mapping, but it is quite simple).
  • Second some bad news: a lot of edge cases involving *tuple[X, ...] were missing everywhere (even couple cases in the code I touched before). I added all that were either simple or important. We can handle more if users will ask, since it is quite tricky.
  • Note that I handle variadic tuples essentially as infinite unions, the core of the logic for this (and for most of this PR FWIW) is in variadic_tuple_subtype().
  • Previously Foo[*tuple[int, ...]] was considered a subtype of Foo[int, int]. I think this is wrong. I didn't find where this is required in the PEP (see one case below however), and mypy currently considers tuple[int, ...] not a subtype of tuple[int, int] (vice versa are subtypes), and similarly (*args: int) vs (x: int, y: int) for callables. Because of the logic I described in the first comment, the same logic now uniformly applies to instances as well.
  • Note however the PEP requires special casing of Foo[*tuple[Any, ...]] (equivalent to bare Foo), and I agree we should do this. I added a minimal special case for this. Note we also do this for callables as well (*args: Any is very different from *args: object). And I think we should special case tuple[Any, ...] <: tuple[int, int] as well. In the future we can even extend the special casing to tuple[int, *tuple[Any, ...], int] in the spirit of Lenient handling of trivial Callable suffixes #15913
  • In this PR I specifically only handle the PEP required item from above for instances. For plain tuples I left a TODO, @hauntsaninja may implement it since it is needed for other unrelated PR.
  • I make the default upper bound for TypeVarTupleType to be tuple[object, ...]. I think it can never be object (and this simplifies some subtyping corner cases).
  • TBH I didn't look into callables subtyping/inference very deeply (unlike instances and tuples), if needed we can improve their handling later.
  • Note I remove some failing unit tests because they test non-nomralized forms that should never appear now. I left TODOs to consider what to do this the remaining unit tests. We should probably add some more unit tests, but TBH I am quite tired now.

@ilevkivskyi
Copy link
Member Author

Oh btw @mehdigmira could you please test this PR on your code?

@github-actions

This comment has been minimized.

@mehdigmira
Copy link

@ilevkivskyi still seems to fail with this code

from __future__ import annotations

from typing import Generic, Tuple

from typing_extensions import TypeVarTuple, Unpack

Ts = TypeVarTuple("Ts")


class Array(Generic[Unpack[Ts]]):
    def _close(self) -> None:
        ...

    def close(self) -> None:
        self._close()

This generates an eroor error: Invalid self argument "Array[Unpack[Ts]]" to attribute function "_close" with type "Callable[[Array[Unpack[Ts]]], None]" [misc]

mypy version is mypy 1.7.0+dev.38cd66c1f4662aba609bcd49a4c3f9f4707f4ce1

@github-actions

This comment has been minimized.

@ilevkivskyi
Copy link
Member Author

@mehdigmira Could you please try again?

@github-actions

This comment has been minimized.

@mehdigmira
Copy link

mehdigmira commented Sep 10, 2023

Found an other issue, but not sure if it is related to this

from __future__ import annotations


from typing_extensions import TypeVarTuple
from typing import Any, Unpack
from collections.abc import Sequence

Ts = TypeVarTuple("Ts")


def f(data: Sequence[tuple[Unpack[Ts]]]) -> list[Any]:
    return [d[0] for d in data]  # error: List comprehension has incompatible type List[Unpack[Ts]]; expected List[Any]  

@mehdigmira
Copy link

Also mypy seems to crash when typevartuple is used within an overload

from __future__ import annotations


from typing_extensions import TypeVarTuple
from typing import Any, Generic, Unpack, overload

Ts = TypeVarTuple("Ts")


class Array(Generic[Unpack[Ts]]):
    ...


class A:
    @overload
    def f(self, x: tuple[Unpack[Ts]]) -> Array[Unpack[Ts]]:
        ...

    @overload
    def f(self, x: Any) -> Any:
        ...

    def f(self, x: Any) -> Any:
        ...
https://mypy.readthedocs.io/en/stable/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 1.7.0+dev.4991a9a29ff5e15ab694d839a85968b2f093e070
Traceback (most recent call last):
  File "/home/vscode/.local/bin/mypy", line 8, in <module>
    sys.exit(console_entry())
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/__main__.py", line 15, in console_entry
    main()
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/main.py", line 99, in main
    res, messages, blockers = run_build(sources, options, fscache, t0, stdout, stderr)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/main.py", line 178, in run_build
    res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 189, in build
    result = _build(
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 262, in _build
    graph = dispatch(sources, manager, stdout)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 2938, in dispatch
    process_graph(graph, manager)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 3336, in process_graph
    process_stale_scc(graph, scc, manager)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 3437, in process_stale_scc
    graph[id].type_check_first_pass()
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 2306, in type_check_first_pass
    self.type_checker().check_first_pass()
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 474, in check_first_pass
    self.accept(d)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 584, in accept
    stmt.accept(self)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/nodes.py", line 1132, in accept
    return visitor.visit_class_def(self)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 2297, in visit_class_def
    self.accept(defn.defs)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 584, in accept
    stmt.accept(self)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/nodes.py", line 1213, in accept
    return visitor.visit_block(self)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 2753, in visit_block
    self.accept(s)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 584, in accept
    stmt.accept(self)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/nodes.py", line 576, in accept
    return visitor.visit_overloaded_func_def(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 621, in visit_overloaded_func_def
    self._visit_overloaded_func_def(defn)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 651, in _visit_overloaded_func_def
    self.check_overlapping_overloads(defn)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 742, in check_overlapping_overloads
    if overload_can_never_match(sig1, sig2):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 7317, in overload_can_never_match
    signature, {tvar.id: erase_def_to_union_or_bound(tvar) for tvar in signature.variables}
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 7317, in <dictcomp>
    signature, {tvar.id: erase_def_to_union_or_bound(tvar) for tvar in signature.variables}
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/typeops.py", line 657, in erase_def_to_union_or_bound
    assert isinstance(tdef, TypeVarType)
AssertionError: 
test.py:15: : note: use --pdb to drop into pdb```

@ilevkivskyi
Copy link
Member Author

OK, thanks! The one with indexing is a known issue and it will go into one of the next PRs, the crash with overloads I will fix in this PR.

@github-actions

This comment has been minimized.

@mehdigmira
Copy link

@ilevkivskyi This is much better now, it seems to handle many code patterns.

Noticed 2 issues though:

  • In incremental mode, I encounter some crashes
test/typing/test_mypy.py::MypyPlainTest::test_mypy_no_plugin[async_sessionmaker.py] failed: Traceback (most recent call last):
  File "/code/.debug/sqlalchemy/test/typing/test_mypy.py", line 17, in test_mypy_no_plugin
    mypy_typecheck_file(path)
  File "/code/.debug/sqlalchemy/lib/sqlalchemy/testing/fixtures/mypy.py", line 122, in run
    stdout, stderr, exitcode = mypy_runner(path, use_plugin=use_plugin)
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/code/.debug/sqlalchemy/lib/sqlalchemy/testing/fixtures/mypy.py", line 113, in run
    stdout, stderr, exitcode = api.run(args)
                               ^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/api.py", line 71, in run
    return _run(
           ^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/api.py", line 58, in _run
    main_wrapper(stdout, stderr)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/api.py", line 72, in <lambda>
    lambda stdout, stderr: main(args=args, stdout=stdout, stderr=stderr, clean_exit=True)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/main.py", line 99, in main
    res, messages, blockers = run_build(sources, options, fscache, t0, stdout, stderr)
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/main.py", line 178, in run_build
    res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 189, in build
    result = _build(
             ^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 262, in _build
    graph = dispatch(sources, manager, stdout)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 2938, in dispatch
    process_graph(graph, manager)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 3336, in process_graph
    process_stale_scc(graph, scc, manager)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 3431, in process_stale_scc
    mypy.semanal_main.semantic_analysis_for_scc(graph, scc, manager.errors)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/semanal_main.py", line 101, in semantic_analysis_for_scc
    check_type_arguments(graph, scc, errors)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/semanal_main.py", line 388, in check_type_arguments
    state.tree.accept(analyzer)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/nodes.py", line 366, in accept
    return visitor.visit_mypy_file(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/semanal_typeargs.py", line 66, in visit_mypy_file
    super().visit_mypy_file(o)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/traverser.py", line 114, in visit_mypy_file
    d.accept(self)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/nodes.py", line 777, in accept
    return visitor.visit_func_def(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/traverser.py", line 133, in visit_func_def
    self.visit_func(o)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/semanal_typeargs.py", line 72, in visit_func
    super().visit_func(defn)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/mixedtraverser.py", line 37, in visit_func
    super().visit_func(o)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/traverser.py", line 130, in visit_func
    o.body.accept(self)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/nodes.py", line 1213, in accept
    return visitor.visit_block(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/semanal_typeargs.py", line 80, in visit_block
    super().visit_block(o)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/traverser.py", line 118, in visit_block
    s.accept(self)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/nodes.py", line 1569, in accept
    return visitor.visit_with_stmt(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/mixedtraverser.py", line 89, in visit_with_stmt
    super().visit_with_stmt(o)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/traverser.py", line 234, in visit_with_stmt
    o.body.accept(self)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/nodes.py", line 1213, in accept
    return visitor.visit_block(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/semanal_typeargs.py", line 80, in visit_block
    super().visit_block(o)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/traverser.py", line 118, in visit_block
    s.accept(self)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/nodes.py", line 1300, in accept
    return visitor.visit_assignment_stmt(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/mixedtraverser.py", line 82, in visit_assignment_stmt
    self.visit_optional_type(o.type)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/mixedtraverser.py", line 112, in visit_optional_type
    t.accept(self)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 1419, in accept
    return visitor.visit_instance(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/semanal_typeargs.py", line 123, in visit_instance
    self.validate_args(info.name, t.args, info.defn.type_vars, t)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/semanal_typeargs.py", line 177, in validate_args
    if not is_subtype(arg, tvar.upper_bound):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/subtypes.py", line 178, in is_subtype
    return _is_subtype(left, right, subtype_context, proper_subtype=False)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/subtypes.py", line 303, in _is_subtype
    is_subtype_of_item = any(
                         ^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/subtypes.py", line 304, in <genexpr>
    is_subtype(orig_left, item, subtype_context=subtype_context)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/subtypes.py", line 158, in is_subtype
    if mypy.typeops.is_recursive_pair(left, right):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/typeops.py", line 84, in is_recursive_pair
    if isinstance(t, TypeAliasType) and t.is_recursive:
                                        ^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 381, in is_recursive
    is_recursive = self.expand_all_if_possible(nothing_args=True) is None
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 364, in expand_all_if_possible
    unrolled, recursed = self._partial_expansion(nothing_args=nothing_args)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 353, in _partial_expansion
    unrolled = alias.accept(unroller)
               ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 398, in accept
    return visitor.visit_type_alias_type(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 3388, in visit_type_alias_type
    result = get_proper_type(t).accept(subvisitor)
             ^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 3016, in get_proper_type
    typ = typ._expand_once()
          ^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 340, in _expand_once
    new_tp = self.alias.target.accept(InstantiateAliasVisitor(mapping))
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 2382, in accept
    return visitor.visit_tuple_type(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/expandtype.py", line 404, in visit_tuple_type
    assert unpacked.type.fullname == "builtins.tuple"
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/nodes.py", line 3387, in __getattribute__
    raise AssertionError(object.__getattribute__(self, "msg"))
AssertionError: De-serialization failure: TypeInfo not fixed
test/typing/test_mypy.py::MypyPlainTest::test_mypy_no_plugin[create_proxy_methods.py] failed: Traceback (most recent call last):
  File "/code/.debug/sqlalchemy/test/typing/test_mypy.py", line 17, in test_mypy_no_plugin
    mypy_typecheck_file(path)
  File "/code/.debug/sqlalchemy/lib/sqlalchemy/testing/fixtures/mypy.py", line 122, in run
    stdout, stderr, exitcode = mypy_runner(path, use_plugin=use_plugin)
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/code/.debug/sqlalchemy/lib/sqlalchemy/testing/fixtures/mypy.py", line 113, in run
    stdout, stderr, exitcode = api.run(args)
                               ^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/api.py", line 71, in run
    return _run(
           ^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/api.py", line 58, in _run
    main_wrapper(stdout, stderr)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/api.py", line 72, in <lambda>
    lambda stdout, stderr: main(args=args, stdout=stdout, stderr=stderr, clean_exit=True)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/main.py", line 99, in main
    res, messages, blockers = run_build(sources, options, fscache, t0, stdout, stderr)
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/main.py", line 178, in run_build
    res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 189, in build
    result = _build(
             ^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 262, in _build
    graph = dispatch(sources, manager, stdout)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 2938, in dispatch
    process_graph(graph, manager)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 3336, in process_graph
    process_stale_scc(graph, scc, manager)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 3437, in process_stale_scc
    graph[id].type_check_first_pass()
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 2306, in type_check_first_pass
    self.type_checker().check_first_pass()
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 474, in check_first_pass
    self.accept(d)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 586, in accept
    report_internal_error(err, self.errors.file, stmt.line, self.errors, self.options)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/errors.py", line 1261, in report_internal_error
    raise err
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 584, in accept
    stmt.accept(self)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/nodes.py", line 1233, in accept
    return visitor.visit_expression_stmt(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 4275, in visit_expression_stmt
    expr_type = self.expr_checker.accept(s.expr, allow_none_return=True, always_allow_any=True)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checkexpr.py", line 5376, in accept
    report_internal_error(
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/errors.py", line 1261, in report_internal_error
    raise err
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checkexpr.py", line 5366, in accept
    typ = self.visit_call_expr(node, allow_none_return=True)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checkexpr.py", line 452, in visit_call_expr
    return self.visit_call_expr_inner(e, allow_none_return=allow_none_return)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checkexpr.py", line 538, in visit_call_expr_inner
    self.accept(e.callee, type_context, always_allow_any=True, is_callee=True)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checkexpr.py", line 5376, in accept
    report_internal_error(
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/errors.py", line 1261, in report_internal_error
    raise err
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checkexpr.py", line 5374, in accept
    typ = node.accept(self)
          ^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/nodes.py", line 1818, in accept
    return visitor.visit_member_expr(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checkexpr.py", line 3161, in visit_member_expr
    result = self.analyze_ordinary_member_access(e, is_lvalue)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checkexpr.py", line 3182, in analyze_ordinary_member_access
    member_type = analyze_member_access(
                  ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checkmember.py", line 201, in analyze_member_access
    result = _analyze_member_access(name, typ, mx, override_info)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checkmember.py", line 227, in _analyze_member_access
    return analyze_type_callable_member_access(name, typ, mx)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checkmember.py", line 396, in analyze_type_callable_member_access
    result = analyze_class_attribute_access(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checkmember.py", line 1055, in analyze_class_attribute_access
    result = add_class_tvars(
             ^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checkmember.py", line 1206, in add_class_tvars
    t = freshen_all_functions_type_vars(t)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/expandtype.py", line 162, in freshen_all_functions_type_vars
    if not t.accept(has_generic_callable):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 1953, in accept
    return visitor.visit_callable_type(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/expandtype.py", line 149, in visit_callable_type
    return t.is_generic() or super().visit_callable_type(t)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/type_visitor.py", line 505, in visit_callable_type
    args = self.query_types(t.arg_types)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/type_visitor.py", line 557, in query_types
    return any(t.accept(self) for t in types)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/type_visitor.py", line 557, in <genexpr>
    return any(t.accept(self) for t in types)
               ^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 2796, in accept
    return visitor.visit_union_type(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/type_visitor.py", line 525, in visit_union_type
    return self.query_types(t.items)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/type_visitor.py", line 557, in query_types
    return any(t.accept(self) for t in types)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/type_visitor.py", line 557, in <genexpr>
    return any(t.accept(self) for t in types)
               ^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 398, in accept
    return visitor.visit_type_alias_type(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/type_visitor.py", line 550, in visit_type_alias_type
    return get_proper_type(t).accept(self)
           ^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 3016, in get_proper_type
    typ = typ._expand_once()
          ^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 340, in _expand_once
    new_tp = self.alias.target.accept(InstantiateAliasVisitor(mapping))
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 2382, in accept
    return visitor.visit_tuple_type(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/expandtype.py", line 397, in visit_tuple_type
    items = self.expand_types_with_unpack(t.items)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/expandtype.py", line 391, in expand_types_with_unpack
    items.extend(self.expand_unpack(item))
                 ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/expandtype.py", line 298, in expand_unpack
    raise RuntimeError(f"Invalid type replacement to expand: {repl}")
RuntimeError: Invalid type replacement to expand: Unpack[builtins.tuple[Any, ...]]

Sorry If the tracebacks are hard to debug, I'm not able to reproduce this with a self contained example yet. I'll see If I can find one. These crashes do not occur in no-incremental mode.

  • I think there is an error with overloads in the following example
from typing import Any, Generic, TypeVarTuple, Unpack, TypeVar, overload, reveal_type


_Ts = TypeVarTuple("_Ts")
_T = TypeVar("_T")
_T2 = TypeVar("_T2")


class Container(Generic[_T]):
    ...


class Array(Generic[Unpack[_Ts]]):
    ...


@overload
def build(entity: Container[_T], /) -> Array[_T]:
    ...


@overload
def build(entity: Container[_T], entity2: Container[_T2], /) -> Array[_T, _T2]:
    ...


@overload
def build(*entities: Container[Any]) -> Array[Unpack[tuple[Any, ...]]]:
    ...


def build(*entities: Container[Any]) -> Array[Unpack[tuple[Any, ...]]]:
    ...


def test(a: Container[Any], b: Container[int], c: Container[str]):
    reveal_type(build(a, b))

Revealed type is Array[builtins.tuple[Any, ...]]. Pyright infers that it's Array[Any, int], which is what I'm expecting.
I think there is a long standing issue regarding how mypy handles Any and overloads, but in this case Array[builtins.tuple[Any, ...]] is wrong. If inferring Array[Any, int] is not possible, it should fallback to Array[Unpack[builtins.tuple[Any, ...]]]

@ilevkivskyi
Copy link
Member Author

@mehdigmira Thanks! I will take a look at these. About the another overload problem: although mypy indeed has special logic for Any in overloads, it looks like a bug. And if it is what I think it is, this will get into one of the next PRs. Incremental crashes look like a mystery so far, but it is always like this, incremental crashes are hard to debug (or even reproduce).

@ilevkivskyi
Copy link
Member Author

@mehdigmira I was not able to reproduce the incremental crashes (I reproduced one that is quite similar to one you posted), but after staring for an hour at type deserialization code, I found two obvious bugs. I guess this may fix your problems with incremental mode.

(The other two things, tuple indexing and overload inference, will go into separate PRs)

@mehdigmira
Copy link

mehdigmira commented Sep 12, 2023

@ilevkivskyi I still have the crash, but seems to only happen when using mypy python api. You can reproduce with the following

 git clone -b mypy-crash git@github.com:mehdigmira/sqlalchemy.git
 cd sqlalchemy
 python -m venv .venv/deser
 source .venv/deser/bin/activate
 python3 -m pip install -U git+https://github.com/ilevkivskyi/mypy.git@variadic-subtyping#egg=mypy
 python test_mypy_crash.py

@mehdigmira
Copy link

An other crash (this time with a self contained example)

from typing import Any, Generic, TypeVar, Unpack, overload
from typing_extensions import TypeVarTuple


_T = TypeVar("_T")
_Ts = TypeVarTuple("_Ts")


class Row(tuple[Unpack[_Ts]]):
    ...


class Query(Generic[_T]):
    ...


class RowReturningQuery(Query[Row[Unpack[_Ts]]]):
    ...


@overload
def func(self, __ent0: _T, /, *entities: Any) -> RowReturningQuery[_T, Unpack[tuple[Any, ...]]]:
    ...


@overload
def func(*entities: Any) -> Query[Any]:
    ...


def func(*entities: Any) -> Query[Any]:
    ...

Traceback is

Traceback (most recent call last):
  File "/home/vscode/.local/bin/mypy", line 8, in <module>
    sys.exit(console_entry())
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/__main__.py", line 15, in console_entry
    main()
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/main.py", line 99, in main
    res, messages, blockers = run_build(sources, options, fscache, t0, stdout, stderr)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/main.py", line 178, in run_build
    res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 189, in build
    result = _build(
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 262, in _build
    graph = dispatch(sources, manager, stdout)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 2938, in dispatch
    process_graph(graph, manager)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 3336, in process_graph
    process_stale_scc(graph, scc, manager)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 3437, in process_stale_scc
    graph[id].type_check_first_pass()
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/build.py", line 2306, in type_check_first_pass
    self.type_checker().check_first_pass()
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 474, in check_first_pass
    self.accept(d)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 584, in accept
    stmt.accept(self)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/nodes.py", line 576, in accept
    return visitor.visit_overloaded_func_def(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 621, in visit_overloaded_func_def
    self._visit_overloaded_func_def(defn)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 651, in _visit_overloaded_func_def
    self.check_overlapping_overloads(defn)
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 761, in check_overlapping_overloads
    if is_unsafe_overlapping_overload_signatures(sig1, sig2, type_vars):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/checker.py", line 7257, in is_unsafe_overlapping_overload_signatures
    return is_callable_compatible(
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/subtypes.py", line 1468, in is_callable_compatible
    unified = unify_generic_callable(
              ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/subtypes.py", line 1770, in unify_generic_callable
    c = mypy.constraints.infer_constraints(
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/constraints.py", line 293, in infer_constraints
    res = _infer_constraints(template, actual, direction, skip_neg_op)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/constraints.py", line 377, in _infer_constraints
    return template.accept(ConstraintBuilderVisitor(actual, direction, skip_neg_op))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 1419, in accept
    return visitor.visit_instance(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/constraints.py", line 775, in visit_instance
    res.extend(infer_constraints(mapped_arg, instance_arg, self.direction))
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/constraints.py", line 296, in infer_constraints
    return _infer_constraints(template, actual, direction, skip_neg_op)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/constraints.py", line 377, in _infer_constraints
    return template.accept(ConstraintBuilderVisitor(actual, direction, skip_neg_op))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 2382, in accept
    return visitor.visit_tuple_type(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/constraints.py", line 1244, in visit_tuple_type
    return self.infer_against_any(template.items, actual)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/constraints.py", line 1280, in infer_against_any
    res.extend(infer_constraints(t, any_type, self.direction))
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/constraints.py", line 296, in infer_constraints
    return _infer_constraints(template, actual, direction, skip_neg_op)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/constraints.py", line 377, in _infer_constraints
    return template.accept(ConstraintBuilderVisitor(actual, direction, skip_neg_op))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/types.py", line 1063, in accept
    return visitor.visit_unpack_type(self)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vscode/.local/lib/python3.11/site-packages/mypy/constraints.py", line 657, in visit_unpack_type
    raise RuntimeError("Mypy bug: unpack should be handled at a higher level.")
RuntimeError: Mypy bug: unpack should be handled at a higher level.

I think this is introduced by the latest commit

@ilevkivskyi
Copy link
Member Author

@mehdigmira Thanks! I think I fixed both now. FWIW one of the crashes falls into what should be in the next PRs, but since it is a crash and the fix is couple lines I added it here.

@github-actions

This comment has been minimized.

@mehdigmira
Copy link

@ilevkivskyi Yes, all good now !

Copy link
Collaborator

@hauntsaninja hauntsaninja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks mehdigmira for testing and ilevkivskyi for fixing all the things! Should we add the test case from #16076 (comment) ?

@ilevkivskyi
Copy link
Member Author

@JukkaL @jhance You can review this now.

@github-actions

This comment has been minimized.

Copy link
Collaborator

@JukkaL JukkaL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a quick pass, looks good! Left some ideas about additional tests. The logic is hard to validate by only looking at code, so tests seem like the best way to ensure correctness. Let's also give Jared some time to have a look at the PR before merging?

t1: Tuple[int, int]
t2: Tuple[int, Unpack[Tuple[int, ...]]]
t3: Tuple[Unpack[Tuple[int, ...]], int]
t4: Tuple[int, Unpack[Tuple[int, ...]], int]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about Tuple[int, int, Unpack[Tuple[int, ...]] (and similarly for a suffix)?

@@ -811,11 +846,82 @@ def visit_overloaded(self, t: Overloaded) -> ProperType:
return meet_types(t, call)
return meet_types(t.fallback, s)

def meet_tuples(self, s: TupleType, t: TupleType) -> list[Type] | None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about adding more unit tests for meet_tuples? This is pretty tricky but should be easy to unit test. This can happen in a follow-up PR.

@@ -440,6 +472,112 @@ def visit_overloaded(self, t: Overloaded) -> ProperType:
return join_types(t, call)
return join_types(t.fallback, s)

def join_tuples(self, s: TupleType, t: TupleType) -> list[Type] | None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about adding more unit tests for join_tuples? This is pretty tricky but should be easy to unit test. This can happen in a follow-up PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a good idea after all. Tests caught a silly bug (missing early return in one place), and also gave me an idea of how to handle some more edge cases essentially for free.


Ts = TypeVarTuple("Ts")
class B(Generic[Unpack[Ts]]): ...
class C1(B[Unpack[Tuple[Any, ...]]]): ...
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about testing other cases of inheritance, such as using B[Unpack[Ts]] in the subclass, or a fixed number of non-Any types as type args (B[int, str]), etc.? This can happen in a follow-up PR (or you can create an issue about this).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some existing tests, I just added few specific test cases for situations that required special-casing (and were not covered by existing test cases). I will check what important cases are currently missing and will add some.

@ilevkivskyi
Copy link
Member Author

Let's also give Jared some time to have a look at the PR before merging?

OK, I can wait some time. I can work on adding (some of) the tests in the meantime.

@github-actions
Copy link
Contributor

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

@ilevkivskyi ilevkivskyi merged commit b327557 into python:master Sep 13, 2023
18 checks passed
@ilevkivskyi ilevkivskyi deleted the variadic-subtyping branch September 13, 2023 22:41
hauntsaninja added a commit to hauntsaninja/mypy that referenced this pull request Sep 14, 2023
Follow up to python#16073 and python#16076
Fix needed for https://github.com/python/mypy/pull/16053/files#r1316481395

I add test cases that would have caught my previous incorrect PR. I add
an explicit case for the new desirable behaviour we see with zip.
hauntsaninja added a commit that referenced this pull request Sep 14, 2023
Follow up to #16073 and #16076
Fix needed for
https://github.com/python/mypy/pull/16053/files#r1316481395

I add test cases that would have caught my previous incorrect PR. I add
an explicit case for the new desirable behaviour we see with zip.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants